/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Jeffrey Stedfast <fejj@ximian.com>
 *
 *  Copyright 2001 Ximian, Inc. (www.ximian.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_NSS
#include "camel-smime-context.h"

#include "camel-mime-filter-from.h"
#include "camel-mime-filter-crlf.h"
#include "camel-stream-filter.h"
#include "camel-stream-fs.h"
#include "camel-stream-mem.h"
#include "camel-mime-part.h"
#include "camel-multipart.h"

#include "nss.h"
#include <cms.h>
#include <cert.h>
#include <certdb.h>
#include <pkcs11.h>
#include <smime.h>

#include <gtk/gtk.h> /* for _() macro */

#define d(x)

struct _CamelSMimeContextPrivate {
	CERTCertDBHandle *certdb;
};


static CamelMimeMessage *smime_sign      (CamelCMSContext *ctx, CamelMimeMessage *message,
					  const char *userid, gboolean signing_time,
					  gboolean detached, CamelException *ex);

static CamelMimeMessage *smime_certsonly (CamelCMSContext *ctx, CamelMimeMessage *message,
					  const char *userid, GPtrArray *recipients,
					  CamelException *ex);

static CamelMimeMessage *smime_encrypt   (CamelCMSContext *ctx, CamelMimeMessage *message,
					  const char *userid, GPtrArray *recipients, 
					  CamelException *ex);

static CamelMimeMessage *smime_envelope  (CamelCMSContext *ctx, CamelMimeMessage *message,
					  const char *userid, GPtrArray *recipients, 
					  CamelException *ex);

static CamelMimeMessage *smime_decode    (CamelCMSContext *ctx, CamelMimeMessage *message,
					  CamelCMSValidityInfo **info, CamelException *ex);

static CamelCMSContextClass *parent_class;

static void
camel_smime_context_init (CamelSMimeContext *context)
{
	context->priv = g_new0 (struct _CamelSMimeContextPrivate, 1);
}

static void
camel_smime_context_finalise (CamelObject *o)
{
	CamelSMimeContext *context = (CamelSMimeContext *)o;
	
	g_free (context->encryption_key);
	g_free (context->priv);
}

static void
camel_smime_context_class_init (CamelSMimeContextClass *camel_smime_context_class)
{
	CamelCMSContextClass *camel_cms_context_class =
		CAMEL_CMS_CONTEXT_CLASS (camel_smime_context_class);
	
	parent_class = CAMEL_CMS_CONTEXT_CLASS (camel_type_get_global_classfuncs (camel_cms_context_get_type ()));
	
	camel_cms_context_class->sign = smime_sign;
	camel_cms_context_class->certsonly = smime_certsonly;
	camel_cms_context_class->encrypt = smime_encrypt;
	camel_cms_context_class->envelope = smime_envelope;
	camel_cms_context_class->decode = smime_decode;
}

CamelType
camel_smime_context_get_type (void)
{
	static CamelType type = CAMEL_INVALID_TYPE;
	
	if (type == CAMEL_INVALID_TYPE) {
		type = camel_type_register (camel_cms_context_get_type (),
					    "CamelSMimeContext",
					    sizeof (CamelSMimeContext),
					    sizeof (CamelSMimeContextClass),
					    (CamelObjectClassInitFunc) camel_smime_context_class_init,
					    NULL,
					    (CamelObjectInitFunc) camel_smime_context_init,
					    (CamelObjectFinalizeFunc) camel_smime_context_finalise);
	}
	
	return type;
}


/**
 * camel_smime_context_new:
 * @session: CamelSession
 * @encryption_key: preferred encryption key (used when attaching cert chains to messages)
 *
 * This creates a new CamelSMimeContext object which is used to sign,
 * verify, encrypt and decrypt streams.
 *
 * Return value: the new CamelSMimeContext
 **/
CamelSMimeContext *
camel_smime_context_new (CamelSession *session, const char *encryption_key)
{
	CamelSMimeContext *context;
	CERTCertDBHandle *certdb;
	
	g_return_val_if_fail (session != NULL, NULL);
	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
	
	certdb = CERT_GetDefaultCertDB ();
	if (!certdb)
		return NULL;
	
	context = CAMEL_SMIME_CONTEXT (camel_object_new (CAMEL_SMIME_CONTEXT_TYPE));
	
	camel_cms_context_construct (CAMEL_CMS_CONTEXT (context), session);
	
	context->encryption_key = g_strdup (encryption_key);
	context->priv->certdb = certdb;
	
	return context;
}


struct _GetPasswdData {
	CamelSession *session;
	const char *userid;
	CamelException *ex;
};

static char *
smime_get_password (PK11SlotInfo *info, PRBool retry, void *arg)
{
	CamelSession *session = ((struct _GetPasswdData *)arg)->session;
	const char *userid = ((struct _GetPasswdData *)arg)->userid;
	CamelException *ex = ((struct _GetPasswdData *)arg)->ex;
	char *prompt, *passwd, *ret;
	
	prompt = g_strdup_printf (_("Please enter your password for %s"), userid);
	passwd = camel_session_get_password (session, prompt, TRUE,
					     NULL, userid, ex);
	g_free (prompt);
	
	ret = PL_strdup (passwd);
	g_free (passwd);
	
	return ret;
}

static PK11SymKey *
decode_key_cb (void *arg, SECAlgorithmID *algid)
{
	return (PK11SymKey *)arg;
}


static NSSCMSMessage *
signed_data (CamelSMimeContext *ctx, const char *userid, gboolean signing_time,
	     gboolean detached, CamelException *ex)
{
	NSSCMSMessage *cmsg = NULL;
	NSSCMSContentInfo *cinfo;
	NSSCMSSignedData *sigd;
	NSSCMSSignerInfo *signerinfo;
	CERTCertificate *cert, *ekpcert;
	
	if (!userid) {
		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
				     _("Please indicate the nickname of a certificate to sign with."));
		return NULL;
	}
	
	if ((cert = CERT_FindCertByNickname (ctx->priv->certdb, (char *) userid)) == NULL) {
		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
				      _("The signature certificate for \"%s\" does not exist."),
				      userid);
		return NULL;
	}
	
	/* create the cms message object */
	cmsg = NSS_CMSMessage_Create (NULL);
	
	/* build chain of objects: message->signedData->data */
	sigd = NSS_CMSSignedData_Create (cmsg);
	
	cinfo = NSS_CMSMessage_GetContentInfo (cmsg);
	NSS_CMSContentInfo_SetContent_SignedData (cmsg, cinfo, sigd); 
	
	cinfo = NSS_CMSSignedData_GetContentInfo (sigd);
	
	/* speciffy whether we want detached signatures or not */
	NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, detached);
	
	/* create & attach signer information */
	signerinfo = NSS_CMSSignerInfo_Create (cmsg, cert, SEC_OID_SHA1);
	
	/* include the cert chain */
	NSS_CMSSignerInfo_IncludeCerts (signerinfo, NSSCMSCM_CertChain, 
					certUsageEmailSigner);
	
	if (signing_time) {
		NSS_CMSSignerInfo_AddSigningTime (signerinfo, PR_Now ());
	}
	
	if (TRUE) {
		/* Add S/MIME Capabilities */
		NSS_CMSSignerInfo_AddSMIMECaps (signerinfo);
	}
	
	if (ctx->encryption_key) {
		/* get the cert, add it to the message */
		ekpcert = CERT_FindCertByNickname (ctx->priv->certdb, ctx->encryption_key);
		if (!ekpcert) {
			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
					      _("The encryption certificate for \"%s\" does not exist."),
					      ctx->encryption_key);
			goto exception;
		}
		
		NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs (signerinfo, ekpcert, ctx->priv->certdb);
		
		NSS_CMSSignedData_AddCertificate (sigd, ekpcert);
	} else {
		/* check signing cert for fitness as encryption cert */
		/* if yes, add signing cert as EncryptionKeyPreference */
		NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs (signerinfo, cert, ctx->priv->certdb);
	}
	
	NSS_CMSSignedData_AddSignerInfo (sigd, signerinfo);
	
	return cmsg;
	
 exception:
	
	NSS_CMSMessage_Destroy (cmsg);
	
	return NULL;
}

