/* $Id: pgpmime.c,v 1.39 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 1999 Atsushi Onoe
 * 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * 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.
 */
#ifdef USE_PGPMIME

#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "pgp.h"
#include "cue.h"

static int
pgpmime_read_passwd(char *pass, int size, struct pgp_pkt *pkt)
{
	
	strlcpy(pass, "PGP/MIME passphrase", size);
	if (pkt->un.pubkey.ref_user != NULL) {
		strlcat(pass, " for ", size);
		strlcat(pass, pkt->un.pubkey.ref_user->un.userid.user, size);
	}
	strlcat(pass, ": ", size);
	return read_passwd(pass, pass, size);
}

static void
pgpmime_pkt_append(struct pgp_pkt **ringp, struct pgp_pkt *pkt)
{
	struct pgp_pkt *prev;

	if (pkt == NULL)
		return;
	for (prev = NULL; *ringp; ringp = &prev->next)
		prev = *ringp;
	*ringp = pkt;
	pkt->prev = prev;
}

static struct pgp_pkt *
pgpmime_read_ring(struct pgp_pkt *ring, char *name)
{
	char path[MAXPATH];
	char *p;
	struct filedb *fdb;
	struct pgp_pkt *new;

	p = getenv("HOME");
	snprintf(path, sizeof(path), "%s/%s", p ? p : "", name);
	if ((fdb = fdb_open(path)) == NULL)
		return ring;
	new = pgp_parseall((u_char *)CP(&fdb->mmap), (u_char *)CE(&fdb->mmap));
	pgpmime_pkt_append(&ring, new);
	return ring;
}

static struct pgp_pkt *
pgpmime_read_secring(void)
{
	struct pgp_pkt *secring;

	secring = pgpmime_read_ring(NULL, ".pgp/secring.cue");
	secring = pgpmime_read_ring(secring, ".gnupg/secring.gpg");
	secring = pgpmime_read_ring(secring, ".pgp/secring.skr");
	secring = pgpmime_read_ring(secring, ".pgp/secring.pgp");
	return secring;
}

static struct pgp_pkt *
pgpmime_read_pubring(void)
{
	struct pgp_pkt *pubring;

	pubring = pgpmime_read_ring(NULL, ".pgp/pubring.cue");
	pubring = pgpmime_read_ring(pubring, ".gnupg/pubring.gpg");
	pubring = pgpmime_read_ring(pubring, ".pgp/pubring.pkr");
	pubring = pgpmime_read_ring(pubring, ".pgp/pubring.pgp");
	return pubring;
}

static int
pgpmime_dearmor(struct filedb *fdb, cbuf_t *ret)
{
	int i;
	ccbuf_t *cb;
	cbuf_t cbuf;
	const char *p;
	int len, size;
	char *buf, *q;

	i = fdb->hdr_lines;

	/* get aomor headerline */
	if ((cb = fdb_read(fdb, i++)) == NULL
	||  !CSUBMATCH("-----BEGIN PGP ", cb))
		return 0;

	/* ignore armor headers */
	for (;;) {
		if ((cb = fdb_read(fdb, i++)) == NULL)
			return 0;
		if (cb->len == 0)
			break;
	}
	size = len = 0;
	buf = NULL;

	/* armored data */
	for (;;) {
		if ((cb = fdb_read(fdb, i++)) == NULL) {
			if (buf != NULL)
				free(buf);
			return 0;
		}
		if (CL(cb) == 0)
			continue;	/*XXX?*/
		p = CP(cb);
		if (*p == '=')
			break;
		if (!decode_text(&p, CE(cb), &cbuf, FDB_ENC_B64 | FDB_NOCONV))
			return 0;
		if (len + CL(&cbuf) > size) {
			q = realloc(buf, size + CHARBLOCK);
			if (q == NULL) {
				free(buf);
				return 0;
			}
			buf = q;
			size += CHARBLOCK;
		}
		memcpy(buf + len, CP(&cbuf), CL(&cbuf));
		len += CL(&cbuf);
	}
	if (buf == NULL)
		return 0;
	
	/* checksum */
	/* ignore CRC 24 stored in cb ... */

	/* get aomor tail */
	if ((cb = fdb_read(fdb, i++)) == NULL
	||  !CSUBMATCH("-----END PGP ", cb)) {
		free(buf);
		return 0;
	}
	ret->ptr = buf;
	ret->len = len;
#ifdef PGPMIME_DEBUG
 { FILE *fp = fopen("/tmp/p.pgp", "w"); fwrite(buf, len, 1, fp); fclose(fp); }
#endif
	return 1;
}

