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

/*-
 * Copyright (c) 1999-2000 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_SMIME

#include <sys/types.h>

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef USE_OPENSSL
#include <openssl/asn1.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/rand.h>
#else /* USE_OPENSSL */
#include <asn1.h>
#include <err.h>
#include <pem.h>
#include <pkcs7.h>
#include <rand.h>
#endif /* USE_OPENSSL */

#include "cue.h"

struct sarg {
	PKCS7	*p7;
	BIO	*p7bio;
};

#if OPENSSL_VERSION_NUMBER >= 0x00904100
static int smime_passwd(char *buf, int size, int verify, void *arg);
#else
static int smime_passwd(char *buf, int size, int verify);
#endif
static int smime_enter(FILE *fp, void *arg, ccbuf_t *cb);
static int smime_sign_part(FILE *fp, void *arg);

static int smime_init_done = 0;

static void
smime_init(void)
{
	smime_init_done = 1;

#ifdef USE_OPENSSL
	OpenSSL_add_all_algorithms();
#else /* USE_OPENSSL */
	SSLeay_add_all_algorithms();
#endif /* USE_OPENSSL */
	ERR_load_crypto_strings();
}

#if OPENSSL_VERSION_NUMBER >= 0x00904100
static int
smime_passwd(char *buf, int size, int verify, void *arg)
#else
static int
smime_passwd(char *buf, int size, int verify)
#endif
{
	return read_passwd("S/MIME passphrase to decrypt private key: ",
			buf, size);
}

