diff options
Diffstat (limited to 'pith/smime.c')
-rw-r--r-- | pith/smime.c | 2377 |
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 */ |