1937 lines
44 KiB
C
1937 lines
44 KiB
C
/*
|
|
* 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: mailprivacy_smime.c,v 1.19 2011/05/03 16:30:22 hoa Exp $
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include "mailprivacy_smime.h"
|
|
#include <string.h>
|
|
|
|
#if __APPLE__
|
|
#include <TargetConditionals.h>
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
# include "win_etpan.h"
|
|
# define WEXITSTATUS(r) (r)
|
|
#else
|
|
# include <sys/mman.h>
|
|
# include <sys/wait.h>
|
|
# include <dirent.h>
|
|
#endif
|
|
#ifdef LIBETPAN_REENTRANT
|
|
#if defined(HAVE_PTHREAD_H) && !defined(IGNORE_PTHREAD_H)
|
|
#include <pthread.h>
|
|
#elif (defined WIN32)
|
|
#include <windows.h>
|
|
#endif
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include "mailprivacy_tools.h"
|
|
#include "mailprivacy_tools_private.h"
|
|
#include "mailprivacy.h"
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <libetpan/libetpan-config.h>
|
|
#include <errno.h>
|
|
|
|
#include "../data-types/syscall_wrappers.h"
|
|
|
|
/*
|
|
global variable
|
|
|
|
TODO : instance of privacy drivers
|
|
*/
|
|
|
|
static int smime_command_passphrase(struct mailprivacy * privacy,
|
|
struct mailmessage * msg,
|
|
char * command,
|
|
char * passphrase,
|
|
char * stdoutfile, char * stderrfile);
|
|
static int mailprivacy_smime_add_encryption_id(struct mailprivacy * privacy,
|
|
mailmessage * msg, char * encryption_id);
|
|
|
|
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) {
|
|
if ((strcasecmp(mime->mm_content_type->ct_subtype, "x-pkcs7-mime") == 0) ||
|
|
(strcasecmp(mime->mm_content_type->ct_subtype, "pkcs7-mime") == 0)) {
|
|
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, "smime-type") == 0) &&
|
|
(strcasecmp(param->pa_value, "signed-data") == 0))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
else {
|
|
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)) {
|
|
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, "smime-type") == 0) &&
|
|
(strcasecmp(param->pa_value, "signed-data") == 0))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
enum {
|
|
NO_ERROR_SMIME = 0,
|
|
ERROR_SMIME_CHECK,
|
|
ERROR_SMIME_COMMAND,
|
|
ERROR_SMIME_FILE,
|
|
ERROR_SMIME_NOPASSPHRASE
|
|
};
|
|
|
|
#define BUF_SIZE 1024
|
|
|
|
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"
|
|
|
|
/* passphrase will be needed */
|
|
|
|
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];
|
|
char decrypted_filename[PATH_MAX];
|
|
char command[PATH_MAX];
|
|
struct mailmime * description_mime;
|
|
struct mailmime * decrypted_mime;
|
|
int r;
|
|
int res;
|
|
int sign_ok;
|
|
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 */
|
|
|
|
r = mailprivacy_get_tmp_filename(privacy, decrypted_filename,
|
|
sizeof(decrypted_filename));
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = MAIL_ERROR_FILE;
|
|
goto unlink_smime;
|
|
}
|
|
|
|
/* description */
|
|
|
|
r = mailprivacy_get_tmp_filename(privacy, description_filename,
|
|
sizeof(description_filename));
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = MAIL_ERROR_FILE;
|
|
goto unlink_decrypted;
|
|
}
|
|
|
|
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;
|
|
|
|
/* get encryption key */
|
|
|
|
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 unlink_description;
|
|
}
|
|
|
|
r = mail_quote_filename(quoted_smime_cert, sizeof(quoted_smime_cert),
|
|
smime_cert);
|
|
if (r < 0) {
|
|
res = MAIL_ERROR_MEMORY;
|
|
goto unlink_description;
|
|
}
|
|
|
|
r = mail_quote_filename(quoted_smime_key, sizeof(quoted_smime_key),
|
|
smime_key);
|
|
if (r < 0) {
|
|
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) {
|
|
res = MAIL_ERROR_MEMORY;
|
|
goto unlink_description;
|
|
}
|
|
|
|
sign_ok = 0;
|
|
snprintf(command, sizeof(command),
|
|
"openssl smime -decrypt -passin fd:0 -in '%s' -inkey '%s' -recip '%s'",
|
|
quoted_smime_filename, quoted_smime_key, quoted_smime_cert);
|
|
|
|
unlink(description_filename);
|
|
r = smime_command_passphrase(privacy, msg, command,
|
|
email, decrypted_filename, description_filename);
|
|
switch (r) {
|
|
case NO_ERROR_SMIME:
|
|
sign_ok = 1;
|
|
break;
|
|
case ERROR_SMIME_CHECK:
|
|
case ERROR_SMIME_NOPASSPHRASE:
|
|
sign_ok = 0;
|
|
break;
|
|
case ERROR_SMIME_COMMAND:
|
|
res = MAIL_ERROR_COMMAND;
|
|
goto unlink_description;
|
|
case ERROR_SMIME_FILE:
|
|
res = MAIL_ERROR_FILE;
|
|
goto unlink_description;
|
|
}
|
|
|
|
if (sign_ok) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!sign_ok) {
|
|
if (chash_count(private_keys) == 0) {
|
|
FILE * description_f;
|
|
|
|
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_FAILED);
|
|
Fclose(description_f);
|
|
}
|
|
}
|
|
else {
|
|
mailprivacy_smime_encryption_id_list_clear(privacy, msg);
|
|
}
|
|
|
|
/* 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, 0,
|
|
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;
|
|
char description_filename[PATH_MAX];
|
|
struct mailmime * multipart;
|
|
char stripped_filename[PATH_MAX];
|
|
struct mailmime * stripped_mime;
|
|
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;
|
|
}
|
|
|
|
r = mailprivacy_get_tmp_filename(privacy,stripped_filename,
|
|
sizeof(stripped_filename));
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = r;
|
|
goto unlink_smime;
|
|
}
|
|
|
|
/* description */
|
|
|
|
r = mailprivacy_get_tmp_filename(privacy, description_filename,
|
|
sizeof(description_filename));
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = r;
|
|
goto unlink_stripped;
|
|
}
|
|
|
|
/* run the command */
|
|
|
|
r = mail_quote_filename(quoted_smime_filename,
|
|
sizeof(quoted_smime_filename), smime_filename);
|
|
if (r < 0) {
|
|
res = MAIL_ERROR_MEMORY;
|
|
goto unlink_description;
|
|
}
|
|
|
|
sign_ok = 0;
|
|
snprintf(command, sizeof(command), "openssl smime -verify -in '%s' %s %s",
|
|
quoted_smime_filename, check_CA, noverify);
|
|
|
|
r = smime_command_passphrase(privacy, msg, command,
|
|
NULL, stripped_filename, description_filename);
|
|
switch (r) {
|
|
case NO_ERROR_SMIME:
|
|
sign_ok = 1;
|
|
break;
|
|
case ERROR_SMIME_NOPASSPHRASE:
|
|
case ERROR_SMIME_CHECK:
|
|
sign_ok = 0;
|
|
break;
|
|
case ERROR_SMIME_COMMAND:
|
|
res = MAIL_ERROR_COMMAND;
|
|
goto unlink_description;
|
|
case ERROR_SMIME_FILE:
|
|
res = MAIL_ERROR_FILE;
|
|
goto unlink_description;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* insert the signed part */
|
|
if (!sign_ok) {
|
|
if (mime->mm_type == MAILMIME_MULTIPLE) {
|
|
clistiter * child_iter;
|
|
struct mailmime * child;
|
|
|
|
child_iter = clist_begin(mime->mm_data.mm_multipart.mm_mp_list);
|
|
child = clist_content(child_iter);
|
|
|
|
r = mailprivacy_fetch_mime_body_to_file(privacy,
|
|
stripped_filename, sizeof(stripped_filename),
|
|
msg, child);
|
|
}
|
|
}
|
|
|
|
r = mailprivacy_get_part_from_file(privacy, 1, 0,
|
|
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) || smime_is_signed(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);
|
|
else 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* passphrase is needed */
|
|
|
|
static int smime_sign(struct mailprivacy * privacy,
|
|
mailmessage * msg,
|
|
struct mailmime * mime, struct mailmime ** result)
|
|
{
|
|
char signed_filename[PATH_MAX];
|
|
FILE * signed_f;
|
|
int res;
|
|
int r;
|
|
int col;
|
|
char description_filename[PATH_MAX];
|
|
char signature_filename[PATH_MAX];
|
|
char command[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 */
|
|
|
|
r = mailprivacy_get_tmp_filename(privacy, signature_filename,
|
|
sizeof(signature_filename));
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = r;
|
|
goto unlink_signed;
|
|
}
|
|
|
|
r = mailprivacy_get_tmp_filename(privacy, description_filename,
|
|
sizeof(description_filename));
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = r;
|
|
goto unlink_signature;
|
|
}
|
|
|
|
r = mail_quote_filename(quoted_signed_filename,
|
|
sizeof(quoted_signed_filename), signed_filename);
|
|
if (r < 0) {
|
|
res = MAIL_ERROR_MEMORY;
|
|
goto unlink_description;
|
|
}
|
|
|
|
r = mail_quote_filename(quoted_smime_key,
|
|
sizeof(quoted_smime_key), smime_key);
|
|
if (r < 0) {
|
|
res = MAIL_ERROR_MEMORY;
|
|
goto unlink_description;
|
|
}
|
|
|
|
r = mail_quote_filename(quoted_smime_cert,
|
|
sizeof(quoted_smime_cert), smime_cert);
|
|
if (r < 0) {
|
|
res = MAIL_ERROR_MEMORY;
|
|
goto unlink_description;
|
|
}
|
|
|
|
snprintf(command, sizeof(command),
|
|
"openssl smime -sign -passin fd:0 -in '%s' -signer '%s' -inkey '%s'",
|
|
quoted_signed_filename,
|
|
quoted_smime_cert, quoted_smime_key);
|
|
|
|
r = smime_command_passphrase(privacy, msg, command,
|
|
email, signature_filename, description_filename);
|
|
switch (r) {
|
|
case NO_ERROR_SMIME:
|
|
break;
|
|
case ERROR_SMIME_NOPASSPHRASE:
|
|
case ERROR_SMIME_CHECK:
|
|
res = MAIL_ERROR_COMMAND;
|
|
goto unlink_description;
|
|
break;
|
|
case ERROR_SMIME_COMMAND:
|
|
res = MAIL_ERROR_COMMAND;
|
|
goto unlink_description;
|
|
case ERROR_SMIME_FILE:
|
|
res = MAIL_ERROR_FILE;
|
|
goto unlink_description;
|
|
}
|
|
|
|
/* signature part */
|
|
|
|
r = mailprivacy_get_part_from_file(privacy, 0, 0,
|
|
signature_filename, &signed_mime);
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = r;
|
|
goto unlink_description;
|
|
}
|
|
strip_mime_headers(signed_mime);
|
|
|
|
unlink(description_filename);
|
|
/* unlink(signature_filename); */
|
|
/* unlink(signed_filename); */
|
|
|
|
* result = signed_mime;
|
|
|
|
return MAIL_NO_ERROR;
|
|
|
|
unlink_description:
|
|
unlink(description_filename);
|
|
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, "\'", * len);
|
|
(* len) --;
|
|
strncat(recipient, quoted_filename, * len);
|
|
(* len) -= buflen;
|
|
strncat(recipient, "\'", * len);
|
|
(* len) --;
|
|
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;
|
|
|
|
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,
|
|
mailmessage * msg,
|
|
struct mailmime * mime, struct mailmime ** result)
|
|
{
|
|
char encrypted_filename[PATH_MAX];
|
|
int res;
|
|
int r;
|
|
int col;
|
|
char description_filename[PATH_MAX];
|
|
char decrypted_filename[PATH_MAX];
|
|
FILE * decrypted_f;
|
|
char command[PATH_MAX];
|
|
char quoted_decrypted_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 */
|
|
|
|
r = mailprivacy_get_tmp_filename(privacy, encrypted_filename,
|
|
sizeof(encrypted_filename));
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = MAIL_ERROR_FILE;
|
|
goto unlink_decrypted;
|
|
}
|
|
|
|
r = mailprivacy_get_tmp_filename(privacy, description_filename,
|
|
sizeof(description_filename));
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = MAIL_ERROR_FILE;
|
|
goto unlink_encrypted;
|
|
}
|
|
|
|
r = mail_quote_filename(quoted_decrypted_filename,
|
|
sizeof(quoted_decrypted_filename), decrypted_filename);
|
|
if (r < 0) {
|
|
res = MAIL_ERROR_MEMORY;
|
|
goto unlink_description;
|
|
}
|
|
|
|
snprintf(command, sizeof(command), "openssl smime -encrypt -in '%s' %s",
|
|
quoted_decrypted_filename, recipient);
|
|
|
|
r = smime_command_passphrase(privacy, msg, command,
|
|
NULL, encrypted_filename, description_filename);
|
|
switch (r) {
|
|
case NO_ERROR_SMIME:
|
|
break;
|
|
case ERROR_SMIME_NOPASSPHRASE:
|
|
case ERROR_SMIME_CHECK:
|
|
res = MAIL_ERROR_COMMAND;
|
|
goto unlink_description;
|
|
break;
|
|
case ERROR_SMIME_COMMAND:
|
|
res = MAIL_ERROR_COMMAND;
|
|
goto unlink_encrypted;
|
|
case ERROR_SMIME_FILE:
|
|
res = MAIL_ERROR_FILE;
|
|
goto unlink_description;
|
|
}
|
|
|
|
/* encrypted part */
|
|
|
|
r = mailprivacy_get_part_from_file(privacy, 0, 0,
|
|
encrypted_filename, &encrypted_mime);
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = r;
|
|
goto unlink_description;
|
|
}
|
|
strip_mime_headers(encrypted_mime);
|
|
|
|
unlink(description_filename);
|
|
unlink(encrypted_filename);
|
|
unlink(decrypted_filename);
|
|
|
|
* result = encrypted_mime;
|
|
|
|
return MAIL_NO_ERROR;
|
|
|
|
unlink_description:
|
|
unlink(description_filename);
|
|
unlink_encrypted:
|
|
unlink(encrypted_filename);
|
|
unlink_decrypted:
|
|
unlink(decrypted_filename);
|
|
err:
|
|
return res;
|
|
}
|
|
|
|
|
|
/* passphrase will be needed */
|
|
|
|
static int smime_sign_encrypt(struct mailprivacy * privacy,
|
|
mailmessage * msg,
|
|
struct mailmime * mime, struct mailmime ** result)
|
|
{
|
|
struct mailmime * signed_part;
|
|
struct mailmime * encrypted;
|
|
int r;
|
|
int res;
|
|
|
|
r = smime_sign(privacy, msg, mime, &signed_part);
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = r;
|
|
goto err;
|
|
}
|
|
|
|
r = smime_encrypt(privacy, msg, signed_part, &encrypted);
|
|
if (r != MAIL_NO_ERROR) {
|
|
res = r;
|
|
goto free_signed;
|
|
}
|
|
|
|
* result = encrypted;
|
|
|
|
return MAIL_NO_ERROR;
|
|
|
|
free_signed:
|
|
mailprivacy_mime_clear(signed_part);
|
|
mailmime_free(signed_part);
|
|
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
|
|
};
|
|
|
|
#ifdef LIBETPAN_REENTRANT
|
|
#if defined(HAVE_PTHREAD_H) && !defined(IGNORE_PTHREAD_H)
|
|
static pthread_mutex_t encryption_id_hash_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
#define LOCK() pthread_mutex_lock(&encryption_id_hash_lock)
|
|
#define UNLOCK() pthread_mutex_unlock(&encryption_id_hash_lock)
|
|
#elif (defined WIN32)
|
|
static CRITICAL_SECTION encryption_id_hash_lock = {0};
|
|
#define LOCK() EnterCriticalSection(&encryption_id_hash_lock);
|
|
#define UNLOCK() LeaveCriticalSection(&encryption_id_hash_lock);
|
|
#endif
|
|
#else
|
|
#define LOCK() do {} while (0)
|
|
#define UNLOCK() do {} while (0)
|
|
#endif
|
|
|
|
static void mailprivacy_smime_init_lock(void)
|
|
{
|
|
#ifdef LIBETPAN_REENTRANT
|
|
#if defined(HAVE_PTHREAD_H) && !defined(IGNORE_PTHREAD_H)
|
|
#elif (defined WIN32)
|
|
static int mailprivacy_smime_init_lock_done = 0;
|
|
if (InterlockedExchange(&mailprivacy_smime_init_lock_done, 1) == 0) {
|
|
InitializeCriticalSection(&encryption_id_hash_lock);
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
int mailprivacy_smime_init(struct mailprivacy * privacy)
|
|
{
|
|
mailprivacy_smime_init_lock();
|
|
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 = (unsigned int) strlen(buf);
|
|
data.data = filename;
|
|
data.len = (unsigned int) 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 = (unsigned int) strlen(buf);
|
|
r = chash_get(hash, &key, &data);
|
|
if (r < 0)
|
|
return NULL;
|
|
|
|
return data.data;
|
|
}
|
|
|
|
#define CERTIFICATE_SUFFIX "-cert.pem"
|
|
|
|
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) {
|
|
#if 0
|
|
char quoted_filename[PATH_MAX];
|
|
char filename[PATH_MAX];
|
|
char command[PATH_MAX];
|
|
char buf[MAX_EMAIL_SIZE];
|
|
FILE * p;
|
|
int r;
|
|
|
|
snprintf(filename, sizeof(filename),
|
|
"%s/%s", directory, ent->d_name);
|
|
|
|
r = mail_quote_filename(quoted_filename, sizeof(quoted_filename), filename);
|
|
|
|
snprintf(command, sizeof(command),
|
|
"openssl x509 -email -noout -in '%s' 2>/dev/null", quoted_filename);
|
|
|
|
p = popen(command, "r");
|
|
if (p == NULL)
|
|
continue;
|
|
|
|
while (fgets(buf, sizeof(buf), p) != NULL)
|
|
set_file(certificates, buf, filename);
|
|
|
|
pclose(p);
|
|
#endif
|
|
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, CERTIFICATE_SUFFIX);
|
|
if (p == NULL)
|
|
continue;
|
|
|
|
if (strlen(p) != sizeof(CERTIFICATE_SUFFIX) - 1)
|
|
continue;
|
|
|
|
* p = 0;
|
|
|
|
if (* email == '\0')
|
|
continue;
|
|
|
|
set_file(certificates, email, filename);
|
|
}
|
|
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 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 TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
|
//https://github.com/dinhviethoa/libetpan/issues/275
|
|
//get_cert_from_sig is not needed on iOS
|
|
return MAIL_ERROR_COMMAND;
|
|
#endif
|
|
|
|
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" CERTIFICATE_SUFFIX, 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);
|
|
|
|
// N.B. This is only about compilation - we already don't execute this function with iPhone.
|
|
#if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
|
|
//https://github.com/dinhviethoa/libetpan/issues/275
|
|
//system() is not supported on iOS 11.
|
|
r = system(command);
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
- try private keys without passphrase and try those
|
|
for which we already have a passphrase,
|
|
- try recipient list and ask passphrase,
|
|
- then, ask passphrase for all private keys
|
|
*/
|
|
|
|
static char * get_passphrase(struct mailprivacy * privacy,
|
|
char * user_id);
|
|
|
|
static int smime_command_passphrase(struct mailprivacy * privacy,
|
|
struct mailmessage * msg,
|
|
char * command,
|
|
char * userid,
|
|
char * stdoutfile, char * stderrfile)
|
|
{
|
|
char * passphrase;
|
|
int res;
|
|
int bad_passphrase;
|
|
|
|
bad_passphrase = 0;
|
|
|
|
passphrase = NULL;
|
|
if (userid != NULL)
|
|
passphrase = get_passphrase(privacy, userid);
|
|
|
|
res = mailprivacy_spawn_and_wait(command, passphrase, stdoutfile, stderrfile,
|
|
&bad_passphrase);
|
|
if (res != NO_ERROR_PASSPHRASE) {
|
|
switch (res) {
|
|
case ERROR_PASSPHRASE_COMMAND:
|
|
return ERROR_SMIME_COMMAND;
|
|
case ERROR_PASSPHRASE_FILE:
|
|
return ERROR_SMIME_FILE;
|
|
default:
|
|
return ERROR_SMIME_COMMAND;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
if (bad_passphrase) {
|
|
if (userid != NULL) {
|
|
mailprivacy_smime_add_encryption_id(privacy, msg, userid);
|
|
return ERROR_SMIME_NOPASSPHRASE;
|
|
}
|
|
|
|
return ERROR_SMIME_CHECK;
|
|
}
|
|
|
|
return NO_ERROR_SMIME;
|
|
}
|
|
|
|
|
|
|
|
static chash * encryption_id_hash = NULL;
|
|
|
|
static clist * get_list(struct mailprivacy * privacy, mailmessage * msg)
|
|
{
|
|
clist * encryption_id_list;
|
|
|
|
encryption_id_list = NULL;
|
|
if (encryption_id_hash != NULL) {
|
|
chashdatum key;
|
|
chashdatum value;
|
|
int r;
|
|
|
|
key.data = &msg;
|
|
key.len = sizeof(msg);
|
|
r = chash_get(encryption_id_hash, &key, &value);
|
|
if (r == 0) {
|
|
encryption_id_list = value.data;
|
|
}
|
|
}
|
|
|
|
return encryption_id_list;
|
|
}
|
|
|
|
void mailprivacy_smime_encryption_id_list_clear(struct mailprivacy * privacy,
|
|
mailmessage * msg)
|
|
{
|
|
clist * encryption_id_list;
|
|
clistiter * iter;
|
|
|
|
LOCK();
|
|
encryption_id_list = get_list(privacy, msg);
|
|
if (encryption_id_list != NULL) {
|
|
chashdatum key;
|
|
|
|
for(iter = clist_begin(encryption_id_list) ;
|
|
iter != NULL ; iter = clist_next(iter)) {
|
|
char * str;
|
|
|
|
str = clist_content(iter);
|
|
free(str);
|
|
}
|
|
clist_free(encryption_id_list);
|
|
|
|
key.data = &msg;
|
|
key.len = sizeof(msg);
|
|
chash_delete(encryption_id_hash, &key, NULL);
|
|
|
|
if (chash_count(encryption_id_hash) == 0) {
|
|
chash_free(encryption_id_hash);
|
|
encryption_id_hash = NULL;
|
|
}
|
|
}
|
|
UNLOCK();
|
|
}
|
|
|
|
clist * mailprivacy_smime_encryption_id_list(struct mailprivacy * privacy,
|
|
mailmessage * msg)
|
|
{
|
|
clist * encryption_id_list;
|
|
|
|
LOCK();
|
|
encryption_id_list = get_list(privacy, msg);
|
|
UNLOCK();
|
|
|
|
return encryption_id_list;
|
|
}
|
|
|
|
static int mailprivacy_smime_add_encryption_id(struct mailprivacy * privacy,
|
|
mailmessage * msg, char * encryption_id)
|
|
{
|
|
clist * encryption_id_list;
|
|
int r;
|
|
int res;
|
|
|
|
LOCK();
|
|
|
|
res = -1;
|
|
|
|
encryption_id_list = get_list(privacy, msg);
|
|
if (encryption_id_list == NULL) {
|
|
if (encryption_id_hash == NULL)
|
|
encryption_id_hash = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYKEY);
|
|
|
|
if (encryption_id_hash != NULL) {
|
|
encryption_id_list = clist_new();
|
|
if (encryption_id_list != NULL) {
|
|
chashdatum key;
|
|
chashdatum value;
|
|
|
|
key.data = &msg;
|
|
key.len = sizeof(msg);
|
|
value.data = encryption_id_list;
|
|
value.len = 0;
|
|
r = chash_set(encryption_id_hash, &key, &value, NULL);
|
|
if (r < 0)
|
|
clist_free(encryption_id_list);
|
|
}
|
|
}
|
|
}
|
|
|
|
encryption_id_list = get_list(privacy, msg);
|
|
if (encryption_id_list != NULL) {
|
|
char * str;
|
|
|
|
str = strdup(encryption_id);
|
|
if (str != NULL) {
|
|
r = clist_append(encryption_id_list, str);
|
|
if (r < 0) {
|
|
free(str);
|
|
}
|
|
else {
|
|
res = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
UNLOCK();
|
|
|
|
return res;
|
|
}
|
|
|
|
static chash * passphrase_hash = NULL;
|
|
|
|
int mailprivacy_smime_set_encryption_id(struct mailprivacy * privacy,
|
|
char * user_id, char * passphrase)
|
|
{
|
|
chashdatum key;
|
|
chashdatum value;
|
|
int r;
|
|
char buf[MAX_EMAIL_SIZE];
|
|
char * n;
|
|
|
|
strncpy(buf, user_id, sizeof(buf));
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
for(n = buf ; * n != '\0' ; n ++)
|
|
* n = toupper((unsigned char) * n);
|
|
|
|
if (passphrase_hash == NULL) {
|
|
passphrase_hash = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYALL);
|
|
if (passphrase_hash == NULL)
|
|
return MAIL_ERROR_MEMORY;
|
|
}
|
|
|
|
key.data = buf;
|
|
key.len = (unsigned int) strlen(buf) + 1;
|
|
value.data = passphrase;
|
|
value.len = (unsigned int) strlen(passphrase) + 1;
|
|
|
|
r = chash_set(passphrase_hash, &key, &value, NULL);
|
|
if (r < 0) {
|
|
return MAIL_ERROR_MEMORY;
|
|
}
|
|
|
|
return MAIL_NO_ERROR;
|
|
}
|
|
|
|
static char * get_passphrase(struct mailprivacy * privacy,
|
|
char * user_id)
|
|
{
|
|
chashdatum key;
|
|
chashdatum value;
|
|
int r;
|
|
char * passphrase;
|
|
char buf[MAX_EMAIL_SIZE];
|
|
char * n;
|
|
|
|
strncpy(buf, user_id, sizeof(buf));
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
for(n = buf ; * n != '\0' ; n ++)
|
|
* n = toupper((unsigned char) * n);
|
|
|
|
if (passphrase_hash == NULL)
|
|
return NULL;
|
|
|
|
key.data = buf;
|
|
key.len = (unsigned int) strlen(buf) + 1;
|
|
|
|
r = chash_get(passphrase_hash, &key, &value);
|
|
if (r < 0)
|
|
return NULL;
|
|
|
|
passphrase = strdup(value.data);
|
|
|
|
return passphrase;
|
|
}
|