static int
smime_decrypt(struct state *state)
{
	int i;
	PKCS7 *p7;
	const char *p, *s, *ep;
	BIO *in, *p7bio;
	EVP_PKEY *pkey;
	X509 *x509;
	X509_STORE_CTX cert_ctx;
	X509_STORE *cert_store;
	struct filedb *fdb, *edb;
	struct abuf abuf;
	cbuf_t cbuf;
	ccbuf_t ebuf;
	struct header *hdr;
	char buf[BUFSIZ];
#if OPENSSL_VERSION_NUMBER < 0x00909000L
	STACK_OF(PKCS7_RECIP_INFO) *sk;
	PKCS7_RECIP_INFO *ri;
#endif

	if (!smime_init_done)
		smime_init();

	message_open(state, 0);
	fdb = state->message;
	if (!CMATCH("Application/x-pkcs7-mime", &fdb->type)
	&&  !CMATCH("Application/pkcs7-mime", &fdb->type)) {
		strlcpy(state->status, "Not encrypted messsage",
		    sizeof(state->status));
		return 0;
	}
	p = CP(&fdb->buf_body);
	ep = CE(&fdb->buf_body);
	decode_text(&p, ep, &cbuf, fdb->flags | FDB_NOCONV);
	p7bio = BIO_new_mem_buf(cbuf.ptr, cbuf.len);
	if (p7bio == NULL) {
		strlcpy(state->status, "no more memory", sizeof(state->status));
		return -1;
	}
	p7 = d2i_PKCS7_bio(p7bio, NULL);
	BIO_free(p7bio);
	if (p7 == NULL) {
		strlcpy(state->status, "Invalid pkcs7 data",
		    sizeof(state->status));
		return -1;
	}
#ifdef SMIME_DEBUG
 { FILE *fp = fopen("/tmp/smime.p7m", "w"); i2d_PKCS7_fp(fp, p7); fclose(fp); }
#endif

	p = getenv("SSL_CLIENT_CERT");
	if (p == NULL || (in = BIO_new_file(p, "r")) == NULL) {
		strlcpy(state->status, "Cannot open certificate file",
		    sizeof(state->status));
		return -1;
	}
#if OPENSSL_VERSION_NUMBER >= 0x00904100
	if ((x509 = PEM_read_bio_X509(in, NULL, NULL, NULL)) == NULL)
#else
	if ((x509 = PEM_read_bio_X509(in, NULL, NULL)) == NULL)
#endif
	{
		strlcpy(state->status, "certificate read failed",
		    sizeof(state->status));
		BIO_free(in);
		return -1;
	}

#if OPENSSL_VERSION_NUMBER < 0x00909000L
	/* confirmed 0.9.9, may be fixed before this version */
	/*
	 * XXX FIX OPENSSL
	 *	openssl-0.9.1c pkcs7 decryption checks first recipientinfo
	 *	only.
	 *	To avoid this bug, I modify recipient info here so that
	 *	the recipient info for me is the first one.
	 *			-- 2/16/99, onoe
	 */
	switch (OBJ_obj2nid(p7->type)) {
	case NID_pkcs7_signed:
		sk = NULL;
		break;
	case NID_pkcs7_signedAndEnveloped:
		sk = p7->d.signed_and_enveloped->recipientinfo;
		break;
	case NID_pkcs7_enveloped:
		sk = p7->d.enveloped->recipientinfo;
		break;
	default:
		sk = NULL;
		break;
	}
	if (sk) {
		for (;;) {
			if (sk_PKCS7_RECIP_INFO_num(sk) == 0) {
				strlcpy(state->status,
				    "Cannot find recipient info for me\n",
				    sizeof(state->status));
				BIO_free(in);
				X509_free(x509);
				PKCS7_free(p7);
				return -1;
			}
			ri=sk_PKCS7_RECIP_INFO_value(sk,0);
			if (X509_name_cmp(ri->issuer_and_serial->issuer, X509_get_issuer_name(x509)) == 0
			&&  ASN1_INTEGER_cmp(ri->issuer_and_serial->serial, X509_get_serialNumber(x509)) == 0)
				break;
			sk_PKCS7_RECIP_INFO_shift(sk);
			PKCS7_RECIP_INFO_free(ri);
		}
	}
#endif

	if ((p = getenv("SSL_CLIENT_KEY")) != NULL) {
		BIO_free(in);
		if ((in = BIO_new_file(p, "r")) == NULL) {
			strlcpy(state->status, "Cannot open private key file",
			    sizeof(state->status));
			X509_free(x509);
			PKCS7_free(p7);
			return -1;
		}
	} else {
		(void)BIO_reset(in);
	}
#if OPENSSL_VERSION_NUMBER >= 0x00904100
	pkey = PEM_read_bio_PrivateKey(in, NULL, smime_passwd, NULL);
#else
	pkey = PEM_read_bio_PrivateKey(in, NULL, smime_passwd);
#endif
	BIO_free(in);
	if (pkey == NULL) {
		strlcpy(state->status, "private key read failed",
		    sizeof(state->status));
		X509_free(x509);
		PKCS7_free(p7);
		return -1;
	}

	cert_store = X509_STORE_new();
	X509_STORE_set_default_paths(cert_store);
	X509_STORE_load_locations(cert_store, getenv("SSL_CLIENT_CERT"),
	    getenv("SSL_CERT_DIR"));
	X509_STORE_CTX_init(&cert_ctx, cert_store, x509, NULL);
#if OPENSSL_VERSION_NUMBER >= 0x00906000
	X509_STORE_CTX_set_time(&cert_ctx, 0,
	    state->folder->msg[state->folder->pos].date);
#endif

	ERR_clear_error();

#ifdef USE_OPENSSL
	if ((p7bio = PKCS7_dataDecode(p7,pkey,NULL,x509)) == NULL)
#else /* USE_OPENSSL */
	if ((p7bio = PKCS7_dataDecode(p7,pkey,NULL,cert_store)) == NULL)
#endif /* USE_OPENSSL */
	{
		strlcpy(state->status, "Decode failed", sizeof(state->status));
		goto end;
	}

	/* XXX: duplicated with pgp/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);
	}
	while ((i = BIO_read(p7bio, buf, sizeof(buf))) > 0) {
		for (s = p = buf, ep = p + i; ; 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));
	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 */

	/* TODO verify for pkcs7_signedAndEnveloped */
	strlcpy(state->status, "S/MIME Decrypted", sizeof(state->status));
	BIO_free(p7bio);
	X509_STORE_free(cert_store);
	X509_free(x509);
	PKCS7_free(p7);
	return 1;

  end:
	X509_STORE_free(cert_store);
	X509_free(x509);
	PKCS7_free(p7);
	return -1;
}

