/*-
 * Copyright (c) 2012,2013,2014,2015,2016 Alistair Crooks <agc@NetBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/mman.h>

#include <arpa/inet.h>

#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "bzlib.h"
#include "zlib.h"

#include "array.h"
#include "b64.h"
#include "bn.h"
#include "bufgap.h"
#include "digest.h"
#include "misc.h"
#include "pgpsum.h"
#include "rsa.h"
#include "verify.h"

/* 64bit key ids */
#define PGPV_KEYID_LEN		8
#define PGPV_STR_KEYID_LEN	(PGPV_KEYID_LEN + PGPV_KEYID_LEN + 1)

/* bignum structure */
typedef struct pgpv_bignum_t {
	void			*bn;	/* hide the implementation details */
	uint16_t		 bits;	/* cached number of bits */
} pgpv_bignum_t;

/* right now, our max binary digest length is 20 bytes */
#define PGPV_MAX_HASH_LEN	64

/* fingerprint */
typedef struct pgpv_fingerprint_t {
	uint8_t			hashalg;	/* algorithm for digest */
	uint8_t			v[PGPV_MAX_HASH_LEN];	/* the digest */
	uint32_t		len;		/* its length */
} pgpv_fingerprint_t;

/* specify size for array of bignums */
#define PGPV_MAX_PUBKEY_BN	4

/* public key */
typedef struct pgpv_pubkey_t {
	pgpv_fingerprint_t	 fingerprint;	/* key fingerprint i.e. digest */
	uint8_t			 keyid[PGPV_KEYID_LEN];	/* last 8 bytes of v4 keys */
	int64_t		 	 birth;		/* creation time */
	int64_t			 expiry;	/* expiry time */
	pgpv_bignum_t		 bn[PGPV_MAX_PUBKEY_BN]; /* bignums */
	uint8_t			 keyalg;	/* key algorithm */
	uint8_t			 hashalg;	/* hash algorithm */
	uint8_t			 version;	/* key version */
} pgpv_pubkey_t;

#define PGPV_MAX_SESSKEY_BN	2

/* a (size, byte array) string */
typedef struct pgpv_string_t {
	size_t			 size;
	uint8_t			*data;
	uint8_t			 allocated;
} pgpv_string_t;

typedef struct pgpv_ref_t {
	void			*vp;
	size_t			 offset;
	unsigned		 mem;
} pgpv_ref_t;

#define PGPV_MAX_SECKEY_BN	4

typedef struct pgpv_compress_t {
	pgpv_string_t		 s;
	uint8_t			 compalg;
} pgpv_compress_t;

/* a packet dealing with trust */
typedef struct pgpv_trust_t {
	uint8_t			level;
	uint8_t			amount;
} pgpv_trust_t;

/* a signature sub packet */
typedef struct pgpv_sigsubpkt_t {
	pgpv_string_t		 s;
	uint8_t			 tag;
	uint8_t			 critical;
} pgpv_sigsubpkt_t;

#define PGPV_MAX_SIG_BN		2

typedef struct pgpv_signature_t {
	uint8_t			 signer[PGPV_KEYID_LEN]; /* key id of signer */
	pgpv_ref_t		 hashstart;
	uint8_t			*hash2;
	uint8_t			*mpi;
	int64_t			 birth;
	int64_t			 keyexpiry;
	int64_t			 expiry;
	uint32_t		 hashlen;
	uint8_t			 version;
	uint8_t			 type;
	uint8_t			 keyalg;
	uint8_t			 hashalg;
	uint8_t			 trustlevel;
	uint8_t			 trustamount;
	pgpv_bignum_t		 bn[PGPV_MAX_SIG_BN];
	char			*regexp;
	char			*pref_key_server;
	char			*policy;
	char			*features;
	char			*why_revoked;
	uint8_t			*revoke_fingerprint;
	uint8_t			 revoke_alg;
	uint8_t			 revoke_sensitive;
	uint8_t			 trustsig;
	uint8_t			 revocable;
	uint8_t			 pref_symm_alg;
	uint8_t			 pref_hash_alg;
	uint8_t			 pref_compress_alg;
	uint8_t			 key_server_modify;
	uint8_t			 notation;
	uint8_t			 type_key;
	uint8_t			 primary_userid;
	uint8_t			 revoked;	/* subtract 1 to get real reason, 0 == not revoked */
} pgpv_signature_t;

/* a signature packet */
typedef struct pgpv_sigpkt_t {
	pgpv_signature_t	 sig;
	uint16_t		 subslen;
	uint16_t		 unhashlen;
	ARRAY(uint64_t,	 	 subpackets);
} pgpv_sigpkt_t;

/* a one-pass signature packet */
typedef struct pgpv_onepass_t {
	uint8_t			 keyid[PGPV_KEYID_LEN];
	uint8_t			 version;
	uint8_t			 type;
	uint8_t			 hashalg;
	uint8_t			 keyalg;
	uint8_t			 nested;
} pgpv_onepass_t;

/* a literal data packet */
typedef struct pgpv_litdata_t {
	pgpv_string_t		 filename;
	pgpv_string_t		 s;
	uint32_t		 secs;
	uint8_t			 namelen;
	char			 format;
	unsigned		 mem;
	size_t			 offset;
	size_t			 len;
} pgpv_litdata_t;

/* user attributes - images */
typedef struct pgpv_userattr_t {
	size_t 			 len;
	ARRAY(pgpv_string_t, 	 subattrs);
} pgpv_userattr_t;

/* a general PGP packet */
typedef struct pgpv_pkt_t {
	uint8_t			 tag;
	uint8_t			 newfmt;
	uint8_t			 allocated;
	uint8_t			 mement;
	size_t			 offset;
	pgpv_string_t		 s;
	union {
		pgpv_sigpkt_t	sigpkt;
		pgpv_onepass_t	onepass;
		pgpv_litdata_t	litdata;
		pgpv_compress_t	compressed;
		pgpv_trust_t	trust;
		pgpv_pubkey_t	pubkey;
		pgpv_string_t	userid;
		pgpv_userattr_t	userattr;
	} u;
} pgpv_pkt_t;

/* a memory structure */
typedef struct pgpv_mem_t {
	size_t			 size;
	size_t			 cc;
	uint8_t			*mem;
	FILE			*fp;
	uint8_t			 dealloc;
	const char		*allowed;	/* the types of packet that are allowed */
} pgpv_mem_t;

/* packet parser */

typedef struct pgpv_signed_userid_t {
	pgpv_string_t	 	 userid;
	ARRAY(uint64_t, 	 signatures);
	uint8_t			 primary_userid;
	uint8_t			 revoked;
} pgpv_signed_userid_t;

typedef struct pgpv_signed_userattr_t {
	pgpv_userattr_t	 	 userattr;
	ARRAY(uint64_t, 	 signatures);
	uint8_t			 revoked;
} pgpv_signed_userattr_t;

typedef struct pgpv_signed_subkey_t {
	pgpv_pubkey_t	 	 subkey;
	pgpv_signature_t 	 revoc_self_sig;
	ARRAY(uint64_t, 	 signatures);
} pgpv_signed_subkey_t;

typedef struct pgpv_primarykey_t {
	pgpv_pubkey_t 		 primary;
	pgpv_signature_t 	 revoc_self_sig;
	ARRAY(uint64_t, 	 signatures);
	ARRAY(uint64_t, 	 signed_userids);
	ARRAY(uint64_t, 	 signed_userattrs);
	ARRAY(uint64_t, 	 signed_subkeys);
	size_t			 fmtsize;
	uint8_t			 primary_userid;
} pgpv_primarykey_t;

/* everything stems from this structure */
struct pgpv_t {
	ARRAY(pgpv_pkt_t, 	 pkts);		/* packet array */
	ARRAY(pgpv_primarykey_t, primaries);	/* array of primary keys */
	ARRAY(pgpv_mem_t,	 areas);	/* areas we read packets from */
	ARRAY(size_t,	 	 datastarts);	/* starts of data packets */
	ARRAY(pgpv_signature_t,	 signatures);	/* all signatures */
	ARRAY(pgpv_signed_userid_t, signed_userids); /* all signed userids */
	ARRAY(pgpv_signed_userattr_t, signed_userattrs); /* all signed user attrs */
	ARRAY(pgpv_signed_subkey_t, signed_subkeys); /* all signed subkeys */
	ARRAY(pgpv_sigsubpkt_t,	 subpkts);	/* all sub packets */
	size_t		 	 pkt;		/* when parsing, current pkt number */
	const char		*op;		/* the operation we're doing */
	unsigned		 ssh;		/* using ssh keys */
};

#define PGPV_REASON_LEN		128

/* when searching, we define a cursor, and fill in an array of subscripts */
struct pgpv_cursor_t {
	pgpv_t			*pgp;			/* pointer to pgp tree */
	char			*field;			/* field we're searching on */
	char			*op;			/* operation we're doing */
	char			*value;			/* value we're searching for */
	void			*ptr;			/* for regexps etc */
	ARRAY(uint32_t,	 	 found);		/* array of matched pimary key subscripts */
	ARRAY(size_t,	 	 datacookies);		/* cookies to retrieve matched data */
	int64_t			 sigtime;		/* time of signature */
	char			 why[PGPV_REASON_LEN];	/* reason for bad signature */
};

#ifndef USE_ARG
#define USE_ARG(x)	/*LINTED*/(void)&(x)
#endif

#ifndef __dead
#define __dead				__attribute__((__noreturn__))
#endif

#ifndef __printflike
#define __printflike(n, m)		__attribute__((format(printf,n,m)))
#endif

#ifndef MIN
#define MIN(a,b)			(((a)<(b))?(a):(b))
#endif

#ifndef howmany
#define howmany(x, y)   		(((x)+((y)-1))/(y))
#endif

#define BITS_TO_BYTES(b)		(((b) + (CHAR_BIT - 1)) / CHAR_BIT)

/* packet types */
#define SIGNATURE_PKT			2
#define ONEPASS_SIGNATURE_PKT		4
#define PUBKEY_PKT			6
#define COMPRESSED_DATA_PKT		8
#define MARKER_PKT			10
#define LITDATA_PKT			11
#define TRUST_PKT			12
#define USERID_PKT			13
#define PUB_SUBKEY_PKT			14
#define USER_ATTRIBUTE_PKT		17

/* only allow certain packets at certain times */
#define PUBRING_ALLOWED			"\002\006\014\015\016\021"
#define SIGNATURE_ALLOWED		"\002\004\010\013"

/* actions to do on close */
#define FREE_MEM			0x01
#define UNMAP_MEM			0x02

/* types of pubkey we encounter */
#define PUBKEY_RSA_ENCRYPT_OR_SIGN	1
#define PUBKEY_RSA_ENCRYPT		2
#define PUBKEY_RSA_SIGN			3
#define PUBKEY_ELGAMAL_ENCRYPT		16
#define PUBKEY_DSA			17
#define PUBKEY_ECDH			18
#define PUBKEY_ECDSA			19
#define PUBKEY_ELGAMAL_ENCRYPT_OR_SIGN	20

/* hash algorithm definitions */
#define PGPV_HASH_MD5			1
#define PGPV_HASH_SHA1			2
#define PGPV_HASH_RIPEMD		3
#define PGPV_HASH_SHA256		8
#define PGPV_HASH_SHA384		9
#define PGPV_HASH_SHA512		10

/* pubkey defs for bignums */
#define RSA_N				0
#define RSA_E				1
#define DSA_P				0
#define DSA_Q				1
#define DSA_G				2
#define DSA_Y				3
#define ELGAMAL_P			0
#define ELGAMAL_G			1
#define ELGAMAL_Y			2

/* sesskey indices */
#define RSA_SESSKEY_ENCRYPTED_M		0
#define RSA_SESSKEY_M			1
#define ELGAMAL_SESSKEY_G_TO_K		0
#define ELGAMAL_SESSKEY_ENCRYPTED_M	1

/* seckey indices */
#define RSA_SECKEY_D			0
#define RSA_SECKEY_P			1
#define RSA_SECKEY_Q			2
#define RSA_SECKEY_U			3
#define DSA_SECKEY_X			0
#define ELGAMAL_SECKEY_X		0

/* signature mpi indices in bignumber array */
#define RSA_SIG				0
#define DSA_R				0
#define DSA_S				1
#define ELGAMAL_SIG_R			0
#define ELGAMAL_SIG_S			1

/* signature types */
#define SIGTYPE_BINARY_DOC		0x00	/* Signature of a binary document */
#define SIGTYPE_TEXT			0x01	/* Signature of a canonical text document */
#define SIGTYPE_STANDALONE		0x02	/* Standalone signature */

#define SIGTYPE_GENERIC_USERID		0x10	/* Generic certification of a User ID and Public Key packet */
#define SIGTYPE_PERSONA_USERID		0x11	/* Persona certification of a User ID and Public Key packet */
#define SIGTYPE_CASUAL_USERID		0x12	/* Casual certification of a User ID and Public Key packet */
#define SIGTYPE_POSITIVE_USERID		0x13	/* Positive certification of a User ID and Public Key packet */

#define SIGTYPE_SUBKEY_BINDING		0x18	/* Subkey Binding Signature */
#define SIGTYPE_PRIMARY_KEY_BINDING	0x19	/* Primary Key Binding Signature */
#define SIGTYPE_DIRECT_KEY		0x1f	/* Signature directly on a key */

#define SIGTYPE_KEY_REVOCATION		0x20	/* Key revocation signature */
#define SIGTYPE_SUBKEY_REVOCATION	0x28	/* Subkey revocation signature */
#define SIGTYPE_CERT_REVOCATION		0x30	/* Certification revocation signature */

#define SIGTYPE_TIMESTAMP_SIG		0x40	/* Timestamp signature */
#define SIGTYPE_3RDPARTY		0x50	/* Third-Party Confirmation signature */

/* Forward declarations */
static int read_all_packets(pgpv_t */*pgp*/, pgpv_mem_t */*mem*/, const char */*op*/);
static int read_binary_file(pgpv_t */*pgp*/, const char */*op*/, const char */*fmt*/, ...) __printflike(3, 4);
static int read_binary_memory(pgpv_t */*pgp*/, const char */*op*/, const void */*memory*/, size_t /*size*/);

/* output buffer structure */
typedef struct obuf_t {
	size_t	 alloc;		/* amount of memory allocated */
	size_t	 c;		/* # of chars used so far */
	uint8_t	*v;		/* array of bytes */
	uint32_t endian;	/* byte order of output stream */
} obuf_t;

/* grow the buffer, if needed */
static int
growbuf(obuf_t *obuf, size_t cc)
{
	size_t	 newalloc;
	uint8_t	*newv;

	if (obuf->c + cc > obuf->alloc) {
		newalloc = howmany(obuf->alloc + cc, 128) * 128;
		newv = realloc(obuf->v, newalloc);
		if (newv == NULL) {
			return 0;
		}
		obuf->v = newv;
		obuf->alloc = newalloc;
	}
	return 1;
}

/* add a fixed-length area of memory */
static int
obuf_add_mem(obuf_t *obuf, const void *s, size_t len)
{
	if (obuf && s && len > 0) {
		if (!growbuf(obuf, len)) {
			return 0;
		}
		memcpy(&obuf->v[obuf->c], s, len);
		obuf->c += len;
		return 1;
	}
	return 0;
}

/* read a file into the pgpv_mem_t struct */
static int
read_file(pgpv_t *pgp, const char *f)
{
	struct stat	 st;
	pgpv_mem_t	*mem;

	ARRAY_EXPAND(pgp->areas);
	ARRAY_COUNT(pgp->areas) += 1;
	mem = &ARRAY_LAST(pgp->areas);
	memset(mem, 0x0, sizeof(*mem));
	if ((mem->fp = fopen(f, "r")) == NULL) {
		fprintf(stderr, "can't read '%s'", f);
		return 0;
	}
	fstat(fileno(mem->fp), &st);
	mem->size = (size_t)st.st_size;
	mem->mem = mmap(NULL, mem->size, PROT_READ, MAP_SHARED, fileno(mem->fp), 0);
	mem->dealloc = UNMAP_MEM;
	return 1;
}