static void
smime_sign_restore (CamelMimePart *mime_part, GSList **encodings)
{
	CamelDataWrapper *wrapper;
	
	wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
	if (!wrapper)
		return;
	
	if (CAMEL_IS_MULTIPART (wrapper)) {
		int parts, i;
		
		parts = camel_multipart_get_number (CAMEL_MULTIPART (wrapper));
		for (i = 0; i < parts; i++) {
			CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (wrapper), i);
			
			smime_sign_restore (part, encodings);
			*encodings = (*encodings)->next;
		}
	} else {
		CamelMimePartEncodingType encoding;
		
		if (CAMEL_IS_MIME_MESSAGE (wrapper)) {
			/* restore the message parts' subparts */
			smime_sign_restore (CAMEL_MIME_PART (wrapper), encodings);
		} else {
			encoding = GPOINTER_TO_INT ((*encodings)->data);
			
			camel_mime_part_set_encoding (mime_part, encoding);
		}
	}
}

static void
smime_sign_prepare (CamelMimePart *mime_part, GSList **encodings)
{
	CamelDataWrapper *wrapper;
	int parts, i;
	
	wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
	if (!wrapper)
		return;
	
	if (CAMEL_IS_MULTIPART (wrapper)) {
		parts = camel_multipart_get_number (CAMEL_MULTIPART (wrapper));
		for (i = 0; i < parts; i++) {
			CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (wrapper), i);
			
			smime_sign_prepare (part, encodings);
		}
	} else {
		CamelMimePartEncodingType encoding;
		
		if (CAMEL_IS_MIME_MESSAGE (wrapper)) {
			/* prepare the message parts' subparts */
			smime_sign_prepare (CAMEL_MIME_PART (wrapper), encodings);
		} else {
			encoding = camel_mime_part_get_encoding (mime_part);
			
			/* FIXME: find the best encoding for this part and use that instead?? */
			/* the encoding should really be QP or Base64 */
			if (encoding != CAMEL_MIME_PART_ENCODING_BASE64)
				camel_mime_part_set_encoding (mime_part, CAMEL_MIME_PART_ENCODING_QUOTEDPRINTABLE);
			
			*encodings = g_slist_append (*encodings, GINT_TO_POINTER (encoding));
		}
	}
}