static char *
smime_x509_email(X509 *x)
{
	int i, len;
	ASN1_OCTET_STRING *str;
	unsigned char *p;
	static char buf[CHARBLOCK];	/*XXX*/

	if (X509_NAME_get_text_by_NID(X509_get_subject_name(x),
				NID_pkcs9_emailAddress, buf, sizeof(buf)) == 0)
		return buf;
	i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1);
	if (i >= 0) {
		str = X509_EXTENSION_get_data(X509_get_ext(x, i));
		/* XXX GENERAL_NAMES (SEQUENCE) */
		p = ASN1_STRING_data(str);
		len = ASN1_STRING_length(str);
		p += 2;
		len -= 2;

		/* XXX GENERAL_NAME (type + string) */
		p += 2;
		len -= 2;

		memcpy(buf, p, len);
		buf[len] = '\0';
		return buf;
	}
	return NULL;
}

static int
smime_enter(FILE *fp, void *arg, ccbuf_t *cb)
{
	struct sarg *sarg = arg;
	BIO *p7bio = sarg->p7bio;
	const char *s, *p, *ep;

	p = CP(cb);
	ep = CE(cb);

	while (p < ep) {
		for (s = p; p < ep && *p != '\n'; p++)
			;
		BIO_write(p7bio, s, p - s);
		if (fp)
			fwrite(s, p - s, 1, fp);
		if (p == ep)
			break;
		if (p == s || p[-1] != '\r')
			BIO_write(p7bio, "\r\n", 2);
		else
			BIO_write(p7bio, "\n", 1);
		if (fp)
			putc('\n', fp);
		p++;
	}
	return 0;
}

static int
smime_sign_part(FILE *fp, void *arg)
{
	struct sarg *sarg = arg;
	PKCS7 *p7 = sarg->p7;
	BIO *p7bio = sarg->p7bio;
	int len;
	char *p;
	cbuf_t cbuf;

	PKCS7_dataFinal(p7, p7bio);

	fprintf(fp, "Content-Type: application/x-pkcs7-signature; name=\"smime.p7s\"\n");
	fprintf(fp, "Content-Transfer-Encoding: base64\n");
	fprintf(fp, "Content-Disposition: attachment; filename=\"smime.p7s\"\n");
	fprintf(fp, "Content-Description: S/MIME Cryptographic Signature\n");
	putc('\n', fp);

	len = i2d_PKCS7(p7, NULL);
	cbuf.ptr = p = malloc(len);
	i2d_PKCS7(p7, (u_char **)&p);
	cbuf.len = p - cbuf.ptr;
	save_base64_encode(fp, &cbuf);
	return 0;
}

int
smime_verify(struct state *state)
{
	struct filedb *fdb, *ddb, *sdb;
	cbuf_t dbuf;
	ccbuf_t cbuf, from;
	ccbuf_t *ccb, sproto;
	int i, ret;
	PKCS7 *p7;
	STACK_OF(PKCS7_SIGNER_INFO) *sk;
	const char *p, *ep;
	BIO *detached, *p7bio;
	PKCS7_SIGNER_INFO *si;
	char buf[BUFSIZ];
	X509_STORE_CTX cert_ctx;
	X509_STORE *cert_store;
	ASN1_UTCTIME *tm;
	struct sarg sarg;

	if (!smime_init_done)
		smime_init();

	message_open(state, 0);
	fdb = state->message;
	if (fdb->flags & FDB_ENCRYPTED)
		return smime_decrypt(state);
	if (!(fdb->flags & FDB_SIGNED)) {
		strlcpy(state->status, "Not signed", sizeof(state->status));
		return 0;
	}
	sproto = fdb->sproto;
	if (!CMATCH("Application/x-pkcs7-signature", &sproto)
	&&  !CMATCH("Application/pkcs7-signature", &sproto)) {
		snprintf(state->status, sizeof(state->status),
		    "Not supported sign protocol: %.*s",
		    CL(&sproto), CP(&sproto));
		return 0;
	}
	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);
	}

	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;
	}
	p = CP(&sdb->buf_body);
	ep = CE(&sdb->buf_body);
	decode_text(&p, ep, &dbuf, sdb->flags);
#ifdef SMIME_DEBUG
 { FILE *fp; fp = fopen("/tmp/smime.p7s", "w"); fwrite(dbuf.ptr, dbuf.len, 1, fp); fclose(fp); }