/* DTRT and free resources */
static int
closemem(pgpv_mem_t *mem)
{
	switch(mem->dealloc) {
	case FREE_MEM:
		free(mem->mem);
		mem->size = 0;
		break;
	case UNMAP_MEM:
		munmap(mem->mem, mem->size);
		fclose(mem->fp);
		break;
	}
	return 1;
}

/* make a reference to a memory area, and its offset */
static void
make_ref(pgpv_t *pgp, uint8_t mement, pgpv_ref_t *ref)
{
	ref->mem = mement;
	ref->offset = ARRAY_ELEMENT(pgp->areas, ref->mem).cc;
	ref->vp = pgp;
}

/* return the pointer we wanted originally */
static uint8_t *
get_ref(pgpv_ref_t *ref)
{
	pgpv_mem_t	*mem;
	pgpv_t		*pgp = (pgpv_t *)ref->vp;

	mem = &ARRAY_ELEMENT(pgp->areas, ref->mem);
	return &mem->mem[ref->offset];
}

#define IS_PARTIAL(x)		((x) >= 224 && (x) < 255)
#define DECODE_PARTIAL(x)	(1 << ((x) & 0x1f))

#define PKT_LENGTH(m, off)						\
	((m[off] < 192) ? (m[off]) : 					\
	 (m[off] < 224) ? ((m[off] - 192) << 8) + (m[off + 1]) + 192 :	\
	 (m[off + 1] << 24) | ((m[off + 2]) << 16) | ((m[off + 3]) << 8)  | (m[off + 4]))

#define PKT_LENGTH_LENGTH(m, off)					\
	((m[off] < 192) ? 1 : (m[off] < 224) ? 2 : 5)

/* fix up partial body lengths, return new size */
static size_t
fixup_partials(pgpv_t *pgp, uint8_t *p, size_t totlen, size_t filesize, size_t *cc)
{
	pgpv_mem_t	*mem;
	size_t		 partial;
	size_t		 newcc;

	if (totlen > filesize) {
		printf("fixup_partial: filesize %zu is less than encoded size %zu\n", filesize, totlen);
		return 0;
	}
	ARRAY_EXPAND(pgp->areas);
	ARRAY_COUNT(pgp->areas) += 1;
	mem = &ARRAY_LAST(pgp->areas);
	mem->size = totlen;
	if ((mem->mem = calloc(1, mem->size + 5)) == NULL) {
		printf("fixup_partial: can't allocate %zu length\n", totlen);
		return 0;
	}
	newcc = 0;
	mem->dealloc = FREE_MEM;
	for (*cc = 0 ; *cc < totlen ; newcc += partial, *cc += partial + 1) {
		if (IS_PARTIAL(p[*cc])) {
			partial = DECODE_PARTIAL(p[*cc]);
			memcpy(&mem->mem[newcc], &p[*cc + 1], partial);
		} else {
			partial = PKT_LENGTH(p, *cc);
			*cc += PKT_LENGTH_LENGTH(p, *cc);
			memcpy(&mem->mem[newcc], &p[*cc], partial);
			newcc += partial;
			*cc += partial;
			break;
		}
	}
	return newcc;
}

/* get the weirdo packet length */
static size_t
get_pkt_len(uint8_t newfmt, uint8_t *p, size_t filesize, int isprimary)
{
	size_t	lenbytes;
	size_t	len;

	if (newfmt) {
		if (IS_PARTIAL(*p)) {
			if (!isprimary) {
				/* for sub-packets, only 1, 2 or 4 byte sizes allowed */
				return ((*p - 192) << 8) + *(p + 1) + 192;
			}
			lenbytes = 1;
			for (len = DECODE_PARTIAL(*p) ; IS_PARTIAL(p[len + lenbytes]) ; lenbytes++) {
				len += DECODE_PARTIAL(p[len + lenbytes]);
			}
			len += get_pkt_len(newfmt, &p[len + lenbytes], filesize, 1);
			return len;
		}
		return PKT_LENGTH(p, 0);
	} else {
		switch(*--p & 0x3) {
		case 0:
			return *(p + 1);
		case 1:
			return (*(p + 1) << 8) | *(p + 2);
		case 2:
			return (*(p + 1) << 24) | (*(p + 2) << 16) | (*(p + 3) << 8)  | *(p + 4);
		default:
			return filesize;
		}
	}
}

static const uint8_t	base64s[] =
/* 000 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 016 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 032 */       "\0\0\0\0\0\0\0\0\0\0\0?\0\0\0@"
/* 048 */       "56789:;<=>\0\0\0\0\0\0"
/* 064 */       "\0\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17"
/* 080 */       "\20\21\22\23\24\25\26\27\30\31\32\0\0\0\0\0"
/* 096 */       "\0\33\34\35\36\37 !\"#$%&'()"
/* 112 */       "*+,-./01234\0\0\0\0\0"
/* 128 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 144 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 160 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 176 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 192 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 208 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 224 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
/* 240 */       "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";


/* short function to decode from base64 */
/* inspired by an ancient copy of b64.c, then rewritten, the bugs are all mine */
static int
frombase64(char *dst, const char *src, size_t size, int flag)
{
	uint8_t	out[3];
	uint8_t	in[4];
	uint8_t	b;
	size_t	srcc;
	int	dstc;
	int	gotc;
	int	i;

	USE_ARG(flag);
	for (dstc = 0, srcc = 0 ; srcc < size; ) {
		for (gotc = 0, i = 0; i < 4 && srcc < size; i++) {
			for (b = 0x0; srcc < size && b == 0x0 ; ) {
				b = base64s[(unsigned)src[srcc++]];
			}
			if (srcc < size) {
				gotc += 1;
				if (b) {
					in[i] = (uint8_t)(b - 1);
				}
			} else {
				in[i] = 0x0;
			}
		}
		if (gotc) {
			out[0] = (uint8_t)((unsigned)in[0] << 2 |
						(unsigned)in[1] >> 4);
			out[1] = (uint8_t)((unsigned)in[1] << 4 |
						(unsigned)in[2] >> 2);
			out[2] = (uint8_t)(((in[2] << 6) & 0xc0) | in[3]);
			for (i = 0; i < gotc - 1; i++) {
				*dst++ = out[i];
			}
			dstc += gotc - 1;
		}
	}
	return dstc;
}

/* get the length of the packet length field */
static unsigned
get_pkt_len_len(uint8_t newfmt, uint8_t *p, int isprimary)
{
	if (newfmt) {
		if (IS_PARTIAL(*p)) {
			return (isprimary) ? 1 : 2;
		}
		return PKT_LENGTH_LENGTH(p, 0);
	} else {
		switch(*--p & 0x3) {
		case 0:
			return 1;
		case 1:
			return 2;
		case 2:
			return 4;
		default:
			return 0;
		}
	}
}

/* copy the 32bit integer in memory in network order */
static unsigned
fmt_32(uint8_t *p, uint32_t a)
{
	a = pgp_hton32(a);
	memcpy(p, &a, sizeof(a));
	return sizeof(a);
}

/* copy the 16bit integer in memory in network order */
static unsigned
fmt_16(uint8_t *p, uint16_t a)
{
	a = pgp_hton16(a);
	memcpy(p, &a, sizeof(a));
	return sizeof(a);
}

/* format a binary string in memory */
static size_t
fmt_binary(obuf_t *obuf, const uint8_t *bin, unsigned len)
{
	unsigned	i;
	char		newbuf[3];

	for (i = 0 ; i < len ; i++) {
		snprintf(newbuf, sizeof(newbuf), "%02hhx", bin[i]);
		if (!obuf_add_mem(obuf, newbuf, 2)) {
			return 0;
		}
	}
	return 1;
}

/* format an mpi into memory */
static unsigned
fmt_binary_mpi(pgpv_bignum_t *mpi, uint8_t *p, size_t size)
{
	unsigned	 bytes;
	PGPV_BIGNUM		*bn;

	bytes = BITS_TO_BYTES(mpi->bits);
	if ((size_t)bytes + 2 + 1 > size) {
		fprintf(stderr, "truncated mpi");
		return 0;
	}
	bn = (PGPV_BIGNUM *)mpi->bn;
	if (bn == NULL || PGPV_BN_is_zero(bn)) {
		fmt_32(p, 0);
		return 2 + 1;
	}
	fmt_16(p, mpi->bits);
	PGPV_BN_bn2bin(bn, &p[2]);
	return bytes + 2;
}

/* dump an mpi value onto stdout */
static size_t
fmt_mpi(char *s, size_t size, pgpv_bignum_t *bn, const char *name, int pbits)
{
	size_t	 cc;
	char	*buf;

	cc = snprintf(s, size, "%s=", name);
	if (pbits) {
		cc += snprintf(&s[cc], size - cc, "[%u bits] ", bn->bits);
	}
	buf = PGPV_BN_bn2hex(bn->bn);
	cc += snprintf(&s[cc], size - cc, "%s\n", buf);
	free(buf);
	return cc;
}

#define ALG_IS_RSA(alg)	(((alg) == PUBKEY_RSA_ENCRYPT_OR_SIGN) ||	\
			 ((alg) == PUBKEY_RSA_ENCRYPT) ||		\
			 ((alg) == PUBKEY_RSA_SIGN))

#define ALG_IS_DSA(alg)	((alg) == PUBKEY_DSA)

/* format key mpis into memory */
static unsigned
fmt_key_mpis(pgpv_pubkey_t *pubkey, uint8_t *buf, size_t size)
{
	size_t	cc;

	cc = 0;
	buf[cc++] = pubkey->version;
	cc += fmt_32(&buf[cc], (uint32_t)pubkey->birth); /* XXX - do this portably! */
	buf[cc++] = pubkey->keyalg;	/* XXX - sign, or encrypt and sign? */
	switch(pubkey->keyalg) {
	case PUBKEY_RSA_ENCRYPT_OR_SIGN:
	case PUBKEY_RSA_ENCRYPT:
	case PUBKEY_RSA_SIGN:
		cc += fmt_binary_mpi(&pubkey->bn[RSA_N], &buf[cc], size - cc);
		cc += fmt_binary_mpi(&pubkey->bn[RSA_E], &buf[cc], size - cc);
		break;
	case PUBKEY_DSA:
		cc += fmt_binary_mpi(&pubkey->bn[DSA_P], &buf[cc], size - cc);
		cc += fmt_binary_mpi(&pubkey->bn[DSA_Q], &buf[cc], size - cc);
		cc += fmt_binary_mpi(&pubkey->bn[DSA_G], &buf[cc], size - cc);
		cc += fmt_binary_mpi(&pubkey->bn[DSA_Y], &buf[cc], size - cc);
		break;
	default:
		cc += fmt_binary_mpi(&pubkey->bn[ELGAMAL_P], &buf[cc], size - cc);
		cc += fmt_binary_mpi(&pubkey->bn[ELGAMAL_G], &buf[cc], size - cc);
		cc += fmt_binary_mpi(&pubkey->bn[ELGAMAL_Y], &buf[cc], size - cc);
		break;
	}
	return (unsigned)cc;
}

/* calculate the fingerprint, RFC 4880, section 12.2 */
static int
pgpv_calc_fingerprint(pgpv_fingerprint_t *fingerprint, pgpv_pubkey_t *pubkey, const char *hashtype)
{
	digest_t	 fphash;
	uint16_t	 cc;
	uint8_t		 ch = 0x99;
	uint8_t		 buf[8192 + 2 + 1];
	uint8_t		 len[2];

	memset(&fphash, 0x0, sizeof(fphash));
	if (pubkey->version == 4) {
		/* v4 keys */
		fingerprint->hashalg = digest_get_alg(hashtype);
		digest_init(&fphash, (unsigned)fingerprint->hashalg);
		cc = fmt_key_mpis(pubkey, buf, sizeof(buf));
		digest_update(&fphash, &ch, 1);
		fmt_16(len, cc);
		digest_update(&fphash, len, 2);
		digest_update(&fphash, buf, (unsigned)cc);
		fingerprint->len = digest_final(fingerprint->v, &fphash);
		return 1;
	}
	if (ALG_IS_RSA(pubkey->keyalg)) {
		/* v3 keys are RSA */
		fingerprint->hashalg = digest_get_alg("md5");
		digest_init(&fphash, (unsigned)fingerprint->hashalg);
		if (pubkey->bn[RSA_N].bn && pubkey->bn[RSA_E].bn) {
			cc = fmt_binary_mpi(&pubkey->bn[RSA_N], buf, sizeof(buf));
			digest_update(&fphash, &buf[2], (unsigned)(cc - 2));
			cc = fmt_binary_mpi(&pubkey->bn[RSA_E], buf, sizeof(buf));
			digest_update(&fphash, &buf[2], (unsigned)(cc - 2));
			fingerprint->len = digest_final(fingerprint->v, &fphash);
			return 1;
		}
	}
	if (pubkey->bn[RSA_N].bn) {
		if ((cc = fmt_binary_mpi(&pubkey->bn[RSA_N], buf, sizeof(buf))) >= PGPV_KEYID_LEN) {
			memcpy(fingerprint->v, &buf[cc - PGPV_KEYID_LEN], PGPV_KEYID_LEN);
			fingerprint->len = PGPV_KEYID_LEN;
			return 1;
		}
	}
	/* exhausted all avenues, really */
	memset(fingerprint->v, 0xff, fingerprint->len = PGPV_KEYID_LEN);
	return 1;
}

/* format a fingerprint into memory */
static int
fmt_fingerprint(obuf_t *obuf, pgpv_fingerprint_t *fingerprint, const char *name)
{
	unsigned	i;
	char		newbuf[3];
	int		cc;

	if (!obuf_add_mem(obuf, name, strlen(name)) ||
	    !obuf_add_mem(obuf, " ", 1)) {
		return 0;
	}
	for (i = 0 ; i < fingerprint->len ; i++) {
		cc = snprintf(newbuf, sizeof(newbuf), "%02hhx",
			fingerprint->v[i]);
		if (!obuf_add_mem(obuf, newbuf, cc)) {
			return 0;
		}
		if (i % 2 == 1 && !obuf_add_mem(obuf, " ", 1)) {
			return 0;
		}
	}
	return obuf_add_mem(obuf, "\n", 1);
}

/* calculate keyid from a pubkey */
static int 
calc_keyid(pgpv_pubkey_t *key, const char *hashtype)
{
	pgpv_calc_fingerprint(&key->fingerprint, key, hashtype);
	memcpy(key->keyid, &key->fingerprint.v[key->fingerprint.len - PGPV_KEYID_LEN], PGPV_KEYID_LEN);
	return 1;
}

/* convert a hex string to a 64bit key id (in big endian byte order */
static void
str_to_keyid(const char *s, uint8_t *keyid)
{
	uint64_t	u;

	u = (uint64_t)strtoull(s, NULL, 16);
	u =     ((u & 0x00000000000000FFULL) << 56) | 
		((u & 0x000000000000FF00ULL) << 40) | 
		((u & 0x0000000000FF0000ULL) << 24) | 
		((u & 0x00000000FF000000ULL) <<  8) | 
		((u & 0x000000FF00000000ULL) >>  8) | 
		((u & 0x0000FF0000000000ULL) >> 24) | 
		((u & 0x00FF000000000000ULL) >> 40) | 
		((u & 0xFF00000000000000ULL) >> 56);
	memcpy(keyid, &u, PGPV_KEYID_LEN);
}

#define PKT_ALWAYS_ON			0x80
#define PKT_NEWFMT_MASK			0x40
#define PKT_NEWFMT_TAG_MASK		0x3f
#define PKT_OLDFMT_TAG_MASK		0x3c

