You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

3689 lines
107 KiB

// This file is under GNU General Public License 3.0
// see LICENSE.txt
#include "pEp_internal.h"
#include "message_api.h"
#include "platform.h"
#include "mime.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#ifndef _MIN
#define _MIN(A, B) ((B) > (A) ? (A) : (B))
#endif
#ifndef _MAX
#define _MAX(A, B) ((B) > (A) ? (B) : (A))
#endif
// These are globals used in generating message IDs and should only be
// computed once, as they're either really constants or OS-dependent
int _pEp_rand_max_bits;
double _pEp_log2_36;
static bool is_a_pEpmessage(const message *msg)
{
for (stringpair_list_t *i = msg->opt_fields; i && i->value ; i=i->next) {
if (strcasecmp(i->value->key, "X-pEp-Version") == 0)
return true;
}
return false;
}
static bool is_wrapper(message* src)
{
bool retval = false;
if (src) {
unsigned char pepstr[] = PEP_SUBJ_STRING;
if (is_a_pEpmessage(src) || (src->shortmsg == NULL || strcmp(src->shortmsg, "pEp") == 0 ||
_unsigned_signed_strcmp(pepstr, src->shortmsg, PEP_SUBJ_BYTELEN) == 0) ||
(strcmp(src->shortmsg, "p=p") == 0)) {
char* plaintext = src->longmsg;
if (plaintext) {
const char *line_end = strchr(plaintext, '\n');
if (line_end != NULL) {
size_t n = line_end - plaintext;
char* copycat = calloc(n + 1, 1);
if (copycat) {
strlcpy(copycat, plaintext, n+1);
if (strstr(copycat, PEP_MSG_WRAP_KEY) && strstr(copycat, "OUTER"))
retval = true;
free(copycat);
}
}
}
}
}
return retval;
}
/*
* static stringpair_t* search_optfields(const message* msg, const char* key) {
* if (msg && key) {
* stringpair_list_t* opt_fields = msg->opt_fields;
*
* const stringpair_list_t* curr;
*
* for (curr = opt_fields; curr && curr->value; curr = curr->next) {
* if (curr->value->key) {
* if (strcasecmp(curr->value->key, key) == 0)
* return curr->value;
* }
* }
* }
* return NULL;
* }
*/
static char * keylist_to_string(const stringlist_t *keylist)
{
if (keylist) {
size_t size = stringlist_length(keylist);
const stringlist_t *_kl;
for (_kl = keylist; _kl && _kl->value; _kl = _kl->next) {
size += strlen(_kl->value);
}
char *result = calloc(size, 1);
if (result == NULL)
return NULL;
char *_r = result;
for (_kl = keylist; _kl && _kl->value; _kl = _kl->next) {
_r = stpcpy(_r, _kl->value);
if (_kl->next && _kl->next->value)
_r = stpcpy(_r, ",");
}
return result;
}
else {
return NULL;
}
}
static const char * rating_to_string(PEP_rating rating)
{
switch (rating) {
case PEP_rating_cannot_decrypt:
return "cannot_decrypt";
case PEP_rating_have_no_key:
return "have_no_key";
case PEP_rating_unencrypted:
return "unencrypted";
case PEP_rating_unencrypted_for_some:
return "unencrypted_for_some";
case PEP_rating_unreliable:
return "unreliable";
case PEP_rating_reliable:
return "reliable";
case PEP_rating_trusted:
return "trusted";
case PEP_rating_trusted_and_anonymized:
return "trusted_and_anonymized";
case PEP_rating_fully_anonymous:
return "fully_anonymous";
case PEP_rating_mistrust:
return "mistrust";
case PEP_rating_b0rken:
return "b0rken";
case PEP_rating_under_attack:
return "under_attack";
default:
return "undefined";
}
}
void add_opt_field(message *msg, const char *name, const char *value)
{
assert(msg && name && value);
if (msg && name && value) {
stringpair_t *pair = new_stringpair(name, value);
if (pair == NULL)
return;
stringpair_list_t *field = stringpair_list_add(msg->opt_fields, pair);
if (field == NULL)
{
free_stringpair(pair);
return;
}
if (msg->opt_fields == NULL)
msg->opt_fields = field;
}
}
void replace_opt_field(message *msg, const char *name, const char *value)
{
assert(msg && name && value);
if (msg && name && value) {
stringpair_list_t* opt_fields = msg->opt_fields;
stringpair_t* pair = NULL;
if (opt_fields) {
while (opt_fields) {
pair = opt_fields->value;
if (pair && (strcmp(name, pair->key) == 0))
break;
pair = NULL;
opt_fields = opt_fields->next;
}
}
if (pair) {
free(pair->value);
pair->value = strdup(value);
}
else {
add_opt_field(msg, name, value);
}
}
}
static void decorate_message(
message *msg,
PEP_rating rating,
stringlist_t *keylist,
bool add_version
)
{
assert(msg);
if (add_version)
replace_opt_field(msg, "X-pEp-Version", PEP_VERSION);
if (rating != PEP_rating_undefined)
replace_opt_field(msg, "X-EncStatus", rating_to_string(rating));
if (keylist) {
char *_keylist = keylist_to_string(keylist);
replace_opt_field(msg, "X-KeyList", _keylist);
free(_keylist);
}
}
static char* _get_resource_ptr_noown(char* uri) {
char* uri_delim = strstr(uri, "://");
if (!uri_delim)
return uri;
else
return uri + 3;
}
// static bool is_file_uri(char* str) {
// return(strncmp(str, "file://", 7) == 0);
// }
static bool is_cid_uri(const char* str) {
return(strncmp(str, "cid://", 6) == 0);
}
static bool string_equality(const char *s1, const char *s2)
{
if (s1 == NULL || s2 == NULL)
return false;
assert(s1 && s2);
return strcmp(s1, s2) == 0;
}
static bool is_mime_type(const bloblist_t *bl, const char *mt)
{
assert(mt);
return bl && string_equality(bl->mime_type, mt);
}
//
// This function presumes the file ending is a proper substring of the
// filename (i.e. if bl->filename is "a.pgp" and fe is ".pgp", it will
// return true, but if bl->filename is ".pgp" and fe is ".pgp", it will
// return false. This is desired behaviour.
//
static bool is_fileending(const bloblist_t *bl, const char *fe)
{
assert(fe);
if (bl == NULL || bl->filename == NULL || fe == NULL || is_cid_uri(bl->filename))
return false;
assert(bl && bl->filename);
size_t fe_len = strlen(fe);
size_t fn_len = strlen(bl->filename);
if (fn_len <= fe_len)
return false;
assert(fn_len > fe_len);
return strcmp(bl->filename + (fn_len - fe_len), fe) == 0;
}
static char * encapsulate_message_wrap_info(const char *msg_wrap_info, const char *longmsg)
{
assert(msg_wrap_info);
if (!msg_wrap_info) {
if (!longmsg)
return NULL;
else {
char *result = strdup(longmsg);
assert(result);
return result;
}
}
if (longmsg == NULL)
longmsg = "";
const char * const newlines = "\n\n";
const size_t NL_LEN = 2;
const size_t bufsize = PEP_MSG_WRAP_KEY_LEN + strlen(msg_wrap_info) + NL_LEN + strlen(longmsg) + 1;
char * ptext = calloc(bufsize, 1);
assert(ptext);
if (ptext == NULL)
return NULL;
strlcpy(ptext, PEP_MSG_WRAP_KEY, bufsize);
strlcat(ptext, msg_wrap_info, bufsize);
strlcat(ptext, newlines, bufsize);
strlcat(ptext, longmsg, bufsize);
return ptext;
}
static char * combine_short_and_long(const char *shortmsg, const char *longmsg)
{
assert(shortmsg);
unsigned char pepstr[] = PEP_SUBJ_STRING;
assert(strcmp(shortmsg, "pEp") != 0 && _unsigned_signed_strcmp(pepstr, shortmsg, PEP_SUBJ_BYTELEN) != 0);
if (!shortmsg || strcmp(shortmsg, "pEp") == 0 ||
_unsigned_signed_strcmp(pepstr, shortmsg, PEP_SUBJ_BYTELEN) == 0) {
if (!longmsg) {
return NULL;
}
else {
char *result = strdup(longmsg);
assert(result);
return result;
}
}
if (longmsg == NULL)
longmsg = "";
const char * const newlines = "\n\n";
const size_t NL_LEN = 2;
const size_t bufsize = PEP_SUBJ_KEY_LEN + strlen(shortmsg) + NL_LEN + strlen(longmsg) + 1;
char * ptext = calloc(bufsize, 1);
assert(ptext);
if (ptext == NULL)
return NULL;
strlcpy(ptext, PEP_SUBJ_KEY, bufsize);
strlcat(ptext, shortmsg, bufsize);
strlcat(ptext, newlines, bufsize);
strlcat(ptext, longmsg, bufsize);
return ptext;
}
static PEP_STATUS replace_subject(message* msg) {
unsigned char pepstr[] = PEP_SUBJ_STRING;
if (msg->shortmsg && *(msg->shortmsg) != '\0') {
char* longmsg = combine_short_and_long(msg->shortmsg, msg->longmsg);
if (!longmsg)
return PEP_OUT_OF_MEMORY;
else {
free(msg->longmsg);
msg->longmsg = longmsg;
}
}
free(msg->shortmsg);
#ifdef WIN32
msg->shortmsg = strdup("pEp");
#else
msg->shortmsg = strdup((char*)pepstr);
#endif
if (!msg->shortmsg)
return PEP_OUT_OF_MEMORY;
return PEP_STATUS_OK;
}
unsigned long long get_bitmask(int num_bits) {
if (num_bits <= 0)
return 0;
unsigned long long bitmask = 0;
int i;
for (i = 1; i < num_bits; i++) {
bitmask = bitmask << 1;
bitmask |= 1;
}
return bitmask;
}
static char* get_base_36_rep(unsigned long long value, int num_sig_bits) {
int bufsize = ceil(num_sig_bits / _pEp_log2_36) + 1;
// based on
// https://en.wikipedia.org/wiki/Base36#C_implementation
// ok, we supposedly have a 64-bit kinda sorta random blob
const char base_36_symbols[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char* retbuf = calloc(bufsize, 1);
int i = bufsize - 1; // (end index)
while (i > 0) {
retbuf[--i] = base_36_symbols[value % 36];
value /= 36;
}
return retbuf;
}
static char* message_id_prand_part(void) {
// RAND modulus
int num_bits = _pEp_rand_max_bits;
if (num_bits < 0)
return NULL;
const int DESIRED_BITS = 64;
num_bits = _MIN(num_bits, DESIRED_BITS);
int i;
// at least 64 bits
unsigned long long bitmask = get_bitmask(num_bits);
unsigned long long output_value = 0;
i = DESIRED_BITS;
int bitshift = 0;
while (i > 0) {
int randval = rand();
unsigned long long temp_val = randval & bitmask;
output_value |= temp_val;
i -= num_bits;
bitshift = _MIN(num_bits, i);
output_value <<= bitshift;
bitmask = get_bitmask(bitshift);
}
return get_base_36_rep(output_value, DESIRED_BITS);
}
static PEP_STATUS generate_message_id(message* msg) {
if (!msg || !msg->from || !msg->from->address)
return PEP_ILLEGAL_VALUE;
char* time_prefix = NULL;
char* random_id = NULL;
char* retval = NULL;
size_t buf_len = 2; // NUL + @
char* from_addr = msg->from->address;
char* domain_ptr = strstr(from_addr, "@");
if (!domain_ptr || *(domain_ptr + 1) == '\0')
domain_ptr = "localhost";
else
domain_ptr++;
buf_len += strlen(domain_ptr);
if (msg->id)
free(msg->id);
msg->id = NULL;
time_t curr_time = time(NULL);
time_prefix = get_base_36_rep(curr_time, ceil(log2(curr_time)));
if (!time_prefix)
goto enomem;
buf_len += strlen(time_prefix);
random_id = message_id_prand_part();
if (!random_id)
goto enomem;
buf_len += strlen(random_id);
// make a new uuid - depending on rand() impl, time precision, etc,
// we may still not be unique. We'd better make sure. So.
char new_uuid[37];
pEpUUID uuid;
uuid_generate_random(uuid);
uuid_unparse_upper(uuid, new_uuid);
buf_len += strlen(new_uuid);
buf_len += 6; // "pEp" and 3 '.' chars
retval = calloc(buf_len, 1);
if (!retval)
goto enomem;
strlcpy(retval, "pEp.", buf_len);
strlcat(retval, time_prefix, buf_len);
strlcat(retval, ".", buf_len);
strlcat(retval, random_id, buf_len);
strlcat(retval, ".", buf_len);
strlcat(retval, new_uuid, buf_len);
strlcat(retval, "@", buf_len);
strlcat(retval, domain_ptr, buf_len);
msg->id = retval;
free(time_prefix);
free(random_id);
return PEP_STATUS_OK;
enomem:
free(time_prefix);
free(random_id);
return PEP_OUT_OF_MEMORY;
}
/*
WARNING: For the moment, this only works for the first line of decrypted
plaintext because we don't need more. IF WE DO, THIS MUST BE EXPANDED, or
we need a delineated section to parse separately
Does case-insensitive compare of keys, so sending in a lower-cased
string constant saves a bit of computation
*/
static PEP_STATUS get_data_from_encapsulated_line(const char* plaintext, const char* key,
const size_t keylen, char** data,
char** modified_msg) {
char* _data = NULL;
char* _modified = NULL;
if (strncasecmp(plaintext, key, keylen) == 0) {
const char *line_end = strchr(plaintext, '\n');
if (line_end == NULL) {
_data = strdup(plaintext + keylen);
assert(_data);
if (_data == NULL)
return PEP_OUT_OF_MEMORY;
}
else {
size_t n = line_end - plaintext;
if (*(line_end - 1) == '\r')
_data = strndup(plaintext + keylen, n - (keylen + 1));
else
_data = strndup(plaintext + keylen, n - keylen);
assert(_data);
if (_data == NULL)
return PEP_OUT_OF_MEMORY;
while (*(plaintext + n) && (*(plaintext + n) == '\n' || *(plaintext + n) == '\r'))
++n;
if (*(plaintext + n)) {
_modified = strdup(plaintext + n);
assert(_modified);
if (_modified == NULL)
return PEP_OUT_OF_MEMORY;
}
}
}
*data = _data;
*modified_msg = _modified;
return PEP_STATUS_OK;
}
static int separate_short_and_long(const char *src, char **shortmsg, char** msg_wrap_info, char **longmsg)
{
char *_shortmsg = NULL;
char *_msg_wrap_info = NULL;
char *_longmsg = NULL;
assert(src);
assert(shortmsg);
assert(msg_wrap_info);
assert(longmsg);
if (src == NULL || shortmsg == NULL || msg_wrap_info == NULL || longmsg == NULL)
return -1;
*shortmsg = NULL;
*longmsg = NULL;
*msg_wrap_info = NULL;
// We generated the input here. If we ever need more than one header value to be
// encapsulated and hidden in the encrypted text, we will have to modify this.
// As is, we're either doing this with a version 1.0 client, in which case
// the only encapsulated header value is subject, or 2.0+, in which the
// message wrap info is the only encapsulated header value. If we need this
// to be more complex, we're going to have to do something more elegant
// and efficient.
PEP_STATUS status = get_data_from_encapsulated_line(src, PEP_SUBJ_KEY_LC,
PEP_SUBJ_KEY_LEN,
&_shortmsg, &_longmsg);
if (_shortmsg) {
if (status == PEP_STATUS_OK)
*shortmsg = _shortmsg;
else
goto enomem;
}
else {
status = get_data_from_encapsulated_line(src, PEP_MSG_WRAP_KEY_LC,
PEP_MSG_WRAP_KEY_LEN,
&_msg_wrap_info, &_longmsg);
if (_msg_wrap_info) {
if (status == PEP_STATUS_OK)
*msg_wrap_info = _msg_wrap_info;
else
goto enomem;
}
}
// If there was no secret data hiding in the first line...
if (!_shortmsg && !_msg_wrap_info) {
_longmsg = strdup(src);
assert(_longmsg);
if (_longmsg == NULL)
goto enomem;
}
*longmsg = _longmsg;
return 0;
enomem:
free(_shortmsg);
free(_msg_wrap_info);
free(_longmsg);
return -1;
}
static PEP_STATUS copy_fields(message *dst, const message *src)
{
assert(dst);
assert(src);
if(!(dst && src))
return PEP_ILLEGAL_VALUE;
free_timestamp(dst->sent);
dst->sent = NULL;
if (src->sent) {
dst->sent = timestamp_dup(src->sent);
if (dst->sent == NULL)
return PEP_OUT_OF_MEMORY;
}
free_timestamp(dst->recv);
dst->recv = NULL;
if (src->recv) {
dst->recv = timestamp_dup(src->recv);
if (dst->recv == NULL)
return PEP_OUT_OF_MEMORY;
}
free_identity(dst->from);
dst->from = NULL;
if (src->from) {
dst->from = identity_dup(src->from);
if (dst->from == NULL)
return PEP_OUT_OF_MEMORY;
}
free_identity_list(dst->to);
dst->to = NULL;
if (src->to && src->to->ident) {
dst->to = identity_list_dup(src->to);
if (dst->to == NULL)
return PEP_OUT_OF_MEMORY;
}
free_identity(dst->recv_by);
dst->recv_by = NULL;
if (src->recv_by) {
dst->recv_by = identity_dup(src->recv_by);
if (dst->recv_by == NULL)
return PEP_OUT_OF_MEMORY;
}
free_identity_list(dst->cc);
dst->cc = NULL;
if (src->cc && src->cc->ident) {
dst->cc = identity_list_dup(src->cc);
if (dst->cc == NULL)
return PEP_OUT_OF_MEMORY;
}
free_identity_list(dst->bcc);
dst->bcc = NULL;
if (src->bcc && src->bcc->ident) {
dst->bcc = identity_list_dup(src->bcc);
if (dst->bcc == NULL)
return PEP_OUT_OF_MEMORY;
}
free_identity_list(dst->reply_to);
dst->reply_to = NULL;
if (src->reply_to && src->reply_to->ident) {
dst->reply_to = identity_list_dup(src->reply_to);
if (dst->reply_to == NULL)
return PEP_OUT_OF_MEMORY;
}
free_stringlist(dst->in_reply_to);
dst->in_reply_to = NULL;
if (src->in_reply_to && src->in_reply_to->value) {
dst->in_reply_to = stringlist_dup(src->in_reply_to);
if (dst->in_reply_to == NULL)
return PEP_OUT_OF_MEMORY;
}
free_stringlist(dst->references);
dst->references = NULL;
if (src->references) {
dst->references = stringlist_dup(src->references);
if (dst->references == NULL)
return PEP_OUT_OF_MEMORY;
}
free_stringlist(dst->keywords);
dst->keywords = NULL;
if (src->keywords && src->keywords->value) {
dst->keywords = stringlist_dup(src->keywords);
if (dst->keywords == NULL)
return PEP_OUT_OF_MEMORY;
}
free(dst->comments);
dst->comments = NULL;
if (src->comments) {
dst->comments = strdup(src->comments);
assert(dst->comments);
if (dst->comments == NULL)
return PEP_OUT_OF_MEMORY;
}
free_stringpair_list(dst->opt_fields);
dst->opt_fields = NULL;
if (src->opt_fields) {
dst->opt_fields = stringpair_list_dup(src->opt_fields);
if (dst->opt_fields == NULL)
return PEP_OUT_OF_MEMORY;
}
return PEP_STATUS_OK;
}
// FIXME: error mem leakage
static message* extract_minimal_envelope(const message* src,
PEP_msg_direction direct) {
message* envelope = new_message(direct);
if (!envelope)
return NULL;
envelope->shortmsg = _pep_subj_copy();
if (!envelope->shortmsg)
goto enomem;
if (src->from) {
envelope->from = identity_dup(src->from);
if (!envelope->from)
goto enomem;
}
if (src->to) {
envelope->to = identity_list_dup(src->to);
if (!envelope->to)
goto enomem;
}
if (src->cc) {
envelope->cc = identity_list_dup(src->cc);
if (!envelope->cc)
goto enomem;
}
if (src->bcc) {
envelope->bcc = identity_list_dup(src->bcc);
if (!envelope->bcc)
goto enomem;
}
// For Outlook Force-Encryption
// const char* pull_keys[] = {"pEp-auto-consume",
// "pEp-force-protection",
// "X-pEp-Never-Unsecure"};
// int pull_keys_len = 3; // UPDATE WHEN MORE ADDED ABOVE
//
// int i = 0;
// stringpair_t* opt_add = NULL;
// for( ; i < pull_keys_len; i++) {
// opt_add = search_optfields(src, pull_keys[i]);
// stringpair_list_t* add_ptr = NULL;
// if (opt_add) {
// add_ptr = stringpair_list_add(src->opt_fields, stringpair_dup(opt_add));
// if (!add_ptr)
// goto enomem;
// }
// opt_add = NULL;
// add_ptr = NULL;
// }
envelope->enc_format = src->enc_format;
return envelope;
enomem:
free(envelope);
return NULL;
}
static message * clone_to_empty_message(const message * src)
{
PEP_STATUS status;
message * msg = NULL;
assert(src);
if (src == NULL)
return NULL;
msg = calloc(1, sizeof(message));
assert(msg);
if (msg == NULL)
goto enomem;
msg->dir = src->dir;
status = copy_fields(msg, src);
if (status != PEP_STATUS_OK)
goto enomem;
return msg;
enomem:
free_message(msg);
return NULL;
}
static message* wrap_message_as_attachment(message* envelope,
message* attachment, bool keep_orig_subject) {
if (!attachment)
return NULL;
message* _envelope = envelope;
PEP_STATUS status = PEP_STATUS_OK;
replace_opt_field(attachment, "X-pEp-Version", PEP_VERSION);
if (!_envelope) {
_envelope = extract_minimal_envelope(attachment, PEP_dir_outgoing);
status = generate_message_id(_envelope);
if (status != PEP_STATUS_OK)
goto enomem;
attachment->longmsg = encapsulate_message_wrap_info("INNER", attachment->longmsg);
_envelope->longmsg = encapsulate_message_wrap_info("OUTER", _envelope->longmsg);
}
else {
_envelope->longmsg = encapsulate_message_wrap_info("TRANSPORT", _envelope->longmsg);
}
if (!attachment->id || attachment->id[0] == '\0') {
free(attachment->id);
if (!_envelope->id) {
status = generate_message_id(_envelope);
if (status != PEP_STATUS_OK)
goto enomem;
}
attachment->id = strdup(_envelope->id);
}
char* message_text = NULL;
/* prevent introduction of pEp in inner message */
if (!attachment->shortmsg) {
attachment->shortmsg = strdup("");
if (!attachment->shortmsg)
goto enomem;
}
/* Turn message into a MIME-blob */
status = _mime_encode_message_internal(attachment, false, &message_text, true);
if (status != PEP_STATUS_OK)
goto enomem;
size_t message_len = strlen(message_text);
bloblist_t* message_blob = new_bloblist(message_text, message_len,
"message/rfc822", NULL);
_envelope->attachments = message_blob;
if (keep_orig_subject && attachment->shortmsg)
_envelope->shortmsg = strdup(attachment->shortmsg);
return _envelope;
enomem:
if (!envelope) {
free_message(_envelope);
}
return NULL;
}
static PEP_STATUS update_identity_recip_list(PEP_SESSION session,
identity_list* list) {
PEP_STATUS status = PEP_STATUS_OK;
if (!session)
return PEP_UNKNOWN_ERROR;
identity_list* id_list_ptr = NULL;
for (id_list_ptr = list; id_list_ptr; id_list_ptr = id_list_ptr->next) {
pEp_identity* curr_identity = id_list_ptr->ident;
if (curr_identity) {
if (!is_me(session, curr_identity))
status = update_identity(session, curr_identity);
else
status = myself(session, curr_identity);
if (status == PEP_ILLEGAL_VALUE || status == PEP_OUT_OF_MEMORY)
return status;
}
else
break;
}
return PEP_STATUS_OK;
}
static PEP_STATUS encrypt_PGP_MIME(
PEP_SESSION session,
const message *src,
stringlist_t *keys,
message *dst,
PEP_encrypt_flags_t flags
)
{
PEP_STATUS status = PEP_STATUS_OK;
bool free_ptext = false;
char *ptext = NULL;
char *ctext = NULL;
char *mimetext = NULL;
size_t csize;
assert(dst->longmsg == NULL);
dst->enc_format = PEP_enc_PGP_MIME;
if (src->shortmsg)
dst->shortmsg = strdup(src->shortmsg);
message *_src = calloc(1, sizeof(message));
assert(_src);
if (_src == NULL)
goto enomem;
// _src->longmsg = ptext;
_src->longmsg = src->longmsg;
_src->longmsg_formatted = src->longmsg_formatted;
_src->attachments = src->attachments;
_src->enc_format = PEP_enc_none;
bool mime_encode = !is_wrapper(_src);
status = _mime_encode_message_internal(_src, true, &mimetext, mime_encode);
assert(status == PEP_STATUS_OK);
if (status != PEP_STATUS_OK)
goto pep_error;
if (free_ptext){
free(ptext);
free_ptext=0;
}
free(_src);
_src = NULL;
assert(mimetext);
if (mimetext == NULL)
goto pep_error;
if (flags & PEP_encrypt_flag_force_unsigned)
status = encrypt_only(session, keys, mimetext, strlen(mimetext),
&ctext, &csize);
else
status = encrypt_and_sign(session, keys, mimetext, strlen(mimetext),
&ctext, &csize);
free(mimetext);
if (ctext == NULL)
goto pep_error;
dst->longmsg = strdup("this message was encrypted with p≡p "
"https://pEp-project.org");
assert(dst->longmsg);
if (dst->longmsg == NULL)
goto enomem;
char *v = strdup("Version: 1");
assert(v);
if (v == NULL)
goto enomem;
bloblist_t *_a = new_bloblist(v, strlen(v), "application/pgp-encrypted", NULL);
if (_a == NULL)
goto enomem;
dst->attachments = _a;
_a = bloblist_add(_a, ctext, csize, "application/octet-stream",
"file://msg.asc");
if (_a == NULL)
goto enomem;
return PEP_STATUS_OK;
enomem:
status = PEP_OUT_OF_MEMORY;
pep_error:
if (free_ptext)
free(ptext);
free(_src);
free(ctext);
return status;
}
static PEP_rating _rating(PEP_comm_type ct, PEP_rating rating)
{
if (ct == PEP_ct_unknown)
return PEP_rating_undefined;
else if (ct == PEP_ct_key_not_found)
return PEP_rating_have_no_key;
else if (ct == PEP_ct_compromized)
return PEP_rating_under_attack;
else if (ct == PEP_ct_mistrusted)
return PEP_rating_mistrust;
if (rating == PEP_rating_unencrypted_for_some)
return PEP_rating_unencrypted_for_some;
if (ct == PEP_ct_no_encryption || ct == PEP_ct_no_encrypted_channel ||
ct == PEP_ct_my_key_not_included) {
if (rating > PEP_rating_unencrypted_for_some)
return PEP_rating_unencrypted_for_some;
else
return PEP_rating_unencrypted;
}
if (rating == PEP_rating_unencrypted)
return PEP_rating_unencrypted_for_some;
if (ct >= PEP_ct_confirmed_enc_anon)
return PEP_rating_trusted_and_anonymized;
else if (ct >= PEP_ct_strong_encryption)
return PEP_rating_trusted;
else if (ct >= PEP_ct_strong_but_unconfirmed && ct < PEP_ct_confirmed)
return PEP_rating_reliable;
else
return PEP_rating_unreliable;
}
static bool is_encrypted_attachment(const bloblist_t *blob)
{
assert(blob);
if (blob == NULL || blob->filename == NULL || is_cid_uri(blob->filename))
return false;
char *ext = strrchr(blob->filename, '.');
if (ext == NULL)
return false;
if (strcmp(blob->mime_type, "application/octet-stream") == 0) {
if (strcmp(ext, ".pgp") == 0 || strcmp(ext, ".gpg") == 0 ||
strcmp(ext, ".asc") == 0)
return true;
}
else if (strcmp(blob->mime_type, "text/plain") == 0) {
if (strcmp(ext, ".asc") == 0)
return true;
}
return false;
}
static bool is_encrypted_html_attachment(const bloblist_t *blob)
{
assert(blob);
assert(blob->filename);
if (blob == NULL || blob->filename == NULL || is_cid_uri(blob->filename))
return false;
const char* bare_filename_ptr = _get_resource_ptr_noown(blob->filename);
if (strncmp(bare_filename_ptr, "PGPexch.htm.", 12) == 0) {
if (strcmp(bare_filename_ptr + 11, ".pgp") == 0 ||
strcmp(bare_filename_ptr + 11, ".asc") == 0)
return true;
}
return false;
}
static char * without_double_ending(const char *filename)
{
assert(filename);
if (filename == NULL || is_cid_uri(filename))
return NULL;
char *ext = strrchr(filename, '.');
if (ext == NULL)
return NULL;
char *result = strndup(filename, ext - filename);
assert(result);
return result;
}
static PEP_rating decrypt_rating(PEP_STATUS status)
{
switch (status) {
case PEP_UNENCRYPTED:
case PEP_VERIFIED:
case PEP_VERIFY_NO_KEY:
case PEP_VERIFIED_AND_TRUSTED:
return PEP_rating_unencrypted;
case PEP_DECRYPTED:
case PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH:
return PEP_rating_unreliable;
case PEP_DECRYPTED_AND_VERIFIED:
return PEP_rating_reliable;
case PEP_DECRYPT_NO_KEY:
return PEP_rating_have_no_key;
case PEP_DECRYPT_WRONG_FORMAT:
case PEP_CANNOT_DECRYPT_UNKNOWN:
return PEP_rating_cannot_decrypt;
default:
return PEP_rating_undefined;
}
}
static PEP_rating key_rating(PEP_SESSION session, const char *fpr)
{
assert(session);
assert(fpr);
if (session == NULL || fpr == NULL)
return PEP_rating_undefined;
PEP_comm_type bare_comm_type = PEP_ct_unknown;
PEP_comm_type resulting_comm_type = PEP_ct_unknown;
PEP_STATUS status = get_key_rating(session, fpr, &bare_comm_type);
if (status != PEP_STATUS_OK)
return PEP_rating_undefined;
PEP_comm_type least_comm_type = PEP_ct_unknown;
least_trust(session, fpr, &least_comm_type);
if (least_comm_type == PEP_ct_unknown) {
resulting_comm_type = bare_comm_type;
} else if (least_comm_type < PEP_ct_strong_but_unconfirmed ||
bare_comm_type < PEP_ct_strong_but_unconfirmed) {
// take minimum if anything bad
resulting_comm_type = least_comm_type < bare_comm_type ?
least_comm_type :
bare_comm_type;
} else {
resulting_comm_type = least_comm_type;
}
return _rating(resulting_comm_type, PEP_rating_undefined);
}
static PEP_rating worst_rating(PEP_rating rating1, PEP_rating rating2) {
return ((rating1 < rating2) ? rating1 : rating2);
}
static PEP_rating keylist_rating(PEP_SESSION session, stringlist_t *keylist, char* sender_fpr, PEP_rating sender_rating)
{
PEP_rating rating = sender_rating;
assert(keylist && keylist->value);
if (keylist == NULL || keylist->value == NULL)
return PEP_rating_undefined;
stringlist_t *_kl;
for (_kl = keylist; _kl && _kl->value; _kl = _kl->next) {
// Ignore own fpr
if(_same_fpr(sender_fpr, strlen(sender_fpr), _kl->value, strlen(_kl->value)))
continue;
PEP_rating _rating_ = key_rating(session, _kl->value);
if (_rating_ <= PEP_rating_mistrust)
return _rating_;
if (_rating_ == PEP_rating_unencrypted)
{
if (rating > PEP_rating_unencrypted_for_some)
rating = worst_rating(rating, PEP_rating_unencrypted_for_some);
}
else
{
rating = worst_rating(rating, _rating_);
}
}
return rating;
}
// Internal function WARNING:
// Only call this on an ident that might have its FPR set from retrieval!
// (or on one without an fpr)
// We do not want myself() setting the fpr here.
static PEP_comm_type _get_comm_type(
PEP_SESSION session,
PEP_comm_type max_comm_type,
pEp_identity *ident
)
{
PEP_STATUS status = PEP_STATUS_OK;
if (max_comm_type == PEP_ct_compromized)
return PEP_ct_compromized;
if (max_comm_type == PEP_ct_mistrusted)
return PEP_ct_mistrusted;
if (!is_me(session, ident))
status = update_identity(session, ident);
else
status = myself(session, ident);
if (status == PEP_STATUS_OK) {
if (ident->comm_type == PEP_ct_compromized)
return PEP_ct_compromized;
else if (ident->comm_type == PEP_ct_mistrusted)
return PEP_ct_mistrusted;
else
return _MIN(max_comm_type, ident->comm_type);
}
else {
return PEP_ct_unknown;
}
}
static void free_bl_entry(bloblist_t *bl)
{
if (bl) {
free(bl->value);
free(bl->mime_type);
free(bl->filename);
free(bl);
}
}
static bool is_key(const bloblist_t *bl)
{
return (// workaround for Apple Mail bugs
(is_mime_type(bl, "application/x-apple-msg-attachment") &&
is_fileending(bl, ".asc")) ||
// as binary, by file name
((bl->mime_type == NULL ||
is_mime_type(bl, "application/octet-stream")) &&
(is_fileending(bl, ".pgp") || is_fileending(bl, ".gpg") ||
is_fileending(bl, ".key") || is_fileending(bl, ".asc"))) ||
// explicit mime type
is_mime_type(bl, "application/pgp-keys") ||
// as text, by file name
(is_mime_type(bl, "text/plain") &&
(is_fileending(bl, ".pgp") || is_fileending(bl, ".gpg") ||
is_fileending(bl, ".key") || is_fileending(bl, ".asc")))
);
}
static void remove_attached_keys(message *msg)
{
if (msg) {
bloblist_t *last = NULL;
for (bloblist_t *bl = msg->attachments; bl && bl->value; ) {
bloblist_t *next = bl->next;
if (is_key(bl)) {
if (last) {
last->next = next;
}
else {
msg->attachments = next;
}
free_bl_entry(bl);
}
else {
last = bl;
}
bl = next;
}
}
}
bool import_attached_keys(
PEP_SESSION session,
const message *msg,
identity_list **private_idents
)
{
assert(session);
assert(msg);
if (session == NULL || msg == NULL)
return false;
bool remove = false;
int i = 0;
for (bloblist_t *bl = msg->attachments; i < MAX_KEYS_TO_IMPORT && bl && bl->value;
bl = bl->next, i++)
{
if (bl && bl->value && bl->size && bl->size < MAX_KEY_SIZE
&& is_key(bl))
{
identity_list *local_private_idents = NULL;
import_key(session, bl->value, bl->size, &local_private_idents);
remove = true;
if (private_idents && *private_idents == NULL && local_private_idents != NULL)
*private_idents = local_private_idents;
else
free_identity_list(local_private_idents);
}
}
return remove;
}
PEP_STATUS _attach_key(PEP_SESSION session, const char* fpr, message *msg)
{
char *keydata = NULL;
size_t size;
PEP_STATUS status = export_key(session, fpr, &keydata, &size);
assert(status == PEP_STATUS_OK);
if (status != PEP_STATUS_OK)
return status;
assert(size);
bloblist_t *bl = bloblist_add(msg->attachments, keydata, size, "application/pgp-keys",
"file://pEpkey.asc");
if (msg->attachments == NULL && bl)
msg->attachments = bl;
return PEP_STATUS_OK;
}
#define ONE_WEEK (7*24*3600)
void attach_own_key(PEP_SESSION session, message *msg)
{
assert(session);
assert(msg);
if (msg->dir == PEP_dir_incoming)
return;
assert(msg->from && msg->from->fpr);
if (msg->from == NULL || msg->from->fpr == NULL)
return;
if(_attach_key(session, msg->from->fpr, msg) != PEP_STATUS_OK)
return;
char *revoked_fpr = NULL;
uint64_t revocation_date = 0;
if(get_revoked(session, msg->from->fpr,
&revoked_fpr, &revocation_date) == PEP_STATUS_OK &&
revoked_fpr != NULL)
{
time_t now = time(NULL);
if (now < (time_t)revocation_date + ONE_WEEK)
{
_attach_key(session, revoked_fpr, msg);
}
}
free(revoked_fpr);
}
PEP_cryptotech determine_encryption_format(message *msg)
{
assert(msg);
if (is_PGP_message_text(msg->longmsg)) {
msg->enc_format = PEP_enc_pieces;
return PEP_crypt_OpenPGP;
}
else if (msg->attachments && msg->attachments->next &&
is_mime_type(msg->attachments, "application/pgp-encrypted") &&
is_PGP_message_text(msg->attachments->next->value)
) {
msg->enc_format = PEP_enc_PGP_MIME;
return PEP_crypt_OpenPGP;
}
else if (msg->attachments && msg->attachments->next &&
is_mime_type(msg->attachments->next, "application/pgp-encrypted") &&
is_PGP_message_text(msg->attachments->value)
) {
msg->enc_format = PEP_enc_PGP_MIME_Outlook1;
return PEP_crypt_OpenPGP;
}
else {
msg->enc_format = PEP_enc_none;
return PEP_crypt_none;
}
}
DYNAMIC_API PEP_STATUS encrypt_message(
PEP_SESSION session,
message *src,
stringlist_t * extra,
message **dst,
PEP_enc_format enc_format,
PEP_encrypt_flags_t flags
)
{
PEP_STATUS status = PEP_STATUS_OK;
message * msg = NULL;
stringlist_t * keys = NULL;
message* _src = src;
assert(session);
assert(src);
assert(dst);
if (!(session && src && dst))
return ADD_TO_LOG(PEP_ILLEGAL_VALUE);
if (src->dir == PEP_dir_incoming)
return ADD_TO_LOG(PEP_ILLEGAL_VALUE);
determine_encryption_format(src);
// TODO: change this for multi-encryption in message format 2.0
if (src->enc_format != PEP_enc_none)
return ADD_TO_LOG(PEP_ILLEGAL_VALUE);
*dst = NULL;
if (src->from && (!src->from->user_id || src->from->user_id[0] == '\0')) {
char* own_id = NULL;
status = get_default_own_userid(session, &own_id);
if (own_id) {
free(src->from->user_id);
src->from->user_id = own_id; // ownership transfer
}
}
status = myself(session, src->from);
if (status != PEP_STATUS_OK)
GOTO(pep_error);
keys = new_stringlist(src->from->fpr);
if (keys == NULL)
goto enomem;
stringlist_t *_k = keys;
if (extra) {
_k = stringlist_append(_k, extra);
if (_k == NULL)
goto enomem;
}
bool dest_keys_found = true;
bool has_pep_user = false;
PEP_comm_type max_comm_type = PEP_ct_pEp;
identity_list * _il;
if (enc_format != PEP_enc_none && (_il = src->bcc) && _il->ident)
{
// BCC limited support:
// - App splits mails with BCC in multiple mails.
// - Each email is encrypted separately
if(_il->next || (src->to && src->to->ident) || (src->cc && src->cc->ident))
{
// Only one Bcc with no other recipient allowed for now
return PEP_ILLEGAL_VALUE;
}
PEP_STATUS _status = PEP_STATUS_OK;
if (!is_me(session, _il->ident)) {
_status = update_identity(session, _il->ident);
if (_status == PEP_CANNOT_FIND_IDENTITY) {
_il->ident->comm_type = PEP_ct_key_not_found;
_status = PEP_STATUS_OK;
}
if (!has_pep_user)
is_pep_user(session, _il->ident, &has_pep_user);
}
else
_status = myself(session, _il->ident);
if (_status != PEP_STATUS_OK) {
status = PEP_UNENCRYPTED;
GOTO(pep_error);
}
if (_il->ident->fpr && _il->ident->fpr[0]) {
_k = stringlist_add(_k, _il->ident->fpr);
if (_k == NULL)
goto enomem;
max_comm_type = _get_comm_type(session, max_comm_type,
_il->ident);
}
else {
dest_keys_found = false;
status = PEP_KEY_NOT_FOUND;
}
}
else
{
for (_il = src->to; _il && _il->ident; _il = _il->next) {
PEP_STATUS _status = PEP_STATUS_OK;
if (!is_me(session, _il->ident)) {
_status = update_identity(session, _il->ident);
if (_status == PEP_CANNOT_FIND_IDENTITY) {
_il->ident->comm_type = PEP_ct_key_not_found;
_status = PEP_STATUS_OK;
}
if (!has_pep_user)
is_pep_user(session, _il->ident, &has_pep_user);
}
else
_status = myself(session, _il->ident);
if (_status != PEP_STATUS_OK) {
status = PEP_UNENCRYPTED;
GOTO(pep_error);
}
if (_il->ident->fpr && _il->ident->fpr[0]) {
_k = stringlist_add(_k, _il->ident->fpr);
if (_k == NULL)
goto enomem;
max_comm_type = _get_comm_type(session, max_comm_type,
_il->ident);
}
else {
dest_keys_found = false;
status = PEP_KEY_NOT_FOUND;
}
}
for (_il = src->cc; _il && _il->ident; _il = _il->next) {
PEP_STATUS _status = PEP_STATUS_OK;
if (!is_me(session, _il->ident)) {
_status = update_identity(session, _il->ident);
if (_status == PEP_CANNOT_FIND_IDENTITY) {
_il->ident->comm_type = PEP_ct_key_not_found;
_status = PEP_STATUS_OK;
}
if (!has_pep_user)
is_pep_user(session, _il->ident, &has_pep_user);
}
else
_status = myself(session, _il->ident);
if (_status != PEP_STATUS_OK)
{
status = PEP_UNENCRYPTED;
GOTO(pep_error);
}
if (_il->ident->fpr && _il->ident->fpr[0]) {
_k = stringlist_add(_k, _il->ident->fpr);
if (_k == NULL)
goto enomem;
max_comm_type = _get_comm_type(session, max_comm_type,
_il->ident);
}
else {
dest_keys_found = false;
}
}
}
if (enc_format == PEP_enc_none || !dest_keys_found ||
stringlist_length(keys) == 0 ||
_rating(max_comm_type,
PEP_rating_undefined) < PEP_rating_reliable)
{
free_stringlist(keys);
if ((has_pep_user || !session->passive_mode) &&
!(flags & PEP_encrypt_flag_force_no_attached_key)) {
attach_own_key(session, src);
decorate_message(src, PEP_rating_undefined, NULL, true);
}
return ADD_TO_LOG(PEP_UNENCRYPTED);
}
else {
// FIXME - we need to deal with transport types (via flag)
if ((max_comm_type | PEP_ct_confirmed) == PEP_ct_pEp) {
_src = wrap_message_as_attachment(NULL, src, session->unencrypted_subject);
if (!_src)
goto pep_error;
}
else {
// hide subject
if (!session->unencrypted_subject) {
status = replace_subject(_src);
if (status == PEP_OUT_OF_MEMORY)
goto enomem;
}
}
if (!(flags & PEP_encrypt_flag_force_no_attached_key))
attach_own_key(session, _src);
msg = clone_to_empty_message(_src);
if (msg == NULL)
goto enomem;
switch (enc_format) {
case PEP_enc_PGP_MIME:
case PEP_enc_PEP: // BUG: should be implemented extra
status = encrypt_PGP_MIME(session, _src, keys, msg, flags);
break;
// case PEP_enc_pieces:
// status = encrypt_PGP_in_pieces(session, src, keys, msg, flags);
// break;
/* case PEP_enc_PEP:
// TODO: implement
NOT_IMPLEMENTED */
default:
assert(0);
status = PEP_ILLEGAL_VALUE;
GOTO(pep_error);
}
if (status == PEP_OUT_OF_MEMORY)
goto enomem;
if (status != PEP_STATUS_OK)
GOTO(pep_error);
}
free_stringlist(keys);
if (msg && msg->shortmsg == NULL) {
msg->shortmsg = strdup("");
assert(msg->shortmsg);
if (msg->shortmsg == NULL)
goto enomem;
}
if (msg) {
decorate_message(msg, PEP_rating_undefined, NULL, true);
if (_src->id) {
msg->id = strdup(_src->id);
assert(msg->id);
if (msg->id == NULL)
goto enomem;
}
}
*dst = msg;
// ??? FIXME: Check to be sure we don't have references btw _src and msg.
// I don't think we do.
if (_src && _src != src)
free_message(_src);
return ADD_TO_LOG(status);
enomem:
status = PEP_OUT_OF_MEMORY;
pep_error:
free_stringlist(keys);
free_message(msg);
if (_src && _src != src)
free_message(_src);
return ADD_TO_LOG(status);
}
DYNAMIC_API PEP_STATUS encrypt_message_for_self(
PEP_SESSION session,
pEp_identity* target_id,
message *src,
message **dst,
PEP_enc_format enc_format,
PEP_encrypt_flags_t flags
)
{
PEP_STATUS status = PEP_STATUS_OK;
message * msg = NULL;
stringlist_t * keys = NULL;
message* _src = src;
assert(session);
assert(src);
assert(dst);
assert(enc_format != PEP_enc_none);
if (!(session && src && dst && enc_format != PEP_enc_none))
return ADD_TO_LOG(PEP_ILLEGAL_VALUE);
if (src->dir == PEP_dir_incoming)
return ADD_TO_LOG(PEP_ILLEGAL_VALUE);
determine_encryption_format(src);
if (src->enc_format != PEP_enc_none)
return ADD_TO_LOG(PEP_ILLEGAL_VALUE);
if (target_id && (!target_id->user_id || target_id->user_id[0] == '\0')) {
char* own_id = NULL;
status = get_default_own_userid(session, &own_id);
if (own_id) {
free(target_id->user_id);
target_id->user_id = own_id; // ownership transfer
}
}
status = myself(session, target_id);
if (status != PEP_STATUS_OK)
GOTO(pep_error);
*dst = NULL;
// PEP_STATUS _status = update_identity(session, target_id);
// if (_status != PEP_STATUS_OK) {
// status = _status;
// goto pep_error;
// }
char* target_fpr = target_id->fpr;
if (!target_fpr)
return PEP_KEY_NOT_FOUND; // FIXME: Error condition
keys = new_stringlist(target_fpr);
/* KG: did we ever do this??? */
if (!(flags & PEP_encrypt_flag_force_no_attached_key))
_attach_key(session, target_fpr, src);
_src = wrap_message_as_attachment(NULL, src, session->unencrypted_subject);
if (!_src)
goto pep_error;
msg = clone_to_empty_message(_src);
if (msg == NULL)
goto enomem;
switch (enc_format) {
case PEP_enc_PGP_MIME:
case PEP_enc_PEP: // BUG: should be implemented extra
status = encrypt_PGP_MIME(session, _src, keys, msg, flags);
break;
default:
assert(0);
status = PEP_ILLEGAL_VALUE;
goto pep_error;
}
if (status == PEP_OUT_OF_MEMORY)
goto enomem;
if (status != PEP_STATUS_OK)
goto pep_error;
if (msg && msg->shortmsg == NULL) {
msg->shortmsg = _pep_subj_copy();
assert(msg->shortmsg);
if (msg->shortmsg == NULL)
goto enomem;
}
if (msg) {
if (_src->id) {
msg->id = strdup(_src->id);
assert(msg->id);
if (msg->id == NULL)
goto enomem;
}
}
*dst = msg;
if (src != _src)
free_message(_src);
return status;
enomem:
status = PEP_OUT_OF_MEMORY;
pep_error:
free_stringlist(keys);
free_message(msg);
if (src != _src)
free_message(_src);
return ADD_TO_LOG(status);
}
// static PEP_STATUS _update_identity_for_incoming_message(
// PEP_SESSION session,
// const message *src
// )
// {
// PEP_STATUS status;
//
// if (src->from && src->from->address) {
// if (!is_me(session, src->from))
// status = update_identity(session, src->from);
// else
// status = myself(session, src->from);
// if (status == PEP_STATUS_OK
// && is_a_pEpmessage(src)
// && src->from->comm_type >= PEP_ct_OpenPGP_unconfirmed
// && src->from->comm_type != PEP_ct_pEp_unconfirmed
// && src->from->comm_type != PEP_ct_pEp)
// {
// src->from->comm_type |= PEP_ct_pEp_unconfirmed;
// status = set_identity(session, src->from);
// }
// return status;
// }
// return PEP_ILLEGAL_VALUE;
// }
static PEP_STATUS _get_detached_signature(message* msg,
bloblist_t** signature_blob) {
bloblist_t* attach_curr = msg->attachments;
*signature_blob = NULL;
while (attach_curr) {
if (strcasecmp(attach_curr->mime_type, "application/pgp-signature") == 0) {
*signature_blob = attach_curr;
break;
}
attach_curr = attach_curr->next;
}
return PEP_STATUS_OK;
}
static PEP_STATUS _get_signed_text(const char* ptext, const size_t psize,
char** stext, size_t* ssize) {
char* signed_boundary = NULL;
char* signpost = strstr(ptext, "Content-Type: multipart/signed");
*ssize = 0;
*stext = NULL;
if (!signpost)
return PEP_UNKNOWN_ERROR;
char* curr_line = signpost;
// const char* end_text = ptext + psize;
const char* boundary_key = "boundary=";
const size_t BOUNDARY_KEY_SIZE = 9;
char* start_boundary = strstr(curr_line, boundary_key);
if (!start_boundary)
return PEP_UNKNOWN_ERROR;
start_boundary += BOUNDARY_KEY_SIZE;
bool quoted = (*start_boundary == '"');
if (quoted)
start_boundary++;
char* end_boundary = (quoted ? strstr(start_boundary, "\"") : strstr(start_boundary, ";")); // FIXME: third possiblity is CRLF, or?
if (!end_boundary)
return PEP_UNKNOWN_ERROR;
// Add space for the "--"
size_t boundary_strlen = (end_boundary - start_boundary) + 2;
signed_boundary = calloc(boundary_strlen + 1, 1);
strlcpy(signed_boundary, "--", boundary_strlen + 1);
strlcat(signed_boundary, start_boundary, boundary_strlen + 1);
start_boundary = strstr(end_boundary, signed_boundary);
if (!start_boundary)
return PEP_UNKNOWN_ERROR;
start_boundary += boundary_strlen;
if (*start_boundary == '\r') {
if (*(start_boundary + 1) == '\n')
start_boundary += 2;
}
else if (*start_boundary == '\n')
start_boundary++;
end_boundary = strstr(start_boundary + boundary_strlen, signed_boundary);
if (!end_boundary)
return PEP_UNKNOWN_ERROR;
// See RFC3156 section 5...
end_boundary--;
if (*(end_boundary - 1) == '\r')
end_boundary--;
*ssize = end_boundary - start_boundary;
*stext = start_boundary;
free(signed_boundary);
return PEP_STATUS_OK;
}
static PEP_STATUS combine_keylists(PEP_SESSION session, stringlist_t** verify_in,
stringlist_t** keylist_in_out,
pEp_identity* from) {
if (!verify_in || !(*verify_in)) // this isn't really a problem.
return PEP_STATUS_OK;
stringlist_t* orig_verify = *verify_in;
stringlist_t* verify_curr = NULL;
stringlist_t* from_keys = NULL;
/* FIXME: what to do if head needs to be null */
PEP_STATUS status = find_keys(session, from->address, &from_keys);
stringlist_t* from_fpr_node = NULL;
stringlist_t* from_curr;
for (from_curr = from_keys; from_curr; from_curr = from_curr->next) {
for (verify_curr = orig_verify; verify_curr; verify_curr = verify_curr->next) {
if (from_curr->value && verify_curr->value &&
_same_fpr(from_curr->value, strlen(from_curr->value),
verify_curr->value, strlen(verify_curr->value))) {
from_fpr_node = from_curr;
break;
}
}
}
if (!from_fpr_node) {
status = PEP_KEY_NOT_FOUND;
goto free;
}
verify_curr = orig_verify;
/* put "from" signer at the beginning of the list */
if (!_same_fpr(orig_verify->value, strlen(orig_verify->value),
from_fpr_node->value, strlen(from_fpr_node->value))) {
orig_verify = stringlist_delete(orig_verify, from_fpr_node->value);
verify_curr = new_stringlist(from_fpr_node->value);
verify_curr->next = orig_verify;
}
/* append keylist to signers */
if (keylist_in_out && *keylist_in_out && (*keylist_in_out)->value) {
stringlist_t** tail_pp = &verify_curr->next;
while (*tail_pp) {
tail_pp = &((*tail_pp)->next);
}
stringlist_t* second_list = *keylist_in_out;
if (second_list) {
char* listhead_val = second_list->value;
if (!listhead_val || listhead_val[0] == '\0') {
/* remove head, basically. This can happen when,
for example, the signature is detached and
verification is not seen directly after
decryption, so no signer is presumed in
the first construction of the keylist */
*keylist_in_out = (*keylist_in_out)->next;
second_list->next = NULL;
free_stringlist(second_list);
}
}
*tail_pp = *keylist_in_out;
}
*keylist_in_out = verify_curr;
status = PEP_STATUS_OK;
free:
free_stringlist(from_keys);
return status;
}
static PEP_STATUS amend_rating_according_to_sender_and_recipients(
PEP_SESSION session,
PEP_rating *rating,
pEp_identity *sender,
stringlist_t *recipients) {
PEP_STATUS status = PEP_STATUS_OK;
if (*rating > PEP_rating_mistrust) {
if (recipients == NULL) {
*rating = PEP_rating_undefined;
return PEP_STATUS_OK;
}
char *fpr = recipients->value;
if (!(sender && sender->user_id && sender->user_id[0] && fpr && fpr[0])) {
*rating = PEP_rating_unreliable;
}
else {
pEp_identity *_sender = new_identity(sender->address, fpr,
sender->user_id, sender->username);
if (_sender == NULL)
return PEP_OUT_OF_MEMORY;
status = get_trust(session, _sender);
if (_sender->comm_type == PEP_ct_unknown) {
get_key_rating(session, fpr, &_sender->comm_type);
}
if (_sender->comm_type != PEP_ct_unknown) {
*rating = keylist_rating(session, recipients,
fpr, _rating(_sender->comm_type,
PEP_rating_undefined));
}
free_identity(_sender);
if (status == PEP_CANNOT_FIND_IDENTITY)
status = PEP_STATUS_OK;
}
}
return status;
}
// FIXME: Do we need to remove the attachment? I think we do...
static bool pull_up_attached_main_msg(message* src) {
char* slong = src->longmsg;
char* sform = src->longmsg_formatted;
bloblist_t* satt = src->attachments;
if ((!slong || slong[0] == '\0')
&& (!sform || sform[0] == '\0')) {
if (satt) {
const char* inner_mime_type = satt->mime_type;
if (strcasecmp(inner_mime_type, "text/plain") == 0) {
free(slong); /* in case of "" */
src->longmsg = strndup(satt->value, satt->size);
bloblist_t* next_node = satt->next;
if (next_node) {
inner_mime_type = next_node->mime_type;
if (strcasecmp(inner_mime_type, "text/html") == 0) {
free(sform);
src->longmsg_formatted = strndup(next_node->value, next_node->size);
}
}
}
else if (strcasecmp(inner_mime_type, "text/html") == 0) {
free(sform);
src->longmsg_formatted = strndup(satt->value, satt->size);
}
}
return true;
}
return false;
}
static PEP_STATUS unencapsulate_hidden_fields(message* src, message* msg,
char** msg_wrap_info) {
if (!src)
return PEP_ILLEGAL_VALUE;
unsigned char pepstr[] = PEP_SUBJ_STRING;
PEP_STATUS status = PEP_STATUS_OK;
bool change_source_in_place = (msg ? false : true);
if (change_source_in_place)
msg = src;
switch (src->enc_format) {
case PEP_enc_PGP_MIME:
case PEP_enc_pieces:
case PEP_enc_PGP_MIME_Outlook1:
// case PEP_enc_none: // FIXME - this is wrong
if (!change_source_in_place)
status = copy_fields(msg, src);
if (status != PEP_STATUS_OK)
return status;
// FIXME: This is a mess. Talk with VB about how far we go to identify
if (is_a_pEpmessage(src) || (src->shortmsg == NULL || strcmp(src->shortmsg, "pEp") == 0 ||
_unsigned_signed_strcmp(pepstr, src->shortmsg, PEP_SUBJ_BYTELEN) == 0) ||
(strcmp(src->shortmsg, "p=p") == 0))
{
char * shortmsg = NULL;
char * longmsg = NULL;
if (msg->longmsg) {
int r = separate_short_and_long(msg->longmsg,
&shortmsg,
msg_wrap_info,
&longmsg);
if (r == -1)
return PEP_OUT_OF_MEMORY;
}
// We only use the shortmsg in version 1.0 messages; if it occurs where we
// didn't replace the subject, we ignore this all
if (!(*msg_wrap_info || change_source_in_place)) {
if (!shortmsg ||
(src->shortmsg != NULL && strcmp(src->shortmsg, "pEp") != 0 &&
_unsigned_signed_strcmp(pepstr, src->shortmsg, PEP_SUBJ_BYTELEN) != 0 &&
strcmp(src->shortmsg, "p=p") != 0)) {
if (shortmsg != NULL)
free(shortmsg);
if (src->shortmsg == NULL) {
shortmsg = strdup("");
}
else {
// FIXME: is msg->shortmsg always a copy of
// src->shortmsg already?
// if so, we need to change the logic so
// that in this case, we don't free msg->shortmsg
// and do this strdup, etc
shortmsg = strdup(src->shortmsg);
}
}
free(msg->shortmsg);
msg->shortmsg = shortmsg;
}
free(msg->longmsg);
msg->longmsg = longmsg;
}
else {
if (!change_source_in_place) {
msg->shortmsg = strdup(src->shortmsg);
assert(msg->shortmsg);
if (msg->shortmsg == NULL)
return PEP_OUT_OF_MEMORY;
}
}
break;
default:
// BUG: must implement more
NOT_IMPLEMENTED
}
return PEP_STATUS_OK;
}
static PEP_STATUS get_crypto_text(message* src, char** crypto_text, size_t* text_size) {
// this is only here because of how NOT_IMPLEMENTED works
PEP_STATUS status = PEP_STATUS_OK;
switch (src->enc_format) {
case PEP_enc_PGP_MIME:
*crypto_text = src->attachments->next->value;
*text_size = src->attachments->next->size;
break;
case PEP_enc_PGP_MIME_Outlook1:
*crypto_text = src->attachments->value;
*text_size = src->attachments->size;
break;
case PEP_enc_pieces:
*crypto_text = src->longmsg;
*text_size = strlen(*crypto_text);
break;
default:
NOT_IMPLEMENTED
}
return status;
}
static PEP_STATUS verify_decrypted(PEP_SESSION session,
message* src,
message* msg,
char* plaintext,
size_t plaintext_size,
stringlist_t** keylist,
PEP_STATUS* decrypt_status,
PEP_cryptotech crypto) {
assert(src && src->from);
if (!src && !src->from)
return PEP_ILLEGAL_VALUE;
PEP_STATUS _cached_decrypt_status = *decrypt_status;
pEp_identity* sender = src->from;
bloblist_t* detached_sig = NULL;
PEP_STATUS status = _get_detached_signature(msg, &detached_sig);
stringlist_t *verify_keylist = NULL;
if (detached_sig) {
char* dsig_text = detached_sig->value;
size_t dsig_size = detached_sig->size;
size_t ssize = 0;
char* stext = NULL;
status = _get_signed_text(plaintext, plaintext_size, &stext, &ssize);
if (ssize > 0 && stext) {
status = cryptotech[crypto].verify_text(session, stext,
ssize, dsig_text, dsig_size,
&verify_keylist);
}
if (status == PEP_VERIFIED || status == PEP_VERIFIED_AND_TRUSTED)
{
*decrypt_status = PEP_DECRYPTED_AND_VERIFIED;
status = combine_keylists(session, &verify_keylist, keylist, sender);
}
}
else {
size_t csize, psize;
char* ctext;
char* ptext;
get_crypto_text(src, &ctext, &csize);
// reverify - we may have imported a key in the meantime
// status = cryptotech[crypto].verify_text(session, ctext,
// csize, NULL, 0,
// &verify_keylist);
free_stringlist(*keylist);
*decrypt_status = decrypt_and_verify(session, ctext, csize,
NULL, 0,
&ptext, &psize, keylist);
}
if (*decrypt_status != PEP_DECRYPTED_AND_VERIFIED)
*decrypt_status = _cached_decrypt_status;
return PEP_STATUS_OK;
}
static PEP_STATUS _decrypt_in_pieces(PEP_SESSION session,
message* src,
message** msg_ptr,
char* ptext,
size_t psize) {
PEP_STATUS status = PEP_STATUS_OK;
*msg_ptr = clone_to_empty_message(src);
if (*msg_ptr == NULL)
return PEP_OUT_OF_MEMORY;
message* msg = *msg_ptr;
msg->longmsg = ptext;
ptext = NULL;
bloblist_t *_m = msg->attachments;
if (_m == NULL && src->attachments && src->attachments->value) {
msg->attachments = new_bloblist(NULL, 0, NULL, NULL);
_m = msg->attachments;
}
bloblist_t *_s;
for (_s = src->attachments; _s; _s = _s->next) {
if (_s->value == NULL && _s->size == 0){
_m = bloblist_add(_m, NULL, 0, _s->mime_type, _s->filename);
if (_m == NULL)
return PEP_OUT_OF_MEMORY;
}
else if (is_encrypted_attachment(_s)) {
stringlist_t *_keylist = NULL;
char *attctext = _s->value;
size_t attcsize = _s->size;
free(ptext);
ptext = NULL;
// FIXME: What about attachments with separate sigs???
status = decrypt_and_verify(session, attctext, attcsize,
NULL, 0,
&ptext, &psize, &_keylist);
free_stringlist(_keylist); // FIXME: Why do we do this?
if (ptext) {
if (is_encrypted_html_attachment(_s)) {
msg->longmsg_formatted = ptext;
ptext = NULL;
}
else {
static const char * const mime_type = "application/octet-stream";
char * const filename =
without_double_ending(_s->filename);
if (filename == NULL)
return PEP_OUT_OF_MEMORY;
_m = bloblist_add(_m, ptext, psize, mime_type,
filename);
free(filename);
if (_m == NULL)
return PEP_OUT_OF_MEMORY;
ptext = NULL;
if (msg->attachments == NULL)
msg->attachments = _m;
}
}
else {
char *copy = malloc(_s->size);
assert(copy);
if (copy == NULL)
return PEP_OUT_OF_MEMORY;
memcpy(copy, _s->value, _s->size);
_m = bloblist_add(_m, copy, _s->size, _s->mime_type, _s->filename);
if (_m == NULL)
return PEP_OUT_OF_MEMORY;
}
}
else {
char *copy = malloc(_s->size);
assert(copy);
if (copy == NULL)
return PEP_OUT_OF_MEMORY;
memcpy(copy, _s->value, _s->size);
_m = bloblist_add(_m, copy, _s->size, _s->mime_type, _s->filename);
if (_m == NULL)
return PEP_OUT_OF_MEMORY;
}
}
return status;
}
static PEP_STATUS import_priv_keys_from_decrypted_msg(PEP_SESSION session,
message* src,
message* msg,
bool* imported_keys,
bool* imported_private,
identity_list** private_il) {
PEP_STATUS status = PEP_STATUS_OK;
// check for private key in decrypted message attachment while importing
identity_list *_private_il = NULL;
*imported_keys = import_attached_keys(session, msg, &_private_il);
if (_private_il && identity_list_length(_private_il) == 1 &&
_private_il->ident->address)
*imported_private = true;
if (private_il && imported_private) {
// the private identity list should NOT be subject to myself() or
// update_identity() at this point.
// If the receiving app wants them to be in the trust DB, it
// should call myself() on them upon return.
// We do, however, prepare these so the app can use them
// directly in a myself() call by putting the own_id on it.
char* own_id = NULL;
status = get_default_own_userid(session, &own_id);
if (status != PEP_STATUS_OK) {
free(own_id);
own_id = NULL;
}
identity_list* il = _private_il;
for ( ; il; il = il->next) {
if (own_id) {
free(il->ident->user_id);
il->ident->user_id = strdup(own_id);
}
il->ident->me = true;
}
*private_il = _private_il;
free(own_id);
}
else
free_identity_list(_private_il);
return status;
}
static PEP_STATUS update_sender_to_pep_trust(
PEP_SESSION session,
pEp_identity* sender,
stringlist_t* keylist)
{
assert(session);
assert(sender);
assert(keylist && !EMPTYSTR(keylist->value));
if (!session || !sender || !keylist || EMPTYSTR(keylist->value))
return PEP_ILLEGAL_VALUE;
free(sender->fpr);
sender->fpr = NULL;
PEP_STATUS status =
is_me(session, sender) ? myself(session, sender) : update_identity(session, sender);
if (EMPTYSTR(sender->fpr) || strcmp(sender->fpr, keylist->value) != 0) {
free(sender->fpr);
sender->fpr = strdup(keylist->value);
if (!sender->fpr)
return PEP_OUT_OF_MEMORY;
status = get_trust(session, sender);
if (status == PEP_CANNOT_FIND_IDENTITY || sender->comm_type == PEP_ct_unknown) {
PEP_comm_type ct = PEP_ct_unknown;
status = get_key_rating(session, sender->fpr, &ct);
if (status != PEP_STATUS_OK)
return status;
sender->comm_type = ct;
}
}
// Could be done elegantly, but we do this explicitly here for readability.
// This file's code is difficult enough to parse. But change at will.
switch (sender->comm_type) {
case PEP_ct_OpenPGP_unconfirmed:
case PEP_ct_OpenPGP:
sender->comm_type = PEP_ct_pEp_unconfirmed | (sender->comm_type & PEP_ct_confirmed);
status = set_trust(session, sender);
break;
default:
status = PEP_CANNOT_SET_TRUST;
break;
}
return status;
}
DYNAMIC_API PEP_STATUS _decrypt_message(
PEP_SESSION session,
message *src,
message **dst,
stringlist_t **keylist,
PEP_rating *rating,
PEP_decrypt_flags_t *flags,
identity_list **private_il
)
{
assert(session);
assert(src);
assert(dst);
assert(keylist);
assert(rating);
assert(flags);
if (!(session && src && dst && keylist && rating && flags))
return ADD_TO_LOG(PEP_ILLEGAL_VALUE);
/*** Begin init ***/
PEP_STATUS status = PEP_STATUS_OK;
PEP_STATUS decrypt_status = PEP_CANNOT_DECRYPT_UNKNOWN;
message *msg = NULL;
char *ctext;
size_t csize;
char *ptext = NULL;
size_t psize;
stringlist_t *_keylist = NULL;
char* signer_fpr = NULL;
bool is_pep_msg = is_a_pEpmessage(src);
*dst = NULL;
*keylist = NULL;
*rating = PEP_rating_undefined;
*flags = 0;
/*** End init ***/
// Ok, before we do anything, if it's a pEp message, regardless of whether it's
// encrypted or not, we set the sender as a pEp user. This has NOTHING to do
// with the key.
if (src->from && !(is_me(session, src->from))) {
if (is_pep_msg) {
pEp_identity* tmp_from = src->from;
// Ensure there's a user id
if (EMPTYSTR(tmp_from->user_id) && tmp_from->address) {
status = update_identity(session, tmp_from);
if (status == PEP_CANNOT_FIND_IDENTITY) {
tmp_from->user_id = calloc(1, strlen(tmp_from->address) + 6);
if (!tmp_from->user_id)
return PEP_OUT_OF_MEMORY;
snprintf(tmp_from->user_id, strlen(tmp_from->address) + 6,
"TOFU_%s", tmp_from->address);
status = PEP_STATUS_OK;
}
}
if (status == PEP_STATUS_OK) {
// Now set user as PEP (may also create an identity if none existed yet)
status = set_as_pep_user(session, tmp_from);
}
}
}
// We really need key used in signing to do anything further on the pEp comm_type.
// So we can't adjust the rating of the sender just yet.
/*** Begin Import any attached public keys and update identities accordingly ***/
// Private key in unencrypted mail are ignored -> NULL
bool imported_keys = import_attached_keys(session, src, NULL);
// FIXME: is this really necessary here?
if (!is_me(session, src->from))
status = update_identity(session, src->from);