#endif
	p7bio = BIO_new_mem_buf(dbuf.ptr, dbuf.len);
	if (p7bio == NULL) {
		strlcpy(state->status, "no more memory", sizeof(state->status));
		return -1;
	}
	p7 = d2i_PKCS7_bio(p7bio, NULL);
	BIO_free(p7bio);
	if (p7 == NULL) {
		strlcpy(state->status, "Invalid sign", sizeof(state->status));
		return -1;
	}

#ifdef SMIME_DEBUG
	detached = BIO_new(BIO_s_file());
	BIO_write_filename(detached, "/tmp/smime.txt");
#else
	detached = BIO_new(BIO_s_mem());
#endif

	CSETP(&cbuf, CP(&ddb->buf_mhdr));
	CSETE(&cbuf, CE(&ddb->buf_body));
	sarg.p7bio = detached;
	smime_enter(NULL, &sarg, &cbuf);
#ifdef SMIME_DEBUG
BIO_free(detached);
detached = BIO_new(BIO_s_file());
BIO_read_filename(detached, "/tmp/smime.txt");
#endif
	p7bio = PKCS7_dataInit(p7, detached);
	/* We now have to 'read' from p7bio to calculate digests etc. */
	while (BIO_read(p7bio, buf, sizeof(buf)) > 0)
		;

	cert_store = X509_STORE_new();
	X509_STORE_set_default_paths(cert_store);
	X509_STORE_load_locations(cert_store, getenv("SSL_CLIENT_CERT"),
	    getenv("SSL_CERT_DIR"));
	ERR_clear_error();

	sk = PKCS7_get_signer_info(p7);
	strlcpy(state->status, "S/MIME verify OK", sizeof(state->status));
	ret = 1;
	for (i = 0; i < sk_PKCS7_SIGNER_INFO_num(sk); i++) {
		si = sk_PKCS7_SIGNER_INFO_value(sk, i);
		if (PKCS7_dataVerify(cert_store, &cert_ctx, p7bio, p7, si) <= 0) {
			ret = -1;
			snprintf(state->status, sizeof(state->status),
			    "S/MIME verify failed: %s",
			    cert_ctx.error ?
			    X509_verify_cert_error_string(cert_ctx.error) :
			    "signature failure");
			if (cert_ctx.error == X509_V_ERR_CERT_HAS_EXPIRED) {
				tm = X509_get_notAfter(cert_ctx.current_cert);
				snprintf(state->status + strlen(state->status),
				    sizeof(state->status) - strlen(state->status),
				    ": %.*s", tm->length, tm->data);
			}
#ifdef notdef
			break;
#endif
		} else {
			if (i == 0) {	/* XXX: ??? */
				p = smime_x509_email(cert_ctx.current_cert);
				if (p == NULL
				||  strlen(p) != CL(&from)
				||  strncasecmp(p, CP(&from), CL(&from)) != 0) {
					if (p == NULL)
						p = "unknown";
#ifdef notdef
					/*
					 * Netscape fails to verify if from
					 * address is not matched with cert.
					 * But I cannot find any decription
					 * to this behaviour.
					 */
					ret = -1;
					snprintf(state->status, sizeof(state->status),
					    "S/MIME verify failed: cert for %s", p);
#else
					snprintf(state->status, sizeof(state->status),
					    "S/MIME verify OK: cert for %s", p);
#endif
				}
			}
		}
	}
	X509_STORE_free(cert_store);
	BIO_free(p7bio);
	BIO_free(detached);
	PKCS7_free(p7);
	if (ret == 1) {
		fdb->flags |= FDB_VERIFIED;
		folder_purge(state, fdb->msgnum);
	}
	return ret;
}