#define SUBPKT_CRITICAL_MASK		0x80
#define SUBPKT_TAG_MASK			0x7f

#define SUBPKT_SIG_BIRTH		2
#define SUBPKT_SIG_EXPIRY		3
#define SUBPKT_EXPORT_CERT		4
#define SUBPKT_TRUST_SIG		5
#define SUBPKT_REGEXP			6
#define SUBPKT_REVOCABLE		7
#define SUBPKT_KEY_EXPIRY		9
#define SUBPKT_BWD_COMPAT		10
#define SUBPKT_PREF_SYMMETRIC_ALG	11
#define SUBPKT_REVOCATION_KEY		12
#define SUBPKT_ISSUER			16
#define SUBPKT_NOTATION			20
#define SUBPKT_PREF_HASH_ALG		21
#define SUBPKT_PREF_COMPRESS_ALG	22
#define SUBPKT_KEY_SERVER_PREFS		23
#define SUBPKT_PREF_KEY_SERVER		24
#define SUBPKT_PRIMARY_USER_ID		25
#define SUBPKT_POLICY_URI		26
#define SUBPKT_KEY_FLAGS		27
#define SUBPKT_SIGNER_ID		28
#define SUBPKT_REVOCATION_REASON	29
#define SUBPKT_FEATURES			30
#define SUBPKT_SIGNATURE_TARGET		31
#define SUBPKT_EMBEDDED_SIGNATURE	32

#define UNCOMPRESSED			0
#define ZIP_COMPRESSION			1
#define ZLIB_COMPRESSION		2
#define BZIP2_COMPRESSION		3

/* get a 16 bit integer, in host order */
static uint16_t
get_16(uint8_t *p)
{
	uint16_t	u16;

	memcpy(&u16, p, sizeof(u16));
	return pgp_ntoh16(u16);
}

/* get a 32 bit integer, in host order */
static uint32_t
get_32(uint8_t *p)
{
	uint32_t	u32;

	memcpy(&u32, p, sizeof(u32));
	return pgp_ntoh32(u32);
}

#define HOURSECS	(int64_t)(60 * 60)
#define DAYSECS		(int64_t)(24 * 60 * 60)
#define MONSECS		(int64_t)(30 * DAYSECS)
#define YEARSECS	(int64_t)(365 * DAYSECS)