static CamelMimeMessage *
smime_sign (CamelCMSContext *ctx, CamelMimeMessage *message,
	    const char *userid, gboolean signing_time,
	    gboolean detached, CamelException *ex)
{
	CamelMimeMessage *mesg = NULL;
	NSSCMSMessage *cmsg = NULL;
	struct _GetPasswdData *data;
	PLArenaPool *arena;
	NSSCMSEncoderContext *ecx;
	SECItem output = { 0, 0, 0 };
	CamelStream *stream;
	GSList *list, *encodings = NULL;
	GByteArray *buf;
	
	cmsg = signed_data (CAMEL_SMIME_CONTEXT (ctx), userid, signing_time, detached, ex);
	if (!cmsg)
		return NULL;
	
	arena = PORT_NewArena (1024);
	data = g_new (struct _GetPasswdData, 1);
	data->session = ctx->session;
	data->userid = userid;
	data->ex = ex;
	ecx = NSS_CMSEncoder_Start (cmsg, NULL, NULL, &output, arena,
				    smime_get_password, data, NULL, NULL,
				    NULL, NULL);
	
	stream = camel_stream_mem_new ();
	
	smime_sign_prepare (CAMEL_MIME_PART (message), &encodings);
	camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream);
	list = encodings;
	smime_sign_restore (CAMEL_MIME_PART (message), &list);
	g_slist_free (encodings);
	
	buf = CAMEL_STREAM_MEM (stream)->buffer;
	
	NSS_CMSEncoder_Update (ecx, buf->data, buf->len);
	NSS_CMSEncoder_Finish (ecx);
	
	camel_object_unref (CAMEL_OBJECT (stream));
	g_free (data);
	
	/* write the result to a camel stream */
	stream = camel_stream_mem_new ();
	camel_stream_write (stream, output.data, output.len);
	PORT_FreeArena (arena, PR_FALSE);
	
	NSS_CMSMessage_Destroy (cmsg);
	
	/* parse the stream into a new CamelMimeMessage */
	mesg = camel_mime_message_new ();
	camel_stream_reset (stream);
	camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), stream);
	camel_object_unref (CAMEL_OBJECT (stream));
	
	return mesg;
}