int
smime_sign(struct state *state)
{
	int i;
	PKCS7 *p7;
	STACK_OF(X509) *sk;
	char *p;
	BIO *in, *p7bio;
	PKCS7_SIGNER_INFO *si;
	EVP_PKEY *pkey;
	X509 *x509, *x;
	X509_STORE_CTX cert_ctx;
	X509_STORE *cert_store;
	ASN1_UTCTIME *sign_time;
	struct filedb *fdb;
	struct sarg sarg;

	if (!smime_init_done)
		smime_init();

	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/x-pkcs7-signature", &fdb->sproto)
	  || CMATCH("Application/pkcs7-signature", &fdb->sproto)))
		return mpart_unsign(state);

	p = getenv("SSL_CLIENT_CERT");
	if (p == NULL || (in = BIO_new_file(p, "r")) == NULL) {
		strlcpy(state->status, "Cannot open certificate file",
		    sizeof(state->status));
		return 0;
	}
#if OPENSSL_VERSION_NUMBER >= 0x00904100
	if ((x509 = PEM_read_bio_X509(in, NULL, NULL, NULL)) == NULL)
#else
	if ((x509 = PEM_read_bio_X509(in, NULL, NULL)) == NULL)
#endif
	{
		strlcpy(state->status, "certificate read failed",
		    sizeof(state->status));
		BIO_free(in);
		return 0;
	}
	if ((p = getenv("SSL_CLIENT_KEY")) != NULL) {
		BIO_free(in);
		if ((in = BIO_new_file(p, "r")) == NULL) {
			strlcpy(state->status, "Cannot open private key file",
			    sizeof(state->status));
			X509_free(x509);
			return 0;
		}
	} else {
		(void)BIO_reset(in);
	}
#if OPENSSL_VERSION_NUMBER >= 0x00904100
	pkey = PEM_read_bio_PrivateKey(in, NULL, smime_passwd, NULL);
#else
	pkey = PEM_read_bio_PrivateKey(in, NULL, smime_passwd);
#endif
	BIO_free(in);
	if (pkey == NULL) {
		strlcpy(state->status, "private key read failed",
		    sizeof(state->status));
		X509_free(x509);
		return 0;
	}

	p7 = PKCS7_new();
	PKCS7_set_type(p7,NID_pkcs7_signed);

	si = PKCS7_add_signature(p7, x509, pkey, EVP_sha1());
	if (si == NULL) {
		strlcpy(state->status, "signature failed",
		    sizeof(state->status));
		X509_free(x509);
		PKCS7_free(p7);
		return 0;
	}
#if OPENSSL_VERSION_NUMBER < 0x00909000L
	/* confirmed 0.9.9, may be fixed before this version */
	/*
	 * XXX FIX OPENSSL (not sure)
	 *	openssl-0.9.1c pkcs7(sha1) uses sha1WithRSAEncryption
	 *	as digestEncryptionAlgorithm.
	 *	RFC 2315 (PKCS #7) 9.4 Note 2 says that if the input is
	 *	shorter than the RSA key, digest can be ommitted.
	 *	And Netscape 4.05 seems to reject any digest encryption
	 *	algorithm.  Use simple RSA encryption instead to avoid
	 *	this problem.
	 *			-- 1/28/99, onoe
	 */
	si->digest_enc_alg->algorithm = OBJ_nid2obj(NID_rsaEncryption);
#endif

	PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, (char *)OBJ_nid2obj(NID_pkcs7_data));

	sign_time = X509_gmtime_adj(NULL,0);
	PKCS7_add_signed_attribute(si, NID_pkcs9_signingTime, V_ASN1_UTCTIME,(char *)sign_time);

	cert_store = X509_STORE_new();
	X509_STORE_set_default_paths(cert_store);
	X509_STORE_load_locations(cert_store, getenv("SSL_CLIENT_CERT"),
	    getenv("SSL_CERT_DIR"));
	X509_STORE_CTX_init(&cert_ctx, cert_store, x509, NULL);
	ERR_clear_error();
	if (X509_verify_cert(&cert_ctx) <= 0) {
		snprintf(state->status, sizeof(state->status),
		    "Failed to verify my certificate: %s",
		    X509_verify_cert_error_string(X509_STORE_CTX_get_error(&cert_ctx)));
		X509_STORE_free(cert_store);
		X509_free(x509);
		PKCS7_free(p7);
		return 0;
	}
	sk = X509_STORE_CTX_get_chain(&cert_ctx);
	for (i = 0; i < sk_X509_num(sk); i++) {
		x = sk_X509_value(sk, i);
		PKCS7_add_certificate(p7, x);
	}

	PKCS7_content_new(p7,NID_pkcs7_data);
	PKCS7_set_detached(p7,1);
	if ((p7bio = PKCS7_dataInit(p7,NULL)) == NULL) {
		strlcpy(state->status, "PKCS7_dataInit failed",
		    sizeof(state->status));
		X509_STORE_free(cert_store);
		X509_free(x509);
		PKCS7_free(p7);
		return 0;
	}

	p = "Multipart/Signed; protocol=\"application/x-pkcs7-signature\"; micalg=sha1";
	sarg.p7 = p7;
	sarg.p7bio = p7bio;
	if (mpart_sign(state, p, smime_enter, smime_sign_part, &sarg) == 0) {
		strlcpy(state->status, "Sign failed", sizeof(state->status));
		BIO_free(p7bio);
		X509_STORE_free(cert_store);
		X509_free(x509);
		PKCS7_free(p7);
		return 0;
	}