static int
pgpmime_verify_pgp(struct pgp_pkt *pgpmail, ccbuf_t *data, int legacy, ccbuf_t *from, char *errstr, size_t errstrlen)
{
	struct pgp_pkt *sign, *pubkey, *pubring, *pkt;
	time_t now;
	ccbuf_t addr;
	char *p;

	if ((sign = pgp_getpkt(pgpmail, PGP_TAG_SIGN)) == NULL) {
		strlcpy(errstr, "PGP no sign", errstrlen);
		return 0;
	}
	if ((pubring = pgpmime_read_pubring()) == NULL) {
		strlcpy(errstr, "PGP public key read failed", errstrlen);
		return 0;
	}
	for (pkt = pubring; ; pkt = pkt->next) {
		if ((pkt = pgp_getpkt(pkt, PGP_TAG_PUBLIC_KEY)) == NULL) {
			strlcpy(errstr, "PGP verify failed: ", errstrlen);
			if (sign->un.sign.mdbuf == NULL)
				strlcat(errstr,
				    "public key is not found", errstrlen);
			else
				strlcat(errstr,
				    "modified or public key is not found",
				    errstrlen);
			goto err;
		}
		if (pkt->un.pubkey.pubalg != sign->un.sign.pubalg)
			continue;
		if (memcmp(pkt->un.pubkey.keyid, sign->un.sign.keyid, 8) != 0
		&&  memcmp(sign->un.sign.keyid, pgp_nullkeyid, 8) != 0)
			continue;
		if (sign->un.sign.mdbuf == NULL) {
			if (pgp_hash_data_init(sign) < 0
			||  pgp_hash_data_update(CP(data), CE(data), sign, legacy) < 0
			||  pgp_hash_data_final(sign) < 0) {
				strlcpy(errstr, "PGP make hash failed", errstrlen);
				goto err;
			}
		}
		if (pgp_verify_sign(sign, pkt) < 0)
			continue;
		pubkey = pkt;
		break;
	}

	strlcpy(errstr, "PGP verify OK", errstrlen);
	for (pkt = pubkey->un.pubkey.ref_sign; pkt != NULL;
	    pkt = pkt->un.userid.ref_sign) {
		if (pkt->un.sign.type == PGP_SIG_REVOKE_KEY ||
		    pkt->un.sign.type == PGP_SIG_REVOKE_SUBKEY)
			break;
	}
	if (pkt != NULL)
		strlcat(errstr, " (REVOKED)", errstrlen);
	else if (pubkey->un.pubkey.expire &&
	    pubkey->un.pubkey.tstamp + pubkey->un.pubkey.expire * 86400 <
	    time(&now))
		strlcat(errstr, " (EXPIRED)", errstrlen);
	/* check from */
	for (pkt = pubkey->un.pubkey.ref_user; pkt != NULL;
	    pkt = pkt->un.userid.ref_user) {
		p = pkt->un.userid.user;
		(void)message_parse_addr(p, p + strlen(p), &addr);
		if (addr.len == from->len
		&&  memcmp(addr.ptr, from->ptr, from->len) == 0)
			break;
	}
	if (pkt == NULL) {
		p = pubkey->un.pubkey.ref_user ?
			pubkey->un.pubkey.ref_user->un.userid.user :
			"unknown user";
#ifdef notdef
		snprintf(errstr, errstrlen,
		    "PGP verify failed: signed by %s", p);
		goto err;
#else
		strlcat(errstr, ": signed by ", errstrlen);
		strlcat(errstr, p, errstrlen);
#endif
	}
	pgp_freepkt(pubring);
	return 1;

  err:
	pgp_freepkt(pubring);
	return 0;
}

static struct pgp_pkt *
pgpmime_decrypt_pgp(struct pgp_pkt *pgpmail, char *errstr, size_t errstrlen)
{
	struct pgp_pkt *secring, *seckey, *seskey, *pkt, *pgpdata;

	secring = NULL;
	if ((secring = pgpmime_read_secring()) == NULL) {
		if (errstr)
			strlcpy(errstr, "PGP secret key read failed",
			    errstrlen);
		return NULL;
	}

  try_next:
	if ((pgpdata = pgp_getpkt(pgpmail, PGP_TAG_SYMENC_DATA)) == NULL &&
	    (pgpdata = pgp_getpkt(pgpmail, PGP_TAG_SYMENC_MDC_DATA)) == NULL) {
		/* XXX */
		for (pkt = pgpmail; pkt; pkt = pkt->next) {
			if (pkt->tag == PGP_TAG_LITERAL_DATA ||
			    pkt->tag == PGP_TAG_COMP_DATA)
				goto data;
		}
		if (errstr)
			strlcpy(errstr, "PGP not encrypted", errstrlen);
		goto bad;
	}

	if (pgp_getpkt(pgpmail, PGP_TAG_PUBENC_SESKEY) == NULL) {
		/* TODO: shared key */
		if (errstr)
			strlcpy(errstr, "PGP session key not found", errstrlen);
		goto bad;
	}
	for (seckey = secring; seckey; seckey = seckey->next) {
		if (seckey->tag != PGP_TAG_SECRET_KEY
		&&  seckey->tag != PGP_TAG_SECRET_SUBKEY)
			continue;
		for (pkt = pgpmail; pkt; pkt = pkt->next) {
			if (pkt == pgpdata)
				break;
			if (pkt->tag != PGP_TAG_PUBENC_SESKEY)
				continue;
			if (pkt->un.pubses.pubalg != seckey->un.pubkey.pubalg)
				continue;
			if (memcmp(pkt->un.pubses.keyid, seckey->un.pubkey.keyid, 8) != 0
			&&  memcmp(pkt->un.pubses.keyid, pgp_nullkeyid, 8) != 0)
				continue;
			if (pgp_decrypt_seckey(seckey, pgpmime_read_passwd) < 0) {
				if (errstr)
					strlcpy(errstr,
					    "PGP decrypt secret key failed",
					    errstrlen);
				goto bad;
			}
			if (pgp_decrypt_seskey(pkt, seckey) == 0) {
				seskey = pkt;
				goto got_seskey;
			}
		}
	}
	if ((pkt = pgpdata->next) != NULL) {
		/* support mixed format */
		pkt->prev = NULL;
		pgpdata->next = NULL;
		pgp_freepkt(pgpmail);
		pgpmail = pkt;
		goto try_next;
	}
	if (errstr)
		strlcpy(errstr, "PGP session key not found for me", errstrlen);
	goto bad;
  got_seskey:
	if (pgp_decrypt_symdat(pgpdata, seskey) < 0) {
		if (errstr)
			strlcpy(errstr, "PGP decrypt data failed", errstrlen);
		goto bad;
	}
	pkt = pgpdata->un.symdat.pkt;
  data:
	/* XXX: why compression twice? */
	while (pkt->tag == PGP_TAG_COMP_DATA) {
		if (pgp_decomp_data(pkt) < 0) {
			if (errstr)
				strlcpy(errstr, "PGP decompress data failed",
				    errstrlen);
			goto bad;
		}
		pkt = pkt->un.compdat.pkt;
	}
	if (pkt->tag == PGP_TAG_SIGN || pkt->tag == PGP_TAG_ONEPASS_SIGN)
		pgpdata = pkt->next;
	else
		pgpdata = pkt;

	if (pgpdata == NULL || pgpdata->tag != PGP_TAG_LITERAL_DATA) {
		if (errstr)
			snprintf(errstr, errstrlen, "PGP decompressed data is not literal (%d)", pgpdata->tag);
		goto bad;
	}

	/* all decrypt done! */
#ifdef PGPMIME_DEBUG
    {
	char *s, *p, *ep;
	FILE *fp = fopen("/tmp/p.txt", "w");
	for (s = p = (char *)pgpdata->dbuf, ep = p + pgpdata->dlen; ; p++) {
		if (p == ep || (*p == '\r' && p[1] == '\n')) {
			fwrite(s, p - s, 1, fp);
			if (p == ep)
				break;
			putc('\n', fp);
			p++;
			s = p + 1;
		}
	}
	fclose(fp);
    }
#endif
	pgp_freepkt(secring);
	return pkt;

  bad:
	pgp_freepkt(secring);
	return NULL;
}