/* format (human readable) time into memory */
static size_t
fmt_time(obuf_t *obuf, const char *header, int64_t n, const char *trailer, int relative)
{
	struct tm	tm;
	time_t		elapsed;
	time_t		now;
	time_t		t;
	char		newbuf[128];
	int		cc;

	t = (time_t)n;
	now = time(NULL);
	elapsed = now - t;
	gmtime_r(&t, &tm);            
	cc = snprintf(newbuf, sizeof(newbuf), "%04d-%02d-%02d",
		tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
	if (!obuf_add_mem(obuf, header, strlen(header)) ||
	    !obuf_add_mem(obuf, newbuf, cc)) {
		return 0;
	}
	if (relative) {
		cc = snprintf(newbuf, sizeof(newbuf),
			" (%lldy %lldm %lldd %lldh %s)",
			llabs((long long)elapsed / YEARSECS),
			llabs(((long long)elapsed % YEARSECS) / MONSECS),
			llabs(((long long)elapsed % MONSECS) / DAYSECS),
			llabs(((long long)elapsed % DAYSECS) / HOURSECS),
			(now > t) ? "ago" : "ahead");
		if (!obuf_add_mem(obuf, newbuf, cc)) {
			return 0;
		}
	}
	return (*trailer) ? obuf_add_mem(obuf, trailer, strlen(trailer)) : 1;
}

/* dump key mpis to stdout */
static void
print_key_mpis(pgpv_bignum_t *v, uint8_t keyalg)
{
	char	s[8192];

	switch(keyalg) {
	case PUBKEY_RSA_ENCRYPT_OR_SIGN:
	case PUBKEY_RSA_ENCRYPT:
	case PUBKEY_RSA_SIGN:
		fmt_mpi(s, sizeof(s), &v[RSA_N], "rsa.n", 1);
		printf("%s", s);
		fmt_mpi(s, sizeof(s), &v[RSA_E], "rsa.e", 1);
		printf("%s", s);
		break;
	case PUBKEY_ELGAMAL_ENCRYPT:
		fmt_mpi(s, sizeof(s), &v[ELGAMAL_P], "elgamal.p", 1);
		printf("%s", s);
		fmt_mpi(s, sizeof(s), &v[ELGAMAL_Y], "elgamal.y", 1);
		printf("%s", s);
		break;
	case PUBKEY_DSA:
		fmt_mpi(s, sizeof(s), &v[DSA_P], "dsa.p", 1);
		printf("%s", s);
		fmt_mpi(s, sizeof(s), &v[DSA_Q], "dsa.q", 1);
		printf("%s", s);
		fmt_mpi(s, sizeof(s), &v[DSA_G], "dsa.g", 1);
		printf("%s", s);
		fmt_mpi(s, sizeof(s), &v[DSA_Y], "dsa.y", 1);
		printf("%s", s);
		break;
	default:
		printf("hi, unusual keyalg %u\n", keyalg);
		break;
	}
}

/* get an mpi, including 2 byte length */
static int
get_mpi(pgpv_bignum_t *mpi, uint8_t *p, size_t pktlen, size_t *off)
{
	size_t	bytes;

	mpi->bits = get_16(p);
	if ((bytes = (size_t)BITS_TO_BYTES(mpi->bits)) > pktlen) {
		return 0;
	}
	*off += sizeof(mpi->bits);
	mpi->bn = PGPV_BN_bin2bn(&p[sizeof(mpi->bits)], (int)bytes, NULL);
	*off += bytes;
	return 1;
}

/* read mpis in signature */
static int
read_signature_mpis(pgpv_sigpkt_t *sigpkt, uint8_t *p, size_t pktlen)
{
	size_t	off;

	off = 0;
	switch(sigpkt->sig.keyalg) {
	case PUBKEY_RSA_ENCRYPT_OR_SIGN:
	case PUBKEY_RSA_SIGN:
	case PUBKEY_RSA_ENCRYPT:
		if (!get_mpi(&sigpkt->sig.bn[RSA_SIG], p, pktlen, &off)) {
			printf("sigpkt->version %d, rsa sig weird\n", sigpkt->sig.version);
			return 0;
		}
		break;
	case PUBKEY_DSA:
	case PUBKEY_ECDSA:
	case PUBKEY_ELGAMAL_ENCRYPT_OR_SIGN: /* deprecated */
		if (!get_mpi(&sigpkt->sig.bn[DSA_R], p, pktlen, &off) ||
		    !get_mpi(&sigpkt->sig.bn[DSA_S], &p[off], pktlen, &off)) {
			printf("sigpkt->version %d, dsa/elgamal sig weird\n", sigpkt->sig.version);
			return 0;
		}
		break;
	default:
		printf("weird type of sig! %d\n", sigpkt->sig.keyalg);
		return 0;
	}
	return 1;
}

/* add the signature sub packet to the signature packet */
static int
add_subpacket(pgpv_t *pgp, pgpv_sigpkt_t *sigpkt, uint8_t tag, uint8_t *p, uint16_t len)
{
	pgpv_sigsubpkt_t	subpkt;

	memset(&subpkt, 0x0, sizeof(subpkt));
	subpkt.s.size = len;
	subpkt.critical = 0;
	subpkt.tag = tag;
	subpkt.s.data = p;
	ARRAY_APPEND(sigpkt->subpackets, ARRAY_COUNT(pgp->subpkts));
	ARRAY_APPEND(pgp->subpkts, subpkt);
	return 1;
}

/* read the subpackets in the signature */
static int
read_sig_subpackets(pgpv_t *pgp, pgpv_sigpkt_t *sigpkt, uint8_t *p, size_t pktlen)
{
	pgpv_sigsubpkt_t	 subpkt;
	const int		 is_subpkt = 0;
	unsigned		 lenlen;
	unsigned		 i;
	uint8_t			*start;

	start = p;
	for (i = 0 ; (unsigned)(p - start) < sigpkt->subslen ; i++) {
		memset(&subpkt, 0x0, sizeof(subpkt));
		subpkt.s.size = get_pkt_len(1, p, 0, is_subpkt);
		lenlen = get_pkt_len_len(1, p, is_subpkt);
		if (lenlen > pktlen) {
			printf("weird lenlen %u\n", lenlen);
			return 0;
		}
		p += lenlen;
		subpkt.critical = (*p & SUBPKT_CRITICAL_MASK);
		subpkt.tag = (*p & SUBPKT_TAG_MASK);
		p += 1;
		switch(subpkt.tag) {
		case SUBPKT_SIG_BIRTH:
			sigpkt->sig.birth = (int64_t)get_32(p);
			break;
		case SUBPKT_SIG_EXPIRY:
			sigpkt->sig.expiry = (int64_t)get_32(p);
			break;
		case SUBPKT_KEY_EXPIRY:
			sigpkt->sig.keyexpiry = (int64_t)get_32(p);
			break;
		case SUBPKT_ISSUER:
			memcpy(sigpkt->sig.signer, p, sizeof(sigpkt->sig.signer));
			break;
		case SUBPKT_SIGNER_ID:
			memcpy(sigpkt->sig.signer, p, sizeof(sigpkt->sig.signer));
			break;
		case SUBPKT_TRUST_SIG:
			sigpkt->sig.trustsig = *p;
			break;
		case SUBPKT_REGEXP:
			sigpkt->sig.regexp = (char *)(void *)p;
			break;
		case SUBPKT_REVOCABLE:
			sigpkt->sig.revocable = *p;
			break;
		case SUBPKT_PREF_SYMMETRIC_ALG:
			sigpkt->sig.pref_symm_alg = *p;
			break;
		case SUBPKT_REVOCATION_KEY:
			sigpkt->sig.revoke_sensitive = (*p & 0x40);
			sigpkt->sig.revoke_alg = p[1];
			sigpkt->sig.revoke_fingerprint = &p[2];
			break;
		case SUBPKT_NOTATION:
			sigpkt->sig.notation = *p;
			break;
		case SUBPKT_PREF_HASH_ALG:
			sigpkt->sig.pref_hash_alg = *p;
			break;
		case SUBPKT_PREF_COMPRESS_ALG:
			sigpkt->sig.pref_compress_alg = *p;
			break;
		case SUBPKT_PREF_KEY_SERVER:
			sigpkt->sig.pref_key_server = (char *)(void *)p;
			break;
		case SUBPKT_KEY_SERVER_PREFS:
			sigpkt->sig.key_server_modify = *p;
			break;
		case SUBPKT_KEY_FLAGS:
			sigpkt->sig.type_key = *p;
			break;
		case SUBPKT_PRIMARY_USER_ID:
			sigpkt->sig.primary_userid = *p;
			break;
		case SUBPKT_POLICY_URI:
			sigpkt->sig.policy = (char *)(void *)p;
			break;
		case SUBPKT_FEATURES:
			sigpkt->sig.features = (char *)(void *)p;
			break;
		case SUBPKT_REVOCATION_REASON:
			sigpkt->sig.revoked = *p++ + 1;
			sigpkt->sig.why_revoked = (char *)(void *)p;
			break;
		default:
			printf("Ignoring unusual/reserved signature subpacket %d\n", subpkt.tag);
			break;
		}
		subpkt.s.data = p;
		p += subpkt.s.size - 1;
		ARRAY_APPEND(sigpkt->subpackets, ARRAY_COUNT(pgp->subpkts));
		ARRAY_APPEND(pgp->subpkts, subpkt);
	}
	return 1;
}

/* parse signature packet */
static int
read_sigpkt(pgpv_t *pgp, uint8_t mement, pgpv_sigpkt_t *sigpkt, uint8_t *p, size_t pktlen)
{
	unsigned	 lenlen;
	uint8_t		*base;

	make_ref(pgp, mement, &sigpkt->sig.hashstart);
	base = p;
	switch(sigpkt->sig.version = *p++) {
	case 2:
	case 3:
		if ((lenlen = *p++) != 5) {
			printf("read_sigpkt: hashed length not 5\n");
			return 0;
		}
		sigpkt->sig.hashlen = lenlen;
		/* put birthtime into a subpacket */
		sigpkt->sig.type = *p++;
		add_subpacket(pgp, sigpkt, SUBPKT_SIG_BIRTH, p, sizeof(uint32_t));
		sigpkt->sig.birth = (int64_t)get_32(p);
		p += sizeof(uint32_t);
		memcpy(sigpkt->sig.signer, p, sizeof(sigpkt->sig.signer));
		add_subpacket(pgp, sigpkt, SUBPKT_SIGNER_ID, p, PGPV_KEYID_LEN);
		p += PGPV_KEYID_LEN;
		sigpkt->sig.keyalg = *p++;
		sigpkt->sig.hashalg = *p++;
		sigpkt->sig.hash2 = p;
		if (!read_signature_mpis(sigpkt, sigpkt->sig.mpi = p + 2, pktlen)) {
			printf("read_sigpkt: can't read sigs v3\n");
			return 0;
		}
		break;
	case 4:
		sigpkt->sig.type = *p++;
		sigpkt->sig.keyalg = *p++;
		sigpkt->sig.hashalg = *p++;
		sigpkt->subslen = get_16(p);
		p += sizeof(sigpkt->subslen);
		if (!read_sig_subpackets(pgp, sigpkt, p, pktlen)) {
			printf("read_sigpkt: can't read sig subpackets, v4\n");
			return 0;
		}
		if (sigpkt->sig.signer[0] == 0x0) {
			memcpy(sigpkt->sig.signer,
				get_ref(&sigpkt->sig.hashstart) + 16,
				sizeof(sigpkt->sig.signer));
		}
		p += sigpkt->subslen;
		sigpkt->sig.hashlen = (unsigned)(p - base);
		sigpkt->unhashlen = get_16(p);
		p += sizeof(sigpkt->unhashlen) + sigpkt->unhashlen;
		sigpkt->sig.hash2 = p;
		if (!read_signature_mpis(sigpkt, sigpkt->sig.mpi = p + 2, pktlen)) {
			printf("read_sigpkt: can't read sigs, v4\n");
			return 0;
		}
		break;
	default:
		printf("read_sigpkt: unusual signature version (%u)\n", sigpkt->sig.version);
		break;
	}
	return 1;
}


/* this parses compressed data, decompresses it, and calls the parser again */
static int
read_compressed(pgpv_t *pgp, pgpv_compress_t *compressed, uint8_t *p, size_t len)
{
	pgpv_mem_t	*unzmem;
	bz_stream	 bz;
	z_stream	 z;
	int		 ok = 0;

	compressed->compalg = *p;
	compressed->s.size = len;
	if ((compressed->s.data = calloc(1, len)) == NULL) {
		printf("read_compressed: can't allocate %zu length\n", len);
		return 0;
	}
	switch(compressed->compalg) {
	case UNCOMPRESSED:
		printf("not implemented %d compression yet\n", compressed->compalg);
		return 0;
	default:
		break;
	}
	ARRAY_EXPAND(pgp->areas);
	ARRAY_COUNT(pgp->areas) += 1;
	unzmem = &ARRAY_LAST(pgp->areas);
	unzmem->size = len * 10;
	unzmem->dealloc = FREE_MEM;
	if ((unzmem->mem = calloc(1, unzmem->size)) == NULL) {
		printf("read_compressed: calloc failed!\n");
		return 0;
	}
	switch(compressed->compalg) {
	case ZIP_COMPRESSION:
	case ZLIB_COMPRESSION:
		memset(&z, 0x0, sizeof(z));
		z.next_in = p + 1;
		z.avail_in = (unsigned)(len - 1);
		z.total_in = (unsigned)(len - 1);
		z.next_out = unzmem->mem;
		z.avail_out = (unsigned)unzmem->size;
		z.total_out = (unsigned)unzmem->size;
		break;
	case BZIP2_COMPRESSION:
		memset(&bz, 0x0, sizeof(bz));
		bz.avail_in = (unsigned)(len - 1);
		bz.next_in = (char *)(void *)p + 1;
		bz.next_out = (char *)(void *)unzmem->mem;
		bz.avail_out = (unsigned)unzmem->size;
		break;
	}
	switch(compressed->compalg) {
	case ZIP_COMPRESSION:
		ok = (inflateInit2(&z, -15) == Z_OK);
		break;
	case ZLIB_COMPRESSION:
		ok = (inflateInit(&z) == Z_OK);
		break;
	case BZIP2_COMPRESSION:
		ok = (BZ2_bzDecompressInit(&bz, 1, 0) == BZ_OK);
		break;
	}
	if (!ok) {
		printf("read_compressed: initialisation failed!\n");
		return 0;
	}
	switch(compressed->compalg) {
	case ZIP_COMPRESSION:
	case ZLIB_COMPRESSION:
		ok = (inflate(&z, Z_FINISH) == Z_STREAM_END);
		unzmem->size = z.total_out;
		break;
	case BZIP2_COMPRESSION:
		ok = (BZ2_bzDecompress(&bz) == BZ_STREAM_END);
		unzmem->size = ((uint64_t)bz.total_out_hi32 << 32) | bz.total_out_lo32;
		break;
	}
	if (!ok) {
		printf("read_compressed: inflate failed!\n");
		return 0;
	}
	return 1;
}

/* parse one pass signature packet */
static int
read_onepass_sig(pgpv_onepass_t *onepasspkt, uint8_t *mem)
{
	onepasspkt->version = mem[0];
	onepasspkt->type = mem[1];
	onepasspkt->hashalg = mem[2];
	onepasspkt->keyalg = mem[3];
	memcpy(onepasspkt->keyid, &mem[4], sizeof(onepasspkt->keyid));
	onepasspkt->nested = mem[12];
	return 1;
}

/* parse public key packet */
static int
read_pubkey(pgpv_pubkey_t *pubkey, uint8_t *mem, size_t pktlen, int pbn)
{
	size_t		 off;

	off = 0;
	pubkey->version = mem[off++];
	pubkey->birth = get_32(&mem[off]);
	off += 4;
	if (pubkey->version == 2 || pubkey->version == 3) {
		pubkey->expiry = get_16(&mem[off]) * DAYSECS;
		off += 2;
	}
	if ((pubkey->keyalg = mem[off++]) == 0) {
		pubkey->keyalg = PUBKEY_RSA_ENCRYPT_OR_SIGN;
		printf("got unusual pubkey keyalg %u\n", mem[off - 1]);
	}
	switch(pubkey->keyalg) {
	case PUBKEY_RSA_ENCRYPT_OR_SIGN:
	case PUBKEY_RSA_ENCRYPT:
	case PUBKEY_RSA_SIGN:
		if (!get_mpi(&pubkey->bn[RSA_N], &mem[off], pktlen, &off) ||
		    !get_mpi(&pubkey->bn[RSA_E], &mem[off], pktlen, &off)) {
			return 0;
		}
		break;
	case PUBKEY_ELGAMAL_ENCRYPT:
	case PUBKEY_ELGAMAL_ENCRYPT_OR_SIGN:
		if (!get_mpi(&pubkey->bn[ELGAMAL_P], &mem[off], pktlen, &off) ||
		    !get_mpi(&pubkey->bn[ELGAMAL_Y], &mem[off], pktlen, &off)) {
			return 0;
		}
		break;
	case PUBKEY_DSA:
		if (!get_mpi(&pubkey->bn[DSA_P], &mem[off], pktlen, &off) ||
		    !get_mpi(&pubkey->bn[DSA_Q], &mem[off], pktlen, &off) ||
		    !get_mpi(&pubkey->bn[DSA_G], &mem[off], pktlen, &off) ||
		    !get_mpi(&pubkey->bn[DSA_Y], &mem[off], pktlen, &off)) {
			return 0;
		}
		break;
	default:
		printf("hi, different type of pubkey here %u\n", pubkey->keyalg);
		break;
	}
	if (pbn) {
		print_key_mpis(pubkey->bn, pubkey->keyalg);
	}
	return 1;
}

/* parse a user attribute */
static int
read_userattr(pgpv_userattr_t *userattr, uint8_t *p, size_t pktlen)
{
	pgpv_string_t	subattr;
	const int 	is_subpkt = 0;
	const int	indian = 1;
	unsigned	lenlen;
	uint16_t	imagelen;
	size_t		cc;

	userattr->len = pktlen;
	for (cc = 0 ; cc < pktlen ; cc += subattr.size + lenlen + 1) {
		subattr.size = get_pkt_len(1, p, 0, is_subpkt);
		lenlen = get_pkt_len_len(1, p, is_subpkt);
		if (lenlen > pktlen) {
			printf("weird lenlen %u\n", lenlen);
			return 0;
		}
		p += lenlen;
		if (*p++ != 1) {
			printf("image type (%u) != 1. weird packet\n", *(p - 1));
		}
		memcpy(&imagelen, p, sizeof(imagelen));
		if (!*(const char *)(const void *)&indian) {
			/* big endian - byteswap length */
			imagelen = (((unsigned)imagelen & 0xff) << 8) | (((unsigned)imagelen >> 8) & 0xff);
		}
		subattr.data = p + 3;
		p += subattr.size;
		ARRAY_APPEND(userattr->subattrs, subattr);
	}
	return 1;
}

#define LITDATA_BINARY	'b'
#define LITDATA_TEXT	't'
#define LITDATA_UTF8	'u'

/* parse literal packet */
static int
read_litdata(pgpv_t *pgp, pgpv_litdata_t *litdata, uint8_t *p, size_t size)
{
	size_t	cc;

	cc = 0;
	switch(litdata->format = p[cc++]) {
	case LITDATA_BINARY:
	case LITDATA_TEXT:
	case LITDATA_UTF8:
		litdata->namelen = 0;
		break;
	default:
		printf("weird litdata format %u\n", litdata->format);
		break;
	}
	litdata->filename.size = litdata->namelen = p[cc++];
	litdata->filename.data = &p[cc];
	litdata->filename.allocated = 0;
	cc += litdata->namelen;
	litdata->secs = get_32(&p[cc]);
	cc += 4;
	litdata->s.data = &p[cc];
	litdata->len = litdata->s.size = size - cc;
	litdata->mem = ARRAY_COUNT(pgp->areas) - 1;
	litdata->offset = cc;
	return 1;
}

/* parse a single packet */
static int
read_pkt(pgpv_t *pgp, pgpv_mem_t *mem)
{
	const int	 isprimary = 1;
	pgpv_pkt_t	 pkt;
	pgpv_mem_t	*newmem;
	unsigned	 lenlen;
	uint8_t		 ispartial;
	size_t		 size;

	memset(&pkt, 0x0, sizeof(pkt));
	pkt.tag = mem->mem[mem->cc++];
	if (!(pkt.tag & PKT_ALWAYS_ON)) {
		printf("BAD PACKET - bit 7 not 1, offset %zu!\n", mem->cc - 1);
	}
	pkt.newfmt = (pkt.tag & PKT_NEWFMT_MASK);
	pkt.tag = (pkt.newfmt) ?
		(pkt.tag & PKT_NEWFMT_TAG_MASK) :
		(((unsigned)pkt.tag & PKT_OLDFMT_TAG_MASK) >> 2);
	ispartial = (pkt.newfmt && IS_PARTIAL(mem->mem[mem->cc]));
	pkt.s.size = get_pkt_len(pkt.newfmt, &mem->mem[mem->cc], mem->size - mem->cc, isprimary);
	lenlen = get_pkt_len_len(pkt.newfmt, &mem->mem[mem->cc], isprimary);
	pkt.offset = mem->cc;
	mem->cc += lenlen;
	pkt.mement = (uint8_t)(mem - ARRAY_ARRAY(pgp->areas));
	pkt.s.data = &mem->mem[mem->cc];
	if (strchr(mem->allowed, pkt.tag) == NULL) {
		printf("packet %d not allowed for operation %s\n", pkt.tag, pgp->op);
		return 0;
	}
	size = pkt.s.size;
	if (ispartial) {
		pkt.s.size = fixup_partials(pgp, &mem->mem[mem->cc - lenlen], pkt.s.size, mem->size, &size);
		newmem = &ARRAY_LAST(pgp->areas);
		pkt.mement = (uint8_t)(newmem - ARRAY_ARRAY(pgp->areas));
		pkt.s.data = newmem->mem;
		size -= 1;
	}
	switch(pkt.tag) {
	case SIGNATURE_PKT:
		if (!read_sigpkt(pgp, pkt.mement, &pkt.u.sigpkt, pkt.s.data, pkt.s.size)) {
			return 0;
		}
		break;
	case ONEPASS_SIGNATURE_PKT:
		read_onepass_sig(&pkt.u.onepass, pkt.s.data);
		break;
	case PUBKEY_PKT:
	case PUB_SUBKEY_PKT:
		break;
	case LITDATA_PKT:
		read_litdata(pgp, &pkt.u.litdata, pkt.s.data, pkt.s.size);
		break;
	case TRUST_PKT:
		pkt.u.trust.level = pkt.s.data[0];
		pkt.u.trust.amount = pkt.s.data[1];
		break;
	case USERID_PKT:
		pkt.u.userid.size = pkt.s.size;
		pkt.u.userid.data = pkt.s.data;
		pkt.u.userid.allocated = 0;
		break;
	case COMPRESSED_DATA_PKT:
		read_compressed(pgp, &pkt.u.compressed, pkt.s.data, pkt.s.size);
		ARRAY_APPEND(pgp->pkts, pkt);
		read_all_packets(pgp, &ARRAY_LAST(pgp->areas), pgp->op);
		break;
	case USER_ATTRIBUTE_PKT:
		read_userattr(&pkt.u.userattr, pkt.s.data, pkt.s.size);
		break;
	default:
		printf("hi, need to implement %d, offset %zu\n", pkt.tag, mem->cc);
		break;
	}
	mem->cc += size;
	if (pkt.tag != COMPRESSED_DATA_PKT) {
		/* compressed was added earlier to preserve pkt ordering */
		ARRAY_APPEND(pgp->pkts, pkt);
	}
	return 1;
}

/* checks the tag type of a packet */
static int
pkt_is(pgpv_t *pgp, int wanted)
{
	return (ARRAY_ELEMENT(pgp->pkts, pgp->pkt).tag == wanted);
}

/* checks the packet is a signature packet, and the signature type is the expected one */
static int
pkt_sigtype_is(pgpv_t *pgp, int wanted)
{
	if (!pkt_is(pgp, SIGNATURE_PKT)) {
		return 0;
	}
	return (ARRAY_ELEMENT(pgp->pkts, pgp->pkt).u.sigpkt.sig.type == wanted);
}

/* check for expected type of packet, and move to the next */
static int
pkt_accept(pgpv_t *pgp, int expected)
{
	int	got;

	if ((got = ARRAY_ELEMENT(pgp->pkts, pgp->pkt).tag) == expected) {
		pgp->pkt += 1;
		return 1;
	}
	printf("problem at token %zu, expcted %d, got %d\n", pgp->pkt, expected, got);
	return 0;
}

/* recognise signature (and trust) packet */
static int
recog_signature(pgpv_t *pgp, pgpv_signature_t *signature)
{
	if (!pkt_is(pgp, SIGNATURE_PKT)) {
		printf("recog_signature: not a signature packet\n");
		return 0;
	}
	memcpy(signature, &ARRAY_ELEMENT(pgp->pkts, pgp->pkt).u.sigpkt.sig, sizeof(*signature));
	pgp->pkt += 1;
	if (pkt_is(pgp, TRUST_PKT)) {
		pkt_accept(pgp, TRUST_PKT);
	}
	return 1;
}

/* recognise user id packet */
static int
recog_userid(pgpv_t *pgp, pgpv_signed_userid_t *userid)
{
	pgpv_signature_t	 signature;
	pgpv_pkt_t		*pkt;

	memset(userid, 0x0, sizeof(*userid));
	if (!pkt_is(pgp, USERID_PKT)) {
		printf("recog_userid: not %d\n", USERID_PKT);
		return 0;
	}
	pkt = &ARRAY_ELEMENT(pgp->pkts, pgp->pkt);
	userid->userid.size = pkt->s.size;
	userid->userid.data = pkt->s.data;
	userid->userid.allocated = 0;
	pgp->pkt += 1;
	while (pkt_is(pgp, SIGNATURE_PKT)) {
		if (!recog_signature(pgp, &signature)) {
			printf("recog_userid: can't recognise signature/trust\n");
			return 0;
		}
		ARRAY_APPEND(userid->signatures, ARRAY_COUNT(pgp->signatures));
		ARRAY_APPEND(pgp->signatures, signature);
		if (signature.primary_userid) {
			userid->primary_userid = signature.primary_userid;
		}
		if (signature.revoked) {
			userid->revoked = signature.revoked;
		}
	}
	return 1;
}

/* recognise user attributes packet */
static int
recog_userattr(pgpv_t *pgp, pgpv_signed_userattr_t *userattr)
{
	pgpv_signature_t	 signature;

	memset(userattr, 0x0, sizeof(*userattr));
	if (!pkt_is(pgp, USER_ATTRIBUTE_PKT)) {
		printf("recog_userattr: not %d\n", USER_ATTRIBUTE_PKT);
		return 0;
	}
	userattr->userattr = ARRAY_ELEMENT(pgp->pkts, pgp->pkt).u.userattr;
	pgp->pkt += 1;
	while (pkt_is(pgp, SIGNATURE_PKT)) {
		if (!recog_signature(pgp, &signature)) {
			printf("recog_userattr: can't recognise signature/trust\n");
			return 0;
		}
		ARRAY_APPEND(userattr->signatures, ARRAY_COUNT(pgp->signatures));
		ARRAY_APPEND(pgp->signatures, signature);
		if (signature.revoked) {
			userattr->revoked = signature.revoked;
		}
	}
	return 1;
}

/* recognise a sub key */
static int
recog_subkey(pgpv_t *pgp, pgpv_signed_subkey_t *subkey)
{
	pgpv_signature_t	 signature;
	pgpv_pkt_t		*pkt;

	pkt = &ARRAY_ELEMENT(pgp->pkts, pgp->pkt);
	memset(subkey, 0x0, sizeof(*subkey));
	read_pubkey(&subkey->subkey, pkt->s.data, pkt->s.size, 0);
	pgp->pkt += 1;
	if (pkt_sigtype_is(pgp, SIGTYPE_KEY_REVOCATION) ||
	    pkt_sigtype_is(pgp, SIGTYPE_SUBKEY_REVOCATION) ||
	    pkt_sigtype_is(pgp, SIGTYPE_CERT_REVOCATION)) {
		recog_signature(pgp, &signature);
		subkey->revoc_self_sig = signature;
	}
	do {
		if (!pkt_is(pgp, SIGNATURE_PKT)) {
			printf("recog_subkey: not signature packet at %zu\n", pgp->pkt);
			return 0;
		}
		if (!recog_signature(pgp, &signature)) {
			printf("recog_subkey: bad signature/trust at %zu\n", pgp->pkt);
			return 0;
		}
		ARRAY_APPEND(subkey->signatures, ARRAY_COUNT(pgp->signatures));
		ARRAY_APPEND(pgp->signatures, signature);
		if (signature.keyexpiry) {
			/* XXX - check it's a good key expiry */
			subkey->subkey.expiry = signature.keyexpiry;
		}
	} while (pkt_is(pgp, SIGNATURE_PKT));
	return 1;
}

/* use a sparse map for the text strings here to save space */
static const char	*keyalgs[] = {
	"[Unknown]",
	"RSA (Encrypt or Sign)",
	"RSA (Encrypt Only)",
	"RSA (Sign Only)",
	"Elgamal (Encrypt Only)",
	"DSA",
	"Elliptic Curve",
	"ECDSA",
	"Elgamal (Encrypt or Sign)"
};

#define MAX_KEYALG	21

static const char *keyalgmap = "\0\01\02\03\0\0\0\0\0\0\0\0\0\0\0\0\04\05\06\07\010\011";

/* return human readable name for key algorithm */
static const char *
fmtkeyalg(uint8_t keyalg)
{
	return keyalgs[(uint8_t)keyalgmap[(keyalg >= MAX_KEYALG) ? 0 : keyalg]];
}

/* return the number of bits in the public key */
static unsigned
numkeybits(const pgpv_pubkey_t *pubkey)
{
	switch(pubkey->keyalg) {
	case PUBKEY_RSA_ENCRYPT_OR_SIGN:
	case PUBKEY_RSA_ENCRYPT:
	case PUBKEY_RSA_SIGN:
		return pubkey->bn[RSA_N].bits;
	case PUBKEY_DSA:
	case PUBKEY_ECDSA:
		return pubkey->bn[DSA_P].bits;
		//return BITS_TO_BYTES(pubkey->bn[DSA_Q].bits) * 64;
	case PUBKEY_ELGAMAL_ENCRYPT:
	case PUBKEY_ELGAMAL_ENCRYPT_OR_SIGN:
		return pubkey->bn[ELGAMAL_P].bits;
	default:
		return 0;
	}
}

/* print a public key */
static int
fmt_pubkey(obuf_t *obuf, pgpv_pubkey_t *pubkey, const char *leader)
{
	char	newbuf[128];
	int	cc;

	cc = snprintf(newbuf, sizeof(newbuf), " %u/%s ",
		numkeybits(pubkey), fmtkeyalg(pubkey->keyalg));
	if (!obuf_add_mem(obuf, leader, strlen(leader)) ||
	    !obuf_add_mem(obuf, newbuf, cc)) {
		return 0;
	}
	if (!fmt_binary(obuf, pubkey->keyid, PGPV_KEYID_LEN)) {
		return 0;
	}
	if (!fmt_time(obuf, " ", pubkey->birth, "", 0)) {
		return 0;
	}
	if (pubkey->expiry) {
		if (!fmt_time(obuf, " [Expiry ", pubkey->birth + pubkey->expiry, "]", 0)) {
			return 0;
		}
	}
	if (!obuf_add_mem(obuf, "\n", 1)) {
		return 0;
	}
	return fmt_fingerprint(obuf, &pubkey->fingerprint, "fingerprint  ");
}

/* we add 1 to revocation value to denote compromised */
#define COMPROMISED	(0x02 + 1)

/* format a userid - used to order the userids when formatting */
static int
fmt_userid(obuf_t *obuf, pgpv_t *pgp, pgpv_primarykey_t *primary, uint8_t u)
{
	pgpv_signed_userid_t	*userid;
	const char		*s;
	uint64_t		 id;

	id = ARRAY_ELEMENT(primary->signed_userids, u);
	userid = &ARRAY_ELEMENT(pgp->signed_userids, id);
	s = (userid->revoked == COMPROMISED) ? " [COMPROMISED AND REVOKED]\n" :
		(userid->revoked) ? " [REVOKED]\n" : "\n";
	return obuf_add_mem(obuf, "uid           ", 14) &&
		obuf_add_mem(obuf, userid->userid.data, userid->userid.size) &&
		obuf_add_mem(obuf, s, strlen(s));
}

/* format a trust sig - used to order the userids when formatting */
static int
fmt_trust(obuf_t *obuf, pgpv_signature_t *sig)
{
	if (!obuf_add_mem(obuf, "trust          ", 15) ||
	    !fmt_binary(obuf, sig->signer, PGPV_KEYID_LEN)) {
		return 0;
	}
	return obuf_add_mem(obuf, "\n", 1);
}

/* print a primary key, per RFC 4880 */
static int
fmt_primary(obuf_t *obuf, pgpv_t *pgp, pgpv_primarykey_t *primary, unsigned subkey, const char *modifiers)
{
	pgpv_signed_userid_t	*userid;
	pgpv_signed_subkey_t	*signed_subkey;
	pgpv_pubkey_t		*pubkey;
	unsigned		 i;
	unsigned		 j;
	uint64_t		 id;

	if (subkey == 0) {
		pubkey = &primary->primary;
	} else {
		id = ARRAY_ELEMENT(primary->signed_subkeys, subkey);
		pubkey = &ARRAY_ELEMENT(pgp->signed_subkeys, id).subkey;
	}
	if (!fmt_pubkey(obuf, pubkey, "signature    ")) {
		return 0;
	}
	if (!fmt_userid(obuf, pgp, primary, primary->primary_userid)) {
		return 0;
	}
	for (i = 0 ; i < ARRAY_COUNT(primary->signed_userids) ; i++) {
		if (i != primary->primary_userid) {
			if (!fmt_userid(obuf, pgp, primary, i)) {
				return 0;
			}
			if (strcasecmp(modifiers, "trust") == 0) {
				id = ARRAY_ELEMENT(primary->signed_userids, i);
				userid = &ARRAY_ELEMENT(pgp->signed_userids, id);
				for (j = 0 ; j < ARRAY_COUNT(userid->signatures) ; j++) {
					if (!fmt_trust(obuf, &ARRAY_ELEMENT(pgp->signatures,
							ARRAY_ELEMENT(userid->signatures, j)))) {
						return 0;
					}
				}
			}
		}
	}
	if (strcasecmp(modifiers, "subkeys") == 0) {
		for (i = 0 ; i < ARRAY_COUNT(primary->signed_subkeys) ; i++) {
			id = ARRAY_ELEMENT(primary->signed_subkeys, i);
			signed_subkey = &ARRAY_ELEMENT(pgp->signed_subkeys, id);
			if (!fmt_pubkey(obuf, &signed_subkey->subkey, "encryption")) {
				return 0;
			}
		}
	}
	return obuf_add_mem(obuf, "\n", 1);
}


/* check the padding on the signature */
static int
rsa_padding_check_none(uint8_t *to, int tlen, const uint8_t *from, int flen, int num)
{
	USE_ARG(num);
	if (flen > tlen) {
		printf("from length larger than to length\n");
		return -1;
	}
	(void) memset(to, 0x0, (size_t)(tlen - flen));
	(void) memcpy(to + tlen - flen, from, (size_t)flen);
	return tlen;
}

#define RSA_MAX_MODULUS_BITS	16384
#define RSA_SMALL_MODULUS_BITS	3072
#define RSA_MAX_PUBEXP_BITS	64 /* exponent limit enforced for "large" modulus only */

/* check against the exponent/moudulo operation */
static int
lowlevel_rsa_public_check(const uint8_t *encbuf, int enclen, uint8_t *dec, const rsa_pubkey_t *rsa)
{
	uint8_t		*decbuf;
	PGPV_BIGNUM		*decbn;
	PGPV_BIGNUM		*encbn;
	int		 decbytes;
	int		 nbytes;
	int		 r;

	nbytes = 0;
	r = -1;
	decbuf = NULL;
	decbn = encbn = NULL;
	if (PGPV_BN_num_bits(rsa->n) > RSA_MAX_MODULUS_BITS) {
		printf("rsa r modulus too large\n");
		goto err;
	}
	if (PGPV_BN_cmp(rsa->n, rsa->e) <= 0) {
		printf("rsa r bad n value\n");
		goto err;
	}
	if (PGPV_BN_num_bits(rsa->n) > RSA_SMALL_MODULUS_BITS &&
	    PGPV_BN_num_bits(rsa->e) > RSA_MAX_PUBEXP_BITS) {
		printf("rsa r bad exponent limit\n");
		goto err;
	}
	nbytes = PGPV_BN_num_bytes(rsa->n);
	if ((encbn = PGPV_BN_new()) == NULL ||
	    (decbn = PGPV_BN_new()) == NULL ||
	    (decbuf = calloc(1, (size_t)nbytes)) == NULL) {
		printf("allocation failure\n");
		goto err;
	}
	if (enclen > nbytes) {
		printf("rsa r > mod len\n");
		goto err;
	}
	if (PGPV_BN_bin2bn(encbuf, enclen, encbn) == NULL) {
		printf("null encrypted BN\n");
		goto err;
	}
	if (PGPV_BN_cmp(encbn, rsa->n) >= 0) {
		printf("rsa r data too large for modulus\n");
		goto err;
	}
	if (PGPV_BN_mod_exp(decbn, encbn, rsa->e, rsa->n, NULL) < 0) {
		printf("PGPV_BN_mod_exp < 0\n");
		goto err;
	}
	decbytes = PGPV_BN_num_bytes(decbn);
	(void) PGPV_BN_bn2bin(decbn, decbuf);
	if ((r = rsa_padding_check_none(dec, nbytes, decbuf, decbytes, 0)) < 0) {
		printf("rsa r padding check failed\n");
	}
err:
	PGPV_BN_clear_free(encbn);
	PGPV_BN_clear_free(decbn);
	if (decbuf != NULL) {
		(void) memset(decbuf, 0x0, nbytes);
		free(decbuf);
	}
	return r;
}

/* verify */
static int
rsa_public_decrypt(int enclen, const unsigned char *enc, unsigned char *dec, RSA *rsa, int padding)
{
	rsa_pubkey_t	pub;
	int		ret;

	if (enc == NULL || dec == NULL || rsa == NULL) {
		return 0;
	}
	USE_ARG(padding);
	(void) memset(&pub, 0x0, sizeof(pub));
	pub.n = PGPV_BN_dup(rsa->n);
	pub.e = PGPV_BN_dup(rsa->e);
	ret = lowlevel_rsa_public_check(enc, enclen, dec, &pub);
	PGPV_BN_clear_free(pub.n);
	PGPV_BN_clear_free(pub.e);
	return ret;
}

#define SUBKEY_LEN(x)	(80 + 80)
#define SIG_LEN		80
#define UID_LEN		80

/* return worst case number of bytes needed to format a primary key */
static size_t
estimate_primarykey_size(pgpv_primarykey_t *primary)
{
	size_t		cc;

	cc = SUBKEY_LEN("signature") +
		(ARRAY_COUNT(primary->signed_userids) * UID_LEN) +
		(ARRAY_COUNT(primary->signed_subkeys) * SUBKEY_LEN("encrypt uids"));
	return cc;
}

/* use public decrypt to verify a signature */
static int 
pgpv_rsa_public_decrypt(uint8_t *out, const uint8_t *in, size_t length, const pgpv_pubkey_t *pubkey)
{
	RSA            *orsa;
	int             n;

	if ((orsa = calloc(1, sizeof(*orsa))) == NULL) {
		return 0;
	}
	orsa->n = pubkey->bn[RSA_N].bn;
	orsa->e = pubkey->bn[RSA_E].bn;
	n = rsa_public_decrypt((int)length, in, out, orsa, RSA_NO_PADDING);
	orsa->n = orsa->e = NULL;
	free(orsa);
	return n;
}

/* verify rsa signature */
static int
rsa_verify(uint8_t *calculated, unsigned calclen, uint8_t hashalg, pgpv_bignum_t *bn, pgpv_pubkey_t *pubkey)
{
	unsigned	 prefixlen;
	unsigned	 decryptc;
	unsigned	 i;
	uint8_t		 decrypted[8192];
	uint8_t		 sigbn[8192];
	uint8_t		 prefix[64];
	size_t		 keysize;

	keysize = BITS_TO_BYTES(pubkey->bn[RSA_N].bits);
	PGPV_BN_bn2bin(bn[RSA_SIG].bn, sigbn);
	decryptc = pgpv_rsa_public_decrypt(decrypted, sigbn, BITS_TO_BYTES(bn[RSA_SIG].bits), pubkey);
	if (decryptc != keysize || (decrypted[0] != 0 || decrypted[1] != 1)) {
		return 0;
	}
	if ((prefixlen = digest_get_prefix((unsigned)hashalg, prefix, sizeof(prefix))) == 0) {
		printf("rsa_verify: unknown hash algorithm: %d\n", hashalg);
		return 0;
	}
	for (i = 2 ; i < keysize - prefixlen - calclen - 1 ; i++) {
		if (decrypted[i] != 0xff) {
			return 0;
		}
	}
	if (decrypted[i++] != 0x0) {
		return 0;
	}
	if (memcmp(&decrypted[i], prefix, prefixlen) != 0) {
		printf("rsa_verify: wrong hash algorithm\n");
		return 0;
	}
	return memcmp(&decrypted[i + prefixlen], calculated, calclen) == 0;
}

/* return 1 if bn <= 0 */
static int
bignum_is_bad(PGPV_BIGNUM *bn)
{
	return PGPV_BN_is_zero(bn) || PGPV_BN_is_negative(bn);
}

#define BAD_BIGNUM(s, k)	\
	(bignum_is_bad((s)->bn) || PGPV_BN_cmp((s)->bn, (k)->bn) >= 0)

#ifndef DSA_MAX_MODULUS_BITS
#define DSA_MAX_MODULUS_BITS      10000
#endif

/* verify DSA signature */
static int
verify_dsa_sig(uint8_t *calculated, unsigned calclen, pgpv_bignum_t *sig, pgpv_pubkey_t *pubkey)
{
	PGPV_BIGNUM	 *M;
	PGPV_BIGNUM	 *W;
	PGPV_BIGNUM	 *t1;
	unsigned	  qbits;
	uint8_t		  calcnum[128];
	uint8_t		  signum[128];
	int		  ret;

	if (pubkey->bn[DSA_P].bn == NULL ||
	    pubkey->bn[DSA_Q].bn == NULL ||
	    pubkey->bn[DSA_G].bn == NULL) {
		return 0;
	}
	M = W = t1 = NULL;
	qbits = pubkey->bn[DSA_Q].bits;
	switch(qbits) {
	case 160:
	case 224:
	case 256:
		break;
	default:
		printf("dsa: bad # of Q bits\n");
		return 0;
	}
	if (pubkey->bn[DSA_P].bits > DSA_MAX_MODULUS_BITS) {
		printf("dsa: p too large\n");
		return 0;
	}
	if (calclen > SHA256_DIGEST_LENGTH) {
		printf("dsa: digest too long\n");
		return 0;
	}
	ret = 0;
	if ((M = PGPV_BN_new()) == NULL || (W = PGPV_BN_new()) == NULL || (t1 = PGPV_BN_new()) == NULL ||
	    BAD_BIGNUM(&sig[DSA_R], &pubkey->bn[DSA_Q]) ||
	    BAD_BIGNUM(&sig[DSA_S], &pubkey->bn[DSA_Q]) ||
	    PGPV_BN_mod_inverse(W, sig[DSA_S].bn, pubkey->bn[DSA_Q].bn, NULL) == NULL) {
		goto done;
	}
	if (calclen > qbits / 8) {
		calclen = qbits / 8;
	}
	if (PGPV_BN_bin2bn(calculated, (int)calclen, M) == NULL ||
	    !PGPV_BN_mod_mul(M, M, W, pubkey->bn[DSA_Q].bn, NULL) ||
	    !PGPV_BN_mod_mul(W, sig[DSA_R].bn, W, pubkey->bn[DSA_Q].bn, NULL) ||
	    !PGPV_BN_mod_exp(t1, pubkey->bn[DSA_G].bn, M, pubkey->bn[DSA_P].bn, NULL) ||
	    !PGPV_BN_mod_exp(W, pubkey->bn[DSA_Y].bn, W, pubkey->bn[DSA_P].bn, NULL) ||
	    !PGPV_BN_mod_mul(t1, t1, W, pubkey->bn[DSA_P].bn, NULL) ||
	    !PGPV_BN_div(NULL, t1, t1, pubkey->bn[DSA_Q].bn, NULL)) {
		goto done;
	}
	/* only compare the first q bits */
	PGPV_BN_bn2bin(t1, calcnum);
	PGPV_BN_bn2bin(sig[DSA_R].bn, signum);
	ret = memcmp(calcnum, signum, BITS_TO_BYTES(qbits)) == 0;
done:
	if (M) {
		PGPV_BN_clear_free(M);
	}
	if (W) {
		PGPV_BN_clear_free(W);
	}
	if (t1) {
		PGPV_BN_clear_free(t1);
	}
	return ret;
}

#define TIME_SNPRINTF(_cc, _buf, _size, _fmt, _val)	do {		\
	time_t	 _t;							\
	char	*_s;							\
									\
	_t = _val;							\
	_s = ctime(&_t);						\
	_cc += snprintf(_buf, _size, _fmt, _s);				\
} while(/*CONSTCOND*/0)

