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.
pEpEngine/src/message_api.c

5518 lines
179 KiB

// This file is under GNU General Public License 3.0
// see LICENSE.txt
#include "pEp_internal.h"
#include "message_api.h"
#include "pEpEngine.h"
#include "platform.h"
#include "mime.h"
#include "blacklist.h"
#include "baseprotocol.h"
#include "KeySync_fsm.h"
#include "base64.h"
#include "resource_id.h"
#include "internal_format.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
// 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 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_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";
}
}
bool _memnmemn(const char* needle,
size_t needle_size,
const char* haystack,
size_t haystack_size)
{
if (needle_size > haystack_size) {
return false;
}
else if (needle_size == 0) {
return true;
}
bool found = true;
const char* haystack_ptr = haystack;
unsigned int i = 0;
size_t remaining_hay = haystack_size;
for (i = 0; i < haystack_size && (remaining_hay >= needle_size); i++, haystack_ptr++) {
found = false;
const char* needle_ptr = needle;
if (*haystack_ptr == *needle) {
const char* haystack_tmp = haystack_ptr;
unsigned int j;
found = true;
for (j = 0; j < needle_size; j++) {
if (*needle_ptr++ != *haystack_tmp++) {
found = false;
break;
}
}
if (found)
break;
}
remaining_hay--;
}
return found;
}
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,
bool clobber)
{
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) {
if (clobber) {
free(pair->value);
pair->value = strdup(value);
}
}
else {
add_opt_field(msg, name, value);
}
}
}
void decorate_message(
message *msg,
PEP_rating rating,
stringlist_t *keylist,
bool add_version,
bool clobber
)
{
assert(msg);
if (add_version)
replace_opt_field(msg, "X-pEp-Version", PEP_VERSION, clobber);
if (rating != PEP_rating_undefined)
replace_opt_field(msg, "X-EncStatus", rating_to_string(rating), clobber);
if (keylist) {
char *_keylist = keylist_to_string(keylist);
replace_opt_field(msg, "X-KeyList", _keylist, clobber);
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 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;
}
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);
// in case encrypt_message() is called twice with a different passphrase this was done already
if (strcmp(shortmsg, "pEp") == 0 || _unsigned_signed_strcmp(pEpstr, shortmsg, PEP_SUBJ_BYTELEN) == 0) {
char *ptext = strdup(longmsg);
assert(ptext);
if (!ptext)
return NULL;
return ptext;
}
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 = ((int) ceil((double) 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[37] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char* retbuf = calloc(bufsize, 1);
assert(retbuf);
if (!retbuf)
return NULL;
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;
while (i > 0) {
int bitshift = 0;
int randval = rand();
unsigned long long temp_val = randval & bitmask;
output_value |= temp_val;
i -= MIN(num_bits, i);
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, (int) ceil(log2((double) 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, message_wrap_type wrap_type,
bool keep_orig_subject, stringlist_t* extra_keys,
unsigned int max_major, unsigned int max_minor) {
if (!attachment)
return NULL;
message* _envelope = envelope;
PEP_STATUS status = PEP_STATUS_OK;
replace_opt_field(attachment, "X-pEp-Version", PEP_VERSION, true);
if (extra_keys) {
char* ex_keystr = stringlist_to_string(extra_keys);
if (ex_keystr)
add_opt_field(attachment, "X-pEp-extra-keys", ex_keystr);
}
if (!_envelope && (wrap_type != PEP_message_transport)) {
_envelope = extract_minimal_envelope(attachment, PEP_dir_outgoing);
status = generate_message_id(_envelope);
if (status != PEP_STATUS_OK)
goto enomem;
const char* inner_type_string = "";
switch (wrap_type) {
case PEP_message_key_reset:
inner_type_string = "KEY_RESET";
break;
default:
inner_type_string = "INNER";
}
if (max_major < 2 || (max_major == 2 && max_minor == 0)) {
attachment->longmsg = encapsulate_message_wrap_info(inner_type_string, attachment->longmsg);
_envelope->longmsg = encapsulate_message_wrap_info("OUTER", _envelope->longmsg);
}
else {
_envelope->longmsg = strdup(
"This message was encrypted with p≡p (https://pep.software). If you are seeing this message,\n"
"your client does not support raising message attachments. Please click on the message attachment to\n"
"to view it, or better yet, consider using p≡p!\n"
);
}
// 2.1, to replace the above
add_opt_field(attachment, X_PEP_MSG_WRAP_KEY, inner_type_string);
}
else if (_envelope) {
// 2.1 - how do we peel this particular union when we get there?
_envelope->longmsg = encapsulate_message_wrap_info("TRANSPORT", _envelope->longmsg);
}
else {
return NULL;
}
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;
}
/* add sender fpr to inner message */
add_opt_field(attachment,
"X-pEp-Sender-FPR",
(attachment->_sender_fpr ? attachment->_sender_fpr : "")
);
/* Turn message into a MIME-blob */
status = mime_encode_message(attachment, false, &message_text, false);
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 encrypt_PGP_inline(
PEP_SESSION session,
const message *src,
stringlist_t *keys,
message *dst,
PEP_encrypt_flags_t flags
)
{
char *ctext = NULL;
size_t csize = 0;
PEP_STATUS status = encrypt_and_sign(session, keys, src->longmsg,
strlen(src->longmsg), &ctext, &csize);
if (status)
return status;
dst->enc_format = src->enc_format;
// shortmsg is being copied
if (src->shortmsg) {
dst->shortmsg = strdup(src->shortmsg);
assert(dst->shortmsg);
if (!dst->shortmsg)
return PEP_OUT_OF_MEMORY;
}
// id is staying the same
if (src->id) {
dst->id = strdup(src->id);
assert(dst->id);
if (!dst->id)
return PEP_OUT_OF_MEMORY;
}
char *_ctext = realloc(ctext, csize + 1);
assert(_ctext);
if (!_ctext)
return PEP_OUT_OF_MEMORY;
_ctext[csize] = 0;
dst->longmsg = _ctext;
dst->attachments = new_bloblist(NULL, 0, NULL, NULL);
if (!dst->attachments)
return PEP_OUT_OF_MEMORY;
bloblist_t *ad = dst->attachments;
if (!EMPTYSTR(src->longmsg_formatted)) {
status = encrypt_and_sign(session, keys, src->longmsg_formatted,
strlen(src->longmsg_formatted), &ctext, &csize);
if (status)
return status;
char *_ctext = realloc(ctext, csize + 1);
assert(_ctext);
if (!_ctext)
return PEP_OUT_OF_MEMORY;
_ctext[csize] = 0;
ad = bloblist_add(ad, _ctext, csize + 1, "text/html", NULL);
if (!ad)
return PEP_OUT_OF_MEMORY;
ad->disposition = PEP_CONTENT_DISP_INLINE;
}
if (src->attachments && src->attachments->value) {
bloblist_t *as;
for (as = src->attachments; as && as->value; as = as->next) {
char *value = NULL;
size_t size = 0;
if (src->enc_format == PEP_enc_inline_EA) {
status = encode_internal(as->value, as->size, as->mime_type,
&value, &size);
if (status)
return status;
if (!value) {
value = as->value;
size = as->size;
}
}
else {
value = as->value;
size = as->size;
}
status = encrypt_and_sign(session, keys, value, size, &ctext,
&csize);
if (value != as->value)
free(value);
if (status)
return status;
char *_ctext = realloc(ctext, csize + 1);
assert(_ctext);
if (!_ctext)
return PEP_OUT_OF_MEMORY;
_ctext[csize] = 0;
size_t len = strlen(as->filename);
char *filename = malloc(len + 5);
assert(filename);
if (!filename)
return PEP_OUT_OF_MEMORY;
memcpy(filename, as->filename, len);
memcpy(filename + len, ".pgp", 5);
ad = bloblist_add(ad, _ctext, csize + 1, "application/octet-stream", filename);
free(filename);
filename = NULL;
if (!ad)
return PEP_OUT_OF_MEMORY;
ad->disposition = as->disposition;
}
}
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,
message_wrap_type wrap_type
)
{
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 wrapped = (wrap_type != PEP_message_unwrapped);
status = mime_encode_message(_src, true, &mimetext, wrapped);
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 || status)
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 bool _has_PGP_MIME_format(message* msg) {
if (!msg || !msg->attachments || !msg->attachments->next)
return false;
if (msg->attachments->next->next)
return false;
if (!msg->attachments->mime_type)
return false;
if (strcmp(msg->attachments->mime_type, "application/pgp-encrypted") != 0)
return false;
if (!msg->attachments->next->mime_type ||
strcmp(msg->attachments->next->mime_type, "application/octet-stream") != 0)
return false;
return true;
}
*/
static inline PEP_rating _rating(PEP_comm_type ct)
{
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_compromised)
return PEP_rating_under_attack;
else if (ct == PEP_ct_mistrusted)
return PEP_rating_mistrust;
if (ct == PEP_ct_no_encryption || ct == PEP_ct_no_encrypted_channel ||
ct == PEP_ct_my_key_not_included)
return PEP_rating_unencrypted;
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;
}
DYNAMIC_API PEP_rating rating_from_comm_type(PEP_comm_type ct)
{
return _rating(ct);
}
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)
return true;
}
if (strcmp(ext, ".asc") == 0 && blob->size > 0) {
const char* pubk_needle = "BEGIN PGP PUBLIC KEY";
size_t pubk_needle_size = strlen(pubk_needle);
const char* privk_needle = "BEGIN PGP PRIVATE KEY";
size_t privk_needle_size = strlen(privk_needle);
if (!(_memnmemn(pubk_needle, pubk_needle_size, blob->value, blob->size)) &&
!(_memnmemn(privk_needle, privk_needle_size, blob->value, blob->size)))
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);
bare_filename_ptr += strlen(bare_filename_ptr) - 15;
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_VERIFY_SIGNER_KEY_REVOKED:
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);
}
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_;
rating = worst_rating(rating, _rating_);
}
return rating;
}
// KB: Fixme - the first statement below is probably unnecessary now.
// Internal function WARNING:
// Should be called on ident that might have its FPR set from retrieval!
// (or on one without an fpr)
// We do not want myself() setting the fpr here.
//
// Cannot return passphrase statuses. No keygen or renewal allowed here.
static PEP_comm_type _get_comm_type(
PEP_SESSION session,
PEP_comm_type max_comm_type,
pEp_identity *ident
)
{
if (!ident)
return PEP_ILLEGAL_VALUE;
PEP_STATUS status = PEP_STATUS_OK;
if (max_comm_type == PEP_ct_compromised)
return PEP_ct_compromised;
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, false, false, false, true);
}
if (status == PEP_STATUS_OK) {
if (ident->comm_type == PEP_ct_compromised)
return PEP_ct_compromised;
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 PEP_comm_type _get_comm_type_preview(
PEP_SESSION session,
PEP_comm_type max_comm_type,
pEp_identity *ident
)
{
assert(session);
assert(ident);
PEP_STATUS status = PEP_STATUS_OK;
if (max_comm_type == PEP_ct_compromised)
return PEP_ct_compromised;
if (max_comm_type == PEP_ct_mistrusted)
return PEP_ct_mistrusted;
PEP_comm_type comm_type = PEP_ct_unknown;
if (ident && !EMPTYSTR(ident->address) && !EMPTYSTR(ident->user_id)) {
pEp_identity *ident2;
status = get_identity(session, ident->address, ident->user_id, &ident2);
comm_type = ident2 ? ident2->comm_type : PEP_ct_unknown;
free_identity(ident2);
if (status == PEP_STATUS_OK) {
if (comm_type == PEP_ct_compromised)
comm_type = PEP_ct_compromised;
else if (comm_type == PEP_ct_mistrusted)
comm_type = PEP_ct_mistrusted;
else
comm_type = _MIN(max_comm_type, comm_type);
}
else {
comm_type = PEP_ct_unknown;
}
}
return comm_type;
}
// 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;
// }
// }
// }
static bool compare_first_n_bytes(const char* first, const char* second, size_t n) {
size_t i;
for (i = 0; i < n; i++) {
char num1 = *first;
char num2 = *second;
if (num1 != num2)
return false;
if (num1 == '\0') {
if (num2 == '\0')
return true;
}
first++;
second++;
}
return true;
}
bool import_attached_keys(
PEP_SESSION session,
message *msg,
identity_list **private_idents,
stringlist_t** imported_key_list,
uint64_t* changed_keys
)
{
assert(session);
assert(msg);
if (session == NULL || msg == NULL)
return false;
bool remove = false;
int i = 0;
bloblist_t* prev = NULL;
bool do_not_advance = false;
const char* pubkey_header = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
const char* privkey_header = "-----BEGIN PGP PRIVATE KEY BLOCK-----";
// Hate my magic numbers at your peril, but I don't want a strlen each time
const size_t PUBKEY_HSIZE = 36;
const size_t PRIVKEY_HSIZE = 37;
for (bloblist_t *bl = msg->attachments; i < MAX_KEYS_TO_IMPORT && bl && bl->value;
i++)
{
do_not_advance = false;
if (bl && bl->value && bl->size && bl->size < MAX_KEY_SIZE
&& is_key(bl))
{
char* blob_value = bl->value;
size_t blob_size = bl->size;
bool free_blobval = false;
if (is_encrypted_attachment(bl)) {
char* bl_ptext = NULL;
size_t bl_psize = 0;
stringlist_t* bl_keylist = NULL;
PEP_STATUS _status = decrypt_and_verify(session,
blob_value, blob_size,
NULL, 0,
&bl_ptext, &bl_psize,
&bl_keylist,
NULL);
free_stringlist(bl_keylist); // we don't care about key encryption as long as we decrypt
if (_status == PEP_DECRYPTED || _status == PEP_DECRYPTED_AND_VERIFIED) {
free_blobval = true;
blob_value = bl_ptext;
blob_size = bl_psize;
}
else {
// This is an encrypted attachment we can do nothing with.
// We shouldn't delete it or import it, because we can't
// do the latter.
free(bl_ptext);
prev = bl;
bl = bl->next;
continue;
}
}
identity_list *local_private_idents = NULL;
PEP_STATUS import_status = _import_key_with_fpr_return(
session, blob_value, blob_size,
&local_private_idents,
imported_key_list,
changed_keys);
bloblist_t* to_delete = NULL;
switch (import_status) {
case PEP_NO_KEY_IMPORTED:
break;
case PEP_KEY_IMPORT_STATUS_UNKNOWN:
// We'll delete armoured stuff, at least
if (blob_size <= PUBKEY_HSIZE)
break;
if ((!compare_first_n_bytes(pubkey_header, (const char*)blob_value, PUBKEY_HSIZE)) &&
(!compare_first_n_bytes(privkey_header, (const char*)blob_value, PRIVKEY_HSIZE)))
break;
// else fall through and delete
case PEP_KEY_IMPORTED:
case PEP_STATUS_OK:
to_delete = bl;
if (prev)
prev->next = bl->next;
else
msg->attachments = bl->next;
bl = bl->next;
to_delete->next = NULL;
free_bloblist(to_delete);
do_not_advance = true;
remove = true;
break;
default:
// bad stuff, but ok.
break;
}
if (private_idents && *private_idents == NULL && local_private_idents != NULL)
*private_idents = local_private_idents;
else
free_identity_list(local_private_idents);
if (free_blobval)
free(blob_value);
}
if (!do_not_advance) {
prev = bl;
bl = bl->next;
}
}
return remove;
}
PEP_STATUS _attach_key(PEP_SESSION session, const char* fpr, message *msg)
{
char *keydata = NULL;
size_t size = 0;
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)) {
if (msg->enc_format != PEP_enc_inline_EA)
msg->enc_format = PEP_enc_inline;
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;
}
}
static void _cleanup_src(message* src, bool remove_attached_key) {
assert(src);
if (!src)
return;
char* longmsg = NULL;
char* shortmsg = NULL;
char* msg_wrap_info = NULL;
if (src->longmsg)
separate_short_and_long(src->longmsg, &shortmsg, &msg_wrap_info,
&longmsg);
if (longmsg) {
free(src->longmsg);
free(shortmsg);
free(msg_wrap_info);
src->longmsg = longmsg;
}
if (remove_attached_key) {
// End of the attachment list
if (src->attachments) {
bloblist_t* tmp = src->attachments;
while (tmp->next && tmp->next->next) {
tmp = tmp->next;
}
free_bloblist(tmp->next);
tmp->next = NULL;
}
}
}
static PEP_STATUS id_list_set_enc_format(PEP_SESSION session, identity_list* id_list, PEP_enc_format enc_format) {
PEP_STATUS status = PEP_STATUS_OK;
identity_list* id_list_curr = id_list;
for ( ; id_list_curr && id_list_curr->ident && status == PEP_STATUS_OK; id_list_curr = id_list_curr->next) {
status = set_ident_enc_format(session, id_list_curr->ident, enc_format);
}
return status;
}
// N.B.
// depends on update_identity and friends having already been called on list
static void update_encryption_format(identity_list* id_list, PEP_enc_format* enc_format) {
identity_list* id_list_curr;
for (id_list_curr = id_list; id_list_curr && id_list_curr->ident; id_list_curr = id_list_curr->next) {
PEP_enc_format format = id_list_curr->ident->enc_format;
if (format != PEP_enc_none) {
*enc_format = format;
break;
}
}
}
PEP_STATUS probe_encrypt(PEP_SESSION session, const char *fpr)
{
assert(session);
if (!session)
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(fpr))
return PEP_KEY_NOT_FOUND;
stringlist_t *keylist = new_stringlist(fpr);
if (!keylist)
return PEP_OUT_OF_MEMORY;
char *ctext = NULL;
size_t csize = 0;
PEP_STATUS status = encrypt_and_sign(session, keylist, "pEp", 4, &ctext, &csize);
free(ctext);
return status;
}
static bool failed_test(PEP_STATUS status)
{
if (status == PEP_OUT_OF_MEMORY ||
status == PEP_PASSPHRASE_REQUIRED ||
status == PEP_WRONG_PASSPHRASE ||
status == PEP_PASSPHRASE_FOR_NEW_KEYS_REQUIRED)
return true;
return false;
}
// CANNOT return PASSPHRASE errors, as no gen or renew allowed below
static PEP_STATUS _update_state_for_ident_list(
PEP_SESSION session,
pEp_identity* from_ident,
identity_list* ident_list,
stringlist_t** keylist,
PEP_comm_type* max_comm_type,
unsigned int* max_version_major,
unsigned int* max_version_minor,
bool* has_pEp_user,
bool* dest_keys_found,
bool suppress_update_for_bcc
)
{
if (!ident_list || !max_version_major || !max_version_minor
|| !has_pEp_user || !dest_keys_found
|| !keylist)
return PEP_ILLEGAL_VALUE;
PEP_STATUS status = PEP_STATUS_OK;
identity_list* _il = ident_list;
for ( ; _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;
}
// 0 unless set, so safe.
if (!suppress_update_for_bcc) {
set_min_version( _il->ident->major_ver, _il->ident->minor_ver,
*max_version_major, *max_version_minor,
max_version_major, max_version_minor);
}
bool is_blacklisted = false;
if (_il->ident->fpr && IS_PGP_CT(_il->ident->comm_type)) {
status = blacklist_is_listed(session, _il->ident->fpr, &is_blacklisted);
if (status != PEP_STATUS_OK) {
// DB error
status = PEP_UNENCRYPTED;
goto pEp_done;
}
if (is_blacklisted) {
bool user_default, ident_default, address_default;
status = get_valid_pubkey(session, _il->ident,
&ident_default, &user_default,
&address_default,
true);
if (status != PEP_STATUS_OK || _il->ident->fpr == NULL) {
_il->ident->comm_type = PEP_ct_key_not_found;
status = PEP_STATUS_OK;
}
}
}
if (!(*has_pEp_user) && !EMPTYSTR(_il->ident->user_id))
is_pEp_user(session, _il->ident, has_pEp_user);
if (!suppress_update_for_bcc && from_ident) {
status = bind_own_ident_with_contact_ident(session, from_ident, _il->ident);
if (status != PEP_STATUS_OK) {
status = PEP_UNKNOWN_DB_ERROR;
goto pEp_done;
}
}
}
else // myself, but don't gen or renew
status = _myself(session, _il->ident, false, false, false, true);
if (status != PEP_STATUS_OK)
goto pEp_done;
if (!EMPTYSTR(_il->ident->fpr)) {
*keylist = stringlist_add(*keylist, _il->ident->fpr);
if (*keylist == NULL) {
status = PEP_OUT_OF_MEMORY;
goto pEp_done;
}
*max_comm_type = _get_comm_type(session, *max_comm_type,
_il->ident);
}
else {
*dest_keys_found = false;
// ? status = PEP_KEY_NOT_FOUND;
}
}
pEp_done:
return status;
}
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;
bool added_key_to_real_src = false;
assert(session);
assert(src && src->from);
assert(dst);
if (!(session && src && src->from && dst))
return PEP_ILLEGAL_VALUE;
if (src->dir == PEP_dir_incoming)
return 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 PEP_ILLEGAL_VALUE;
bool force_v_1 = flags & PEP_encrypt_flag_force_version_1;
*dst = NULL;
if (!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;
// is a passphrase needed?
status = probe_encrypt(session, src->from->fpr);
if (failed_test(status))
return status;
char* send_fpr = strdup(src->from->fpr ? src->from->fpr : "");
src->_sender_fpr = send_fpr;
keys = new_stringlist(send_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;
unsigned int max_version_major = 0;
unsigned int max_version_minor = 0;
pEp_version_major_minor(PEP_VERSION, &max_version_major, &max_version_minor);
identity_list * _il = NULL;
//
// Update the identities and gather key and version information
// for sending
//
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;
}
// If you think this call is a beast, try the cut-and-pasted code 3 x
PEP_STATUS _status = _update_state_for_ident_list(
session, src->from, _il,
&_k,
&max_comm_type,
&max_version_major,
&max_version_minor,
&has_pEp_user,
&dest_keys_found,
true);
switch (_status) {
case PEP_PASSPHRASE_REQUIRED:
case PEP_PASSPHRASE_FOR_NEW_KEYS_REQUIRED:
case PEP_WRONG_PASSPHRASE:
status = _status;
goto pEp_error;
case PEP_STATUS_OK:
break;
default:
status = PEP_UNENCRYPTED;
goto pEp_error;
}
}
else // Non BCC
{
// If you think this call is a beast, try the cut-and-pasted code 3 x
PEP_STATUS _status = PEP_STATUS_OK;
if (src->to) {
_status = _update_state_for_ident_list(
session, src->from, src->to,
&_k,
&max_comm_type,
&max_version_major,
&max_version_minor,
&has_pEp_user,
&dest_keys_found,
false
);
switch (_status) {
case PEP_PASSPHRASE_REQUIRED:
case PEP_PASSPHRASE_FOR_NEW_KEYS_REQUIRED:
case PEP_WRONG_PASSPHRASE:
goto pEp_error;
case PEP_STATUS_OK:
break;
default:
status = PEP_UNENCRYPTED;
goto pEp_error;
}
}
if (src->cc) {
_status = _update_state_for_ident_list(
session, src->from, src->cc,
&_k,
&max_comm_type,
&max_version_major,
&max_version_minor,
&has_pEp_user,
&dest_keys_found,
false
);
switch (_status) {
case PEP_PASSPHRASE_REQUIRED:
case PEP_PASSPHRASE_FOR_NEW_KEYS_REQUIRED:
case PEP_WRONG_PASSPHRASE:
goto pEp_error;
case PEP_STATUS_OK:
break;
default:
status = PEP_UNENCRYPTED;
goto pEp_error;
}
}
}
if (max_version_major < 2)
force_v_1 = true;
if (enc_format == PEP_enc_auto) {
update_encryption_format(src->to, &enc_format);
if (enc_format == PEP_enc_auto && src->cc)
update_encryption_format(src->cc, &enc_format);
if (enc_format == PEP_enc_auto && src->bcc)
update_encryption_format(src->bcc, &enc_format);
if (enc_format == PEP_enc_auto)
enc_format = PEP_enc_PEP;
}
else if (enc_format != PEP_enc_none) {
status = id_list_set_enc_format(session, src->to, enc_format);
status = ((status != PEP_STATUS_OK || !(src->cc)) ? status : id_list_set_enc_format(session, src->cc, enc_format));
status = ((status != PEP_STATUS_OK || !(src->bcc)) ? status : id_list_set_enc_format(session, src->bcc, enc_format));
if (status != PEP_STATUS_OK)
goto pEp_error;
}
if (enc_format == PEP_enc_none || !dest_keys_found ||
stringlist_length(keys) == 0 ||
_rating(max_comm_type) < 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);
added_key_to_real_src = true;
}
decorate_message(src, PEP_rating_undefined, NULL, true, true);
return PEP_UNENCRYPTED;
}
else {
// First, dedup the keylist
if (keys && keys->next)
dedup_stringlist(keys->next);
// FIXME - we need to deal with transport types (via flag)
message_wrap_type wrap_type = PEP_message_unwrapped;
if ((enc_format != PEP_enc_inline) && (enc_format != PEP_enc_inline_EA) && (!force_v_1) && ((max_comm_type | PEP_ct_confirmed) == PEP_ct_pEp)) {
wrap_type = ((flags & PEP_encrypt_flag_key_reset_only) ? PEP_message_key_reset : PEP_message_default);
_src = wrap_message_as_attachment(NULL, src, wrap_type, false, extra, max_version_major, max_version_minor);
if (!_src)
goto pEp_error;
}
else {
// hide subject
if (enc_format != PEP_enc_inline && enc_format != PEP_enc_inline_EA) {
status = replace_subject(_src);
if (status == PEP_OUT_OF_MEMORY)
goto enomem;
}
if (!(flags & PEP_encrypt_flag_force_no_attached_key))
added_key_to_real_src = true;
}
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, wrap_type);
break;
case PEP_enc_inline:
case PEP_enc_inline_EA:
_src->enc_format = enc_format;
status = encrypt_PGP_inline(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;
}
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, 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);
// Do similar for extra key list...
_cleanup_src(src, added_key_to_real_src);
return status;
enomem:
status = PEP_OUT_OF_MEMORY;
pEp_error:
free_stringlist(keys);
free_message(msg);
if (_src && _src != src)
free_message(_src);
_cleanup_src(src, added_key_to_real_src);
return status;
}
DYNAMIC_API PEP_STATUS encrypt_message_and_add_priv_key(
PEP_SESSION session,
message *src,
message **dst,
const char* to_fpr,
PEP_enc_format enc_format,
PEP_encrypt_flags_t flags
)
{
assert(session);
assert(src);
assert(dst);
assert(to_fpr);
if (!session || !src || !dst || !to_fpr)
return PEP_ILLEGAL_VALUE;
if (enc_format == PEP_enc_none)
return PEP_ILLEGAL_VALUE;
if (src->cc || src->bcc)
return PEP_ILLEGAL_VALUE;
if (!src->to || src->to->next)
return PEP_ILLEGAL_VALUE;
if (!src->from->address || !src->to->ident || !src->to->ident->address)
return PEP_ILLEGAL_VALUE;
if (strcasecmp(src->from->address, src->to->ident->address) != 0)
return PEP_ILLEGAL_VALUE;
stringlist_t* keys = NULL;
char* own_id = NULL;
char* default_id = NULL;
pEp_identity* own_identity = NULL;
char* own_private_fpr = NULL;
char* priv_key_data = NULL;
PEP_STATUS status = get_default_own_userid(session, &own_id);
if (!own_id)
return PEP_UNKNOWN_ERROR; // Probably a DB error at this point
if (src->from->user_id) {
if (strcmp(src->from->user_id, own_id) != 0) {
status = get_userid_alias_default(session, src->from->user_id, &default_id);
if (status != PEP_STATUS_OK || !default_id || strcmp(default_id, own_id) != 0) {
status = PEP_ILLEGAL_VALUE;
goto pEp_free;
}
}
}
// Ok, we are at least marginally sure the initial stuff is ok.
// Let's get our own, normal identity
own_identity = identity_dup(src->from);
status = myself(session, own_identity);
if (status != PEP_STATUS_OK)
goto pEp_free;
// is a passphrase needed?
status = probe_encrypt(session, own_identity->fpr);
if (failed_test(status))
goto pEp_free;
// Ok, now we know the address is an own address. All good. Then...
own_private_fpr = own_identity->fpr;
own_identity->fpr = strdup(to_fpr);
status = get_trust(session, own_identity);
if (status != PEP_STATUS_OK) {
if (status == PEP_CANNOT_FIND_IDENTITY)
status = PEP_ILLEGAL_VALUE;
goto pEp_free;
}
if ((own_identity->comm_type & PEP_ct_confirmed) != PEP_ct_confirmed) {
status = PEP_ILLEGAL_VALUE;
goto pEp_free;
}
// Ok, so all the things are now allowed.
// So let's get our own private key and roll with it.
size_t priv_key_size = 0;
status = export_secret_key(session, own_private_fpr, &priv_key_data,
&priv_key_size);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (!priv_key_data) {
status = PEP_CANNOT_EXPORT_KEY;
goto pEp_free;
}
// Ok, fine... let's encrypt yon blob
keys = new_stringlist(own_private_fpr);
if (!keys) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
stringlist_add(keys, to_fpr);
char* encrypted_key_text = NULL;
size_t encrypted_key_size = 0;
if (flags & PEP_encrypt_flag_force_unsigned)
status = encrypt_only(session, keys, priv_key_data, priv_key_size,
&encrypted_key_text, &encrypted_key_size);
else
status = encrypt_and_sign(session, keys, priv_key_data, priv_key_size,
&encrypted_key_text, &encrypted_key_size);
if (status == PEP_PASSPHRASE_REQUIRED || status == PEP_WRONG_PASSPHRASE) {
free(encrypted_key_text);
goto pEp_free;
}
else if (!encrypted_key_text) {
status = PEP_UNKNOWN_ERROR;
goto pEp_free;
}
else if (status != PEP_STATUS_OK) {
free(encrypted_key_text);
goto pEp_free; // FIXME - we need an error return overall
}
// We will have to delete this before returning, as we allocated it.
bloblist_t* created_bl = NULL;
bloblist_t* created_predecessor = NULL;
bloblist_t* old_head = NULL;
if (!src->attachments || src->attachments->value == NULL) {
if (src->attachments && src->attachments->value == NULL) {
old_head = src->attachments;
src->attachments = NULL;
}
src->attachments = new_bloblist(encrypted_key_text, encrypted_key_size,
"application/octet-stream",
"file://pEpkey.asc.pgp");
created_bl = src->attachments;
}
else {
bloblist_t* tmp = src->attachments;
while (tmp && tmp->next) {
tmp = tmp->next;
}
created_predecessor = tmp;
created_bl = bloblist_add(tmp,
encrypted_key_text, encrypted_key_size,
"application/octet-stream",
"file://pEpkey.asc.pgp");
}
if (!created_bl) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
// Ok, it's in there. Let's do this.
status = encrypt_message(session, src, keys, dst, enc_format, flags);
// Delete what we added to src
free_bloblist(created_bl);
if (created_predecessor)
created_predecessor->next = NULL;
else {
if (old_head)
src->attachments = old_head;
else
src->attachments = NULL;
}
pEp_free:
free(own_id);
free(default_id);
free(own_private_fpr);
free(priv_key_data);
free_identity(own_identity);
free_stringlist(keys);
return status;
}
DYNAMIC_API PEP_STATUS encrypt_message_for_self(
PEP_SESSION session,
pEp_identity* target_id,
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(target_id);
assert(src);
assert(dst);
assert(enc_format != PEP_enc_none);
if (!(session && target_id && src && dst && enc_format != PEP_enc_none))
return PEP_ILLEGAL_VALUE;
// if (src->dir == PEP_dir_incoming)
// return PEP_ILLEGAL_VALUE;
determine_encryption_format(src);
if (src->enc_format != PEP_enc_none)
return PEP_ILLEGAL_VALUE;
if (!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
}
}
if (!target_id->user_id || target_id->user_id[0] == '\0')
return PEP_CANNOT_FIND_IDENTITY;
if (target_id->address) {
status = myself(session, target_id);
if (status != PEP_STATUS_OK)
goto pEp_error;
}
else if (!target_id->fpr) {
return PEP_ILLEGAL_VALUE;
}
*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
// is a passphrase needed?
status = probe_encrypt(session, target_fpr);
if (failed_test(status))
return status;
keys = new_stringlist(target_fpr);
stringlist_t *_k = keys;
if (extra) {
_k = stringlist_append(_k, extra);
if (_k == NULL)
goto enomem;
}
/* KG: did we ever do this??? */
// if (!(flags & PEP_encrypt_flag_force_no_attached_key))
// _attach_key(session, target_fpr, src);
unsigned int major_ver, minor_ver;
pEp_version_major_minor(PEP_VERSION, &major_ver, &minor_ver);
_src = wrap_message_as_attachment(NULL, src, PEP_message_default, false, extra, major_ver, minor_ver);
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, PEP_message_default);
if (status == PEP_STATUS_OK || (src->longmsg && strstr(src->longmsg, "INNER")))
_cleanup_src(src, false);
break;
case PEP_enc_inline:
case PEP_enc_inline_EA:
_src->enc_format = enc_format;
status = encrypt_PGP_inline(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) {
if (!src->shortmsg) {
free(msg->shortmsg);
msg->shortmsg = _pEp_subj_copy();
assert(msg->shortmsg);
if (msg->shortmsg == NULL)
goto enomem;
}
else {
if (session->unencrypted_subject && (flags & PEP_encrypt_reencrypt)) {
free(msg->shortmsg);
msg->shortmsg = strdup(src->shortmsg);
}
}
if (_src->id) {
msg->id = strdup(_src->id);
assert(msg->id);
if (msg->id == NULL)
goto enomem;
}
decorate_message(msg, PEP_rating_undefined, NULL, true, true);
}
*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 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;