static NSSCMSMessage *
certsonly_data (CamelSMimeContext *ctx, const char *userid, GPtrArray *recipients, CamelException *ex)
{
	NSSCMSMessage *cmsg = NULL;
	NSSCMSContentInfo *cinfo;
	NSSCMSSignedData *sigd;
	CERTCertificate **rcerts;
	int i = 0;
	
	/* find the signer's and the recipients' certs */
	rcerts = g_new (CERTCertificate *, recipients->len + 2);
	rcerts[0] = CERT_FindCertByNicknameOrEmailAddr (ctx->priv->certdb, (char *) userid);
	if (!rcerts[0]) {
		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
				      _("Failed to find certificate for \"%s\"."),
				      recipients->pdata[i]);
		goto exception;
	}
	
	for (i = 0; i < recipients->len; i++) {
		rcerts[i + 1] = CERT_FindCertByNicknameOrEmailAddr (ctx->priv->certdb,
								    recipients->pdata[i]);
		
		if (!rcerts[i + 1]) {
			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
					      _("Failed to find certificate for \"%s\"."),
					      recipients->pdata[i]);
			goto exception;
		}
	}
	rcerts[i + 1] = NULL;
	
	/* create the cms message object */
	cmsg = NSS_CMSMessage_Create (NULL);
	
	sigd = NSS_CMSSignedData_CreateCertsOnly (cmsg, rcerts[0], PR_TRUE);
	
	/* add the recipient cert chain */
	for (i = 0; i < recipients->len; i++) {
		NSS_CMSSignedData_AddCertChain (sigd, rcerts[i]);
	}
	
	cinfo = NSS_CMSMessage_GetContentInfo (cmsg);
	NSS_CMSContentInfo_SetContent_SignedData (cmsg, cinfo, sigd);
	
	cinfo = NSS_CMSSignedData_GetContentInfo (sigd);
	NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, PR_FALSE);
	
	g_free (rcerts);
	
	return cmsg;
	
 exception:
	
	NSS_CMSMessage_Destroy (cmsg);
	
	g_free (rcerts);
	
	return NULL;
}

static CamelMimeMessage *
smime_certsonly (CamelCMSContext *ctx, CamelMimeMessage *message,
		 const char *userid, GPtrArray *recipients,
		 CamelException *ex)
{
	CamelMimeMessage *mesg = NULL;
	struct _GetPasswdData *data;
	NSSCMSMessage *cmsg = NULL;
	PLArenaPool *arena;
	NSSCMSEncoderContext *ecx;
	SECItem output = { 0, 0, 0 };
	CamelStream *stream;
	GByteArray *buf;
	
	cmsg = certsonly_data (CAMEL_SMIME_CONTEXT (ctx), userid, recipients, ex);
	if (!cmsg)
		return NULL;
	
	arena = PORT_NewArena (1024);
	data = g_new (struct _GetPasswdData, 1);
	data->session = ctx->session;
	data->userid = userid;
	data->ex = ex;
	ecx = NSS_CMSEncoder_Start (cmsg, NULL, NULL, &output, arena,
				    smime_get_password, data, NULL, NULL,
				    NULL, NULL);
	
	stream = camel_stream_mem_new ();
	camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream);
	buf = CAMEL_STREAM_MEM (stream)->buffer;
	
	NSS_CMSEncoder_Update (ecx, buf->data, buf->len);
	NSS_CMSEncoder_Finish (ecx);
	
	camel_object_unref (CAMEL_OBJECT (stream));
	g_free (data);
	
	/* write the result to a camel stream */
	stream = camel_stream_mem_new ();
	camel_stream_write (stream, output.data, output.len);
	PORT_FreeArena (arena, PR_FALSE);
	
	NSS_CMSMessage_Destroy (cmsg);
	
	/* parse the stream into a new CamelMimeMessage */
	mesg = camel_mime_message_new ();
	camel_stream_reset (stream);
	camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), stream);
	camel_object_unref (CAMEL_OBJECT (stream));
	
	return mesg;
}