/* check dates on signature and key are valid */
static size_t
valid_dates(pgpv_signature_t *signature, pgpv_pubkey_t *pubkey, char *buf, size_t size)
{
	time_t	 now;
	time_t	 t;
	size_t	 cc;

	cc = 0;
	if (signature->birth < pubkey->birth) {
		TIME_SNPRINTF(cc, buf, size, "Signature time (%.24s) was before pubkey creation ", signature->birth);
		TIME_SNPRINTF(cc, &buf[cc], size - cc, "(%s)\n", pubkey->birth);
		return cc;
	}
	now = time(NULL);
	if (signature->expiry != 0) {
		if ((t = signature->birth + signature->expiry) < now) {
			TIME_SNPRINTF(cc, buf, size, "Signature expired on %.24s\n", t);
			return cc;
		}
	}
	if (now < signature->birth) {
		TIME_SNPRINTF(cc, buf, size, "Signature not valid before %.24s\n", signature->birth);
		return cc;
	}
	return 0;
}

/* check if the signing key has expired */
static int
key_expired(pgpv_pubkey_t *pubkey, char *buf, size_t size)
{
	time_t	 now;
	time_t	 t;
	size_t	 cc;

	now = time(NULL);
	cc = 0;
	if (pubkey->expiry != 0) {
		if ((t = pubkey->birth + pubkey->expiry) < now) {
			TIME_SNPRINTF(cc, buf, size, "Pubkey expired on %.24s\n", t);
			return (int)cc;
		}
	}
	if (now < pubkey->birth) {
		TIME_SNPRINTF(cc, buf, size, "Pubkey not valid before %.24s\n", pubkey->birth);
		return (int)cc;
	}
	return 0;
}