static int
pgpmime_decrypt(struct state *state)
{
	struct filedb *fdb, *pdb, *edb;
	int i, ret;
	char *p, *s, *ep;
	cbuf_t cbuf;
	ccbuf_t *ccb, from, ebuf, data;
	struct abuf abuf;
	struct header *hdr;
	struct pgp_pkt *pgpmail, *pkt, *pgpsign;

	message_open(state, 0);
	fdb = state->message;
	message_parseall(fdb);

	ccb = &fdb->hdr_val[HT_FROM];
	if (CP(ccb) == NULL) {
		from.ptr = "";
		from.len = 0;
	} else {
		(void)message_parse_addr(CP(ccb), CE(ccb), &from);
	}

	if (!(fdb->flags & FDB_ENCRYPTED)
	||  !(fdb->flags & FDB_MULTIPART)
	||  !CMATCH("Application/pgp-encrypted", &fdb->sproto)
	||  fdb->nextpart == NULL
	||  !CMATCH("Application/pgp-encrypted", &fdb->nextpart->type)
	||  (ccb = fdb_read(fdb->nextpart, fdb->nextpart->hdr_lines)) == NULL
	||  !CMATCH("Version: 1", ccb)
	||  (pdb = fdb->nextpart->nextpart) == NULL
	||  !CMATCH("Application/octet-stream", &fdb->nextpart->nextpart->type)
	||  fdb->nextpart->nextpart->uppart != fdb) {
		if ((fdb->flags & FDB_ENCRYPTED)
		&&  CMATCH("Application/pgp", &fdb->type)) {
			/* XXX: application/pgp */
			pdb = fdb;
		} else {
			strlcpy(state->status, "Not PGP encrypted messsage", sizeof(state->status));
			return 0;
		}
	}

	pgpmail = NULL;
	ret = -1;
	if (!pgpmime_dearmor(pdb, &cbuf)) {
		strlcpy(state->status, "PGP Armor read failed", sizeof(state->status));
		goto bad;
	}
	if ((pgpmail = pgp_parseall((u_char *)CP(&cbuf), (u_char *)CE(&cbuf))) == NULL) {
		strlcpy(state->status, "PGP parse failed", sizeof(state->status));
		goto bad;
	}
	if ((pkt = pgpmime_decrypt_pgp(pgpmail, state->status,
	    sizeof(state->status))) == NULL)
		goto bad;
	if (pkt->tag == PGP_TAG_SIGN ||
	    pkt->tag == PGP_TAG_ONEPASS_SIGN) {
		pgpsign = pkt;
		pkt = pkt->next;
	} else
		pgpsign = NULL;
	/* XXX: duplicated with s/mime */
	/* re-read not to reorder headers */
	abuf.size = CL(&fdb->mmap);	/* estimated size */
	abuf.buf.ptr = malloc(abuf.size);
	abuf.buf.len = 0;
	CSETP(&ebuf, CP(&fdb->buf_mhdr));
	CSETE(&ebuf, CE(&fdb->buf_body));
	edb = fdb_mkpart(&ebuf);
	if (fdb->uppart == NULL)
		edb->flags |= FDB_MAIL; 
	medit_parse(edb);
	copy_abuf(&abuf, CP(&fdb->mmap), CP(&ebuf) - CP(&fdb->mmap));
	copy_abuf(&abuf, CP(&edb->buf_mhdr), CL(&edb->buf_mhdr));
	for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
		if (hdr->type->flags & HTF_OVERWRITE)
			continue;
		copy_abuf(&abuf, CP(&hdr->buf), CL(&hdr->buf));
		copy_abuf(&abuf, "\n", 1);
	}
	if (pdb == fdb)
		copy_abuf(&abuf, "\n", 1);
	for (s = p = (char *)pkt->dbuf, ep = p + pkt->dlen; ; p++) {
		if (p == ep || (*p == '\r' && p[1] == '\n')) {
			copy_abuf(&abuf, s, p - s);
			if (p == ep)
				break;
			copy_abuf(&abuf, "\n", 1);
			p++;
			s = p + 1;
		}
	}
	copy_abuf(&abuf, CE(&ebuf), CE(&fdb->mmap) - CE(&ebuf));

	strlcpy(state->status, "PGP Decrypted", sizeof(state->status));
	if (pgpsign != NULL) {
		data.ptr = (char *)pkt->dbuf;
		data.len = pkt->dlen;
		if (pgpmime_verify_pgp(pgpsign, &data, 1, &from,
		    state->status, sizeof(state->status)))
			fdb->flags |= FDB_VERIFIED;
	}

	fdb_purge(fdb->name);
	fdb = fdb_open(fdb->name);
	fdb_replace(fdb, &abuf.buf);
	fdb->flags |= FDB_NOMMAP | FDB_DECRYPTED;
	fdb->flags &= ~FDB_ENCRYPTED;
	state->message = fdb;
	message_open(state, 1);
	/* XXX: update scan? -- left undecrypted for now */
	ret = 1;

  bad:
	pgp_freepkt(pgpmail);
	free(cbuf.ptr);
	return ret;
}