static NSSCMSMessage *
enveloped_data (CamelSMimeContext *ctx, const char *userid, GPtrArray *recipients, CamelException *ex)
{
	NSSCMSMessage *cmsg = NULL;
	NSSCMSContentInfo *cinfo;
	NSSCMSEnvelopedData *envd;
	NSSCMSRecipientInfo *rinfo;
	CERTCertificate **rcerts;
	SECOidTag bulkalgtag;
	int keysize, i;
	
	/* find the recipient certs by email address or nickname */
	rcerts = g_new (CERTCertificate *, recipients->len + 2);
	rcerts[0] = CERT_FindCertByNicknameOrEmailAddr (ctx->priv->certdb, (char *) userid);
	if (!rcerts[0]) {
		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
				      _("Failed to find certificate for \"%s\"."),
				      userid);
		goto exception;
	}
	
	for (i = 0; i < recipients->len; i++) {
		rcerts[i + 1] = CERT_FindCertByNicknameOrEmailAddr (ctx->priv->certdb,  
								    recipients->pdata[i]);
		if (!rcerts[i + 1]) {
			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
					      _("Failed to find certificate for \"%s\"."),
					      recipients->pdata[i]);
			goto exception;
		}
	}
	rcerts[i + 1] = NULL;
	
	/* find a nice bulk algorithm */
	if (!NSS_SMIMEUtil_FindBulkAlgForRecipients (rcerts, &bulkalgtag, &keysize)) {
		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
				     _("Failed to find a common bulk algorithm."));
		goto exception;
	}
	
	/* create a cms message object */
	cmsg = NSS_CMSMessage_Create (NULL);
	
	envd = NSS_CMSEnvelopedData_Create (cmsg, bulkalgtag, keysize);
	cinfo = NSS_CMSMessage_GetContentInfo (cmsg);
	NSS_CMSContentInfo_SetContent_EnvelopedData (cmsg, cinfo, envd);
	
	cinfo = NSS_CMSEnvelopedData_GetContentInfo (envd);
	NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, PR_FALSE);
	
	/* create & attach recipient information */
	for (i = 0; rcerts[i] != NULL; i++) {
		rinfo = NSS_CMSRecipientInfo_Create (cmsg, rcerts[i]);
		NSS_CMSEnvelopedData_AddRecipient (envd, rinfo);
	}
	
	g_free (rcerts);
	
	return cmsg;
	
 exception:
	
	NSS_CMSMessage_Destroy (cmsg);
	
	g_free (rcerts);
	
	return NULL;
}

