libetpan/src/engine/mailprivacy_smime.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;
}