/* find the leading onepass packet */
static size_t
find_onepass(pgpv_cursor_t *cursor, size_t datastart)
{
	size_t	pkt;

	for (pkt = datastart ; pkt < ARRAY_COUNT(cursor->pgp->pkts) ; pkt++) {
		if (ARRAY_ELEMENT(cursor->pgp->pkts, pkt).tag == ONEPASS_SIGNATURE_PKT) {
			return pkt + 1;
		}
	}
	snprintf(cursor->why, sizeof(cursor->why), "No signature to verify");
	return 0;
}

static const char	*armor_begins[] = {
	"-----BEGIN PGP SIGNED MESSAGE-----\n",
	"-----BEGIN PGP MESSAGE-----\n",
	NULL
};

/* return non-zero if the buf introduces an armored message */
static int
is_armored(const char *buf, size_t size)
{
	const char	**arm;
	const char	 *nl;
	size_t		  n;

	if ((nl = memchr(buf, '\n', size)) == NULL) {
		return 0;
	}
	n = (size_t)(nl - buf);
	for (arm = armor_begins ; *arm ; arm++) {
		if (strncmp(buf, *arm, n) == 0) {
			return 1;
		}
	}
	return 0;
}

/* find first occurrence of pat binary string in block */
static void *
find_bin_string(const void *blockarg, size_t blen, const void *pat, size_t plen)
{
	const uint8_t	*block;
	const uint8_t	*bp;

	if (plen == 0) {
		return __UNCONST(blockarg);
	}
	if (blen < plen) {
		return NULL;
	}
	for (bp = block = blockarg ; (size_t)(bp - block) < blen - plen + 1 ; bp++) {
		if (memcmp(bp, pat, plen) == 0) {
			return __UNCONST(bp);
		}
	}
	return NULL;
}

/* store string in allocated memory */
static uint8_t *
pgpv_strdup(const char *s)
{
	uint8_t	*cp;
	size_t	 len;

	len = strlen(s);
	if ((cp = calloc(len + 1, 1)) != NULL) {
		memcpy(cp, s, len);
	}
	return cp;
}

#define SIGSTART	"-----BEGIN PGP SIGNATURE-----\n"
#define SIGEND		"-----END PGP SIGNATURE-----\n"

/* for ascii armor, we don't get a onepass packet - make one */
static const char 	*cons_onepass = "\304\015\003\0\0\0\0\377\377\377\377\377\377\377\377\1";

/* read ascii armor */
static int
read_ascii_armor(pgpv_cursor_t *cursor, pgpv_mem_t *mem, const char *filename)
{
	pgpv_onepass_t	*onepass;
	pgpv_sigpkt_t	*sigpkt;
	pgpv_pkt_t	 litdata;
	uint8_t		 binsig[8192];
	uint8_t		*datastart;
	uint8_t		*sigend;
	uint8_t		*p;
	size_t		 binsigsize;

	/* cons up litdata pkt */
	memset(&litdata, 0x0, sizeof(litdata));
	litdata.u.litdata.mem = ARRAY_COUNT(cursor->pgp->areas) - 1;
	p = mem->mem;
	/* jump over signed message line */
	if ((p = find_bin_string(mem->mem, mem->size, "\n\n",  2)) == NULL) {
		snprintf(cursor->why, sizeof(cursor->why), "malformed armor at offset 0");
		return 0;
	}
	p += 2;
	litdata.tag = LITDATA_PKT;
	litdata.s.data = p;
	litdata.u.litdata.offset = (size_t)(p - mem->mem);
	litdata.u.litdata.filename.data = pgpv_strdup(filename);
	litdata.u.litdata.filename.allocated = 1;
	if ((p = find_bin_string(datastart = p, mem->size - litdata.offset, SIGSTART, sizeof(SIGSTART) - 1)) == NULL) {
		snprintf(cursor->why, sizeof(cursor->why),
			"malformed armor - no sig - at %zu", (size_t)(p - mem->mem));
		return 0;
	}
	litdata.u.litdata.len = litdata.s.size = (size_t)(p - datastart);
	/* this puts p at the newline character, so it will find \n\n if no version */
	p += strlen(SIGSTART) - 1;
	if ((p = find_bin_string(p, mem->size, "\n\n",  2)) == NULL) {
		snprintf(cursor->why, sizeof(cursor->why),
			"malformed armed signature at %zu", (size_t)(p - mem->mem));
		return 0;
	}
	p += 2;
	sigend = find_bin_string(p, mem->size, SIGEND, sizeof(SIGEND) - 1);
	if (sigend == NULL) {
		snprintf(cursor->why, sizeof(cursor->why),
			"malformed armor - no end sig - at %zu",
			(size_t)(p - mem->mem));
		return 0;
	}
	binsigsize = b64decode((char *)p, (size_t)(sigend - p), binsig, sizeof(binsig));

	read_binary_memory(cursor->pgp, "signature", cons_onepass, 15);
	ARRAY_APPEND(cursor->pgp->pkts, litdata);
	read_binary_memory(cursor->pgp, "signature", binsig, binsigsize - 3);
	/* XXX - hardwired - 3 is format and length */

	/* fix up packets in the packet array now we have them there */
	onepass = &ARRAY_ELEMENT(cursor->pgp->pkts, ARRAY_COUNT(cursor->pgp->pkts) - 1 - 2).u.onepass;
	sigpkt = &ARRAY_LAST(cursor->pgp->pkts).u.sigpkt;
	memcpy(onepass->keyid, sigpkt->sig.signer, sizeof(onepass->keyid));
	onepass->hashalg = sigpkt->sig.hashalg;
	onepass->keyalg = sigpkt->sig.keyalg;
	return 1;
}

/* read ascii armor from a file */
static int
read_ascii_armor_file(pgpv_cursor_t *cursor, const char *filename)
{
	/* cons up litdata pkt */
	read_file(cursor->pgp, filename);
	return read_ascii_armor(cursor, &ARRAY_LAST(cursor->pgp->areas), filename);
}

/* read ascii armor from memory */
static int
read_ascii_armor_memory(pgpv_cursor_t *cursor, const void *p, size_t size)
{
	pgpv_mem_t	*mem;

	/* cons up litdata pkt */
	ARRAY_EXPAND(cursor->pgp->areas);
	ARRAY_COUNT(cursor->pgp->areas) += 1;
	mem = &ARRAY_LAST(cursor->pgp->areas);
	memset(mem, 0x0, sizeof(*mem));
	mem->size = size;
	mem->mem = __UNCONST(p);
	mem->dealloc = 0;
	return read_ascii_armor(cursor, mem, "[stdin]");
}

/* set up the data to verify */
static int
setup_data(pgpv_cursor_t *cursor, pgpv_t *pgp, const void *p, ssize_t size)
{
	FILE		*fp;
	char		 buf[BUFSIZ];

	if (cursor == NULL || pgp == NULL || p == NULL) {
		return 0;
	}
	memset(cursor, 0x0, sizeof(*cursor));
	ARRAY_APPEND(pgp->datastarts, pgp->pkt);
	cursor->pgp = pgp;
	if (size < 0) {
		/* we have a file name in p */
		if ((fp = fopen(p, "r")) == NULL) {
			snprintf(cursor->why, sizeof(cursor->why), "No such file '%s'", (const char *)p);
			return 0;
		}
		if (fgets(buf, (int)sizeof(buf), fp) == NULL) {
			fclose(fp);
			snprintf(cursor->why, sizeof(cursor->why), "can't read file '%s'", (const char *)p);
			return 0;
		}
		if (is_armored(buf, sizeof(buf))) {
			read_ascii_armor_file(cursor, p);
		} else {
			read_binary_file(pgp, "signature", "%s", (const char *)p);
		}
		fclose(fp);
	} else {
		if (is_armored(p, (size_t)size)) {
			read_ascii_armor_memory(cursor, p, (size_t)size);
		} else {
			read_binary_memory(pgp, "signature", p, (size_t)size);
		}
	}
	return 1;
}

/* get the data and size from litdata packet */
static uint8_t *
get_literal_data(pgpv_cursor_t *cursor, pgpv_litdata_t *litdata, size_t *size)
{
	pgpv_mem_t	*mem;

	if (litdata->s.data == NULL && litdata->s.size == 0) {
		mem = &ARRAY_ELEMENT(cursor->pgp->areas, litdata->mem);
		*size = litdata->len;
		return &mem->mem[litdata->offset]; 
	}
	*size = litdata->s.size;
	return litdata->s.data;
}

/*
RFC 4880 describes the structure of v4 keys as:

           Primary-Key
              [Revocation Self Signature]
              [Direct Key Signature...]
               User ID [Signature ...]
              [User ID [Signature ...] ...]
              [User Attribute [Signature ...] ...]
              [[Subkey [Binding-Signature-Revocation]
                      Primary-Key-Binding-Signature] ...]

and that's implemented below as a recursive descent parser.
It has had to be modified, though: see the comment

	some keys out there have user ids where they shouldn't

to look like:

           Primary-Key
              [Revocation Self Signature]
              [Direct Key Signature...]
              [User ID [Signature ...]
                 [User ID [Signature ...] ...]
                 [User Attribute [Signature ...] ...]
                 [Subkey [Binding-Signature-Revocation]
                        Primary-Key-Binding-Signature] ...]

to accommodate keyrings set up by gpg
*/

/* recognise a primary key */
static int
recog_primary_key(pgpv_t *pgp, pgpv_primarykey_t *primary)
{
	pgpv_signed_userattr_t	 userattr;
	pgpv_signed_userid_t	 userid;
	pgpv_signed_subkey_t	 subkey;
	pgpv_signature_t	 signature;
	pgpv_pkt_t		*pkt;

	pkt = &ARRAY_ELEMENT(pgp->pkts, pgp->pkt);
	memset(primary, 0x0, sizeof(*primary));
	read_pubkey(&primary->primary, pkt->s.data, pkt->s.size, 0);
	pgp->pkt += 1;
	if (pkt_sigtype_is(pgp, SIGTYPE_KEY_REVOCATION)) {
		if (!recog_signature(pgp, &primary->revoc_self_sig)) {
			printf("recog_primary_key: no signature/trust at PGPV_SIGTYPE_KEY_REVOCATION\n");
			return 0;
		}
	}
	while (pkt_sigtype_is(pgp, SIGTYPE_DIRECT_KEY)) {
		if (!recog_signature(pgp, &signature)) {
			printf("recog_primary_key: no signature/trust at PGPV_SIGTYPE_DIRECT_KEY\n");
			return 0;
		}
		if (signature.keyexpiry) {
			/* XXX - check it's a good key expiry */
			primary->primary.expiry = signature.keyexpiry;
		}
		ARRAY_APPEND(primary->signatures, ARRAY_COUNT(pgp->signatures));
		ARRAY_APPEND(pgp->signatures, signature);
	}
	/* some keys out there have user ids where they shouldn't */
	do {
		if (!recog_userid(pgp, &userid)) {
			printf("recog_primary_key: not userid\n");
			return 0;
		}
		ARRAY_APPEND(primary->signed_userids, ARRAY_COUNT(pgp->signed_userids));
		ARRAY_APPEND(pgp->signed_userids, userid);
		if (userid.primary_userid) {
			primary->primary_userid = ARRAY_COUNT(primary->signed_userids) - 1;
		}
		while (pkt_is(pgp, USERID_PKT)) {
			if (!recog_userid(pgp, &userid)) {
				printf("recog_primary_key: not signed secondary userid\n");
				return 0;
			}
			ARRAY_APPEND(primary->signed_userids, ARRAY_COUNT(pgp->signed_userids));
			ARRAY_APPEND(pgp->signed_userids, userid);
			if (userid.primary_userid) {
				primary->primary_userid = ARRAY_COUNT(primary->signed_userids) - 1;
			}
		}
		while (pkt_is(pgp, USER_ATTRIBUTE_PKT)) {
			if (!recog_userattr(pgp, &userattr)) {
				printf("recog_primary_key: not signed user attribute\n");
				return 0;
			}
			ARRAY_APPEND(primary->signed_userattrs, ARRAY_COUNT(pgp->signed_userattrs));
			ARRAY_APPEND(pgp->signed_userattrs, userattr);
		}
		while (pkt_is(pgp, PUB_SUBKEY_PKT)) {
			if (!recog_subkey(pgp, &subkey)) {
				printf("recog_primary_key: not signed public subkey\n");
				return 0;
			}
			calc_keyid(&subkey.subkey, "sha1");
			ARRAY_APPEND(primary->signed_subkeys, ARRAY_COUNT(pgp->signed_subkeys));
			ARRAY_APPEND(pgp->signed_subkeys, subkey);
		}
	} while (pgp->pkt < ARRAY_COUNT(pgp->pkts) && pkt_is(pgp, USERID_PKT));
	primary->fmtsize = estimate_primarykey_size(primary);
	return 1;
}