int
pgpmime_verify(struct state *state)
{
	struct filedb *fdb, *ddb, *sdb;
	int i, ret, legacy;
	char *p, *s, *ep;
	struct abuf abuf;
	cbuf_t cbuf;
	ccbuf_t *cb, sproto, from, ebuf, ccbuf, data;
	struct pgp_pkt *pgpmail, *pgpsign, *pkt;

	ret = -1;
	pgpmail = NULL;
	message_open(state, 0);
	fdb = state->message;
	if (fdb->flags & FDB_ENCRYPTED)
		return pgpmime_decrypt(state);
	message_parseall(fdb);
	cb = &fdb->hdr_val[HT_FROM];
	if (CP(cb) == NULL) {
		from.ptr = "";
		from.len = 0;
	} else {
		(void)message_parse_addr(CP(cb), CE(cb), &from);
	}

	/*
	 * CT: application/pgp-encrypted is defined only within the context
	 * of multipart/encrypted in RFC2015, RFC3156.
	 * But someone misuses the CT for PGP encrypt message...
	 */
	if (!(fdb->flags & FDB_SIGNED)
	&&  !(fdb->flags & FDB_MULTIPART)
	&&  CMATCH("application/pgp-encrypted", &fdb->type))
		goto try_decrypt;

	if (!(fdb->flags & FDB_SIGNED)
	&&  !(fdb->flags & FDB_MULTIPART)
	&&  CMATCH("Text/Plain", &fdb->type)) {
		/* check legacy PGP SIGNED MESSAGE */
		for (i = fdb->hdr_lines; (cb = fdb_read(fdb, i)) != NULL; i++) {
			if (CMATCH("-----BEGIN PGP SIGNED MESSAGE-----", cb))
				break;
		}
		if (cb == NULL)
			goto try_decrypt;
		if ((cb = fdb_read(fdb, ++i)) == NULL)
			goto try_decrypt;
		/* ignore 'headers' */
		while (*cb->ptr != '\n') {
			if ((cb = fdb_read(fdb, ++i)) == NULL)
				goto try_decrypt;
		}
		data.ptr = cb->ptr + 1;	/* assume non-encoded */
		for (; (cb = fdb_read(fdb, i)) != NULL; i++) {
			if (CMATCH("-----BEGIN PGP SIGNATURE-----", cb))
				break;
		}
		if (cb == NULL)
			goto try_decrypt;
		data.len = cb->ptr - 1 - data.ptr;
		ccbuf.ptr = cb->ptr;
		ccbuf.len = CE(&fdb->buf_body) - ccbuf.ptr;
		for (; (cb = fdb_read(fdb, i)) != NULL; i++) {
			if (CMATCH("-----END PGP SIGNATURE-----", cb))
				break;
		}
		if (cb != NULL) {
			sdb = fdb_mkpart(&ccbuf);
			legacy = 1;
			goto parse;
		}
  try_decrypt:
		/* check legacy PGP MESSAGE */
		for (i = fdb->hdr_lines; (cb = fdb_read(fdb, i)) != NULL; i++) {
			if (CMATCH("-----BEGIN PGP MESSAGE-----", cb))
				break;
		}
		if (cb == NULL)
			goto nosign;
		ebuf.ptr = CP(cb);
		for (; (cb = fdb_read(fdb, i)) != NULL; i++) {
			if (CMATCH("-----END PGP MESSAGE-----", cb))
				break;
		}
		if (cb == NULL)
			goto nosign;
		ebuf.len = CE(cb) - ebuf.ptr;
		sdb = fdb_mkpart(&ebuf);
		legacy = 1;
		ret = 1;
		if (!pgpmime_dearmor(sdb, &cbuf)) {
			strlcpy(state->status, "PGP Armor read failed", sizeof(state->status));
			goto end;
		}
		if ((pgpmail = pgp_parseall((u_char *)CP(&cbuf), (u_char *)CE(&cbuf))) == NULL) {
			strlcpy(state->status, "PGP parse failed", sizeof(state->status));
			goto end;
		}
		if ((pkt = pgpmime_decrypt_pgp(pgpmail, state->status,
		    sizeof(state->status))) == NULL)
			goto end;
		if (pkt->tag == PGP_TAG_SIGN ||
		    pkt->tag == PGP_TAG_ONEPASS_SIGN) {
			pgpsign = pkt;
			pkt = pkt->next;
		} else
			pgpsign = NULL;
		/* XXX: duplicated with s/mime */
		/* re-read not to reorder headers */
		abuf.size = CL(&fdb->mmap);	/* estimated size */
		abuf.buf.ptr = malloc(abuf.size);
		abuf.buf.len = 0;
		copy_abuf(&abuf, CP(&fdb->mmap), CP(&ebuf) - CP(&fdb->mmap));
		copy_abuf(&abuf, "-----BEGIN PGP DECRYPTED MESSAGE-----\n", 38);
		for (s = p = (char *)pkt->dbuf, ep = p + pkt->dlen; ; p++) {
			if (p == ep || (*p == '\r' && p[1] == '\n')) {
				copy_abuf(&abuf, s, p - s);
				if (p == ep)
					break;
				copy_abuf(&abuf, "\n", 1);
				p++;
				s = p + 1;
			}
		}
		copy_abuf(&abuf, "-----END PGP DECRYPTED MESSAGE-----\n", 36);
		copy_abuf(&abuf, CE(&ebuf), CE(&fdb->mmap) - CE(&ebuf));

		strlcpy(state->status, "PGP Decrypted", sizeof(state->status));
		if (pgpsign != NULL) {
			data.ptr = (char *)pkt->dbuf;
			data.len = pkt->dlen;
			if (pgpmime_verify_pgp(pgpsign, &data, 1, &from,
			    state->status, sizeof(state->status)))
				fdb->flags |= FDB_VERIFIED;
		}

		fdb_purge(fdb->name);
		fdb = fdb_open(fdb->name);
		fdb_replace(fdb, &abuf.buf);
		fdb->flags |= FDB_NOMMAP | FDB_DECRYPTED;
		fdb->flags &= ~FDB_ENCRYPTED;
		folder_purge(state, state->folder->msg[state->folder->pos].num);
		state->message = fdb;
		message_open(state, 1);
		ret = 1;
		goto end;
	}
	legacy = 0;
	if (!(fdb->flags & FDB_SIGNED)) {
  nosign:
		strlcpy(state->status, "Not signed", sizeof(state->status));
		return 0;
	}
	sproto = fdb->sproto;
	if (!CMATCH("Application/pgp-signature", &sproto)) {
		snprintf(state->status, sizeof(state->status),
		    "Not supported sign protocol: %.*s",
		    CL(&sproto), CP(&sproto));
		return 0;
	}

	ddb = fdb->nextpart;
	for (sdb = ddb->nextpart; sdb != NULL; sdb = sdb->nextpart) {
		if (sdb->partdepth <= ddb->partdepth)
			break;
	}
	if (sdb == NULL
	||  CL(&sproto) != CL(&sdb->type)
	||  strncasecmp(CP(&sproto), CP(&sdb->type), CL(&sproto)) != 0) {
		strlcpy(state->status, "Signed part is not found", sizeof(state->status));
		return 0;
	}
	data.ptr = CP(&ddb->buf_mhdr);
	data.len = CE(&ddb->buf_body) - data.ptr;

  parse:
	cbuf.ptr = NULL;
	if (!pgpmime_dearmor(sdb, &cbuf)) {
		strlcpy(state->status, "PGP Armor read failed", sizeof(state->status));
		goto end;
	}
	if ((pgpmail = pgp_parseall((u_char *)CP(&cbuf), (u_char *)CE(&cbuf))) == NULL) {
		strlcpy(state->status, "PGP parse failed", sizeof(state->status));
		goto end;
	}

	if (pgpmime_verify_pgp(pgpmail, &data, legacy, &from,
	    state->status, sizeof(state->status))) {
		fdb->flags |= FDB_VERIFIED;
		folder_purge(state, fdb->msgnum);
		ret = 1;
	}

  end:
	pgp_freepkt(pgpmail);
	if (cbuf.ptr)
		free(cbuf.ptr);
	if (legacy)
		fdb_clear(sdb);
	return ret;
}