static CamelMimeMessage *
smime_envelope (CamelCMSContext *ctx, CamelMimeMessage *message,
		const char *userid, GPtrArray *recipients, 
		CamelException *ex)
{
	CamelMimeMessage *mesg = NULL;
	struct _GetPasswdData *data;
	NSSCMSMessage *cmsg = NULL;
	PLArenaPool *arena;
	NSSCMSEncoderContext *ecx;
	SECItem output = { 0, 0, 0 };
	CamelStream *stream;
	GByteArray *buf;
	
	cmsg = enveloped_data (CAMEL_SMIME_CONTEXT (ctx), userid, recipients, ex);
	if (!cmsg)
		return NULL;
	
	arena = PORT_NewArena (1024);
	data = g_new (struct _GetPasswdData, 1);
	data->session = ctx->session;
	data->userid = userid;
	data->ex = ex;
	ecx = NSS_CMSEncoder_Start (cmsg, NULL, NULL, &output, arena,
				    smime_get_password, data, NULL, NULL,
				    NULL, NULL);
	
	stream = camel_stream_mem_new ();
	camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream);
	buf = CAMEL_STREAM_MEM (stream)->buffer;
	
	NSS_CMSEncoder_Update (ecx, buf->data, buf->len);
	NSS_CMSEncoder_Finish (ecx);
	
	camel_object_unref (CAMEL_OBJECT (stream));
	g_free (data);
	
	/* write the result to a camel stream */
	stream = camel_stream_mem_new ();
	camel_stream_write (stream, output.data, output.len);
	PORT_FreeArena (arena, PR_FALSE);
	
	NSS_CMSMessage_Destroy (cmsg);
	
	/* parse the stream into a new CamelMimeMessage */
	mesg = camel_mime_message_new ();
	camel_stream_reset (stream);
	camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), stream);
	camel_object_unref (CAMEL_OBJECT (stream));
	
	return mesg;
}


struct _BulkKey {
	PK11SymKey *bulkkey;
	SECOidTag bulkalgtag;
	int keysize;
};

static NSSCMSMessage *
encrypted_data (CamelSMimeContext *ctx, GByteArray *input, struct _BulkKey *key,
		CamelStream *ostream, CamelException *ex)
{
	NSSCMSMessage *cmsg = NULL;
	NSSCMSContentInfo *cinfo;
	NSSCMSEncryptedData *encd;
	NSSCMSEncoderContext *ecx = NULL;
	PLArenaPool *arena = NULL;
	SECItem output = { 0, 0, 0 };
	
	/* arena for output */
	arena = PORT_NewArena (1024);
	
	/* create cms message object */
	cmsg = NSS_CMSMessage_Create (NULL);
	
	encd = NSS_CMSEncryptedData_Create (cmsg, key->bulkalgtag, key->keysize);
	
	cinfo = NSS_CMSMessage_GetContentInfo (cmsg);
	NSS_CMSContentInfo_SetContent_EncryptedData (cmsg, cinfo, encd);
	
	cinfo = NSS_CMSEncryptedData_GetContentInfo (encd);
	NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, PR_FALSE);
	
	ecx = NSS_CMSEncoder_Start (cmsg, NULL, NULL, &output, arena, NULL, NULL,
				    decode_key_cb, key->bulkkey, NULL, NULL);
	
	NSS_CMSEncoder_Update (ecx, input->data, input->len);
	
	NSS_CMSEncoder_Finish (ecx);
	
	camel_stream_write (ostream, output.data, output.len);
	
	if (arena)
		PORT_FreeArena (arena, PR_FALSE);
	
	return cmsg;
}

static struct _BulkKey *
get_bulkkey (CamelSMimeContext *ctx, const char *userid, GPtrArray *recipients, CamelException *ex)
{
	struct _BulkKey *bulkkey = NULL;
	NSSCMSMessage *env_cmsg;
	NSSCMSContentInfo *cinfo;
	SECItem dummyOut = { 0, 0, 0 };
	SECItem dummyIn  = { 0, 0, 0 };
	char str[] = "You are not a beautiful and unique snowflake.";
	PLArenaPool *arena;
	int i, nlevels;
	
	/* construct an enveloped data message to obtain bulk keys */
	arena = PORT_NewArena (1024);
	dummyIn.data = (unsigned char *)str;
	dummyIn.len = strlen (str);
	
	env_cmsg = enveloped_data (ctx, userid, recipients, ex);
	NSS_CMSDEREncode (env_cmsg, &dummyIn, &dummyOut, arena);
	/*camel_stream_write (envstream, dummyOut.data, dummyOut.len);*/
	PORT_FreeArena (arena, PR_FALSE);
	
	/* get the content info for the enveloped data */
	nlevels = NSS_CMSMessage_ContentLevelCount (env_cmsg);
	for (i = 0; i < nlevels; i++) {
		SECOidTag typetag;
		
		cinfo = NSS_CMSMessage_ContentLevel (env_cmsg, i);
		typetag = NSS_CMSContentInfo_GetContentTypeTag (cinfo);
		if (typetag == SEC_OID_PKCS7_DATA) {
			bulkkey = g_new (struct _BulkKey, 1);
			
			/* get the symmertic key */
			bulkkey->bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag (cinfo);
			bulkkey->keysize = NSS_CMSContentInfo_GetBulkKeySize (cinfo);
			bulkkey->bulkkey = NSS_CMSContentInfo_GetBulkKey (cinfo);
			
			return bulkkey;
		}
	}
	
	return NULL;
}

