summaryrefslogtreecommitdiff
path: root/pith/smime.c
diff options
context:
space:
mode:
Diffstat (limited to 'pith/smime.c')
-rw-r--r--pith/smime.c2377
1 files changed, 2377 insertions, 0 deletions
diff --git a/pith/smime.c b/pith/smime.c
new file mode 100644
index 00000000..dc332a2a
--- /dev/null
+++ b/pith/smime.c
@@ -0,0 +1,2377 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: smime.c 1176 2008-09-29 21:16:42Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*
+ * This is based on a contribution from Jonathan Paisley
+ *
+ * File: smime.c
+ * Author: paisleyj@dcs.gla.ac.uk
+ * Date: 01/2001
+ */
+
+
+#include "../pith/headers.h"
+
+#ifdef SMIME
+
+#include "../pith/osdep/canaccess.h"
+#include "../pith/helptext.h"
+#include "../pith/store.h"
+#include "../pith/status.h"
+#include "../pith/detach.h"
+#include "../pith/conf.h"
+#include "../pith/smkeys.h"
+#include "../pith/smime.h"
+#include "../pith/mailpart.h"
+#include "../pith/reply.h"
+#include "../pith/tempfile.h"
+#include "../pith/readfile.h"
+#include "../pith/remote.h"
+
+#include <openssl/buffer.h>
+
+
+typedef enum {Public, Private, CACert} WhichCerts;
+
+
+/* internal prototypes */
+static void forget_private_keys(void);
+static int app_RAND_load_file(const char *file);
+static void openssl_extra_randomness(void);
+static int app_RAND_write_file(const char *file);
+static void smime_init(void);
+static const char *openssl_error_string(void);
+static void create_local_cache(char *base, BODY *b);
+static BIO *raw_part_to_bio(long msgno, const char *section);
+static long rfc822_output_func(void *b, char *string);
+static int load_private_key(PERSONAL_CERT *pcert);
+static void setup_pkcs7_body_for_signature(BODY *b, char *description,
+ char *type, char *filename);
+static BIO *body_to_bio(BODY *body);
+static BIO *bio_from_store(STORE_S *store);
+static STORE_S *get_part_contents(long msgno, const char *section);
+static PKCS7 *get_pkcs7_from_part(long msgno, const char *section);
+static int do_signature_verify(PKCS7 *p7, BIO *in, BIO *out);
+static int do_detached_signature_verify(BODY *b, long msgno, char *section);
+static PERSONAL_CERT *find_certificate_matching_pkcs7(PKCS7 *p7);
+static int do_decoding(BODY *b, long msgno, const char *section);
+static void free_smime_struct(SMIME_STUFF_S **smime);
+static void setup_storage_locations(void);
+static int copy_dir_to_container(WhichCerts which);
+static int copy_container_to_dir(WhichCerts which);
+
+
+int (*pith_opt_smime_get_passphrase)(void);
+
+
+static X509_STORE *s_cert_store;
+
+/* State management for randomness functions below */
+static int seeded = 0;
+static int egdsocket = 0;
+
+
+/*
+ * Forget any cached private keys
+ */
+static void
+forget_private_keys(void)
+{
+ PERSONAL_CERT *pcert;
+ size_t len;
+ volatile char *p;
+
+ dprint((9, "forget_private_keys()"));
+ if(ps_global->smime){
+ for(pcert=(PERSONAL_CERT *) ps_global->smime->personal_certs;
+ pcert;
+ pcert=pcert->next){
+
+ if(pcert->key){
+ EVP_PKEY_free(pcert->key);
+ pcert->key = NULL;
+ }
+ }
+
+ ps_global->smime->entered_passphrase = 0;
+ len = sizeof(ps_global->smime->passphrase);
+ p = ps_global->smime->passphrase;
+
+ while(len-- > 0)
+ *p++ = '\0';
+ }
+}
+
+
+/*
+ * taken from openssl/apps/app_rand.c
+ */
+static int
+app_RAND_load_file(const char *file)
+{
+ char buffer[200];
+
+ if(file == NULL)
+ file = RAND_file_name(buffer, sizeof buffer);
+ else if(RAND_egd(file) > 0){
+ /* we try if the given filename is an EGD socket.
+ if it is, we don't write anything back to the file. */
+ egdsocket = 1;
+ return 1;
+ }
+
+ if(file == NULL || !RAND_load_file(file, -1)){
+ if(RAND_status() == 0){
+ dprint((1, "unable to load 'random state'\n"));
+ dprint((1, "This means that the random number generator has not been seeded\n"));
+ dprint((1, "with much random data.\n"));
+ }
+
+ return 0;
+ }
+
+ seeded = 1;
+ return 1;
+}
+
+
+/*
+ * copied and fiddled from imap/src/osdep/unix/auth_ssl.c
+ */
+static void
+openssl_extra_randomness(void)
+{
+#if !defined(WIN32)
+ int fd;
+ unsigned long i;
+ char *tf = NULL;
+ char tmp[MAXPATH];
+ struct stat sbuf;
+ /* if system doesn't have /dev/urandom */
+ if(stat ("/dev/urandom", &sbuf)){
+ tmp[0] = '0';
+ tf = temp_nam(NULL, NULL);
+ if(tf){
+ strncpy(tmp, tf, sizeof(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ fs_give((void **) &tf);
+ }
+
+ if((fd = open(tmp, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0)
+ i = (unsigned long) tmp;
+ else{
+ unlink(tmp); /* don't need the file */
+ fstat(fd, &sbuf); /* get information about the file */
+ i = sbuf.st_ino; /* remember its inode */
+ close(fd); /* or its descriptor */
+ }
+ /* not great but it'll have to do */
+ snprintf(tmp+strlen(tmp), sizeof(tmp)-strlen(tmp), "%.80s%lx%lx%lx",
+ tcp_serverhost (),i,
+ (unsigned long) (time (0) ^ gethostid ()),
+ (unsigned long) getpid ());
+ RAND_seed(tmp, strlen(tmp));
+ }
+#endif
+}
+
+
+/* taken from openssl/apps/app_rand.c */
+static int
+app_RAND_write_file(const char *file)
+{
+ char buffer[200];
+
+ if(egdsocket || !seeded)
+ /*
+ * If we did not manage to read the seed file,
+ * we should not write a low-entropy seed file back --
+ * it would suppress a crucial warning the next time
+ * we want to use it.
+ */
+ return 0;
+
+ if(file == NULL)
+ file = RAND_file_name(buffer, sizeof buffer);
+
+ if(file == NULL || !RAND_write_file(file)){
+ dprint((1, "unable to write 'random state'\n"));
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/* Installed as an atexit() handler to save the random data */
+void
+smime_deinit(void)
+{
+ dprint((9, "smime_deinit()"));
+ app_RAND_write_file(NULL);
+ free_smime_struct(&ps_global->smime);
+}
+
+
+/* Initialise openssl stuff if needed */
+static void
+smime_init(void)
+{
+ if(F_OFF(F_DONT_DO_SMIME, ps_global) && !(ps_global->smime && ps_global->smime->inited)){
+
+ dprint((9, "smime_init()"));
+ if(!ps_global->smime)
+ ps_global->smime = new_smime_struct();
+
+ setup_storage_locations();
+
+ s_cert_store = get_ca_store();
+
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+
+ app_RAND_load_file(NULL);
+
+ openssl_extra_randomness();
+ ps_global->smime->inited = 1;
+ }
+
+ ERR_clear_error();
+}
+
+
+static void
+setup_storage_locations(void)
+{
+ int publiccertcontainer = 0, privatekeycontainer = 0, cacertcontainer = 0;
+ char path[MAXPATH+1], *contents;
+
+ if(!ps_global->smime)
+ return;
+
+#ifdef APPLEKEYCHAIN
+ if(F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){
+ ps_global->smime->publictype = Keychain;
+ }
+ else{
+#endif /* APPLEKEYCHAIN */
+ /* Public certificates in a container */
+ if(ps_global->VAR_PUBLICCERT_CONTAINER && ps_global->VAR_PUBLICCERT_CONTAINER[0]){
+
+ publiccertcontainer = 1;
+ contents = NULL;
+ path[0] = '\0';
+ if(!signature_path(ps_global->VAR_PUBLICCERT_CONTAINER, path, MAXPATH))
+ publiccertcontainer = 0;
+
+ if(publiccertcontainer && !IS_REMOTE(path)
+ && ps_global->VAR_OPER_DIR
+ && !in_dir(ps_global->VAR_OPER_DIR, path)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: First arg is the directory name, second is
+ the file user wants to read but can't. */
+ _("Can't read file outside %s: %s"),
+ ps_global->VAR_OPER_DIR, path);
+ publiccertcontainer = 0;
+ }
+
+ if(publiccertcontainer
+ && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){
+ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE)))
+ &&
+ !(contents = read_file(path, READ_FROM_LOCALE)))
+ publiccertcontainer = 0;
+ }
+
+ if(publiccertcontainer && path[0]){
+ ps_global->smime->publictype = Container;
+ ps_global->smime->publicpath = cpystr(path);
+
+ if(contents){
+ ps_global->smime->publiccontent = contents;
+ ps_global->smime->publiccertlist = mem_to_certlist(contents);
+ }
+ }
+ }
+
+ /* Public certificates in a directory of files */
+ if(!publiccertcontainer){
+ ps_global->smime->publictype = Directory;
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_PUBLICCERT_DIR, path, MAXPATH)
+ && !IS_REMOTE(path)))
+ ps_global->smime->publictype = Nada;
+ else if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ ps_global->smime->publictype = Nada;
+ }
+ }
+
+ if(ps_global->smime->publictype == Directory)
+ ps_global->smime->publicpath = cpystr(path);
+ }
+
+#ifdef APPLEKEYCHAIN
+ }
+#endif /* APPLEKEYCHAIN */
+
+ /* private keys in a container */
+ if(ps_global->VAR_PRIVATEKEY_CONTAINER && ps_global->VAR_PRIVATEKEY_CONTAINER[0]){
+
+ privatekeycontainer = 1;
+ contents = NULL;
+ path[0] = '\0';
+ if(!signature_path(ps_global->VAR_PRIVATEKEY_CONTAINER, path, MAXPATH))
+ privatekeycontainer = 0;
+
+ if(privatekeycontainer && !IS_REMOTE(path)
+ && ps_global->VAR_OPER_DIR
+ && !in_dir(ps_global->VAR_OPER_DIR, path)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: First arg is the directory name, second is
+ the file user wants to read but can't. */
+ _("Can't read file outside %s: %s"),
+ ps_global->VAR_OPER_DIR, path);
+ privatekeycontainer = 0;
+ }
+
+ if(privatekeycontainer
+ && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){
+ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE)))
+ &&
+ !(contents = read_file(path, READ_FROM_LOCALE)))
+ privatekeycontainer = 0;
+ }
+
+ if(privatekeycontainer && path[0]){
+ ps_global->smime->privatetype = Container;
+ ps_global->smime->privatepath = cpystr(path);
+
+ if(contents){
+ ps_global->smime->privatecontent = contents;
+ ps_global->smime->personal_certs = mem_to_personal_certs(contents);
+ }
+ }
+ }
+
+ /* private keys in a directory of files */
+ if(!privatekeycontainer){
+ PERSONAL_CERT *result = NULL;
+
+ ps_global->smime->privatetype = Directory;
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_PRIVATEKEY_DIR, path, MAXPATH)
+ && !IS_REMOTE(path)))
+ ps_global->smime->privatetype = Nada;
+ else if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ ps_global->smime->privatetype = Nada;
+ }
+ }
+
+ if(ps_global->smime->privatetype == Directory){
+ char buf2[MAXPATH];
+ struct dirent *d;
+ DIR *dirp;
+
+ ps_global->smime->privatepath = cpystr(path);
+ dirp = opendir(path);
+ if(dirp){
+
+ while((d=readdir(dirp)) != NULL){
+ X509 *cert;
+ size_t ll;
+
+ if((ll=strlen(d->d_name)) && ll > 4 && !strcmp(d->d_name+ll-4, ".key")){
+
+ /* copy file name to temp buffer */
+ strncpy(buf2, d->d_name, sizeof(buf2)-1);
+ buf2[sizeof(buf2)-1] = '\0';
+ /* chop off ".key" trailier */
+ buf2[strlen(buf2)-4] = 0;
+ /* Look for certificate */
+ cert = get_cert_for(buf2);
+
+ if(cert){
+ PERSONAL_CERT *pc;
+
+ /* create a new PERSONAL_CERT, fill it in */
+
+ pc = (PERSONAL_CERT *) fs_get(sizeof(*pc));
+ pc->cert = cert;
+ pc->name = cpystr(buf2);
+
+ /* Try to load the key with an empty password */
+ pc->key = load_key(pc, "");
+
+ pc->next = result;
+ result = pc;
+ }
+ }
+ }
+
+ closedir(dirp);
+ }
+ }
+
+ ps_global->smime->personal_certs = result;
+ }
+
+ /* extra cacerts in a container */
+ if(ps_global->VAR_CACERT_CONTAINER && ps_global->VAR_CACERT_CONTAINER[0]){
+
+ cacertcontainer = 1;
+ contents = NULL;
+ path[0] = '\0';
+ if(!signature_path(ps_global->VAR_CACERT_CONTAINER, path, MAXPATH))
+ cacertcontainer = 0;
+
+ if(cacertcontainer && !IS_REMOTE(path)
+ && ps_global->VAR_OPER_DIR
+ && !in_dir(ps_global->VAR_OPER_DIR, path)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: First arg is the directory name, second is
+ the file user wants to read but can't. */
+ _("Can't read file outside %s: %s"),
+ ps_global->VAR_OPER_DIR, path);
+ cacertcontainer = 0;
+ }
+
+ if(cacertcontainer
+ && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){
+ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE)))
+ &&
+ !(contents = read_file(path, READ_FROM_LOCALE)))
+ cacertcontainer = 0;
+ }
+
+ if(cacertcontainer && path[0]){
+ ps_global->smime->catype = Container;
+ ps_global->smime->capath = cpystr(path);
+ ps_global->smime->cacontent = contents;
+ }
+ }
+
+ if(!cacertcontainer){
+ ps_global->smime->catype = Directory;
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_CACERT_DIR, path, MAXPATH)
+ && !IS_REMOTE(path)))
+ ps_global->smime->catype = Nada;
+ else if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ ps_global->smime->catype = Nada;
+ }
+ }
+
+ if(ps_global->smime->catype == Directory)
+ ps_global->smime->capath = cpystr(path);
+ }
+}
+
+
+int
+copy_publiccert_dir_to_container(void)
+{
+ return(copy_dir_to_container(Public));
+}
+
+
+int
+copy_publiccert_container_to_dir(void)
+{
+ return(copy_container_to_dir(Public));
+}
+
+
+int
+copy_privatecert_dir_to_container(void)
+{
+ return(copy_dir_to_container(Private));
+}
+
+
+int
+copy_privatecert_container_to_dir(void)
+{
+ return(copy_container_to_dir(Private));
+}
+
+
+int
+copy_cacert_dir_to_container(void)
+{
+ return(copy_dir_to_container(CACert));
+}
+
+
+int
+copy_cacert_container_to_dir(void)
+{
+ return(copy_container_to_dir(CACert));
+}
+
+
+/*
+ * returns 0 on success, -1 on failure
+ */
+int
+copy_dir_to_container(WhichCerts which)
+{
+ int ret = 0;
+ BIO *bio_out = NULL, *bio_in = NULL;
+ char srcpath[MAXPATH+1], dstpath[MAXPATH+1], emailaddr[MAXPATH], file[MAXPATH], line[4096];
+ char *tempfile = NULL;
+ DIR *dirp;
+ struct dirent *d;
+ REMDATA_S *rd = NULL;
+ char *configdir = NULL;
+ char *configpath = NULL;
+ char *filesuffix = NULL;
+
+ dprint((9, "copy_dir_to_container(%s)", which==Public ? "Public" : which==Private ? "Private" : which==CACert ? "CACert" : "?"));
+ smime_init();
+
+ srcpath[0] = '\0';
+ dstpath[0] = '\0';
+ file[0] = '\0';
+ emailaddr[0] = '\0';
+
+ if(which == Public){
+ configdir = ps_global->VAR_PUBLICCERT_DIR;
+ configpath = ps_global->smime->publicpath;
+ filesuffix = ".crt";
+ }
+ else if(which == Private){
+ configdir = ps_global->VAR_PRIVATEKEY_DIR;
+ configpath = ps_global->smime->privatepath;
+ filesuffix = ".key";
+ }
+ else if(which == CACert){
+ configdir = ps_global->VAR_CACERT_DIR;
+ configpath = ps_global->smime->capath;
+ filesuffix = ".crt";
+ }
+
+ if(!(configdir && configdir[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Directory not defined"));
+ return -1;
+ }
+
+ if(!(configpath && configpath[0])){
+#ifdef APPLEKEYCHAIN
+ if(which == Public && F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){
+ q_status_message(SM_ORDER, 3, 3, _("Turn off the Keychain feature above first"));
+ return -1;
+ }
+#endif /* APPLEKEYCHAIN */
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ if(!(filesuffix && strlen(filesuffix) == 4)){
+ return -1;
+ }
+
+
+ /*
+ * If there is a legit directory to read from set up the
+ * container file to write to.
+ */
+ if(signature_path(configdir, srcpath, MAXPATH) && !IS_REMOTE(srcpath)){
+
+ if(IS_REMOTE(configpath)){
+ rd = rd_create_remote(RemImap, configpath, REMOTE_SMIME_SUBTYPE,
+ NULL, "Error: ",
+ _("Can't access remote smime configuration."));
+ if(!rd)
+ return -1;
+
+ (void) rd_read_metadata(rd);
+
+ if(rd->access == MaybeRorW){
+ if(rd->read_status == 'R')
+ rd->access = ReadOnly;
+ else
+ rd->access = ReadWrite;
+ }
+
+ if(rd->access != NoExists){
+
+ rd_check_remvalid(rd, 1L);
+
+ /*
+ * If the cached info says it is readonly but
+ * it looks like it's been fixed now, change it to readwrite.
+ */
+ if(rd->read_status == 'R'){
+ rd_check_readonly_access(rd);
+ if(rd->read_status == 'W'){
+ rd->access = ReadWrite;
+ rd->flags |= REM_OUTOFDATE;
+ }
+ else
+ rd->access = ReadOnly;
+ }
+ }
+
+ if(rd->flags & REM_OUTOFDATE){
+ if(rd_update_local(rd) != 0){
+
+ dprint((1, "copy_dir_to_container: rd_update_local failed\n"));
+ rd_close_remdata(&rd);
+ return -1;
+ }
+ }
+ else
+ rd_open_remote(rd);
+
+ if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
+ rd_close_remdata(&rd);
+ return -1;
+ }
+
+ rd->flags |= DO_REMTRIM;
+
+ strncpy(dstpath, rd->lf, sizeof(dstpath)-1);
+ dstpath[sizeof(dstpath)-1] = '\0';
+ }
+ else{
+ strncpy(dstpath, configpath, sizeof(dstpath)-1);
+ dstpath[sizeof(dstpath)-1] = '\0';
+ }
+
+ /*
+ * dstpath is either the local Container file or the local cache file
+ * for the remote Container file.
+ */
+ tempfile = tempfile_in_same_dir(dstpath, "az", NULL);
+ }
+
+ /*
+ * If there is a legit directory to read from and a tempfile
+ * to write to we continue.
+ */
+ if(tempfile && (bio_out=BIO_new_file(tempfile, "w")) != NULL){
+
+ dirp = opendir(srcpath);
+ if(dirp){
+
+ while((d=readdir(dirp)) && !ret){
+ size_t ll;
+
+ if((ll=strlen(d->d_name)) && ll > 4 && !strcmp(d->d_name+ll-4, filesuffix)){
+
+ /* copy file name to temp buffer */
+ strncpy(emailaddr, d->d_name, sizeof(emailaddr)-1);
+ emailaddr[sizeof(emailaddr)-1] = '\0';
+ /* chop off suffix trailier */
+ emailaddr[strlen(emailaddr)-4] = 0;
+
+ /*
+ * This is the separator between the contents of
+ * different files.
+ */
+ if(which == CACert){
+ if(!((BIO_puts(bio_out, CACERTSTORELEADER) > 0)
+ && (BIO_puts(bio_out, emailaddr) > 0)
+ && (BIO_puts(bio_out, "\n") > 0)))
+ ret = -1;
+ }
+ else{
+ if(!((BIO_puts(bio_out, EMAILADDRLEADER) > 0)
+ && (BIO_puts(bio_out, emailaddr) > 0)
+ && (BIO_puts(bio_out, "\n") > 0)))
+ ret = -1;
+ }
+
+ /* read then write contents of file */
+ build_path(file, srcpath, d->d_name, sizeof(file));
+ if(!(bio_in = BIO_new_file(file, "r")))
+ ret = -1;
+
+ if(!ret){
+ int good_stuff = 0;
+
+ while(BIO_gets(bio_in, line, sizeof(line)) > 0){
+ if(strncmp("-----BEGIN", line, strlen("-----BEGIN")) == 0)
+ good_stuff = 1;
+
+ if(good_stuff)
+ BIO_puts(bio_out, line);
+
+ if(strncmp("-----END", line, strlen("-----END")) == 0)
+ good_stuff = 0;
+ }
+ }
+
+ BIO_free(bio_in);
+ }
+ }
+
+ closedir(dirp);
+ }
+
+ BIO_free(bio_out);
+
+ if(!ret){
+ if(rename_file(tempfile, dstpath) < 0){
+ q_status_message2(SM_ORDER, 3, 3,
+ _("Can't rename %s to %s"), tempfile, dstpath);
+ ret = -1;
+ }
+
+ /* if the container is remote, copy it */
+ if(!ret && IS_REMOTE(configpath)){
+ int e;
+ char datebuf[200];
+
+ datebuf[0] = '\0';
+
+ if((e = rd_update_remote(rd, datebuf)) != 0){
+ if(e == -1){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error opening temporary smime file %s: %s"),
+ rd->lf, error_description(errno));
+ dprint((1,
+ "write_remote_smime: error opening temp file %s\n",
+ rd->lf ? rd->lf : "?"));
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error copying to %s: %s"),
+ rd->rn, error_description(errno));
+ dprint((1,
+ "write_remote_smime: error copying from %s to %s\n",
+ rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
+ }
+
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+ _("Copy of smime key to remote folder failed, NOT saved remotely"));
+ }
+ else{
+ rd_update_metadata(rd, datebuf);
+ rd->read_status = 'W';
+ }
+
+ rd_close_remdata(&rd);
+ }
+ }
+ }
+
+ if(tempfile)
+ fs_give((void **) &tempfile);
+
+ return ret;
+}
+
+
+/*
+ * returns 0 on success, -1 on failure
+ */
+int
+copy_container_to_dir(WhichCerts which)
+{
+ char path[MAXPATH+1], file[MAXPATH+1], buf[MAXPATH+1];
+ char iobuf[4096];
+ char *contents = NULL;
+ char *leader = NULL;
+ char *filesuffix = NULL;
+ char *configdir = NULL;
+ char *configpath = NULL;
+ char *tempfile = NULL;
+ char *p, *q, *line, *name, *certtext, *save_p;
+ int len;
+ BIO *in, *out;
+
+ dprint((9, "copy_container_to_dir(%s)", which==Public ? "Public" : which==Private ? "Private" : which==CACert ? "CACert" : "?"));
+ smime_init();
+
+ path[0] = '\0';
+
+ if(which == Public){
+ leader = EMAILADDRLEADER;
+ contents = ps_global->smime->publiccontent;
+ configdir = ps_global->VAR_PUBLICCERT_DIR;
+ configpath = ps_global->smime->publicpath;
+ filesuffix = ".crt";
+ if(!(configpath && configpath[0])){
+#ifdef APPLEKEYCHAIN
+ if(which == Public && F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){
+ q_status_message(SM_ORDER, 3, 3, _("Turn off the Keychain feature above first"));
+ return -1;
+ }
+#endif /* APPLEKEYCHAIN */
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ fs_give((void **) &ps_global->smime->publicpath);
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_PUBLICCERT_DIR, path, MAXPATH)
+ && !IS_REMOTE(path))){
+ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined"));
+ return -1;
+ }
+
+ if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ return -1;
+ }
+ }
+
+ ps_global->smime->publicpath = cpystr(path);
+ configpath = ps_global->smime->publicpath;
+ }
+ else if(which == Private){
+ leader = EMAILADDRLEADER;
+ contents = ps_global->smime->privatecontent;
+ configdir = ps_global->VAR_PRIVATEKEY_DIR;
+ configpath = ps_global->smime->privatepath;
+ filesuffix = ".key";
+ if(!(configpath && configpath[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ fs_give((void **) &ps_global->smime->privatepath);
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_PRIVATEKEY_DIR, path, MAXPATH)
+ && !IS_REMOTE(path))){
+ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined"));
+ return -1;
+ }
+
+ if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ return -1;
+ }
+ }
+
+ ps_global->smime->privatepath = cpystr(path);
+ configpath = ps_global->smime->privatepath;
+ }
+ else if(which == CACert){
+ leader = CACERTSTORELEADER;
+ contents = ps_global->smime->cacontent;
+ configdir = ps_global->VAR_CACERT_DIR;
+ configpath = ps_global->smime->capath;
+ filesuffix = ".crt";
+ if(!(configpath && configpath[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ fs_give((void **) &ps_global->smime->capath);
+
+ path[0] = '\0';
+ if(!(signature_path(ps_global->VAR_CACERT_DIR, path, MAXPATH)
+ && !IS_REMOTE(path))){
+ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined"));
+ return -1;
+ }
+
+ if(can_access(path, ACCESS_EXISTS)){
+ if(our_mkpath(path, 0700)){
+ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path);
+ return -1;
+ }
+ }
+
+ ps_global->smime->capath = cpystr(path);
+ configpath = ps_global->smime->capath;
+ }
+
+ if(!(configdir && configdir[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Directory not defined"));
+ return -1;
+ }
+
+ if(!(configpath && configpath[0])){
+ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined"));
+ return -1;
+ }
+
+ if(!(filesuffix && strlen(filesuffix) == 4)){
+ return -1;
+ }
+
+
+ if(contents && *contents){
+ for(p = contents; *p != '\0';){
+ line = p;
+
+ while(*p && *p != '\n')
+ p++;
+
+ save_p = NULL;
+ if(*p == '\n'){
+ save_p = p;
+ *p++ = '\0';
+ }
+
+ if(strncmp(leader, line, strlen(leader)) == 0){
+ name = line + strlen(leader);
+ certtext = p;
+ if(strncmp("-----BEGIN", certtext, strlen("-----BEGIN")) == 0){
+ if((q = strstr(certtext, leader)) != NULL){
+ p = q;
+ }
+ else{ /* end of file */
+ q = certtext + strlen(certtext);
+ p = q;
+ }
+
+ strncpy(buf, name, sizeof(buf)-5);
+ buf[sizeof(buf)-5] = '\0';
+ strncat(buf, filesuffix, 5);
+ build_path(file, configpath, buf, sizeof(file));
+
+ in = BIO_new_mem_buf(certtext, q-certtext);
+ if(in){
+ tempfile = tempfile_in_same_dir(file, "az", NULL);
+ out = NULL;
+ if(tempfile)
+ out = BIO_new_file(tempfile, "w");
+
+ if(out){
+ while((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0)
+ BIO_write(out, iobuf, len);
+
+ BIO_free(out);
+
+ if(rename_file(tempfile, file) < 0){
+ q_status_message2(SM_ORDER, 3, 3,
+ _("Can't rename %s to %s"),
+ tempfile, file);
+ return -1;
+ }
+
+ fs_give((void **) &tempfile);
+ }
+
+ BIO_free(in);
+ }
+ }
+ }
+
+ if(save_p)
+ *save_p = '\n';
+ }
+ }
+
+ return 0;
+}
+
+
+#ifdef APPLEKEYCHAIN
+
+int
+copy_publiccert_container_to_keychain(void)
+{
+ /* NOT IMPLEMNTED */
+ return -1;
+}
+
+int
+copy_publiccert_keychain_to_container(void)
+{
+ /* NOT IMPLEMNTED */
+ return -1;
+}
+
+#endif /* APPLEKEYCHAIN */
+
+
+/*
+ * Get a pointer to a string describing the most recent OpenSSL error.
+ * It's statically allocated, so don't change or attempt to free it.
+ */
+static const char *
+openssl_error_string(void)
+{
+ char *errs;
+ const char *data = NULL;
+ long errn;
+
+ errn = ERR_peek_error_line_data(NULL, NULL, &data, NULL);
+ errs = (char*) ERR_reason_error_string(errn);
+
+ if(errs)
+ return errs;
+ else if(data)
+ return data;
+
+ return "unknown error";
+}
+
+
+/* Return true if the body looks like a PKCS7 object */
+int
+is_pkcs7_body(BODY *body)
+{
+ int result;
+
+ result = body->type==TYPEAPPLICATION &&
+ body->subtype &&
+ (strucmp(body->subtype,"pkcs7-mime")==0 ||
+ strucmp(body->subtype,"x-pkcs7-mime")==0 ||
+ strucmp(body->subtype,"pkcs7-signature")==0 ||
+ strucmp(body->subtype,"x-pkcs7-signature")==0);
+
+ return result;
+}
+
+
+#ifdef notdef
+/*
+ * Somewhat useful debug utility to dump the contents of a BIO to a file.
+ * Note that a memory BIO will have its contents eliminated after they
+ * are read so this will break the next step.
+ */
+static void
+dump_bio_to_file(BIO *in, char *filename)
+{
+ char iobuf[4096];
+ int len;
+ BIO *out;
+
+ out = BIO_new_file(filename, "w");
+
+ if(out){
+ if(BIO_method_type(in) != BIO_TYPE_MEM)
+ BIO_reset(in);
+
+ while((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0)
+ BIO_write(out, iobuf, len);
+
+ BIO_free(out);
+ }
+
+ BIO_reset(in);
+}
+#endif
+
+
+/*
+ * Recursively stash a pointer to the decrypted data in our
+ * manufactured body.
+ */
+static void
+create_local_cache(char *base, BODY *b)
+{
+ if(b->type==TYPEMULTIPART){
+ PART *p;
+
+#if 0
+ cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes);
+#else
+ /*
+ * We don't really want to copy the real body contents. It shouldn't be
+ * used, and in the case of a message with attachments, we'll be
+ * duplicating the files multiple times.
+ */
+ cpytxt(&b->contents.text, "BODY UNAVAILABLE", 16);
+#endif
+
+ for(p=b->nested.part; p; p=p->next)
+ create_local_cache(base, (BODY*) p);
+ }
+ else{
+ cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes);
+ }
+}
+
+
+static long
+rfc822_output_func(void *b, char *string)
+{
+ BIO *bio = (BIO *) b;
+
+ return((BIO_puts(bio, string) > 0) ? 1L : 0L);
+}
+
+
+/*
+ * Attempt to load the private key for the given PERSONAL_CERT.
+ * This sets the appropriate passphrase globals in order to
+ * interact with the user correctly.
+ */
+static int
+load_private_key(PERSONAL_CERT *pcert)
+{
+ if(!pcert->key){
+
+ /* Try empty password by default */
+ char *password = "";
+
+ if(ps_global->smime
+ && (ps_global->smime->need_passphrase
+ || ps_global->smime->entered_passphrase)){
+ /* We've already been in here and discovered we need a different password */
+
+ if(ps_global->smime->entered_passphrase)
+ password = (char *) ps_global->smime->passphrase; /* already entered */
+ else
+ return 0;
+ }
+
+ ERR_clear_error();
+
+ if(!(pcert->key = load_key(pcert, password))){
+ long err = ERR_get_error();
+
+ /* Couldn't load key... */
+
+ if(ps_global->smime && ps_global->smime->entered_passphrase){
+
+ /* The user got the password wrong maybe? */
+
+ if((ERR_GET_LIB(err)==ERR_LIB_EVP && ERR_GET_REASON(err)==EVP_R_BAD_DECRYPT) ||
+ (ERR_GET_LIB(err)==ERR_LIB_PEM && ERR_GET_REASON(err)==PEM_R_BAD_DECRYPT))
+ q_status_message(SM_ORDER | SM_DING, 4, 4, _("Incorrect passphrase"));
+ else
+ q_status_message1(SM_ORDER, 4, 4, _("Couldn't read key: %s"),(char*)openssl_error_string());
+
+ /* This passphrase is no good; forget it */
+ ps_global->smime->entered_passphrase = 0;
+ }
+
+ /* Indicate to the UI that we need re-entry (see mailcmd.c:process_cmd())*/
+ if(ps_global->smime)
+ ps_global->smime->need_passphrase = 1;
+
+ if(ps_global->smime){
+ if(ps_global->smime->passphrase_emailaddr)
+ fs_give((void **) &ps_global->smime->passphrase_emailaddr);
+
+ ps_global->smime->passphrase_emailaddr = get_x509_subject_email(pcert->cert);
+ }
+
+ return 0;
+ }
+ else{
+ /* This key will be cached, so we won't be called again */
+ if(ps_global->smime){
+ ps_global->smime->entered_passphrase = 0;
+ ps_global->smime->need_passphrase = 0;
+ }
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static void
+setup_pkcs7_body_for_signature(BODY *b, char *description, char *type, char *filename)
+{
+ b->type = TYPEAPPLICATION;
+ b->subtype = cpystr(type);
+ b->encoding = ENCBINARY;
+ b->description = cpystr(description);
+
+ b->disposition.type = cpystr("attachment");
+ set_parameter(&b->disposition.parameter, "filename", filename);
+
+ set_parameter(&b->parameter, "name", filename);
+}
+
+
+/*
+ * Look for a personal certificate matching the
+ * given address
+ */
+PERSONAL_CERT *
+match_personal_cert_to_email(ADDRESS *a)
+{
+ PERSONAL_CERT *pcert = NULL;
+ char buf[MAXPATH];
+ char *email;
+
+ if(!a || !a->mailbox || !a->host)
+ return NULL;
+
+ snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host);
+
+ if(ps_global->smime){
+ for(pcert=(PERSONAL_CERT *) ps_global->smime->personal_certs;
+ pcert;
+ pcert=pcert->next){
+
+ if(!pcert->cert)
+ continue;
+
+ email = get_x509_subject_email(pcert->cert);
+
+ if(email && strucmp(email,buf)==0){
+ fs_give((void**) &email);
+ break;
+ }
+
+ fs_give((void**) &email);
+ }
+ }
+
+ return pcert;
+}
+
+
+/*
+ * Look for a personal certificate matching the from
+ * (or reply_to? in the given envelope)
+ */
+PERSONAL_CERT *
+match_personal_cert(ENVELOPE *env)
+{
+ PERSONAL_CERT *pcert;
+
+ pcert = match_personal_cert_to_email(env->reply_to);
+ if(!pcert)
+ pcert = match_personal_cert_to_email(env->from);
+
+ return pcert;
+}
+
+
+/*
+ * Flatten the given body into its MIME representation.
+ * Return the result in a BIO.
+ */
+static BIO *
+body_to_bio(BODY *body)
+{
+ BIO *bio = NULL;
+ int len;
+
+ bio = BIO_new(BIO_s_mem());
+ if(!bio)
+ return NULL;
+
+ pine_encode_body(body); /* this attaches random boundary strings to multiparts */
+ pine_write_body_header(body, rfc822_output_func, bio);
+ pine_rfc822_output_body(body, rfc822_output_func, bio);
+
+ /*
+ * Now need to truncate by two characters since the above
+ * appends CRLF.
+ */
+ if((len=BIO_ctrl_pending(bio)) > 1){
+ BUF_MEM *biobuf = NULL;
+
+ BIO_get_mem_ptr(bio, &biobuf);
+ if(biobuf){
+ BUF_MEM_grow(biobuf, len-2); /* remove CRLF */
+ }
+ }
+
+ return bio;
+}
+
+
+static BIO *
+bio_from_store(STORE_S *store)
+{
+ BIO *ret = NULL;
+
+ if(store && store->src == BioType && store->txt){
+ ret = (BIO *) store->txt;
+ }
+
+ return(ret);
+}
+
+/*
+ * Encrypt file; given a path (char *) fp, replace the file
+ * by an encrypted version of it. If (char *) text is not null, then
+ * replace the text of (char *) fp by the encrypted version of (char *) text.
+ */
+int
+encrypt_file(char *fp, char *text)
+{
+ const EVP_CIPHER *cipher = NULL;
+ STACK_OF(X509) *encerts = NULL;
+ X509 *cert;
+ PERSONAL_CERT *pcert;
+ BIO *in;
+ PKCS7 *p7 = NULL;
+ FILE *fpp;
+ int rv = 0;
+
+ smime_init();
+ if((pcert = ps_global->smime->personal_certs) == NULL)
+ return 0;
+
+ cipher = EVP_aes_256_cbc();
+ encerts = sk_X509_new_null();
+
+ if((cert = get_cert_for(pcert->name)) != NULL)
+ sk_X509_push(encerts, cert);
+ else
+ goto end;
+
+ if(text){
+ in = BIO_new(BIO_s_mem());
+ if(in == NULL)
+ goto end;
+ (void) BIO_reset(in);
+ BIO_puts(in, text);
+ }
+ else{
+ if(!(in = BIO_new_file(fp, "rb")))
+ goto end;
+
+ BIO_read_filename(in, fp);
+ }
+
+ if((p7 = PKCS7_encrypt(encerts, in, cipher, 0)) == NULL)
+ goto end;
+ BIO_set_close(in, BIO_CLOSE);
+ BIO_free(in);
+ if(!(in = BIO_new_file(fp, "w")))
+ goto end;
+ BIO_reset(in);
+ rv = PEM_write_bio_PKCS7(in, p7);
+ BIO_flush(in);
+
+end:
+ BIO_free(in);
+ PKCS7_free(p7);
+ sk_X509_pop_free(encerts, X509_free);
+
+ return rv;
+}
+
+/*
+ * Encrypt a message on the way out. Called from call_mailer in send.c
+ * The body may be reallocated.
+ */
+int
+encrypt_outgoing_message(METAENV *header, BODY **bodyP)
+{
+ PKCS7 *p7 = NULL;
+ BIO *in = NULL;
+ BIO *out = NULL;
+ const EVP_CIPHER *cipher = NULL;
+ STACK_OF(X509) *encerts = NULL;
+ STORE_S *outs = NULL;
+ PINEFIELD *pf;
+ ADDRESS *a;
+ BODY *body = *bodyP;
+ BODY *newBody = NULL;
+ int result = 0;
+
+ dprint((9, "encrypt_outgoing_message()"));
+ smime_init();
+
+ cipher = EVP_des_cbc();
+
+ encerts = sk_X509_new_null();
+
+ /* Look for a certificate for each of the recipients */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){
+ for(a=*pf->addr; a; a=a->next){
+ X509 *cert;
+ char buf[MAXPATH];
+
+ snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host);
+
+ cert = get_cert_for(buf);
+ if(cert)
+ sk_X509_push(encerts,cert);
+ else{
+ q_status_message2(SM_ORDER, 1, 1,
+ _("Unable to find certificate for <%s@%s>"),
+ a->mailbox, a->host);
+ goto end;
+ }
+ }
+ }
+
+
+ in = body_to_bio(body);
+
+ p7 = PKCS7_encrypt(encerts, in, cipher, 0);
+
+ outs = so_get(BioType, NULL, EDIT_ACCESS);
+ out = bio_from_store(outs);
+
+ i2d_PKCS7_bio(out, p7);
+ (void) BIO_flush(out);
+
+ so_seek(outs, 0, SEEK_SET);
+
+ newBody = mail_newbody();
+
+ newBody->type = TYPEAPPLICATION;
+ newBody->subtype = cpystr("pkcs7-mime");
+ newBody->encoding = ENCBINARY;
+
+ newBody->disposition.type = cpystr("attachment");
+ set_parameter(&newBody->disposition.parameter, "filename", "smime.p7m");
+
+ newBody->description = cpystr("S/MIME Encrypted Message");
+ set_parameter(&newBody->parameter, "smime-type", "enveloped-data");
+ set_parameter(&newBody->parameter, "name", "smime.p7m");
+
+ newBody->contents.text.data = (unsigned char *) outs;
+
+ *bodyP = newBody;
+
+ result = 1;
+
+end:
+
+ BIO_free(in);
+ PKCS7_free(p7);
+ sk_X509_pop_free(encerts, X509_free);
+
+ dprint((9, "encrypt_outgoing_message returns %d", result));
+ return result;
+}
+
+
+/*
+ * Plonk the contents (mime headers and body) of the given
+ * section of a message to a BIO_s_mem BIO object.
+ */
+static BIO *
+raw_part_to_bio(long msgno, const char *section)
+{
+ unsigned long len;
+ char *text;
+ BIO *bio;
+
+ bio = BIO_new(BIO_s_mem());
+
+ if(bio){
+
+ (void) BIO_reset(bio);
+
+ /* First grab headers of the chap */
+ text = mail_fetch_mime(ps_global->mail_stream, msgno, (char*) section, &len, 0);
+
+ if(text){
+ BIO_write(bio, text, len);
+
+ /** Now grab actual body */
+ text = mail_fetch_body (ps_global->mail_stream, msgno, (char*) section, &len, 0);
+ if(text){
+ BIO_write(bio, text, len);
+ }
+ else{
+ BIO_free(bio);
+ bio = NULL;
+ }
+
+ }
+ else{
+ BIO_free(bio);
+ bio = NULL;
+ }
+ }
+
+ return bio;
+}
+
+
+/*
+ Get (and decode) the body of the given section of msg
+ */
+static STORE_S*
+get_part_contents(long msgno, const char *section)
+{
+ long len;
+ gf_io_t pc;
+ STORE_S *store = NULL;
+ char *err;
+
+ store = so_get(CharStar, NULL, EDIT_ACCESS);
+ if(store){
+ gf_set_so_writec(&pc,store);
+
+ err = detach(ps_global->mail_stream, msgno, (char*) section, 0L, &len, pc, NULL, 0L);
+
+ gf_clear_so_writec(store);
+
+ so_seek(store, 0, SEEK_SET);
+
+ if(err)
+ so_give(&store);
+ }
+
+ return store;
+}
+
+
+static PKCS7 *
+get_pkcs7_from_part(long msgno,const char *section)
+{
+ STORE_S *store = NULL;
+ PKCS7 *p7 = NULL;
+ BIO *in = NULL;
+
+ store = get_part_contents(msgno, section);
+
+ if(store){
+ if(store->src == CharStar){
+ int len;
+
+ /*
+ * We're reaching inside the STORE_S structure. We should
+ * probably have a way to get the length, instead.
+ */
+ len = (int) (store->eod - store->dp);
+ in = BIO_new_mem_buf(store->txt, len);
+ }
+ else{ /* just copy it */
+ unsigned char c;
+
+ in = BIO_new(BIO_s_mem());
+ (void) BIO_reset(in);
+
+ so_seek(store, 0L, 0);
+ while(so_readc(&c, store)){
+ BIO_write(in, &c, 1);
+ }
+ }
+
+ if(in){
+/* dump_bio_to_file(in, "/tmp/decoded-signature"); */
+ if((p7=d2i_PKCS7_bio(in,NULL)) == NULL){
+ /* error */
+ }
+
+ BIO_free(in);
+ }
+
+ so_give(&store);
+ }
+
+ return p7;
+}
+
+
+/*
+ * Try to verify a signature.
+ *
+ * p7 - the pkcs7 object to verify
+ * in - the plain data to verify (NULL if not detached)
+ * out - BIO to which to write the opaque data
+ */
+static int
+do_signature_verify(PKCS7 *p7, BIO *in, BIO *out)
+{
+ STACK_OF(X509) *otherCerts = NULL;
+ int result;
+ const char *data;
+ long err;
+
+#if 0
+ dump_bio_to_file(in,"/tmp/verified-data");
+#endif
+
+ if(!s_cert_store){
+ q_status_message(SM_ORDER | SM_DING, 2, 2,
+ _("Couldn't verify S/MIME signature: No CA Certs were loaded"));
+
+ return -1;
+ }
+
+ result = PKCS7_verify(p7, otherCerts, s_cert_store, in, out, 0);
+
+ if(result){
+ q_status_message(SM_ORDER, 1, 1, _("S/MIME signature verified ok"));
+ }
+ else{
+ err = ERR_peek_error_line_data(NULL, NULL, &data, NULL);
+
+ if(out && err==ERR_PACK(ERR_LIB_PKCS7,PKCS7_F_PKCS7_VERIFY,PKCS7_R_CERTIFICATE_VERIFY_ERROR)){
+
+ /* Retry verification so we can get the plain text */
+ /* Might be better to reimplement PKCS7_verify here? */
+
+ PKCS7_verify(p7, otherCerts, s_cert_store, in, out, PKCS7_NOVERIFY);
+ }
+
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Couldn't verify S/MIME signature: %s"), (char*) openssl_error_string());
+ }
+
+ /* now try to extract the certificates of any signers */
+ {
+ STACK_OF(X509) *signers;
+ int i;
+
+ signers = PKCS7_get0_signers(p7, NULL, 0);
+
+ if(signers)
+ for(i=0; i<sk_X509_num(signers); i++){
+ char *email;
+ X509 *x = sk_X509_value(signers,i);
+ X509 *cert;
+
+ if(!x)
+ continue;
+
+ email = get_x509_subject_email(x);
+
+ if(email){
+ cert = get_cert_for(email);
+ if(cert)
+ X509_free(cert);
+ else
+ save_cert_for(email, x);
+
+ fs_give((void**) &email);
+ }
+ }
+
+ sk_X509_free(signers);
+ }
+
+ return result;
+}
+
+
+void
+free_smime_body_sparep(void **sparep)
+{
+ if(sparep && *sparep){
+ PKCS7_free((PKCS7 *) (*sparep));
+ *sparep = NULL;
+ }
+}
+
+
+/*
+ * Given a multipart body of type multipart/signed, attempt to verify it.
+ * Returns non-zero if the body was changed.
+ */
+static int
+do_detached_signature_verify(BODY *b, long msgno, char *section)
+{
+ PKCS7 *p7 = NULL;
+ BIO *in = NULL;
+ PART *p;
+ int result, modified_the_body = 0;
+ char newSec[100];
+ char *what_we_did;
+
+ dprint((9, "do_detached_signature_verify(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL"));
+ smime_init();
+
+ snprintf(newSec, sizeof(newSec), "%s%s1", section ? section : "", (section && *section) ? "." : "");
+ in = raw_part_to_bio(msgno, newSec);
+
+ if(in){
+
+ snprintf(newSec, sizeof(newSec), "%s%s2", section ? section : "", (section && *section) ? "." : "");
+ p7 = get_pkcs7_from_part(msgno, newSec);
+
+ if(!p7)
+ goto end;
+
+ result = do_signature_verify(p7, in, NULL);
+
+ if(b->subtype)
+ fs_give((void**) &b->subtype);
+
+ b->subtype = cpystr(OUR_PKCS7_ENCLOSURE_SUBTYPE);
+ b->encoding = ENC8BIT;
+
+ if(b->description)
+ fs_give ((void**) &b->description);
+
+ what_we_did = result ? _("This message was cryptographically signed.") :
+ _("This message was cryptographically signed but the signature could not be verified.");
+
+ b->description = cpystr(what_we_did);
+
+ b->sparep = p7;
+ p7 = NULL;
+
+ p = b->nested.part;
+
+ /* p is signed plaintext */
+ if(p && p->next)
+ mail_free_body_part(&p->next); /* hide the pkcs7 from the viewer */
+
+ BIO_free(in);
+
+ modified_the_body = 1;
+ }
+
+end:
+ PKCS7_free(p7);
+
+ return modified_the_body;
+}
+
+
+PERSONAL_CERT *
+find_certificate_matching_recip_info(PKCS7_RECIP_INFO *ri)
+{
+ PERSONAL_CERT *x = NULL;
+
+ if(ps_global->smime){
+ for(x = (PERSONAL_CERT *) ps_global->smime->personal_certs; x; x=x->next){
+ X509 *mine;
+
+ mine = x->cert;
+
+ if(!X509_NAME_cmp(ri->issuer_and_serial->issuer,mine->cert_info->issuer) &&
+ !ASN1_INTEGER_cmp(ri->issuer_and_serial->serial,mine->cert_info->serialNumber)){
+ break;
+ }
+ }
+ }
+
+ return x;
+}
+
+
+static PERSONAL_CERT *
+find_certificate_matching_pkcs7(PKCS7 *p7)
+{
+ int i;
+ STACK_OF(PKCS7_RECIP_INFO) *recips;
+ PERSONAL_CERT *x = NULL;
+
+ recips = p7->d.enveloped->recipientinfo;
+
+ for(i=0; i<sk_PKCS7_RECIP_INFO_num(recips); i++){
+ PKCS7_RECIP_INFO *ri;
+
+ ri = sk_PKCS7_RECIP_INFO_value(recips, i);
+
+ if((x=find_certificate_matching_recip_info(ri))!=0){
+ break;
+ }
+ }
+
+ return x;
+}
+
+/* decrypt an encrypted file.
+ Args: fp - the path to the encrypted file.
+ rv - a code that thells the caller what happened inside the function
+ Returns the decoded text allocated in a char *, whose memory must be
+ freed by caller
+ */
+
+char *
+decrypt_file(char *fp, int *rv)
+{
+ PKCS7 *p7 = NULL;
+ char *text, *tmp;
+ BIO *in = NULL, *out = NULL;
+ EVP_PKEY *pkey = NULL, *key = NULL;
+ PERSONAL_CERT *pcert = NULL;
+ X509 *recip, *cert;
+ STORE_S *outs = NULL, *store, *ins;
+ int i, j;
+ long unsigned int len;
+ void *ret;
+
+ smime_init();
+
+ if((text = read_file(fp, 0)) == NULL)
+ return NULL;
+
+ tmp = fs_get(strlen(text) + (strlen(text) << 6) + 1);
+ for(j = 0, i = strlen("-----BEGIN PKCS7-----") + 1; text[i] != '\0'
+ && text[i] != '-'; j++, i++)
+ tmp[j] = text[i];
+ tmp[j] = '\0';
+
+ ret = rfc822_base64(tmp, strlen(tmp), &len);
+
+ if((in = BIO_new_mem_buf((char *)ret, len)) != NULL){
+ p7 = d2i_PKCS7_bio(in, NULL);
+ BIO_free(in);
+ }
+
+ if(text) fs_give((void **)&text);
+ if(ret) fs_give((void **)&ret);
+
+ if((pcert = ps_global->smime->personal_certs) == NULL)
+ goto end;
+
+ if((i = load_private_key(pcert)) == 0
+ && ps_global->smime
+ && ps_global->smime->need_passphrase
+ && !ps_global->smime->already_auto_asked)
+ for(; i == 0;){
+ ps_global->smime->already_auto_asked = 1;
+ if(pith_opt_smime_get_passphrase){
+ switch((*pith_opt_smime_get_passphrase)()){
+ case 0 : i = load_private_key(pcert);
+ break;
+
+ case 1 : i = -1;
+ break;
+
+ default: break; /* repeat until we cancel */
+ }
+ }
+ else
+ i = -2;
+ }
+
+ if(rv) *rv = i;
+
+ if((key = pcert->key) == NULL)
+ goto end;
+
+ recip = get_cert_for(pcert->name);
+ out = BIO_new(BIO_s_mem());
+ (void) BIO_reset(out);
+
+ i = PKCS7_decrypt(p7, key, recip, out, 0);
+
+ if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global))
+ forget_private_keys();
+
+ if(i == 0){
+ q_status_message1(SM_ORDER, 1, 1, _("Error decrypting: %s"),
+ (char*) openssl_error_string());
+ goto end;
+ }
+
+ BIO_get_mem_data(out, &tmp);
+
+ text = cpystr(tmp);
+ BIO_free(out);
+
+end:
+ PKCS7_free(p7);
+
+ return text;
+}
+
+/*
+ * Try to decode (decrypt or verify a signature) a PKCS7 body
+ * Returns non-zero if something was changed.
+ */
+static int
+do_decoding(BODY *b, long msgno, const char *section)
+{
+ int modified_the_body = 0;
+ BIO *out = NULL;
+ PKCS7 *p7 = NULL;
+ X509 *recip = NULL;
+ EVP_PKEY *key = NULL;
+ PERSONAL_CERT *pcert = NULL;
+ char *what_we_did = "";
+ char null[1];
+
+ dprint((9, "do_decoding(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL"));
+ null[0] = '\0';
+ smime_init();
+
+ /*
+ * Extract binary data from part to an in-memory store
+ */
+
+ if(b->sparep){ /* already done */
+ p7 = (PKCS7*) b->sparep;
+ }
+ else{
+
+ p7 = get_pkcs7_from_part(msgno, section && *section ? section : "1");
+ if(!p7){
+ q_status_message1(SM_ORDER, 2, 2, "Couldn't load PKCS7 object: %s",
+ (char*) openssl_error_string());
+ goto end;
+ }
+
+ /*
+ * Save the PKCS7 object for later dealings by the user interface.
+ * It will be cleaned up when the body is garbage collected.
+ */
+ b->sparep = p7;
+ }
+
+ if(PKCS7_type_is_signed(p7)){
+ int sigok;
+
+ out = BIO_new(BIO_s_mem());
+ (void) BIO_reset(out);
+ BIO_puts(out, "MIME-Version: 1.0\r\n"); /* needed so rfc822_parse_msg_full believes it's MIME */
+
+ sigok = do_signature_verify(p7, NULL, out);
+
+ /* shouldn't really duplicate these messages */
+ what_we_did = sigok ? _("This message was cryptographically signed.") :
+ _("This message was cryptographically signed but the signature could not be verified.");
+
+ /* make sure it's null terminated */
+ BIO_write(out, null, 1);
+ }
+ else if(!PKCS7_type_is_enveloped(p7)){
+ q_status_message(SM_ORDER, 1, 1, "PKCS7 object not recognised.");
+ goto end;
+ }
+ else{ /* It *is* enveloped */
+ int decrypt_result;
+
+ what_we_did = _("This message was encrypted.");
+
+ /* now need to find a cert that can decrypt this */
+ pcert = find_certificate_matching_pkcs7(p7);
+
+ if(!pcert){
+ q_status_message(SM_ORDER, 3, 3, _("Couldn't find the certificate needed to decrypt."));
+ goto end;
+ }
+
+ recip = pcert->cert;
+
+ if(!load_private_key(pcert)
+ && ps_global->smime
+ && ps_global->smime->need_passphrase
+ && !ps_global->smime->already_auto_asked){
+ /* Couldn't load key with blank password, ask user */
+ ps_global->smime->already_auto_asked = 1;
+ if(pith_opt_smime_get_passphrase){
+ (*pith_opt_smime_get_passphrase)();
+ load_private_key(pcert);
+ }
+ }
+
+ key = pcert->key;
+ if(!key)
+ goto end;
+
+ out = BIO_new(BIO_s_mem());
+ (void) BIO_reset(out);
+ BIO_puts(out, "MIME-Version: 1.0\r\n");
+
+ decrypt_result = PKCS7_decrypt(p7, key, recip, out, 0);
+
+ if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global))
+ forget_private_keys();
+
+ if(!decrypt_result){
+ q_status_message1(SM_ORDER, 1, 1, _("Error decrypting: %s"),
+ (char*) openssl_error_string());
+ goto end;
+ }
+
+ BIO_write(out, null, 1);
+ }
+
+ /*
+ * We've now produced a flattened MIME object in BIO out.
+ * It needs to be turned back into a BODY.
+ */
+
+ if(out){
+ BODY *body;
+ ENVELOPE *env;
+ char *h = NULL;
+ char *bstart;
+ STRING s;
+ BUF_MEM *bptr = NULL;
+
+ BIO_get_mem_ptr(out, &bptr);
+ if(bptr)
+ h = bptr->data;
+
+ /* look for start of body */
+ bstart = strstr(h, "\r\n\r\n");
+
+ if(!bstart){
+ q_status_message(SM_ORDER, 3, 3, _("Encrypted data couldn't be parsed."));
+ }
+ else{
+ bstart += 4; /* skip over CRLF*2 */
+
+ INIT(&s, mail_string, bstart, strlen(bstart));
+ rfc822_parse_msg_full(&env, &body, h, bstart-h-2, &s, BADHOST, 0, 0);
+ mail_free_envelope(&env); /* Don't care about this */
+
+ /*
+ * Now convert original body (application/pkcs7-mime)
+ * to a multipart body with one sub-part (the decrypted body).
+ * Note that the sub-part may also be multipart!
+ */
+
+ b->type = TYPEMULTIPART;
+ if(b->subtype)
+ fs_give((void**) &b->subtype);
+
+ /*
+ * This subtype is used in mailview.c to annotate the display of
+ * encrypted or signed messages. We know for sure then that it's a PKCS7
+ * part because the sparep field is set to the PKCS7 object (see above).
+ */
+ b->subtype = cpystr(OUR_PKCS7_ENCLOSURE_SUBTYPE);
+ b->encoding = ENC8BIT;
+
+ if(b->description)
+ fs_give((void**) &b->description);
+
+ b->description = cpystr(what_we_did);
+
+ if(b->disposition.type)
+ fs_give((void **) &b->disposition.type);
+
+ if(b->contents.text.data)
+ fs_give((void **) &b->contents.text.data);
+
+ if(b->parameter)
+ mail_free_body_parameter(&b->parameter);
+
+ /* Allocate mem for the sub-part, and copy over the contents of our parsed body */
+ b->nested.part = fs_get(sizeof(PART));
+ b->nested.part->body = *body;
+ b->nested.part->next = NULL;
+
+ fs_give((void**) &body);
+
+ /*
+ * IMPORTANT BIT: set the body->contents.text.data elements to contain the decrypted
+ * data. Otherwise, it'll try to load it from the original data. Eek.
+ */
+ create_local_cache(bstart, &b->nested.part->body);
+
+ modified_the_body = 1;
+ }
+ }
+
+end:
+ if(out)
+ BIO_free(out);
+
+ return modified_the_body;
+}
+
+
+/*
+ * Recursively handle PKCS7 bodies in our message.
+ *
+ * Returns non-zero if some fiddling was done.
+ */
+static int
+do_fiddle_smime_message(BODY *b, long msgno, char *section)
+{
+ int modified_the_body = 0;
+
+ if(!b)
+ return 0;
+
+ dprint((9, "do_fiddle_smime_message(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL"));
+
+ if(is_pkcs7_body(b)){
+
+ if(do_decoding(b, msgno, section)){
+ /*
+ * b should now be a multipart message:
+ * fiddle it too in case it's been multiply-encrypted!
+ */
+
+ /* fallthru */
+ modified_the_body = 1;
+ }
+ }
+
+ if(b->type==TYPEMULTIPART || MIME_MSG(b->type, b->subtype)){
+
+ PART *p;
+ int partNum;
+ char newSec[100];
+
+ if(MIME_MULT_SIGNED(b->type, b->subtype)){
+
+
+ /*
+ * Ahah. We have a multipart signed entity.
+ *
+ * Multipart/signed
+ * part 1 (signed thing)
+ * part 2 (the pkcs7 signature)
+ *
+ * We're going to convert that to
+ *
+ * Multipart/OUR_PKCS7_ENCLOSURE_SUBTYPE
+ * part 1 (signed thing)
+ * part 2 has been freed
+ *
+ * We also extract the signature from part 2 and save it
+ * in the multipart body->sparep, and we add a description
+ * in the multipart body->description.
+ *
+ *
+ * The results of a decrypted message will be similar. It
+ * will be
+ *
+ * Multipart/OUR_PKCS7_ENCLOSURE_SUBTYPE
+ * part 1 (decrypted thing)
+ */
+
+ modified_the_body += do_detached_signature_verify(b, msgno, section);
+ }
+ else if(MIME_MSG(b->type, b->subtype)){
+ modified_the_body += do_fiddle_smime_message(b->nested.msg->body, msgno, section);
+ }
+ else{
+
+ for(p=b->nested.part,partNum=1; p; p=p->next,partNum++){
+
+ /* Append part number to the section string */
+
+ snprintf(newSec, sizeof(newSec), "%s%s%d", section, *section ? "." : "", partNum);
+
+ modified_the_body += do_fiddle_smime_message(&p->body, msgno, newSec);
+ }
+ }
+ }
+
+ return modified_the_body;
+}
+
+
+/*
+ * Fiddle a message in-place by decrypting/verifying S/MIME entities.
+ * Returns non-zero if something was changed.
+ */
+int
+fiddle_smime_message(BODY *b, long msgno)
+{
+ return do_fiddle_smime_message(b, msgno, "");
+}
+
+
+/********************************************************************************/
+
+
+/*
+ * Output a string in a distinctive style
+ */
+void
+gf_puts_uline(char *txt, gf_io_t pc)
+{
+ pc(TAG_EMBED); pc(TAG_BOLDON);
+ gf_puts(txt, pc);
+ pc(TAG_EMBED); pc(TAG_BOLDOFF);
+}
+
+
+/*
+ * Sign a message. Called from call_mailer in send.c.
+ *
+ * This takes the header for the outgoing message as well as a pointer
+ * to the current body (which may be reallocated).
+ */
+int
+sign_outgoing_message(METAENV *header, BODY **bodyP, int dont_detach)
+{
+ STORE_S *outs = NULL;
+ BODY *body = *bodyP;
+ BODY *newBody = NULL;
+ PART *p1 = NULL;
+ PART *p2 = NULL;
+ PERSONAL_CERT *pcert;
+ BIO *in = NULL;
+ BIO *out = NULL;
+ PKCS7 *p7 = NULL;
+ int result = 0;
+ int flags = dont_detach ? 0 : PKCS7_DETACHED;
+
+ dprint((9, "sign_outgoing_message()"));
+
+ smime_init();
+
+ /* Look for a private key matching the sender address... */
+
+ pcert = match_personal_cert(header->env);
+
+ if(!pcert){
+ q_status_message(SM_ORDER, 3, 3, _("Couldn't find the certificate needed to sign."));
+ goto end;
+ }
+
+ if(!load_private_key(pcert) && ps_global->smime && ps_global->smime->need_passphrase){
+ /* Couldn't load key with blank password, try again */
+ if(pith_opt_smime_get_passphrase){
+ (*pith_opt_smime_get_passphrase)();
+ load_private_key(pcert);
+ }
+ }
+
+ if(!pcert->key)
+ goto end;
+
+ in = body_to_bio(body);
+
+#ifdef notdef
+ dump_bio_to_file(in,"/tmp/signed-data");
+#endif
+
+ p7 = PKCS7_sign(pcert->cert, pcert->key, NULL, in, flags);
+
+ if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global))
+ forget_private_keys();
+
+ if(!p7){
+ q_status_message(SM_ORDER, 1, 1, _("Error creating signed object."));
+ goto end;
+ }
+
+ outs = so_get(BioType, NULL, EDIT_ACCESS);
+ out = bio_from_store(outs);
+
+ i2d_PKCS7_bio(out, p7);
+ (void) BIO_flush(out);
+
+ so_seek(outs, 0, SEEK_SET);
+
+ if((flags&PKCS7_DETACHED)==0){
+
+ /* the simple case: the signed data is in the pkcs7 object */
+
+ newBody = mail_newbody();
+
+ setup_pkcs7_body_for_signature(newBody, "S/MIME Cryptographically Signed Message", "pkcs7-mime", "smime.p7m");
+
+ newBody->contents.text.data = (unsigned char *) outs;
+ *bodyP = newBody;
+
+ result = 1;
+ }
+ else{
+
+ /*
+ * OK.
+ * We have to create a new body as follows:
+ *
+ * multipart/signed; blah blah blah
+ * reference to existing body
+ *
+ * pkcs7 object
+ */
+
+ newBody = mail_newbody();
+
+ newBody->type = TYPEMULTIPART;
+ newBody->subtype = cpystr("signed");
+ newBody->encoding = ENC7BIT;
+
+ set_parameter(&newBody->parameter, "protocol", "application/pkcs7-signature");
+ set_parameter(&newBody->parameter, "micalg", "sha1");
+
+ p1 = mail_newbody_part();
+ p2 = mail_newbody_part();
+
+ /*
+ * This is nasty. We're just copying the body in here,
+ * but since our newBody is freed at the end of call_mailer,
+ * we mustn't let this body (the original one) be freed twice.
+ */
+ p1->body = *body; /* ARRGH. This is special cased at the end of call_mailer */
+
+ p1->next = p2;
+
+ setup_pkcs7_body_for_signature(&p2->body, "S/MIME Cryptographic Signature", "pkcs7-signature", "smime.p7s");
+ p2->body.contents.text.data = (unsigned char *) outs;
+
+ newBody->nested.part = p1;
+
+ *bodyP = newBody;
+
+ result = 1;
+ }
+
+end:
+
+ PKCS7_free(p7);
+ BIO_free(in);
+
+ dprint((9, "sign_outgoing_message returns %d", result));
+ return result;
+}
+
+
+SMIME_STUFF_S *
+new_smime_struct(void)
+{
+ SMIME_STUFF_S *ret = NULL;
+
+ ret = (SMIME_STUFF_S *) fs_get(sizeof(*ret));
+ memset((void *) ret, 0, sizeof(*ret));
+ ret->publictype = Nada;
+
+ return ret;
+}
+
+
+static void
+free_smime_struct(SMIME_STUFF_S **smime)
+{
+ if(smime && *smime){
+ if((*smime)->passphrase_emailaddr)
+ fs_give((void **) &(*smime)->passphrase_emailaddr);
+
+ if((*smime)->publicpath)
+ fs_give((void **) &(*smime)->publicpath);
+
+ if((*smime)->publiccertlist)
+ free_certlist(&(*smime)->publiccertlist);
+
+ if((*smime)->publiccontent)
+ fs_give((void **) &(*smime)->publiccontent);
+
+ if((*smime)->privatepath)
+ fs_give((void **) &(*smime)->privatepath);
+
+ if((*smime)->personal_certs){
+ PERSONAL_CERT *pc;
+
+ pc = (PERSONAL_CERT *) (*smime)->personal_certs;
+ free_personal_certs(&pc);
+ (*smime)->personal_certs = NULL;
+ }
+
+ if((*smime)->privatecontent)
+ fs_give((void **) &(*smime)->privatecontent);
+
+ if((*smime)->capath)
+ fs_give((void **) &(*smime)->capath);
+
+ if((*smime)->cacontent)
+ fs_give((void **) &(*smime)->cacontent);
+
+ fs_give((void **) smime);
+ }
+}
+
+#endif /* SMIME */