struct sarg {
	struct pgp_pkt *seckey;
	struct pgp_pkt *sign;
};

static int pgpmime_enter(FILE *fp, void *arg, ccbuf_t *cb);
static int pgpmime_sign_part(FILE *fp, void *arg);

static int
pgpmime_enter(FILE *fp, void *arg, ccbuf_t *cb)
{
	struct sarg *sarg = arg;

	if (pgp_hash_data_update(CP(cb), CE(cb), sarg->sign, 0) < 0)
		return -1;
	if (fp)
		fwrite(CP(cb), CL(cb), 1, fp);
	return 0;
}

static int
pgpmime_sign_part(FILE *fp, void *arg)
{
	struct sarg *sarg = arg;
	char hdrbuf[CHARBLOCK];
	extern char version[], dev[];

	fprintf(fp, "Content-Type: application/pgp-signature\n");
	fprintf(fp, "Content-Transfer-Encoding: 7bit\n");
	putc('\n', fp);
	if (dev[0])
		snprintf(hdrbuf, sizeof(hdrbuf), "Version: %s (%s)", version,
		    dev);
	else
		snprintf(hdrbuf, sizeof(hdrbuf), "Version: %s", version);

	if (pgp_hash_data_final(sarg->sign) < 0
	||  pgp_sign_sign(sarg->sign, sarg->seckey) < 0
	||  pgp_armor_all(fp, sarg->sign, hdrbuf) < 0)
		return -1;
	return 0;
}


