/* * libEtPan! -- a mail library * * Copyright (C) 2001, 2005 - DINH Viet Hoa * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the libEtPan! project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * $Id$ */ #include "mailprivacy_smime.h" #include #include #include #include #include #include #include #include "mailprivacy_tools.h" #include "mailprivacy.h" #include #include #include #include #include /* global variable TODO : instance of privacy drivers */ static char cert_dir[PATH_MAX] = ""; static chash * certificates = NULL; static chash * private_keys = NULL; static char CAcert_dir[PATH_MAX] = ""; static char * CAfile = NULL; static int CA_check = 1; static int store_cert = 0; static char private_keys_dir[PATH_MAX] = ""; static char * get_cert_file(char * email); static char * get_private_key_file(char * email); static int smime_is_signed(struct mailmime * mime) { if (mime->mm_content_type != NULL) { clistiter * cur; for(cur = clist_begin(mime->mm_content_type->ct_parameters) ; cur != NULL ; cur = clist_next(cur)) { struct mailmime_parameter * param; param = cur->data; if ((strcasecmp(param->pa_name, "protocol") == 0) && ((strcasecmp(param->pa_value, "application/x-pkcs7-signature") == 0) || (strcasecmp(param->pa_value, "application/pkcs7-signature") == 0))) return 1; } } return 0; } static int smime_is_encrypted(struct mailmime * mime) { if (mime->mm_content_type != NULL) { if ((strcasecmp(mime->mm_content_type->ct_subtype, "x-pkcs7-mime") == 0) || (strcasecmp(mime->mm_content_type->ct_subtype, "pkcs7-mime") == 0)) return 1; } return 0; } #define BUF_SIZE 1024 enum { NO_ERROR_SMIME = 0, ERROR_SMIME_CHECK, ERROR_SMIME_COMMAND, ERROR_SMIME_FILE, }; /* write output to a file */ static int get_smime_output(FILE * dest_f, char * command) { FILE * p; char buf[BUF_SIZE]; size_t size; int res; int status; char command_redirected[PATH_MAX]; snprintf(command_redirected, sizeof(command_redirected), "%s 2>&1", command); /* flush buffer so that it is not flushed more than once when forking */ fflush(dest_f); p = popen(command_redirected, "r"); if (p == NULL) { res = ERROR_SMIME_COMMAND; goto err; } while ((size = fread(buf, 1, sizeof(buf), p)) != 0) { size_t written; written = fwrite(buf, 1, size, dest_f); if (written != size) { res = ERROR_SMIME_FILE; goto close; } } status = pclose(p); if (WEXITSTATUS(status) != 0) return ERROR_SMIME_CHECK; else return NO_ERROR_SMIME; close: pclose(p); err: return res; } static char * get_first_from_addr(struct mailmime * mime) { clistiter * cur; struct mailimf_single_fields single_fields; struct mailimf_fields * fields; struct mailimf_mailbox * mb; while (mime->mm_parent != NULL) mime = mime->mm_parent; if (mime->mm_type != MAILMIME_MESSAGE) return NULL; fields = mime->mm_data.mm_message.mm_fields; if (fields == NULL) return NULL; mailimf_single_fields_init(&single_fields, fields); if (single_fields.fld_from == NULL) return NULL; cur = clist_begin(single_fields.fld_from->frm_mb_list->mb_list); if (cur == NULL) return NULL; mb = clist_content(cur); return mb->mb_addr_spec; } #define SMIME_DECRYPT_DESCRIPTION "S/MIME encrypted part\r\n" #define SMIME_DECRYPT_FAILED "S/MIME decryption FAILED\r\n" #define SMIME_DECRYPT_SUCCESS "S/MIME decryption success\r\n" static int smime_decrypt(struct mailprivacy * privacy, mailmessage * msg, struct mailmime * mime, struct mailmime ** result) { char smime_filename[PATH_MAX]; char quoted_smime_filename[PATH_MAX]; char description_filename[PATH_MAX]; FILE * description_f; char decrypted_filename[PATH_MAX]; FILE * decrypted_f; char command[PATH_MAX]; struct mailmime * description_mime; struct mailmime * decrypted_mime; int r; int res; int sign_ok; char quoted_decrypted_filename[PATH_MAX]; struct mailmime * multipart; char * smime_cert; char * smime_key; char quoted_smime_cert[PATH_MAX]; char quoted_smime_key[PATH_MAX]; char * email; chashiter * iter; /* fetch the whole multipart and write it to a file */ r = mailprivacy_fetch_mime_body_to_file(privacy, smime_filename, sizeof(smime_filename), msg, mime); if (r != MAIL_NO_ERROR) { res = r; goto err; } /* we are in a safe directory */ decrypted_f = mailprivacy_get_tmp_file(privacy, decrypted_filename, sizeof(decrypted_filename)); if (decrypted_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_smime; } fclose(decrypted_f); sign_ok = 0; for(iter = chash_begin(private_keys) ; iter != NULL ; iter = chash_next(private_keys, iter)) { chashdatum key; char email_buf[BUF_SIZE]; chash_key(iter, &key); if (key.len >= sizeof(email_buf)) continue; strncpy(email_buf, key.data, key.len); email_buf[key.len] = '\0'; email = email_buf; /* description */ description_f = mailprivacy_get_tmp_file(privacy, description_filename, sizeof(description_filename)); if (description_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_decrypted; } fprintf(description_f, SMIME_DECRYPT_DESCRIPTION); /* get encryption key */ #if 0 email = get_first_from_addr(mime); if (email == NULL) { fclose(description_f); res = MAIL_ERROR_INVAL; goto unlink_description; } #endif smime_key = get_private_key_file(email); smime_cert = get_cert_file(email); if ((smime_cert == NULL) || (smime_key == NULL)) { fclose(description_f); res = MAIL_ERROR_INVAL; goto unlink_description; } r = mail_quote_filename(quoted_smime_cert, sizeof(quoted_smime_cert), smime_cert); if (r < 0) { fclose(description_f); res = MAIL_ERROR_MEMORY; goto unlink_description; } r = mail_quote_filename(quoted_smime_key, sizeof(quoted_smime_key), smime_key); if (r < 0) { fclose(description_f); res = MAIL_ERROR_MEMORY; goto unlink_description; } /* run the command */ r = mail_quote_filename(quoted_smime_filename, sizeof(quoted_smime_filename), smime_filename); if (r < 0) { fclose(description_f); res = MAIL_ERROR_MEMORY; goto unlink_description; } r = mail_quote_filename(quoted_decrypted_filename, sizeof(quoted_decrypted_filename), decrypted_filename); if (r < 0) { fclose(description_f); res = MAIL_ERROR_MEMORY; goto unlink_description; } sign_ok = 0; snprintf(command, PATH_MAX, "openssl smime -decrypt -in %s -out %s -inkey %s -recip %s", quoted_smime_filename, quoted_decrypted_filename, quoted_smime_key, quoted_smime_cert); r = get_smime_output(description_f, command); switch (r) { case NO_ERROR_SMIME: sign_ok = 1; break; case ERROR_SMIME_CHECK: sign_ok = 0; break; case ERROR_SMIME_COMMAND: fclose(description_f); res = MAIL_ERROR_COMMAND; goto unlink_description; case ERROR_SMIME_FILE: fclose(description_f); res = MAIL_ERROR_FILE; goto unlink_description; } if (sign_ok) { fprintf(description_f, SMIME_DECRYPT_SUCCESS); fclose(description_f); break; } else { fclose(description_f); unlink(description_filename); } } if (!sign_ok) { description_f = mailprivacy_get_tmp_file(privacy, description_filename, sizeof(description_filename)); if (description_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_decrypted; } fprintf(description_f, SMIME_DECRYPT_DESCRIPTION); fprintf(description_f, SMIME_DECRYPT_FAILED); fclose(description_f); } /* building multipart */ r = mailmime_new_with_content("multipart/x-decrypted", NULL, &multipart); if (r != MAILIMF_NO_ERROR) { res = MAIL_ERROR_MEMORY; goto unlink_description; } /* building the description part */ description_mime = mailprivacy_new_file_part(privacy, description_filename, "text/plain", MAILMIME_MECHANISM_8BIT); if (description_mime == NULL) { mailprivacy_mime_clear(multipart); mailmime_free(multipart); res = MAIL_ERROR_MEMORY; goto unlink_description; } /* adds the description part */ r = mailmime_smart_add_part(multipart, description_mime); if (r != MAIL_NO_ERROR) { mailprivacy_mime_clear(description_mime); mailmime_free(description_mime); mailprivacy_mime_clear(multipart); mailmime_free(multipart); res = MAIL_ERROR_MEMORY; goto unlink_description; } /* building the decrypted part */ r = mailprivacy_get_part_from_file(privacy, 1, decrypted_filename, &decrypted_mime); if (r == MAIL_NO_ERROR) { /* adds the decrypted part */ r = mailmime_smart_add_part(multipart, decrypted_mime); if (r != MAIL_NO_ERROR) { mailprivacy_mime_clear(decrypted_mime); mailmime_free(decrypted_mime); mailprivacy_mime_clear(multipart); mailmime_free(multipart); res = MAIL_ERROR_MEMORY; goto unlink_description; } } unlink(description_filename); unlink(decrypted_filename); unlink(smime_filename); * result = multipart; return MAIL_NO_ERROR; unlink_description: unlink(description_filename); unlink_decrypted: unlink(decrypted_filename); unlink_smime: unlink(smime_filename); err: return res; } static int get_cert_from_sig(struct mailprivacy * privacy, mailmessage * msg, struct mailmime * mime); #define SMIME_VERIFY_DESCRIPTION "S/MIME verify signed message\r\n" #define SMIME_VERIFY_FAILED "S/MIME verification FAILED\r\n" #define SMIME_VERIFY_SUCCESS "S/MIME verification success\r\n" static int smime_verify(struct mailprivacy * privacy, mailmessage * msg, struct mailmime * mime, struct mailmime ** result) { char smime_filename[PATH_MAX]; char quoted_smime_filename[PATH_MAX]; int res; int r; char command[PATH_MAX]; int sign_ok; struct mailmime * description_mime; FILE * description_f; char description_filename[PATH_MAX]; struct mailmime * multipart; char stripped_filename[PATH_MAX]; struct mailmime * stripped_mime; char quoted_stripped_filename[PATH_MAX]; FILE * stripped_f; char check_CA[PATH_MAX]; char quoted_CAfile[PATH_MAX]; char noverify[PATH_MAX]; if (store_cert) get_cert_from_sig(privacy, msg, mime); * check_CA = '\0'; if (CAfile != NULL) { r = mail_quote_filename(quoted_CAfile, sizeof(quoted_CAfile), CAfile); if (r < 0) { res = MAIL_ERROR_MEMORY; goto err; } snprintf(check_CA, sizeof(check_CA), "-CAfile %s", quoted_CAfile); } * noverify = '\0'; if (!CA_check) { snprintf(noverify, sizeof(noverify), "-noverify"); } /* fetch the whole multipart and write it to a file */ r = mailprivacy_fetch_mime_body_to_file(privacy, smime_filename, sizeof(smime_filename), msg, mime); if (r != MAIL_NO_ERROR) { res = r; goto err; } stripped_f = mailprivacy_get_tmp_file(privacy, stripped_filename, sizeof(stripped_filename)); if (stripped_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_smime; } fclose(stripped_f); /* description */ description_f = mailprivacy_get_tmp_file(privacy, description_filename, sizeof(description_filename)); if (description_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_stripped; } fprintf(description_f, SMIME_VERIFY_DESCRIPTION); /* run the command */ r = mail_quote_filename(quoted_smime_filename, sizeof(quoted_smime_filename), smime_filename); if (r < 0) { fclose(description_f); res = MAIL_ERROR_MEMORY; goto unlink_description; } r = mail_quote_filename(quoted_stripped_filename, sizeof(quoted_stripped_filename), stripped_filename); if (r < 0) { fclose(description_f); res = MAIL_ERROR_MEMORY; goto unlink_description; } sign_ok = 0; snprintf(command, PATH_MAX, "openssl smime -verify -in %s -out %s %s %s", quoted_smime_filename, quoted_stripped_filename, check_CA, noverify); r = get_smime_output(description_f, command); switch (r) { case NO_ERROR_SMIME: sign_ok = 1; break; case ERROR_SMIME_CHECK: sign_ok = 0; break; case ERROR_SMIME_COMMAND: fclose(description_f); res = MAIL_ERROR_COMMAND; goto unlink_description; case ERROR_SMIME_FILE: fclose(description_f); res = MAIL_ERROR_FILE; goto unlink_description; } if (sign_ok) fprintf(description_f, SMIME_VERIFY_SUCCESS); else fprintf(description_f, SMIME_VERIFY_FAILED); fclose(description_f); /* building multipart */ r = mailmime_new_with_content("multipart/x-verified", NULL, &multipart); if (r != MAILIMF_NO_ERROR) { res = MAIL_ERROR_MEMORY; goto unlink_description; } /* building the description part */ description_mime = mailprivacy_new_file_part(privacy, description_filename, "text/plain", MAILMIME_MECHANISM_8BIT); if (description_mime == NULL) { mailprivacy_mime_clear(multipart); mailmime_free(multipart); res = MAIL_ERROR_MEMORY; goto unlink_description; } /* adds the description part */ r = mailmime_smart_add_part(multipart, description_mime); if (r != MAIL_NO_ERROR) { mailprivacy_mime_clear(description_mime); mailmime_free(description_mime); mailprivacy_mime_clear(multipart); mailmime_free(multipart); res = MAIL_ERROR_MEMORY; goto unlink_description; } r = mailprivacy_get_part_from_file(privacy, 1, stripped_filename, &stripped_mime); if (r != MAIL_NO_ERROR) { mailprivacy_mime_clear(multipart); mailmime_free(multipart); res = r; goto unlink_description; } r = mailmime_smart_add_part(multipart, stripped_mime); if (r != MAIL_NO_ERROR) { mailprivacy_mime_clear(stripped_mime); mailmime_free(stripped_mime); mailprivacy_mime_clear(multipart); mailmime_free(multipart); res = MAIL_ERROR_MEMORY; goto unlink_description; } unlink(description_filename); unlink(stripped_filename); unlink(smime_filename); * result = multipart; return MAIL_NO_ERROR; unlink_description: unlink(description_filename); unlink_stripped: unlink(stripped_filename); unlink_smime: unlink(smime_filename); err: return res; } static int smime_test_encrypted(struct mailprivacy * privacy, mailmessage * msg, struct mailmime * mime) { switch (mime->mm_type) { case MAILMIME_MULTIPLE: return smime_is_signed(mime); case MAILMIME_SINGLE: return smime_is_encrypted(mime); } return 0; } static int smime_handler(struct mailprivacy * privacy, mailmessage * msg, struct mailmime * mime, struct mailmime ** result) { int r; struct mailmime * alternative_mime; alternative_mime = NULL; switch (mime->mm_type) { case MAILMIME_MULTIPLE: r = MAIL_ERROR_INVAL; if (smime_is_signed(mime)) r = smime_verify(privacy, msg, mime, &alternative_mime); if (r != MAIL_NO_ERROR) return r; * result = alternative_mime; return MAIL_NO_ERROR; case MAILMIME_SINGLE: r = MAIL_ERROR_INVAL; if (smime_is_encrypted(mime)) r = smime_decrypt(privacy, msg, mime, &alternative_mime); if (r != MAIL_NO_ERROR) return r; * result = alternative_mime; return MAIL_NO_ERROR; } return MAIL_ERROR_INVAL; } static void strip_mime_headers(struct mailmime * mime) { struct mailmime_fields * fields; clistiter * cur; fields = mime->mm_mime_fields; if (fields == NULL) return; for(cur = clist_begin(fields->fld_list) ; cur != NULL ; cur = clist_next(cur)) { struct mailmime_field * field; field = clist_content(cur); if (field->fld_type == MAILMIME_FIELD_VERSION) { mailmime_field_free(field); clist_delete(fields->fld_list, cur); break; } } } static int smime_sign(struct mailprivacy * privacy, struct mailmime * mime, struct mailmime ** result) { char signed_filename[PATH_MAX]; FILE * signed_f; int res; int r; int col; char signature_filename[PATH_MAX]; FILE * signature_f; char command[PATH_MAX]; char quoted_signature_filename[PATH_MAX]; char quoted_signed_filename[PATH_MAX]; struct mailmime * signed_mime; char * smime_cert; char * smime_key; char quoted_smime_cert[PATH_MAX]; char quoted_smime_key[PATH_MAX]; char * email; /* get signing key */ email = get_first_from_addr(mime); if (email == NULL) { res = MAIL_ERROR_INVAL; goto err; } smime_key = get_private_key_file(email); smime_cert = get_cert_file(email); if ((smime_cert == NULL) || (smime_key == NULL)) { res = MAIL_ERROR_INVAL; goto err; } /* part to sign */ /* encode quoted printable all text parts */ mailprivacy_prepare_mime(mime); signed_f = mailprivacy_get_tmp_file(privacy, signed_filename, sizeof(signed_filename)); if (signed_f == NULL) { res = MAIL_ERROR_FILE; goto err; } col = 0; r = mailmime_write(signed_f, &col, mime); if (r != MAILIMF_NO_ERROR) { fclose(signed_f); res = MAIL_ERROR_FILE; goto unlink_signed; } fclose(signed_f); /* prepare destination file for signature */ signature_f = mailprivacy_get_tmp_file(privacy, signature_filename, sizeof(signature_filename)); if (signature_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_signed; } fclose(signature_f); r = mail_quote_filename(quoted_signed_filename, sizeof(quoted_signed_filename), signed_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } r = mail_quote_filename(quoted_signature_filename, sizeof(quoted_signature_filename), signature_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } r = mail_quote_filename(quoted_smime_key, sizeof(quoted_smime_key), smime_key); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } r = mail_quote_filename(quoted_smime_cert, sizeof(quoted_smime_cert), smime_cert); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } snprintf(command, sizeof(command), "openssl smime -sign -in %s -out %s -signer %s -inkey %s 2>/dev/null", quoted_signed_filename, quoted_signature_filename, quoted_smime_cert, quoted_smime_key); r = system(command); if (WEXITSTATUS(r) != 0) { res = MAIL_ERROR_COMMAND; goto unlink_signature; } /* signature part */ r = mailprivacy_get_part_from_file(privacy, 0, signature_filename, &signed_mime); if (r != MAIL_NO_ERROR) { res = r; goto unlink_signature; } strip_mime_headers(signed_mime); unlink(signature_filename); unlink(signed_filename); * result = signed_mime; return MAIL_NO_ERROR; unlink_signature: unlink(signature_filename); unlink_signed: unlink(signed_filename); err: return res; } /* ********************************************************************* */ /* find S/MIME recipient */ static int recipient_add_mb(char * recipient, size_t * len, struct mailimf_mailbox * mb) { char * filename; char quoted_filename[PATH_MAX]; size_t buflen; int r; if (mb->mb_addr_spec == NULL) return MAIL_NO_ERROR; filename = get_cert_file(mb->mb_addr_spec); if (filename == NULL) return MAIL_ERROR_INVAL; r = mail_quote_filename(quoted_filename, sizeof(quoted_filename), filename); if (r < 0) return MAIL_ERROR_MEMORY; buflen = strlen(quoted_filename + 1); if (buflen >= * len) return MAIL_ERROR_MEMORY; strncat(recipient, quoted_filename, * len); (* len) -= buflen; strncat(recipient, " ", * len); (* len) --; return MAIL_NO_ERROR; } static int recipient_add_mb_list(char * recipient, size_t * len, struct mailimf_mailbox_list * mb_list) { clistiter * cur; int r; for(cur = clist_begin(mb_list->mb_list) ; cur != NULL ; cur = clist_next(cur)) { struct mailimf_mailbox * mb; mb = clist_content(cur); r = recipient_add_mb(recipient, len, mb); if (r != MAIL_NO_ERROR) return r; } return MAIL_NO_ERROR; } static int recipient_add_group(char * recipient, size_t * len, struct mailimf_group * group) { return recipient_add_mb_list(recipient, len, group->grp_mb_list); } static int recipient_add_addr(char * recipient, size_t * len, struct mailimf_address * addr) { int r; switch (addr->ad_type) { case MAILIMF_ADDRESS_MAILBOX: r = recipient_add_mb(recipient, len, addr->ad_data.ad_mailbox); break; case MAILIMF_ADDRESS_GROUP: r = recipient_add_group(recipient, len, addr->ad_data.ad_group); break; default: r = MAIL_ERROR_INVAL; } return r; } static int recipient_add_addr_list(char * recipient, size_t * len, struct mailimf_address_list * addr_list) { clistiter * cur; int r; for(cur = clist_begin(addr_list->ad_list) ; cur != NULL ; cur = clist_next(cur)) { struct mailimf_address * addr; addr = clist_content(cur); r = recipient_add_addr(recipient, len, addr); if (r != MAIL_NO_ERROR) return r; } return MAIL_NO_ERROR; } static int collect_smime_cert(char * recipient, size_t size, struct mailimf_fields * fields) { struct mailimf_single_fields single_fields; int r; size_t remaining; int res; * recipient = '\0'; remaining = size; if (fields != NULL) mailimf_single_fields_init(&single_fields, fields); if (single_fields.fld_to != NULL) { r = recipient_add_addr_list(recipient, &remaining, single_fields.fld_to->to_addr_list); if (r != MAIL_NO_ERROR) { res = r; goto err; } } if (single_fields.fld_cc != NULL) { r = recipient_add_addr_list(recipient, &remaining, single_fields.fld_cc->cc_addr_list); if (r != MAIL_NO_ERROR) { res = r; goto err; } } if (single_fields.fld_bcc != NULL) { if (single_fields.fld_bcc->bcc_addr_list != NULL) { r = recipient_add_addr_list(recipient, &remaining, single_fields.fld_bcc->bcc_addr_list); if (r < 0) { res = r; goto err; } } } return MAIL_NO_ERROR; err: return res; } static int smime_encrypt(struct mailprivacy * privacy, struct mailmime * mime, struct mailmime ** result) { char encrypted_filename[PATH_MAX]; FILE * encrypted_f; int res; int r; int col; char decrypted_filename[PATH_MAX]; FILE * decrypted_f; char command[PATH_MAX]; char quoted_decrypted_filename[PATH_MAX]; char quoted_encrypted_filename[PATH_MAX]; struct mailmime * encrypted_mime; struct mailmime * root; struct mailimf_fields * fields; char recipient[PATH_MAX]; root = mime; while (root->mm_parent != NULL) root = root->mm_parent; fields = NULL; if (root->mm_type == MAILMIME_MESSAGE) fields = root->mm_data.mm_message.mm_fields; /* recipient */ r = collect_smime_cert(recipient, sizeof(recipient), fields); if (r != MAIL_NO_ERROR) { res = r; goto err; } /* part to encrypt */ /* encode quoted printable all text parts */ mailprivacy_prepare_mime(mime); decrypted_f = mailprivacy_get_tmp_file(privacy, decrypted_filename, sizeof(decrypted_filename)); if (decrypted_f == NULL) { res = MAIL_ERROR_FILE; goto err; } col = 0; r = mailmime_write(decrypted_f, &col, mime); if (r != MAILIMF_NO_ERROR) { fclose(decrypted_f); res = MAIL_ERROR_FILE; goto unlink_decrypted; } fclose(decrypted_f); /* prepare destination file for encryption */ encrypted_f = mailprivacy_get_tmp_file(privacy, encrypted_filename, sizeof(encrypted_filename)); if (encrypted_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_decrypted; } fclose(encrypted_f); r = mail_quote_filename(quoted_decrypted_filename, sizeof(quoted_decrypted_filename), decrypted_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_encrypted; } r = mail_quote_filename(quoted_encrypted_filename, sizeof(quoted_encrypted_filename), encrypted_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_encrypted; } snprintf(command, sizeof(command), "openssl smime -encrypt -in %s -out %s %s 2>/dev/null", quoted_decrypted_filename, quoted_encrypted_filename, recipient); r = system(command); if (WEXITSTATUS(r) != 0) { res = MAIL_ERROR_COMMAND; goto unlink_encrypted; } /* encrypted part */ r = mailprivacy_get_part_from_file(privacy, 0, encrypted_filename, &encrypted_mime); if (r != MAIL_NO_ERROR) { res = r; goto unlink_encrypted; } strip_mime_headers(encrypted_mime); unlink(encrypted_filename); unlink(decrypted_filename); * result = encrypted_mime; return MAIL_NO_ERROR; unlink_encrypted: unlink(encrypted_filename); unlink_decrypted: unlink(decrypted_filename); err: return res; } static int smime_sign_encrypt(struct mailprivacy * privacy, struct mailmime * mime, struct mailmime ** result) { char encrypted_filename[PATH_MAX]; FILE * encrypted_f; int res; int r; int col; char signature_filename[PATH_MAX]; FILE * signature_f; char decrypted_filename[PATH_MAX]; FILE * decrypted_f; char command[PATH_MAX]; char quoted_decrypted_filename[PATH_MAX]; char quoted_encrypted_filename[PATH_MAX]; char quoted_signature_filename[PATH_MAX]; struct mailmime * encrypted_mime; struct mailmime * root; struct mailimf_fields * fields; char recipient[PATH_MAX]; char * smime_cert; char * smime_key; char quoted_smime_cert[PATH_MAX]; char quoted_smime_key[PATH_MAX]; char * email; root = mime; while (root->mm_parent != NULL) root = root->mm_parent; fields = NULL; if (root->mm_type == MAILMIME_MESSAGE) fields = root->mm_data.mm_message.mm_fields; /* recipient */ r = collect_smime_cert(recipient, sizeof(recipient), fields); if (r != MAIL_NO_ERROR) { res = r; goto err; } /* get signing key */ email = get_first_from_addr(mime); if (email == NULL) { res = MAIL_ERROR_INVAL; goto err; } smime_key = get_private_key_file(email); smime_cert = get_cert_file(email); if ((smime_cert == NULL) || (smime_key == NULL)) { res = MAIL_ERROR_INVAL; goto err; } /* part to encrypt */ /* encode quoted printable all text parts */ mailprivacy_prepare_mime(mime); decrypted_f = mailprivacy_get_tmp_file(privacy, decrypted_filename, sizeof(decrypted_filename)); if (decrypted_f == NULL) { res = MAIL_ERROR_FILE; goto err; } col = 0; r = mailmime_write(decrypted_f, &col, mime); if (r != MAILIMF_NO_ERROR) { fclose(decrypted_f); res = MAIL_ERROR_FILE; goto unlink_decrypted; } fclose(decrypted_f); /* prepare destination file for signature */ signature_f = mailprivacy_get_tmp_file(privacy, signature_filename, sizeof(signature_filename)); if (signature_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_decrypted; } fclose(signature_f); r = mail_quote_filename(quoted_decrypted_filename, sizeof(quoted_decrypted_filename), decrypted_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } r = mail_quote_filename(quoted_signature_filename, sizeof(quoted_signature_filename), signature_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } r = mail_quote_filename(quoted_smime_key, sizeof(quoted_smime_key), smime_key); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } r = mail_quote_filename(quoted_smime_cert, sizeof(quoted_smime_cert), smime_cert); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } snprintf(command, sizeof(command), "openssl smime -sign -in %s -out %s -signer %s -inkey %s 2>/dev/null", quoted_decrypted_filename, quoted_signature_filename, quoted_smime_cert, quoted_smime_key); r = system(command); if (WEXITSTATUS(r) != 0) { res = MAIL_ERROR_COMMAND; goto unlink_signature; } /* prepare destination file for encryption */ encrypted_f = mailprivacy_get_tmp_file(privacy, encrypted_filename, sizeof(encrypted_filename)); if (encrypted_f == NULL) { res = MAIL_ERROR_FILE; goto unlink_signature; } fclose(encrypted_f); r = mail_quote_filename(quoted_encrypted_filename, sizeof(quoted_encrypted_filename), encrypted_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_encrypted; } snprintf(command, sizeof(command), "openssl smime -encrypt -in %s -out %s %s 2>/dev/null", quoted_signature_filename, quoted_encrypted_filename, recipient); r = system(command); if (WEXITSTATUS(r) != 0) { res = MAIL_ERROR_COMMAND; goto unlink_encrypted; } /* encrypted part */ r = mailprivacy_get_part_from_file(privacy, 0, encrypted_filename, &encrypted_mime); if (r != MAIL_NO_ERROR) { res = r; goto unlink_encrypted; } strip_mime_headers(encrypted_mime); unlink(encrypted_filename); unlink(signature_filename); unlink(decrypted_filename); * result = encrypted_mime; return MAIL_NO_ERROR; unlink_encrypted: unlink(encrypted_filename); unlink_signature: unlink(signature_filename); unlink_decrypted: unlink(decrypted_filename); err: return res; } static struct mailprivacy_encryption smime_encryption_tab[] = { /* S/MIME signed part */ { .name = "signed", .description = "S/MIME signed part", .encrypt = smime_sign, }, /* S/MIME encrypted part */ { .name = "encrypted", .description = "S/MIME encrypted part", .encrypt = smime_encrypt, }, /* S/MIME signed & encrypted part */ { .name = "signed-encrypted", .description = "S/MIME signed & encrypted part", .encrypt = smime_sign_encrypt, }, }; static struct mailprivacy_protocol smime_protocol = { .name = "smime", .description = "S/MIME", .is_encrypted = smime_test_encrypted, .decrypt = smime_handler, .encryption_count = (sizeof(smime_encryption_tab) / sizeof(smime_encryption_tab[0])), .encryption_tab = smime_encryption_tab, }; int mailprivacy_smime_init(struct mailprivacy * privacy) { certificates = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYALL); if (certificates == NULL) goto err; private_keys = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYALL); if (private_keys == NULL) goto free_cert; CAcert_dir[0] = '\0'; return mailprivacy_register(privacy, &smime_protocol); free_cert: chash_free(certificates); err: return MAIL_ERROR_MEMORY; } void mailprivacy_smime_done(struct mailprivacy * privacy) { mailprivacy_unregister(privacy, &smime_protocol); chash_free(private_keys); private_keys = NULL; chash_free(certificates); certificates = NULL; if (CAfile != NULL) { unlink(CAfile); free(CAfile); } CAfile = NULL; CAcert_dir[0] = '\0'; } static void strip_string(char * str) { char * p; size_t len; p = strchr(str, '\r'); if (p != NULL) * p = 0; p = strchr(str, '\n'); if (p != NULL) * p = 0; p = str; while ((* p == ' ') || (* p == '\t')) { p ++; } len = strlen(p); memmove(str, p, len); str[len] = 0; if (len == 0) return; p = str; len = len - 1; while ((p[len] == ' ') || (p[len] == '\t')) { p[len] = '\0'; if (len == 0) break; len --; } } #define MAX_EMAIL_SIZE 1024 static void set_file(chash * hash, char * email, char * filename) { char * n; char buf[MAX_EMAIL_SIZE]; chashdatum key; chashdatum data; strncpy(buf, email, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; for(n = buf ; * n != '\0' ; n ++) * n = toupper((unsigned char) * n); strip_string(buf); key.data = buf; key.len = strlen(buf); data.data = filename; data.len = strlen(filename) + 1; chash_set(hash, &key, &data, NULL); } static char * get_file(chash * hash, char * email) { chashdatum key; chashdatum data; char buf[MAX_EMAIL_SIZE]; char * n; int r; strncpy(buf, email, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; for(n = buf ; * n != '\0' ; n ++) * n = toupper((unsigned char) * n); strip_string(buf); key.data = buf; key.len = strlen(buf); r = chash_get(hash, &key, &data); if (r < 0) return NULL; return data.data; } void mailprivacy_smime_set_cert_dir(struct mailprivacy * privacy, char * directory) { DIR * dir; struct dirent * ent; chash_clear(certificates); if (directory == NULL) return; if (* directory == '\0') return; strncpy(cert_dir, directory, sizeof(cert_dir)); cert_dir[sizeof(cert_dir) - 1] = '\0'; dir = opendir(directory); if (dir == NULL) return; while ((ent = readdir(dir)) != NULL) { char filename[PATH_MAX]; char command[PATH_MAX]; char buf[MAX_EMAIL_SIZE]; FILE * p; snprintf(filename, sizeof(filename), "%s/%s", directory, ent->d_name); snprintf(command, sizeof(command), "openssl x509 -email -noout -in %s 2>/dev/null", filename); p = popen(command, "r"); if (p == NULL) continue; while (fgets(buf, sizeof(buf), p) != NULL) set_file(certificates, buf, filename); pclose(p); } closedir(dir); } static char * get_cert_file(char * email) { return get_file(certificates, email); } static char * get_private_key_file(char * email) { return get_file(private_keys, email); } void mail_private_smime_clear_private_keys(struct mailprivacy * privacy) { chash_clear(private_keys); } #define MAX_BUF 1024 void mailprivacy_smime_set_CA_dir(struct mailprivacy * privacy, char * directory) { DIR * dir; struct dirent * ent; FILE * f_CA; char CA_filename[PATH_MAX]; if (directory == NULL) return; if (* directory == '\0') return; /* make a temporary file that contains all the CAs */ if (CAfile != NULL) { unlink(CAfile); free(CAfile); CAfile = NULL; } f_CA = mailprivacy_get_tmp_file(privacy, CA_filename, sizeof(CA_filename)); if (f_CA == NULL) return; strncpy(CAcert_dir, directory, sizeof(CAcert_dir)); CAcert_dir[sizeof(CAcert_dir) - 1] = '\0'; dir = opendir(directory); if (dir == NULL) { fclose(f_CA); goto unlink_CA; } while ((ent = readdir(dir)) != NULL) { char filename[PATH_MAX]; char command[PATH_MAX]; char buf[MAX_BUF]; FILE * f; snprintf(filename, sizeof(filename), "%s/%s", directory, ent->d_name); f = fopen(filename, "r"); if (f == NULL) continue; while (fgets(buf, sizeof(buf), f) != NULL) fputs(buf, f_CA); fclose(f); } closedir(dir); fclose(f_CA); CAfile = strdup(CA_filename); if (CAfile == NULL) goto unlink_CA; return; unlink_CA: unlink(CA_filename); } void mailprivacy_smime_set_CA_check(struct mailprivacy * privacy, int enabled) { CA_check = enabled; } void mailprivacy_smime_set_store_cert(struct mailprivacy * privacy, int enabled) { store_cert = enabled; } static int get_cert_from_sig(struct mailprivacy * privacy, mailmessage * msg, struct mailmime * mime) { clistiter * cur; struct mailmime * signed_mime; struct mailmime * signature_mime; int res; char signature_filename[PATH_MAX]; char quoted_signature_filename[PATH_MAX]; char * email; char * cert_file; char store_cert_filename[PATH_MAX]; char quoted_store_cert_filename[PATH_MAX]; int r; char command[PATH_MAX]; if (* cert_dir == '\0') return MAIL_ERROR_INVAL; if (mime->mm_type != MAILMIME_MULTIPLE) return MAIL_ERROR_INVAL; email = get_first_from_addr(mime); if (email == NULL) return MAIL_ERROR_INVAL; cert_file = get_cert_file(email); if (cert_file != NULL) return MAIL_NO_ERROR; /* get the two parts of the S/MIME message */ cur = clist_begin(mime->mm_data.mm_multipart.mm_mp_list); if (cur == NULL) { res = MAIL_ERROR_INVAL; goto err; } signed_mime = cur->data; cur = clist_next(cur); if (cur == NULL) { res = MAIL_ERROR_INVAL; goto err; } signature_mime = cur->data; r = mailprivacy_fetch_decoded_to_file(privacy, signature_filename, sizeof(signature_filename), msg, signature_mime); if (r != MAILIMF_NO_ERROR) { res = r; goto err; } r = mail_quote_filename(quoted_signature_filename, sizeof(quoted_signature_filename), signature_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } snprintf(store_cert_filename, sizeof(store_cert_filename), "%s/%s-cert.pem", cert_dir, email); r = mail_quote_filename(quoted_store_cert_filename, sizeof(quoted_store_cert_filename), store_cert_filename); if (r < 0) { res = MAIL_ERROR_MEMORY; goto unlink_signature; } snprintf(command, sizeof(command), "openssl pkcs7 -inform DER -in %s -out %s -print_certs 2>/dev/null", quoted_signature_filename, quoted_store_cert_filename); r = system(command); if (WEXITSTATUS(r) != 0) { res = MAIL_ERROR_COMMAND; goto unlink_signature; } unlink(signature_filename); set_file(certificates, email, store_cert_filename); return MAIL_NO_ERROR; unlink_signature: unlink(signature_filename); err: return res; } static void set_private_key(struct mailprivacy * privacy, char * email, char * file) { set_file(private_keys, email, file); } #define PRIVATE_KEY_SUFFIX "-private-key.pem" void mailprivacy_smime_set_private_keys_dir(struct mailprivacy * privacy, char * directory) { DIR * dir; struct dirent * ent; chash_clear(private_keys); if (directory == NULL) return; if (* directory == '\0') return; strncpy(private_keys_dir, directory, sizeof(private_keys_dir)); private_keys_dir[sizeof(private_keys_dir) - 1] = '\0'; dir = opendir(directory); if (dir == NULL) return; while ((ent = readdir(dir)) != NULL) { char filename[PATH_MAX]; char email[PATH_MAX]; char * p; snprintf(filename, sizeof(filename), "%s/%s", directory, ent->d_name); strncpy(email, ent->d_name, sizeof(email)); email[sizeof(email) - 1] = '\0'; p = strstr(email, PRIVATE_KEY_SUFFIX); if (p == NULL) continue; if (strlen(p) != sizeof(PRIVATE_KEY_SUFFIX) - 1) continue; * p = 0; if (* email == '\0') continue; set_private_key(privacy, email, filename); } closedir(dir); }