static CamelMimeMessage *
smime_encrypt (CamelCMSContext *ctx, CamelMimeMessage *message,
	       const char *userid, GPtrArray *recipients, 
	       CamelException *ex)
{
	struct _BulkKey *bulkkey = NULL;
	CamelMimeMessage *mesg = NULL;
	NSSCMSMessage *cmsg = NULL;
	CamelStream *stream;
	GByteArray *buf;
	
	bulkkey = get_bulkkey (CAMEL_SMIME_CONTEXT (ctx), userid, recipients, ex);
	if (!bulkkey)
		return NULL;
	
	buf = g_byte_array_new ();
	stream = camel_stream_mem_new ();
	camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buf);
	camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream);
	camel_object_unref (CAMEL_OBJECT (stream));
	
	stream = camel_stream_mem_new ();
	cmsg = encrypted_data (CAMEL_SMIME_CONTEXT (ctx), buf, bulkkey, stream, ex);
	g_byte_array_free (buf, TRUE);
	g_free (bulkkey);
	if (!cmsg) {
		camel_object_unref (CAMEL_OBJECT (stream));
		return NULL;
	}
	
	NSS_CMSMessage_Destroy (cmsg);
	
	/* parse the stream into a new CamelMimeMessage */
	mesg = camel_mime_message_new ();
	camel_stream_reset (stream);
	camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), stream);
	camel_object_unref (CAMEL_OBJECT (stream));
	
	return mesg;
}