int
pgpmime_sign(struct state *state)
{
	struct filedb *fdb;
	int ret;
	char *p;
	struct pgp_pkt *secring, *seckey, *sign;
	struct sarg sarg;

	if (!(state->folder->flags & FOL_DRAFT)) {
		strlcpy(state->status, "Not draft folder", sizeof(state->status));
		return 0;
	}
	message_open(state, 0);
	for (fdb = state->message; fdb->uppart != NULL; fdb = fdb->uppart)
		;
	if ((fdb->flags & FDB_SIGNED)
	&&  CMATCH("Application/pgp-signature", &fdb->sproto))
		return mpart_unsign(state);

	if ((sign = calloc(1, sizeof(*sign))) == NULL) {
		strlcpy(state->status, "malloc failed", sizeof(state->status));
		return 0;
	}
	ret = 0;
	if ((secring = pgpmime_read_secring()) == NULL) {
		strlcpy(state->status, "PGP secret key read failed", sizeof(state->status));
		goto bad;
	}
	if ((seckey = pgp_getpkt(secring, PGP_TAG_SECRET_KEY)) == NULL) {
		strlcpy(state->status, "PGP secret key get failed", sizeof(state->status));
		goto bad;
	}
	if (pgp_decrypt_seckey(seckey, pgpmime_read_passwd) < 0) {
		strlcpy(state->status, "PGP decrypt secret key failed", sizeof(state->status));
		goto bad;
	}
	sign->tag = PGP_TAG_SIGN;
	sign->un.sign.ver = 3;	/* XXX */
	sign->un.sign.type = PGP_SIG_BINDOC;
	time(&sign->un.sign.ctime);
	sign->un.sign.pubalg = seckey->un.pubkey.pubalg;
	memcpy(sign->un.sign.keyid, seckey->un.pubkey.keyid, 8);
	if (seckey->un.pubkey.pubalg == PGP_PUB_RSA) {
		sign->un.sign.hashalg = PGP_HASH_MD5;
		p = "Multipart/Signed; protocol=\"application/pgp-signature\"; micalg=pgp-md5";
	} else {
		sign->un.sign.hashalg = PGP_HASH_SHA1;
		p = "Multipart/Signed; protocol=\"application/pgp-signature\"; micalg=pgp-sha1";
	}
	sarg.sign = sign;
	sarg.seckey = seckey;

	if (pgp_hash_data_init(sign) < 0) {
		strlcpy(state->status, "PGP hash data failed", sizeof(state->status));
		goto bad;
	}

	if (mpart_sign(state, p, pgpmime_enter, pgpmime_sign_part, &sarg) == 0) {
		strlcpy(state->status, "PGP sign failed", sizeof(state->status));
		return 0;
	}
  bad:
	pgp_freepkt(sign);
	pgp_freepkt(secring);
	return ret;
}

static int pgpmime_enc_enter(FILE *fp, void *arg, ccbuf_t *cb);
static int pgpmime_enc_part(FILE *fp, void *arg);

struct earg {
	int	first;
	int	mixed;
	struct pgp_pkt *seskey;
	struct pgp_pkt *oseskey;
	struct abuf abuf;
};

static int
pgpmime_enc_enter(FILE *fp, void *arg, ccbuf_t *cb)
{
	struct earg *earg = arg;
	const char *s, *p, *ep;

	if (earg->first) {
		earg->first = 0;
		fprintf(fp, "Content-Type: application/pgp-encrypted\n");
		fprintf(fp, "Content-Transfer-Encoding: 7bit\n");
		fprintf(fp, "\n");
		fprintf(fp, "Version: 1\n");
	}
	for (s = p = CP(cb), ep = CE(cb); ; p++) {
		if (p == ep || *p == '\n') {
			copy_abuf(&earg->abuf, s, p - s);
			if (p == ep)
				break;
			copy_abuf(&earg->abuf, "\r\n", 2);
			s = p + 1;
		}
	}
	return 0;
}