/* parse all of the packets for a given operation */
static int
read_all_packets(pgpv_t *pgp, pgpv_mem_t *mem, const char *op)
{
	pgpv_primarykey_t	 primary;

	if (op == NULL) {
		return 0;
	}
	if (strcmp(pgp->op = op, "pubring") == 0) {
		mem->allowed = PUBRING_ALLOWED;
		/* pubrings have thousands of small packets */
		ARRAY_EXPAND_SIZED(pgp->pkts, 0, 5000);
	} else if (strcmp(op, "signature") == 0) {
		mem->allowed = SIGNATURE_ALLOWED;
	} else {
		mem->allowed = "";
	}
	for (mem->cc = 0; mem->cc < mem->size ; ) {
		if (!read_pkt(pgp, mem)) {
			return 0;
		}
	}
	if (strcmp(op, "pubring") == 0) {
		for (pgp->pkt = 0; pgp->pkt < ARRAY_COUNT(pgp->pkts) && recog_primary_key(pgp, &primary) ; ) {
			calc_keyid(&primary.primary, "sha1");
			ARRAY_APPEND(pgp->primaries, primary);
		}
		if (pgp->pkt < ARRAY_COUNT(pgp->pkts)) {
			printf("short pubring recognition???\n");
		}
	}
	pgp->pkt = ARRAY_COUNT(pgp->pkts);
	return 1;
}

/* create a filename, read it, and then parse according to "op" */
static int
read_binary_file(pgpv_t *pgp, const char *op, const char *fmt, ...)
{
	va_list	args;
	char	buf[1024];

	va_start(args, fmt);
	vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);
	if (!read_file(pgp, buf)) {
		return 0;
	}
	return read_all_packets(pgp, &ARRAY_LAST(pgp->areas), op);
}

/* get a bignum from the buffer gap */
static int
getbignum(pgpv_bignum_t *bignum, bufgap_t *bg, char *buf, const char *header)
{
	uint32_t	 len;

	USE_ARG(header);
	(void) bufgap_getbin(bg, &len, sizeof(len));
	len = pgp_ntoh32(len);
	(void) bufgap_seek(bg, sizeof(len), BGFromHere, BGByte);
	(void) bufgap_getbin(bg, buf, len);
	bignum->bn = PGPV_BN_bin2bn((const uint8_t *)buf, (int)len, NULL);
	bignum->bits = PGPV_BN_num_bits(bignum->bn);
	(void) bufgap_seek(bg, len, BGFromHere, BGByte);
	return 1;
}

/* structure for searching for constant strings */
typedef struct str_t {
	const char	*s;		/* string */
	size_t		 len;		/* its length */
	int		 type;		/* return type */
} str_t;

static str_t	pkatypes[] = {
	{	"ssh-rsa",	7,	PUBKEY_RSA_SIGN	},
	{	"ssh-dss",	7,	PUBKEY_DSA	},
	{	"ssh-dsa",	7,	PUBKEY_DSA	},
	{	NULL,		0,	0		}
};

/* look for a string in the given array */
static int
findstr(str_t *array, const char *name)
{
	str_t	*sp;

	for (sp = array ; sp->s ; sp++) {
		if (strncmp(name, sp->s, sp->len) == 0) {
			return sp->type;
		}
	}
	return -1;
}

/* read public key from the ssh pubkey file */
static __printflike(3, 4) int
read_ssh_file(pgpv_t *pgp, pgpv_primarykey_t *primary, const char *fmt, ...)
{
	pgpv_signed_userid_t	 userid;
	pgpv_pubkey_t		*pubkey;
	struct stat		 st;
	bufgap_t		 bg;
	uint32_t		 len;
	int64_t			 off;
	va_list			 args;
	char			 hostname[256];
	char			 owner[256];
	char			*space;
	char		 	*buf;
	char		 	*bin;
	char			 newbuf[2048];
	char			 f[1024];
	int			 ok;
	int			 cc;

	USE_ARG(pgp);
	memset(primary, 0x0, sizeof(*primary));
	(void) memset(&bg, 0x0, sizeof(bg));
	va_start(args, fmt);
	vsnprintf(f, sizeof(f), fmt, args);
	va_end(args);
	if (!bufgap_open(&bg, f)) {
		(void) fprintf(stderr, "pgp_ssh2pubkey: can't open '%s'\n", f);
		return 0;
	}
	(void)stat(f, &st);
	if ((buf = calloc(1, (size_t)st.st_size)) == NULL) {
		(void) fprintf(stderr, "can't calloc %zu bytes for '%s'\n", (size_t)st.st_size, f);
		bufgap_close(&bg);
		return 0;
	}
	if ((bin = calloc(1, (size_t)st.st_size)) == NULL) {
		(void) fprintf(stderr, "can't calloc %zu bytes for '%s'\n", (size_t)st.st_size, f);
		(void) free(buf);
		bufgap_close(&bg);
		return 0;
	}

	/* move past ascii type of key */
	while (bufgap_peek(&bg, 0) != ' ') {
		if (!bufgap_seek(&bg, 1, BGFromHere, BGByte)) {
			(void) fprintf(stderr, "bad key file '%s'\n", f);
			(void) free(buf);
			bufgap_close(&bg);
			return 0;
		}
	}
	if (!bufgap_seek(&bg, 1, BGFromHere, BGByte)) {
		(void) fprintf(stderr, "bad key file '%s'\n", f);
		(void) free(buf);
		bufgap_close(&bg);
		return 0;
	}
	off = bufgap_tell(&bg, BGFromBOF, BGByte);

	if (bufgap_size(&bg, BGByte) - off < 10) {
		(void) fprintf(stderr, "bad key file '%s'\n", f);
		(void) free(buf);
		bufgap_close(&bg);
		return 0;
	}

	/* convert from base64 to binary */
	cc = bufgap_getbin(&bg, buf, (size_t)bg.bcc);
	if ((space = strchr(buf, ' ')) != NULL) {
		cc = (int)(space - buf);
	}
	cc = frombase64(bin, buf, (size_t)cc, 0);
	bufgap_delete(&bg, (uint64_t)bufgap_tell(&bg, BGFromEOF, BGByte));
	bufgap_insert(&bg, bin, cc);
	bufgap_seek(&bg, off, BGFromBOF, BGByte);

	/* get the type of key */
	(void) bufgap_getbin(&bg, &len, sizeof(len));
	len = pgp_ntoh32(len);
	if (len >= st.st_size) {
		(void) fprintf(stderr, "bad public key file '%s'\n", f);
		return 0;
	}
	(void) bufgap_seek(&bg, sizeof(len), BGFromHere, BGByte);
	(void) bufgap_getbin(&bg, buf, len);
	(void) bufgap_seek(&bg, len, BGFromHere, BGByte);

	pubkey = &primary->primary;
	pubkey->hashalg = digest_get_alg("sha256"); /* gets fixed up later */
	pubkey->version = 4;
	pubkey->birth = 0; /* gets fixed up later */
	/* get key type */
	ok = 1;
	switch (pubkey->keyalg = findstr(pkatypes, buf)) {
	case PUBKEY_RSA_ENCRYPT_OR_SIGN:
	case PUBKEY_RSA_SIGN:
		getbignum(&pubkey->bn[RSA_E], &bg, buf, "RSA E");
		getbignum(&pubkey->bn[RSA_N], &bg, buf, "RSA N");
		break;
	case PUBKEY_DSA:
		getbignum(&pubkey->bn[DSA_P], &bg, buf, "DSA P");
		getbignum(&pubkey->bn[DSA_Q], &bg, buf, "DSA Q");
		getbignum(&pubkey->bn[DSA_G], &bg, buf, "DSA G");
		getbignum(&pubkey->bn[DSA_Y], &bg, buf, "DSA Y");
		break;
	default:
		(void) fprintf(stderr, "Unrecognised pubkey type %d for '%s'\n",
				pubkey->keyalg, f);
		ok = 0;
		break;
	}

	/* check for stragglers */
	if (ok && bufgap_tell(&bg, BGFromEOF, BGByte) > 0) {
		printf("%"PRIi64" bytes left\n", bufgap_tell(&bg, BGFromEOF, BGByte));
		printf("[%s]\n", bufgap_getstr(&bg));
		ok = 0;
	}
	if (ok) {
		memset(&userid, 0x0, sizeof(userid));
		(void) gethostname(hostname, sizeof(hostname));
		if (strlen(space + 1) - 1 == 0) {
			(void) snprintf(owner, sizeof(owner), "<root@%s>",
					hostname);
		} else {
			(void) snprintf(owner, sizeof(owner), "<%.*s>",
				(int)strlen(space + 1) - 1,
				space + 1);
		}
		calc_keyid(pubkey, "sha1");
		cc = snprintf(newbuf, sizeof(newbuf), "%s (%s) %s",
			hostname, f, owner);
		userid.userid.size = cc;
		userid.userid.allocated = 1;
		if ((userid.userid.data = calloc(1, cc + 1)) == NULL) {
			ok = 0;
		} else {
			memcpy(userid.userid.data, newbuf, cc);
			ARRAY_APPEND(primary->signed_userids, ARRAY_COUNT(pgp->signed_userids));
			ARRAY_APPEND(pgp->signed_userids, userid);
			primary->fmtsize = estimate_primarykey_size(primary) + 1024;
		}
	}
	(void) free(bin);
	(void) free(buf);
	bufgap_close(&bg);
	return ok;
}

/* parse memory according to "op" */
static int
read_binary_memory(pgpv_t *pgp, const char *op, const void *memory, size_t size)
{
	pgpv_mem_t	*mem;

	ARRAY_EXPAND(pgp->areas);
	ARRAY_COUNT(pgp->areas) += 1;
	mem = &ARRAY_LAST(pgp->areas);
	memset(mem, 0x0, sizeof(*mem));
	mem->size = size;
	mem->mem = __UNCONST(memory);
	mem->dealloc = 0;
	return read_all_packets(pgp, mem, op);
}

/* fixup the detached signature packets */
static int
fixup_detached(pgpv_cursor_t *cursor, const char *f)
{
	pgpv_onepass_t	*onepass;
	const char	*dot;
	pgpv_pkt_t	 sigpkt;
	pgpv_pkt_t	 litdata;
	pgpv_mem_t	*mem;
	size_t		 el;
	char		 original[MAXPATHLEN];

	/* cons up litdata pkt */
	if ((dot = strrchr(f, '.')) == NULL || strcasecmp(dot, ".sig") != 0) {
		printf("weird filename '%s'\n", f);
		return 0;
	}
	/* hold sigpkt in a temp var while we insert onepass and litdata */
	el = ARRAY_COUNT(cursor->pgp->pkts) - 1;
	sigpkt = ARRAY_ELEMENT(cursor->pgp->pkts, el);
	ARRAY_DELETE(cursor->pgp->pkts, el);
	ARRAY_EXPAND(cursor->pgp->pkts);
	/* get onepass packet, append to packets */
	read_binary_memory(cursor->pgp, "signature", cons_onepass, 15);
	onepass = &ARRAY_ELEMENT(cursor->pgp->pkts, el).u.onepass;
	/* read the original file into litdata */
	snprintf(original, sizeof(original), "%.*s", (int)(dot - f), f);
	if (!read_file(cursor->pgp, original)) {
		printf("can't read file '%s'\n", original);
		return 0;
	}
	memset(&litdata, 0x0, sizeof(litdata));
	mem = &ARRAY_LAST(cursor->pgp->areas);
	litdata.tag = LITDATA_PKT;
	litdata.s.data = mem->mem;
	litdata.u.litdata.format = LITDATA_BINARY;
	litdata.u.litdata.offset = 0;
	litdata.u.litdata.filename.data = pgpv_strdup(original);
	litdata.u.litdata.filename.allocated = 1;
	litdata.u.litdata.mem = ARRAY_COUNT(cursor->pgp->areas) - 1;
	litdata.u.litdata.len = litdata.s.size = mem->size;
	ARRAY_APPEND(cursor->pgp->pkts, litdata);
	ARRAY_APPEND(cursor->pgp->pkts, sigpkt);
	memcpy(onepass->keyid, sigpkt.u.sigpkt.sig.signer, sizeof(onepass->keyid));
	onepass->hashalg = sigpkt.u.sigpkt.sig.hashalg;
	onepass->keyalg = sigpkt.u.sigpkt.sig.keyalg;
	return 1;
}

/* match the calculated signature against the one in the signature packet */
static int
match_sig(pgpv_cursor_t *cursor, pgpv_signature_t *signature, pgpv_pubkey_t *pubkey, uint8_t *data, size_t size)
{
	unsigned	calclen;
	uint8_t		calculated[64];
	int		match;

	calclen = pgpv_digest_memory(calculated, sizeof(calculated),
		data, size,
		get_ref(&signature->hashstart), signature->hashlen,
		(signature->type == SIGTYPE_TEXT) ? 't' : 'b');
	if (ALG_IS_RSA(signature->keyalg)) {
		match = rsa_verify(calculated, calclen, signature->hashalg, signature->bn, pubkey);
	} else if (ALG_IS_DSA(signature->keyalg)) {
		match = verify_dsa_sig(calculated, calclen, signature->bn, pubkey);
	} else {
		snprintf(cursor->why, sizeof(cursor->why), "Signature type %u not recognised", signature->keyalg);
		return 0;
	}
	if (!match && signature->type == SIGTYPE_TEXT) {
		/* second try for cleartext data, ignoring trailing whitespace */
		calclen = pgpv_digest_memory(calculated, sizeof(calculated),
			data, size,
			get_ref(&signature->hashstart), signature->hashlen, 'w');
		if (ALG_IS_RSA(signature->keyalg)) {
			match = rsa_verify(calculated, calclen, signature->hashalg, signature->bn, pubkey);
		} else if (ALG_IS_DSA(signature->keyalg)) {
			match = verify_dsa_sig(calculated, calclen, signature->bn, pubkey);
		}
	}
	if (!match) {
		snprintf(cursor->why, sizeof(cursor->why), "Signature on data did not match");
		return 0;
	}
	if (valid_dates(signature, pubkey, cursor->why, sizeof(cursor->why)) > 0) {
		return 0;
	}
	if (key_expired(pubkey, cursor->why, sizeof(cursor->why))) {
		return 0;
	}
	if (signature->revoked) {
		snprintf(cursor->why, sizeof(cursor->why), "Signature was revoked");
		return 0;
	}
	return 1;
}

/* fixup key id, with birth, keyalg and hashalg value from signature */
static int
fixup_ssh_keyid(pgpv_t *pgp, pgpv_signature_t *signature, const char *hashtype)
{
	pgpv_pubkey_t	*pubkey;
	unsigned	 i;

	for (i = 0 ; i < ARRAY_COUNT(pgp->primaries) ; i++) {
		pubkey = &ARRAY_ELEMENT(pgp->primaries, i).primary;
		pubkey->keyalg = signature->keyalg;
		calc_keyid(pubkey, hashtype);
	}
	return 1;
}

/* find key id */
static int
find_keyid(pgpv_t *pgp, const char *strkeyid, uint8_t *keyid, unsigned *sub)
{
	pgpv_signed_subkey_t	*subkey;
	pgpv_primarykey_t	*prim;
	unsigned		 i;
	unsigned		 j;
	uint64_t		 n;
	uint8_t			 binkeyid[PGPV_KEYID_LEN];
	size_t			 off;
	size_t			 cmp;

	if (strkeyid == NULL && keyid == NULL) {
		return 0;
	}
	if (strkeyid) {
		str_to_keyid(strkeyid, binkeyid);
		cmp = strlen(strkeyid) / 2;
	} else {
		memcpy(binkeyid, keyid, sizeof(binkeyid));
		cmp = PGPV_KEYID_LEN;
	}
	*sub = 0;
	off = PGPV_KEYID_LEN - cmp;
	for (i = 0 ; i < ARRAY_COUNT(pgp->primaries) ; i++) {
		prim = &ARRAY_ELEMENT(pgp->primaries, i);
		if (memcmp(&prim->primary.keyid[off], &binkeyid[off], cmp) == 0) {
			return i;
		}
		for (j = 0 ; j < ARRAY_COUNT(prim->signed_subkeys) ; j++) {
			n = ARRAY_ELEMENT(prim->signed_subkeys, j);
			subkey = &ARRAY_ELEMENT(pgp->signed_subkeys, n);
			if (memcmp(&subkey->subkey.keyid[off], &binkeyid[off], cmp) == 0) {
				*sub = j + 1;
				return i;
			}
		}

	}
	return -1;
}