static NSSCMSMessage *
decode_data (CamelSMimeContext *ctx, GByteArray *input, CamelStream *ostream,
	     CamelCMSValidityInfo **info, CamelException *ex)
{
	NSSCMSDecoderContext *dcx;
	struct _GetPasswdData *data;
	CamelCMSValidityInfo *vinfo = NULL;
	NSSCMSMessage *cmsg = NULL;
	NSSCMSContentInfo *cinfo;
	NSSCMSSignedData *sigd = NULL;
	NSSCMSEnvelopedData *envd;
	NSSCMSEncryptedData *encd;
	int nlevels, i, nsigners, j;
	char *signercn;
	NSSCMSSignerInfo *si;
	SECOidTag typetag;
	SECItem *item;
	
	data = g_new (struct _GetPasswdData, 1);
	data->session = CAMEL_CMS_CONTEXT (ctx)->session;
	data->userid = NULL;
	data->ex = ex;
	
	dcx = NSS_CMSDecoder_Start (NULL,
				    NULL, NULL,
				    smime_get_password, data,
				    decode_key_cb,
				    NULL);
	
	NSS_CMSDecoder_Update (dcx, input->data, input->len);
	
	cmsg = NSS_CMSDecoder_Finish (dcx);
	g_free (data);
	if (cmsg == NULL) {
		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
				     _("Failed to decode message."));
		return NULL;
	}
	
	nlevels = NSS_CMSMessage_ContentLevelCount (cmsg);
	for (i = 0; i < nlevels; i++) {
		CamelCMSSigner *signers = NULL;
		
		cinfo = NSS_CMSMessage_ContentLevel (cmsg, i);
		typetag = NSS_CMSContentInfo_GetContentTypeTag (cinfo);
		
		if (info && !vinfo) {
			vinfo = g_new0 (CamelCMSValidityInfo, 1);
			*info = vinfo;
		} else if (vinfo) {
			vinfo->next = g_new0 (CamelCMSValidityInfo, 1);
			vinfo = vinfo->next;
		}
		
		switch (typetag) {
		case SEC_OID_PKCS7_SIGNED_DATA:
			if (vinfo)
				vinfo->type = CAMEL_CMS_TYPE_SIGNED;
			
			sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent (cinfo);
			
			/* import the certificates */
			NSS_CMSSignedData_ImportCerts (sigd, ctx->priv->certdb,
						       certUsageEmailSigner, PR_FALSE);
			
			/* find out about signers */
			nsigners = NSS_CMSSignedData_SignerInfoCount (sigd);
			
			if (nsigners == 0) {
				/* must be a cert transport message */
				SECStatus retval;
				
				/* XXX workaround for bug #54014 */
				NSS_CMSSignedData_ImportCerts (sigd, ctx->priv->certdb, 
							       certUsageEmailSigner, PR_TRUE);
				
				retval = NSS_CMSSignedData_VerifyCertsOnly (sigd, ctx->priv->certdb, 
									    certUsageEmailSigner);
				if (retval != SECSuccess) {
					camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
							     _("Failed to verify certificates."));
					goto exception;
				}
				
				return cmsg;
			}
			
			for (j = 0; vinfo && j < nsigners; j++) {
				if (!signers) {
					signers = g_new0 (CamelCMSSigner, 1);
					vinfo->signers = signers;
				} else {
					signers->next = g_new0 (CamelCMSSigner, 1);
					signers = signers->next;
				}
				
				si = NSS_CMSSignedData_GetSignerInfo (sigd, j);
				signercn = NSS_CMSSignerInfo_GetSignerCommonName (si);
				if (signercn == NULL)
					signercn = "";
				
				NSS_CMSSignedData_VerifySignerInfo (sigd, j, ctx->priv->certdb, 
								    certUsageEmailSigner);
				
				if (signers) {
					signers->signercn = g_strdup (signercn);
					signers->status = g_strdup (
						NSS_CMSUtil_VerificationStatusToString (
							NSS_CMSSignerInfo_GetVerificationStatus (si)));
				}
			}
			break;
		case SEC_OID_PKCS7_ENVELOPED_DATA:
			if (vinfo)
				vinfo->type = CAMEL_CMS_TYPE_ENVELOPED;
			
			envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent (cinfo);
			break;
		case SEC_OID_PKCS7_ENCRYPTED_DATA:
			if (vinfo)
				vinfo->type = CAMEL_CMS_TYPE_ENCRYPTED;
			
			encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent (cinfo);
			break;
		case SEC_OID_PKCS7_DATA:
			break;
		default:
			break;
		}
	}
	
	item = NSS_CMSMessage_GetContent (cmsg);
	camel_stream_write (ostream, item->data, item->len);
	
	return cmsg;
	
 exception:
	
	if (info)
		camel_cms_validity_info_free (*info);
	
	if (cmsg)
		NSS_CMSMessage_Destroy (cmsg);
	
	return NULL;
}


static CamelMimeMessage *
smime_decode (CamelCMSContext *ctx, CamelMimeMessage *message,
	      CamelCMSValidityInfo **info, CamelException *ex)
{
	CamelMimeMessage *mesg = NULL;
	NSSCMSMessage *cmsg = NULL;
	CamelStream *stream, *ostream;
	GByteArray *buf;
	
	stream = camel_stream_mem_new ();
	camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream);
	buf = CAMEL_STREAM_MEM (stream)->buffer;
	
	ostream = camel_stream_mem_new ();
	cmsg = decode_data (CAMEL_SMIME_CONTEXT (ctx), buf, ostream, info, ex);
	camel_object_unref (CAMEL_OBJECT (stream));
	if (!cmsg) {
		camel_object_unref (CAMEL_OBJECT (ostream));
		return NULL;
	}
	
	/* construct a new mime message from the stream */
	mesg = camel_mime_message_new ();
	camel_stream_reset (ostream);
	camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), ostream);
	camel_object_unref (CAMEL_OBJECT (ostream));
	
	return mesg;
}

#endif /* HAVE_NSS */