static int
pgpmime_enc_part(FILE *fp, void *arg)
{
	struct earg *earg = arg;
	char hdrbuf[CHARBLOCK];
	struct pgp_pkt *pkt, *npkt, *out;
	int ret;
	extern char version[], dev[];

	fprintf(fp, "Content-Type: application/octet-stream\n");
	fprintf(fp, "Content-Transfer-Encoding: 7bit\n");
	fprintf(fp, "\n");

	ret = -1;
	out = NULL;
	if ((pkt = calloc(1, sizeof(*pkt))) == NULL)
		goto bad;
	pkt->tag = PGP_TAG_LITERAL_DATA;
	pkt->un.litdat.type = 'b';
	time(&pkt->un.litdat.mtime);
	pkt->dbuf = (u_char *)earg->abuf.buf.ptr;
	pkt->dlen = earg->abuf.buf.len;

	if ((npkt = calloc(1, sizeof(*npkt))) == NULL)
		goto bad;
	npkt->tag = PGP_TAG_COMP_DATA;
	npkt->un.compdat.compalg = PGP_COMP_ZIP;
	npkt->un.compdat.pkt = pkt;
	pkt = npkt;
	if (pgp_comp_data(pkt) < 0)
		goto bad;

	if (earg->oseskey) {
		if ((npkt = calloc(1, sizeof(*npkt))) == NULL)
			goto bad;
		npkt->tag = PGP_TAG_SYMENC_DATA;
		npkt->un.symdat.pkt = pkt;
		pkt = npkt;
		if (pgp_encrypt_symdat(pkt, earg->oseskey) < 0)
			goto bad;
		pgpmime_pkt_append(&out, earg->oseskey);
		pgpmime_pkt_append(&out, pkt);
		earg->oseskey = NULL;
		if (earg->seskey) {
			pkt = npkt->un.symdat.pkt;
			npkt->un.symdat.pkt = NULL;
		}
	}
	if (earg->seskey) {
		if ((npkt = calloc(1, sizeof(*npkt) + 3)) == NULL)
			goto bad;
		npkt->tag = PGP_TAG_MARKER;
		npkt->dbuf = (u_char *)&npkt[1];
		memcpy(npkt->dbuf, "PGP", 3);
		npkt->dlen = 3;
		pgpmime_pkt_append(&out, npkt);
		if ((npkt = calloc(1, sizeof(*npkt))) == NULL)
			goto bad;
		npkt->tag = PGP_TAG_SYMENC_DATA;
		npkt->un.symdat.pkt = pkt;
		pkt = npkt;
		if (pgp_encrypt_symdat(pkt, earg->seskey) < 0)
			goto bad;
		pgpmime_pkt_append(&out, earg->seskey);
		pgpmime_pkt_append(&out, pkt);
		earg->seskey = NULL;
	}
	pkt = NULL;

	if (dev[0])
		snprintf(hdrbuf, sizeof(hdrbuf), "Version: %s (%s)", version,
		    dev);
	else
		snprintf(hdrbuf, sizeof(hdrbuf), "Version: %s", version);

	if (pgp_armor_all(fp, out, hdrbuf) < 0)
		goto bad;
	ret = 0;
  bad:
	if (pkt)
		pgp_freepkt(pkt);
	if (out)
		pgp_freepkt(out);
	return ret;
}

static int
pgpmime_enc_newrec(struct earg *earg, struct pgp_pkt *pubkey)
{
	struct pgp_pkt *seskey, *prev, **keyp;

	if (pubkey->un.pubkey.pubalg != PGP_PUB_RSA
	||  pubkey->un.pubkey.ref_subkey != NULL)
		keyp = &earg->seskey;
	else
		keyp = &earg->oseskey;
	if (pubkey->un.pubkey.ref_subkey)
		pubkey = pubkey->un.pubkey.ref_subkey;
	for (prev = NULL; *keyp; keyp = &prev->next) {
		prev = *keyp;
		if (memcmp(prev->un.pubses.keyid, pubkey->un.pubkey.keyid, 8) == 0)
			return 0;
	}
	if ((seskey = calloc(1, sizeof(*seskey))) == NULL)
		return -1;
	seskey->tag = PGP_TAG_PUBENC_SESKEY;
	seskey->un.pubses.ver = 3;
	seskey->un.pubses.pubalg = pubkey->un.pubkey.pubalg;
	memcpy(seskey->un.pubses.keyid, pubkey->un.pubkey.keyid, 8);
	seskey->un.pubses.ref_pubkey = pubkey;
	*keyp = seskey;
	seskey->prev = prev;
	return 0;
}

static int
pgpmime_enc_encses(struct pgp_pkt *seskey, int alg)
{
	struct pgp_pkt *pkt;
	int ret;

	seskey->un.pubses.symalg = alg;
	if (pgp_generate_seskey(seskey) < 0)
		return -1;
	for (pkt = seskey; pkt; pkt = pkt->next) {
		if (pkt != seskey) {
			pkt->un.pubses.seskey = seskey->un.pubses.seskey;
			pkt->un.pubses.seslen = seskey->un.pubses.seslen;
		}
		ret = pgp_encrypt_seskey(pkt, pkt->un.pubses.ref_pubkey);
		if (pkt != seskey) {
			pkt->un.pubses.seskey = NULL;
			pkt->un.pubses.seslen = 0;
		}
		if (ret < 0)
			return -1;
	}
	return 0;
}