/* match the signature with the id indexed by 'primary' */
static int
match_sig_id(pgpv_cursor_t *cursor, pgpv_t *pgp, pgpv_signature_t *signature, pgpv_litdata_t *litdata, unsigned primary, unsigned sub)
{
	pgpv_primarykey_t	*prim;
	pgpv_pubkey_t		*pubkey;
	uint64_t		 n;
	uint8_t			*data;
	size_t			 insize;

	cursor->sigtime = signature->birth;
	/* calc hash on data packet */
	data = get_literal_data(cursor, litdata, &insize);
	if (sub == 0) {
		pubkey = &ARRAY_ELEMENT(cursor->pgp->primaries, primary).primary;
		return match_sig(cursor, signature, pubkey, data, insize);
	}
	prim = &ARRAY_ELEMENT(cursor->pgp->primaries, primary);
	n = ARRAY_ELEMENT(prim->signed_subkeys, sub - 1);
	pubkey = &ARRAY_ELEMENT(pgp->signed_subkeys, n).subkey;
	return match_sig(cursor, signature, pubkey, data, insize);
}

/* return the packet type */
static const char *
get_packet_type(uint8_t tag)
{
	switch(tag) {
	case SIGNATURE_PKT:
		return "signature packet";
	case ONEPASS_SIGNATURE_PKT:
		return "onepass signature packet";
	case PUBKEY_PKT:
		return "pubkey packet";
	case COMPRESSED_DATA_PKT:
		return "compressed data packet";
	case MARKER_PKT:
		return "marker packet";
	case LITDATA_PKT:
		return "litdata packet";
	case TRUST_PKT:
		return "trust packet";
	case USERID_PKT:
		return "userid packet";
	case PUB_SUBKEY_PKT:
		return "public subkey packet";
	case USER_ATTRIBUTE_PKT:
		return "user attribute packet";
	default:
		return "[UNKNOWN]";
	}
}

/* check return value from getenv */
static const char *
nonnull_getenv(const char *key)
{
	char	*value;

	return ((value = getenv(key)) == NULL) ? "" : value;
}

/* free an array of bignums */
static void
free_bn_array(pgpv_bignum_t *v, unsigned n)
{
	unsigned	i;

	for (i = 0 ; i < n ; i++) {
		PGPV_BN_clear_free(v[i].bn);
		v[i].bn = NULL;
	}
}

/************************************************************************/
/* start of exported functions */
/************************************************************************/

/* close all stuff */
int
pgpv_close(pgpv_t *pgp)
{
	pgpv_primarykey_t	*primary;
	pgpv_pkt_t		*pkt;
	uint64_t		 n;
	unsigned		 i;
	unsigned		 j;

	if (pgp == NULL) {
		return 0;
	}
	for (i = 0 ; i < ARRAY_COUNT(pgp->areas) ; i++) {
		if (ARRAY_ELEMENT(pgp->areas, i).size > 0) {
			closemem(&ARRAY_ELEMENT(pgp->areas, i));
		}
	}
        ARRAY_FREE(pgp->areas);
        for (i = 0 ; i < ARRAY_COUNT(pgp->pkts) ; i++) {
                pkt = &ARRAY_ELEMENT(pgp->pkts, i);
                switch(pkt->tag) {
                case SIGNATURE_PKT:
                        ARRAY_FREE(pkt->u.sigpkt.subpackets);
                        break;
                case LITDATA_PKT:
			if (pkt->u.litdata.filename.allocated) {
				free(pkt->u.litdata.filename.data);
			}
                        break;
		case PUBKEY_PKT:
			free_bn_array(pkt->u.pubkey.bn, PGPV_MAX_PUBKEY_BN);
			break;
                case USERID_PKT:
			if (pkt->u.userid.allocated) {
				free(pkt->u.userid.data);
			}
                        break;
                case USER_ATTRIBUTE_PKT:
                        ARRAY_FREE(pkt->u.userattr.subattrs);
                        break;
                }
        }
        ARRAY_FREE(pgp->pkts);
	for (i = 0 ; i < ARRAY_COUNT(pgp->primaries) ; i++) {
		primary = &ARRAY_ELEMENT(pgp->primaries, i);
		free_bn_array(primary->primary.bn, PGPV_MAX_PUBKEY_BN);
		ARRAY_FREE(primary->signatures);
		for (j = 0 ; j < ARRAY_COUNT(primary->signed_userids) ; j++) {
			n = ARRAY_ELEMENT(primary->signed_userids, j);
			ARRAY_FREE(ARRAY_ELEMENT(pgp->signed_userids, n).signatures);
		}
		ARRAY_FREE(primary->signed_userids);
		ARRAY_FREE(primary->signed_userattrs);
		ARRAY_FREE(primary->signed_subkeys);
	}
	for (i = 0 ; i < ARRAY_COUNT(pgp->signatures) ; i++) {
		free_bn_array(ARRAY_ELEMENT(pgp->signatures, i).bn, PGPV_MAX_SIG_BN);
	}
	for (i = 0 ; i < ARRAY_COUNT(pgp->signed_subkeys) ; i++) {
		free_bn_array(ARRAY_ELEMENT(pgp->signed_subkeys, i).subkey.bn, PGPV_MAX_SIG_BN);
	}
	ARRAY_FREE(pgp->primaries);
	ARRAY_FREE(pgp->datastarts);
	ARRAY_FREE(pgp->signatures);
	ARRAY_FREE(pgp->signed_userids);
	ARRAY_FREE(pgp->signed_userattrs);
	ARRAY_FREE(pgp->signed_subkeys);
	ARRAY_FREE(pgp->subpkts);
	return 1;
}

/* free resources attached to cursor */
int
pgpv_cursor_close(pgpv_cursor_t *cursor)
{
	if (cursor) {
		ARRAY_FREE(cursor->datacookies);
		ARRAY_FREE(cursor->found);
	}
	return 0;
}

/* return the formatted entry for the primary key desired */
size_t
pgpv_get_entry(pgpv_t *pgp, unsigned ent, char **s, const char *modifiers)
{
	unsigned	subkey;
	unsigned	prim;
	obuf_t		obuf;

	prim = ((ent >> 8) & 0xffffff);
	subkey = (ent & 0xff);
	if (s == NULL || pgp == NULL || prim >= ARRAY_COUNT(pgp->primaries)) {
		return 0;
	}
	*s = NULL;
	if (modifiers == NULL || (strcasecmp(modifiers, "trust") != 0 && strcasecmp(modifiers, "subkeys") != 0)) {
		modifiers = "no-subkeys";
	}
	memset(&obuf, 0x0, sizeof(obuf));
	if (!fmt_primary(&obuf, pgp, &ARRAY_ELEMENT(pgp->primaries, prim), subkey, modifiers)) {
		return 0;
	}
	*s = (char *)obuf.v;
	return obuf.c;
}

/* make a new pgpv struct */
pgpv_t *
pgpv_new(void)
{
	return calloc(1, sizeof(pgpv_t));
}

/* make a new pgpv_cursor struct */
pgpv_cursor_t *
pgpv_new_cursor(void)
{
	return calloc(1, sizeof(pgpv_cursor_t));
}

/* get an element from the found array */
int
pgpv_get_cursor_element(pgpv_cursor_t *cursor, size_t element)
{
	if (cursor && element < ARRAY_COUNT(cursor->found)) {
		return (int)ARRAY_ELEMENT(cursor->found, element);
	}
	return -1;
}

/* verify the signed packets we have */
size_t
pgpv_verify(pgpv_cursor_t *cursor, pgpv_t *pgp, const void *p, ssize_t size)
{
	pgpv_signature_t	*signature;
	pgpv_onepass_t		*onepass;
	pgpv_litdata_t		*litdata;
	unsigned		 sub;
	size_t			 pkt;
	obuf_t			 obuf;
	int			 j;

	if (cursor == NULL || pgp == NULL || p == NULL) {
		return 0;
	}
	if (!setup_data(cursor, pgp, p, size)) {
		snprintf(cursor->why, sizeof(cursor->why), "No input data");
		return 0;
	}
	if (ARRAY_COUNT(cursor->pgp->pkts) == ARRAY_LAST(cursor->pgp->datastarts) + 1) {
		/* got detached signature here */
		if (!fixup_detached(cursor, p)) {
			snprintf(cursor->why, sizeof(cursor->why), "Can't read signed file '%s'", (const char *)p);
			return 0;
		}
	}
	if ((pkt = find_onepass(cursor, ARRAY_LAST(cursor->pgp->datastarts))) == 0) {
		snprintf(cursor->why, sizeof(cursor->why), "No signature found");
		return 0;
	}
	pkt -= 1;
	onepass = &ARRAY_ELEMENT(cursor->pgp->pkts, pkt).u.onepass;
	litdata = &ARRAY_ELEMENT(cursor->pgp->pkts, pkt + 1).u.litdata;
	signature = &ARRAY_ELEMENT(cursor->pgp->pkts, pkt + 2).u.sigpkt.sig;
	/* sanity check values in signature and onepass agree */
	if (signature->birth == 0) {
		if (!fmt_time(&obuf, "Signature creation time [",
				signature->birth, "] out of range", 0)) {
		}
		snprintf(cursor->why, sizeof(cursor->why), "%.*s", (int)obuf.c, (char *)obuf.v);
		return 0;
	}
	memset(&obuf, 0x0, sizeof(obuf));
	if (memcmp(onepass->keyid, signature->signer, PGPV_KEYID_LEN) != 0) {
		if (!fmt_binary(&obuf, onepass->keyid, (unsigned)sizeof(onepass->keyid))) {
			snprintf(cursor->why, sizeof(cursor->why), "Memory allocation failure");
			return 0;
		}
		snprintf(cursor->why, sizeof(cursor->why),
			"Signature key id %.*s does not match onepass keyid",
			(int)obuf.c, (char *)obuf.v);
		return 0;
	}
	if (onepass->hashalg != signature->hashalg) {
		snprintf(cursor->why, sizeof(cursor->why),
			"Signature hashalg %u does not match onepass hashalg %u",
			signature->hashalg, onepass->hashalg);
		return 0;
	}
	if (onepass->keyalg != signature->keyalg) {
		snprintf(cursor->why, sizeof(cursor->why),
			"Signature keyalg %u does not match onepass keyalg %u",
			signature->keyalg, onepass->keyalg);
		return 0;
	}
	if (cursor->pgp->ssh) {
		fixup_ssh_keyid(cursor->pgp, signature, "sha1");
	}
	sub = 0;
	if ((j = find_keyid(cursor->pgp, NULL, onepass->keyid, &sub)) < 0) {
		if (!fmt_binary(&obuf, onepass->keyid, (unsigned)sizeof(onepass->keyid))) {
			snprintf(cursor->why, sizeof(cursor->why), "Memory allocation failure");
			return 0;
		}
		snprintf(cursor->why, sizeof(cursor->why),
			"Signature key id %.*s not found ",
			(int)obuf.c, (char *)obuf.v);
		return 0;
	}
	if (!match_sig_id(cursor, pgp, signature, litdata, (unsigned)j, sub)) {
		snprintf(cursor->why, sizeof(cursor->why),
			"Signature does not match %.*s",
			(int)obuf.c, (char *)obuf.v);
		return 0;
	}
	ARRAY_APPEND(cursor->datacookies, pkt);
	j = ((j & 0xffffff) << 8) | (sub & 0xff);
	ARRAY_APPEND(cursor->found, j);
	return pkt + 1;
}

/* set up the pubkey keyring */
int
pgpv_read_pubring(pgpv_t *pgp, const void *keyring, ssize_t size)
{
	if (pgp == NULL) {
		return 0;
	}
	if (keyring) {
		return (size > 0) ?
			read_binary_memory(pgp, "pubring", keyring, (size_t)size) :
			read_binary_file(pgp, "pubring", "%s", (const char *)keyring);
	}
	return read_binary_file(pgp, "pubring", "%s/%s", nonnull_getenv("HOME"), ".gnupg/pubring.gpg");
}

/* set up the pubkey keyring from ssh pub key */
int
pgpv_read_ssh_pubkeys(pgpv_t *pgp, const void *keyring, ssize_t size)
{
	pgpv_primarykey_t	primary;

	USE_ARG(size);
	if (pgp == NULL) {
		return 0;
	}
	if (keyring) {
		if (!read_ssh_file(pgp, &primary, "%s", (const char *)keyring)) {
			return 0;
		}
	} else if (!read_ssh_file(pgp, &primary, "%s/%s", nonnull_getenv("HOME"), ".ssh/id_rsa.pub")) {
		return 0;
	}
	ARRAY_APPEND(pgp->primaries, primary);
	pgp->ssh = 1;
	return 1;
}

/* get verified data as a string, return its size */
size_t
pgpv_get_verified(pgpv_cursor_t *cursor, size_t cookie, char **ret)
{
	pgpv_litdata_t		*litdata;
	uint8_t			*data;
	size_t			 size;
	size_t			 pkt;

	if (ret == NULL || cursor == NULL || cookie == 0) {
		return 0;
	}
	*ret = NULL;
	if ((pkt = find_onepass(cursor, cookie - 1)) == 0) {
		return 0;
	}
	litdata = &ARRAY_ELEMENT(cursor->pgp->pkts, pkt).u.litdata;
	data = get_literal_data(cursor, litdata, &size);
	if ((*ret = calloc(1, size)) == NULL) {
		return 0;
	}
	memcpy(*ret, data, size);
	return size;
}

#define KB(x)	((x) * 1024)

/* dump all packets */
size_t
pgpv_dump(pgpv_t *pgp, char **data)
{
	ssize_t	 dumpc;
	size_t	 alloc;
	size_t	 pkt;
	size_t	 cc;
	size_t	 n;
	char	 buf[800];
	char	*newdata;

	cc = alloc = 0;
	*data = NULL;
	for (pkt = 0 ; pkt < ARRAY_COUNT(pgp->pkts) ; pkt++) {
		if (cc + KB(64) >= alloc) {
			if ((newdata = realloc(*data, alloc + KB(64))) == NULL) {
				return cc;
			}
			alloc += KB(64);
			*data = newdata;
		}
		memset(buf, 0x0, sizeof(buf));
		dumpc = netpgp_hexdump(ARRAY_ELEMENT(pgp->pkts, pkt).s.data,
				MIN((sizeof(buf) / 80) * 16,
				ARRAY_ELEMENT(pgp->pkts, pkt).s.size),
				buf, sizeof(buf));
		n = snprintf(&(*data)[cc], alloc - cc,
			"[%zu] off %zu, len %zu, tag %u, %s\n%.*s",
			pkt,
			ARRAY_ELEMENT(pgp->pkts, pkt).offset,
			ARRAY_ELEMENT(pgp->pkts, pkt).s.size,
			ARRAY_ELEMENT(pgp->pkts, pkt).tag,
			get_packet_type(ARRAY_ELEMENT(pgp->pkts, pkt).tag),
			(int)dumpc, buf);
		cc += n;
	}
	return cc;
}

/* return cursor field as a number */
int64_t
pgpv_get_cursor_num(pgpv_cursor_t *cursor, const char *field)
{
	if (cursor && field) {
		if (strcmp(field, "sigtime") == 0) {
			return cursor->sigtime;
		}
	}
	return 0;
}

/* return cursor field as a string */
char *
pgpv_get_cursor_str(pgpv_cursor_t *cursor, const char *field)
{
	if (cursor && field) {
		if (strcmp(field, "why") == 0) {
			return cursor->why;
		}
	}
	return 0;
}