#ifdef SMIME_DEBUG
 { FILE *fp = fopen("/tmp/smime.p7s", "w"); i2d_PKCS7_fp(fp, p7); fclose(fp); }
#endif
	BIO_free(p7bio);
	PKCS7_free(p7);
	X509_STORE_free(cert_store);
	X509_free(x509);
	return 1;
}

static int
smime_enc_enter(FILE *fp, void *arg, ccbuf_t *cb)
{
	struct sarg *sarg = arg;
	BIO *p7bio = sarg->p7bio;

	BIO_write(p7bio, CP(cb), CL(cb));
	return 0;
}

static int
smime_enc_part(FILE *fp, void *arg)
{
	struct sarg *sarg = arg;
	PKCS7 *p7 = sarg->p7;
	BIO *p7bio = sarg->p7bio;
	int len;
	char *p;
	cbuf_t cbuf;

	(void)BIO_flush(p7bio);
	PKCS7_dataFinal(p7, p7bio);

	fprintf(fp, "Content-Type: application/x-pkcs7-mime; name=\"smime.p7m\"\n");
	fprintf(fp, "Content-Transfer-Encoding: base64\n");
	fprintf(fp, "Content-Disposition: attachment; filename=\"smime.p7m\"\n");
	fprintf(fp, "Content-Description: S/MIME Encrypted Message\n");
	putc('\n', fp);

	len = i2d_PKCS7(p7, NULL);
	cbuf.ptr = p = malloc(len);
	i2d_PKCS7(p7, (u_char **)&p);
	cbuf.len = p - cbuf.ptr;
	save_base64_encode(fp, &cbuf);
	return 0;
}

static X509 *
smime_find_cert(const char *addr, int addrlen)
{
	struct cert_list {
		struct cert_list *next;
		char	*addr;
		int	addrlen;
		X509	*cert;
	} *cl, *ncl;
	static struct cert_list *clhead = NULL;
	char path[BUFSIZ];
	DIR *dirp;
	struct dirent *dp;
	char *dname, *p;
	BIO *in;
	X509 *x509;

	if (addr == NULL) {
		for (cl = clhead; cl; cl = ncl) {
			ncl = cl->next;
			if (cl->addr)
				free(cl->addr);
			if (cl->cert)
				X509_free(cl->cert);
			free(cl);
		}
		clhead = NULL;
		return NULL;
	}
	if (clhead == NULL) {
		/* initialize */
		/*XXX*/
		if ((dname = getenv("SSL_CERT_DIR")) == NULL)
			return NULL;
		if ((dirp = opendir(dname)) != NULL) {
			while ((dp = readdir(dirp)) != NULL) {
				if (dp->d_name[0] == '.')
					continue;
				snprintf(path, sizeof(path), "%s/%s",
				    dname, dp->d_name);
				if ((in = BIO_new_file(path, "r")) == NULL)
					continue;
#if OPENSSL_VERSION_NUMBER >= 0x00904100
				while ((x509 = PEM_read_bio_X509(in, NULL, NULL, NULL)) != NULL)
#else
				while ((x509 = PEM_read_bio_X509(in, NULL, NULL)) != NULL)
#endif
				{
					p = smime_x509_email(x509);
					if (p == NULL) {
						X509_free(x509);
						continue;
					}
					cl = malloc(sizeof(*cl));
					cl->addr = strdup(p);
					cl->addrlen = strlen(p);
					cl->cert = x509;
					cl->next = clhead;
					clhead = cl;
				}
				BIO_free(in);
			}
			closedir(dirp);
		}
	}
	for (cl = clhead; cl; cl = cl->next) {
		if (addrlen == cl->addrlen
		&&  memcmp(addr, cl->addr, addrlen) == 0)
			return cl->cert;
	}
	return NULL;
}