int
pgpmime_encrypt(struct state *state)
{
	struct filedb *fdb;
	const char *p, *ep, *u;
	struct earg earg;
	struct pgp_pkt *secring, *pubring, *seckey, *pubkey, *pkt, *t;
	int ret;
	int i;
	ccbuf_t addr, rec;
	struct header *hdr;

	if (!(state->folder->flags & FOL_DRAFT)) {
		strlcpy(state->status, "Not draft folder", sizeof(state->status));
		return 0;
	}
	if ((fdb = state->message) == NULL) {
		strlcpy(state->status, "Not current message", sizeof(state->status));
		return 0;
	}
	for (fdb = state->message; fdb->uppart != NULL; fdb = fdb->uppart)
		;
	if ((fdb->flags & FDB_ENCRYPTED)
	&&  (fdb->flags & FDB_MULTIPART)
	&&  CMATCH("Application/pgp-encrypted", &fdb->sproto)) {
		strlcpy(state->status, "Already encrypted", sizeof(state->status));
		return 0;
	}
	ret = 0;
	earg.abuf.size = CL(&fdb->mmap);	/* estimated size */
	earg.abuf.buf.ptr = malloc(earg.abuf.size);
	earg.abuf.buf.len = 0;
	earg.first = 1;
	earg.mixed = 0;
	earg.seskey = earg.oseskey = NULL;

	secring = pgpmime_read_secring();
	pubring = pgpmime_read_pubring();
	if (secring == NULL || pubring == NULL) {
		strlcpy(state->status, "PGP secret/public key read failed", sizeof(state->status));
		goto bad;
	}
	if ((seckey = pgp_getpkt(secring, PGP_TAG_SECRET_KEY)) == NULL) {
		strlcpy(state->status, "PGP secret key get failed", sizeof(state->status));
		goto bad;
	}
	if (pgpmime_enc_newrec(&earg, seckey) < 0) {
		strlcpy(state->status, "PGP session key allocate failed", sizeof(state->status));
		goto bad;
	}
	/* XXX */
	for (i = 0, hdr = fdb->hdr; i < fdb->hdrs; i++, hdr++) {
		switch (hdr->type->type) {
		case HT_TO:
		case HT_CC:
			p = hdr->val.ptr;
			ep = p + hdr->val.len;
			break;
		case HT_UNKNOWN:
			if (CSUBMATCH("Bcc:", &hdr->buf)
			||  CSUBMATCH("Dcc:", &hdr->buf)) {
				p = hdr->buf.ptr;
				ep = p + hdr->buf.len;
				p += 4;
				break;
			}
			/*FALLTHRU*/
		default:
			continue;
		}
		while (p < ep) {
			if (*p == ' ' || *p == '\t' || *p == '\n') {
				p++;
				continue;
			}
			p = message_parse_addr(p, ep, &rec);
			if (rec.ptr == NULL)
				break;
			u = "";
			for (pkt = seckey->un.pubkey.ref_user; pkt;
					pkt = pkt->un.userid.ref_user) {
				u = pkt->un.userid.user;
				(void)message_parse_addr(u, u + strlen(u), &addr);
				if (addr.len == rec.len
				&&  memcmp(addr.ptr, rec.ptr, rec.len) == 0)
					break;
			}
			if (pkt != NULL)
				continue;
			(void)message_parse_addr(u, u + strlen(u), &addr);
			pubkey = NULL;
			for (pkt = pubring; pkt; pkt = pkt->next) {
				if (pkt->tag != PGP_TAG_PUBLIC_KEY)
					continue;
				for (t = pkt->un.pubkey.ref_user; t;
						t = t->un.userid.ref_user) {
					u = t->un.userid.user;
					(void)message_parse_addr(u, u + strlen(u), &addr);
					if (addr.len == rec.len
					&&  memcmp(addr.ptr, rec.ptr, rec.len) == 0)
						break;
				}
				if (t) {
					/* prefer newer key */
					if (pubkey == NULL ||
					    pkt->un.pubkey.tstamp >
					    pubkey->un.pubkey.tstamp)
						pubkey = pkt;
				}
			}
			if (pubkey == NULL) {
				snprintf(state->status, sizeof(state->status),
				    "PGP public key not found for %.*s",
				    rec.len, rec.ptr);
				goto bad;
			}
			if (pgpmime_enc_newrec(&earg, pubkey) < 0) {
				strlcpy(state->status, "PGP session key allocate failed", sizeof(state->status));
				goto bad;
			}
		}
	}
	if (earg.oseskey) {
		if (pgpmime_enc_encses(earg.oseskey, PGP_SYM_IDEA) < 0) {
			strlcpy(state->status, "PGP session key encrypt failed", sizeof(state->status));
			goto bad;
		}
	}
	if (earg.seskey) {
		i = PGP_SYM_DES3;	/*XXX*/
		if (pgpmime_enc_encses(earg.seskey, i) < 0) {
			strlcpy(state->status, "PGP session key encrypt failed", sizeof(state->status));
			goto bad;
		}
	}
	if (earg.oseskey && earg.seskey)
		earg.mixed = 1;

	p = "Multipart/Encrypted; protocol=\"application/pgp-encrypted\"";
	if (mpart_sign(state, p, pgpmime_enc_enter, pgpmime_enc_part, &earg) == 0) {
		strlcpy(state->status, "PGP encrypt failed", sizeof(state->status));
		goto bad;
	}
	ret = 1;
	strlcpy(state->status, "PGP encrypted", sizeof(state->status));
	if (earg.mixed)
		strlcat(state->status, " with mixed format",
		    sizeof(state->status));
  bad:
	if (earg.abuf.buf.ptr)
		free(earg.abuf.buf.ptr);
	if (earg.oseskey)
		pgp_freepkt(earg.oseskey);
	if (earg.seskey)
		pgp_freepkt(earg.seskey);
	if (pubring)
		pgp_freepkt(pubring);
	if (secring)
		pgp_freepkt(secring);
	return 0;
}

#endif /* USE_PGPMIME */