int
smime_encrypt(struct state *state)
{
	int i;
	PKCS7 *p7;
	const char *p, *ep;
	BIO *in, *p7bio;
	X509 *x509, *x;
	struct filedb *fdb;
	struct sarg sarg;
	struct header *hdr;
	ccbuf_t rec;

	if (!smime_init_done)
		smime_init();

	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_ENCRYPTED)
	&&  CMATCH("Application/x-pkcs7-mime", &fdb->type)) {
		strlcpy(state->status, "Already encrypted",
		    sizeof(state->status));
		return 0;
	}

	p = getenv("SSL_CLIENT_CERT");
	if (p == NULL || (in = BIO_new_file(p, "r")) == NULL) {
		strlcpy(state->status, "Cannot open certificate file",
		    sizeof(state->status));
		return 0;
	}
#if OPENSSL_VERSION_NUMBER >= 0x00904100
	if ((x509 = PEM_read_bio_X509(in, NULL, NULL, NULL)) == NULL)
#else
	if ((x509 = PEM_read_bio_X509(in, NULL, NULL)) == NULL)
#endif
	{
		strlcpy(state->status, "certificate read failed",
		    sizeof(state->status));
		BIO_free(in);
		return 0;
	}
	BIO_free(in);

	p7 = PKCS7_new();
	PKCS7_set_type(p7, NID_pkcs7_enveloped);

        if (!PKCS7_set_cipher(p7, EVP_des_ede3_cbc())) {	/* XXX */
		strlcpy(state->status, "cipher set failed",
		    sizeof(state->status));
		PKCS7_free(p7);
		X509_free(x509);
		return 0;
	}
	if (PKCS7_add_recipient(p7, x509) == NULL) {
		strlcpy(state->status, "recipient add failed",
		    sizeof(state->status));
		PKCS7_free(p7);
		X509_free(x509);
		return 0;
	}
	/* 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);
			x = smime_find_cert(rec.ptr, rec.len);
			if (x == NULL) {
				snprintf(state->status, sizeof(state->status),
				    "S/MIME certificate not found for %.*s",
				    rec.len, rec.ptr);
				(void)smime_find_cert(NULL, 0);
				PKCS7_free(p7);
				X509_free(x509);
				return 0;
			}
			if (PKCS7_add_recipient(p7, x) == NULL) {
				strlcpy(state->status, "recipient add failed",
				    sizeof(state->status));
				(void)smime_find_cert(NULL, 0);
				PKCS7_free(p7);
				X509_free(x509);
				return 0;
			}
		}
	}

        if ((p7bio = PKCS7_dataInit(p7, NULL)) == NULL) {
		strlcpy(state->status, "pkcs7 init failed",
		    sizeof(state->status));
		(void)smime_find_cert(NULL, 0);
		PKCS7_free(p7);
		X509_free(x509);
		return 0;
	}

	sarg.p7 = p7;
	sarg.p7bio = p7bio;
	if (mpart_sign(state, NULL, smime_enc_enter, smime_enc_part, &sarg) == 0) {
		strlcpy(state->status, "Encrypt failed", sizeof(state->status));
		BIO_free(p7bio);
		(void)smime_find_cert(NULL, 0);
		PKCS7_free(p7);
		X509_free(x509);
		return 0;
	}
#ifdef SMIME_DEBUG
 { FILE *fp = fopen("/tmp/smime.p7m", "w"); i2d_PKCS7_fp(fp, p7); fclose(fp); }
#endif
	BIO_free(p7bio);
	(void)smime_find_cert(NULL, 0);
	PKCS7_free(p7);
	X509_free(x509);
	return 1;
}

#endif /* USE_SMIME */
