You cannot 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

7377 lines
240 KiB
C

/**
* @file message_api.c
* @brief implementation of pEp engine API for message handling and evaluation and related functions
* @license 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 "baseprotocol.h"
#include "KeySync_fsm.h"
#include "base64.h"
#include "resource_id.h"
#include "internal_format.h"
#include "keymanagement.h"
#include "sync_codec.h"
#include "distribution_codec.h"
#include "keymanagement_internal.h"
#include "group.h"
#include "group_internal.h"
#include "status_to_string.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;
/**
* @internal
*
* <!-- is_a_pEpmessage() -->
*
* @brief TODO
*
* @param[in] *msg const message
*
* @retval bool
*/
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;
}
/**
* @internal
*
* <!-- keylist_to_string() -->
*
* @brief TODO
*
* @param[in] *keylist const stringlist_t
*
*/
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;
}
}
/**
* @internal
*
* <!-- rating_to_string() -->
*
* @brief TODO
*
* @param[in] rating PEP_rating
*
*/
static const char * rating_to_string(PEP_rating rating)
{
switch (rating) {
case PEP_rating_undefined:
return "undefined";
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:
assert(0);
return "invalid rating (this should never happen)";
}
}
/**
* @internal
*
* <!-- _memnmemn() -->
*
* @brief TODO
*
* @param[in] *needle const char
* @param[in] needle_size size_t
* @param[in] *haystack const char
* @param[in] haystack_size size_t
*
*/
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;
}
}
/**
* @internal
*
* <!-- replace_opt_field() -->
*
* @brief TODO
*
* @param[in] *msg message
* @param[in] *name const char
* @param[in] *value const char
* @param[in] clobber bool
*
*/
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);
}
}
}
/**
* @internal
*
* <!-- sync_message_attached() -->
*
* @brief TODO
*
* @param[in] *msg message
*
* @retval bool
*/
static bool sync_message_attached(message *msg)
{
if (!(msg && msg->attachments))
return false;
for (bloblist_t *a = msg->attachments; a && a->value ; a = a->next) {
if (a->mime_type && strcasecmp(a->mime_type, "application/pEp.sync") == 0)
return true;
}
return false;
}
/**
* @internal
*
* <!-- set_receiverRating() -->
*
* @brief TODO
*
* @param[in] session session handle
* @param[in] *msg message
* @param[in] rating PEP_rating
*
* @retval PEP_STATUS_OK
* @retval PEP_ILLEGAL_VALUE illegal parameter values
* @retval PEP_OUT_OF_MEMORY out of memory
* @retval PEP_SYNC_NO_CHANNEL
* @retval any other value on error
*/
PEP_STATUS set_receiverRating(PEP_SESSION session, message *msg, PEP_rating rating)
{
if (!(session && msg && rating))
return PEP_ILLEGAL_VALUE;
if (!(msg->recv_by && msg->recv_by->fpr && msg->recv_by->fpr[0]))
return PEP_SYNC_NO_CHANNEL;
// don't add a second sync message
if (sync_message_attached(msg))
return PEP_STATUS_OK;
Sync_t *res = new_Sync_message(Sync_PR_keysync, KeySync_PR_receiverRating);
if (!res)
return PEP_OUT_OF_MEMORY;
res->choice.keysync.choice.receiverRating.rating = (Rating_t) rating;
char *payload;
size_t size;
PEP_STATUS status = encode_Sync_message(res, &payload, &size);
free_Sync_message(res);
if (status)
return status;
return base_decorate_message(session, msg, BASE_SYNC, payload, size, msg->recv_by->fpr);
}
/**
* @internal
*
* <!-- get_receiverRating() -->
*
* @brief TODO
*
* @param[in] session session handle
* @param[in] *msg message
* @param[in] *rating PEP_rating
*
* @retval PEP_STATUS_OK
* @retval PEP_ILLEGAL_VALUE illegal parameter values
* @retval PEP_SYNC_NO_CHANNEL
* @retval any other value on error
*/
PEP_STATUS get_receiverRating(PEP_SESSION session, message *msg, PEP_rating *rating)
{
if (!(session && msg && rating))
return PEP_ILLEGAL_VALUE;
*rating = PEP_rating_undefined;
size_t size;
const char *payload;
char *fpr;
PEP_STATUS status = base_extract_message(session, msg, BASE_SYNC, &size, &payload, &fpr);
if (status)
return status;
if (!fpr)
return PEP_SYNC_NO_CHANNEL;
bool own_key;
status = is_own_key(session, fpr, &own_key);
free(fpr);
if (status)
return status;
if (!own_key)
return PEP_SYNC_NO_CHANNEL;
// This only decodes the payload - there is no update_identity/myself shenanigans going on here
// (important for _decrypt_message - if it changes, this MUST be reflected in username caching
// by the caller)
Sync_t *res;
status = decode_Sync_message(payload, size, &res);
if (status)
return status;
if (!(res->present == Sync_PR_keysync && res->choice.keysync.present == KeySync_PR_receiverRating)) {
free_Sync_message(res);
return PEP_SYNC_NO_CHANNEL;
}
*rating = res->choice.keysync.choice.receiverRating.rating;
replace_opt_field(msg, "X-EncStatus", rating_to_string(*rating), true);
return PEP_STATUS_OK;
}
void decorate_message(
PEP_SESSION session,
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);
set_receiverRating(session, msg, rating);
}
if (keylist) {
char *_keylist = keylist_to_string(keylist);
replace_opt_field(msg, "X-KeyList", _keylist, clobber);
free(_keylist);
}
msg->rating = rating;
}
/**
* @internal
*
* <!-- _get_resource_ptr_noown() -->
*
* @brief TODO
*
* @param[in] *uri char
*
* @retval bool
*/
static char* _get_resource_ptr_noown(char* uri) {
char* uri_delim = strstr(uri, "://");
if (!uri_delim)
return uri;
else
return uri + 3;
}
/**
* @internal
*
* <!-- string_equality() -->
*
* @brief TODO
*
* @param[in] *s1 const char
* @param[in] *s2 const char
*
* @retval bool
*/
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;
}
/**
* @internal
*
* <!-- is_mime_type() -->
*
* @brief TODO
*
* @param[in] *bl const bloblist_t
* @param[in] *mt const char
*
* @retval bool
*/
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.
//
/**
* @internal
*
* <!-- is_fileending() -->
*
* @brief TODO
*
* @param[in] *bl const bloblist_t
* @param[in] *fe const char
*
*/
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;
}
/**
* @internal
*
* <!-- encapsulate_message_wrap_info() -->
*
* @brief TODO
*
* @param[in] *msg_wrap_info const char
* @param[in] *longmsg const char
*
*/
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;
}
/**
* @internal
*
* <!-- combine_short_and_long() -->
*
* @brief TODO
*
* @param[in] *shortmsg const char
* @param[in] *longmsg const char
*
*/
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;
}
/**
* @internal
*
* <!-- replace_subject() -->
*
* @brief TODO
*
* @param[in] *msg message
*
* @retval PEP_STATUS_OK
* @retval PEP_OUT_OF_MEMORY out of memory
*/
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;
}
/**
* @internal
*
* <!-- get_bitmask() -->
*
* @brief TODO
*
* @param[in] num_bits int
*
*/
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;
}
/**
* @internal
*
* <!-- get_base_36_rep() -->
*
* @brief TODO
*
* @param[in] value unsigned long long
* @param[in] num_sig_bits int
*
*/
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;
}
/**
* @internal
*
* <!-- message_id_prand_part() -->
*
* @brief TODO
*
*
*/
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);
}
/**
* @internal
*
* <!-- generate_message_id() -->
*
* @brief TODO
*
* @param[in] *msg message
*
* @retval PEP_STATUS_OK
* @retval PEP_ILLEGAL_VALUE illegal parameter values
* @retval PEP_OUT_OF_MEMORY out of memory
*/
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;
/* Look for the *last* occurrence of '@' within the string beginning at
from_addr; if no '@' exists or if it is at the very end then keep the
"localhost" default as domain. */
char *p;
char *domain_ptr = "localhost";
for (p = from_addr + strlen (from_addr); p >= from_addr; p --)
if (* p == '@')
{
if (p [1] != '\0')
domain_ptr = p + 1;
break;
}
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
*/
/**
* @internal
*
* <!-- get_data_from_encapsulated_line() -->
*
* @brief TODO
*
* @param[in] *plaintext const char
* @param[in] *key const char
* @param[in] keylen const size_t
* @param[in] **data char
* @param[in] **modified_msg char
*
* @retval PEP_STATUS_OK
* @retval PEP_OUT_OF_MEMORY out of memory
*/
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;
}
/**
* @internal
*
* <!-- separate_short_and_long() -->
*
* @brief TODO
*
* @param[in] *src const char
* @param[in] **shortmsg char
* @param[in] **msg_wrap_info char
* @param[in] **longmsg char
*
*/
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;
}
/**
* @internal
*
* <!-- copy_fields() -->
*
* @brief TODO
*
* @param[in] *dst message
* @param[in] *src const message
*
* @retval PEP_STATUS_OK
* @retval PEP_ILLEGAL_VALUE illegal parameter values
* @retval PEP_OUT_OF_MEMORY out of memory
*/
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
/**
* @internal
*
* <!-- extract_minimal_envelope() -->
*
* @brief TODO
*
* @param[in] *src const message
* @param[in] direct PEP_msg_direction
*
*/
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;
}
/**
* @internal
*
* <!-- clone_to_empty_message() -->
*
* @brief TODO
*
* @param[in] *src const message
*
*/
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;
}
/**
* @internal
*
* <!-- wrap_message_as_attachment() -->
*
* @brief TODO
*
* @param[in] *envelope message
* @param[in] *attachment message
* @param[in] wrap_type message_wrap_type
* @param[in] keep_orig_subject bool
* @param[in] *extra_keys stringlist_t
* @param[in] max_major unsignedint
* @param[in] max_minor unsignedint
*
*/
static PEP_STATUS wrap_message_as_attachment(message* envelope,
message* attachment, message** new_message, message_wrap_type wrap_type,
bool keep_orig_subject, stringlist_t* extra_keys,
unsigned int max_major, unsigned int max_minor) {
*new_message = NULL;
if (!attachment)
return PEP_ILLEGAL_VALUE;
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 pEp_error;
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\n"
"to view it, or better yet, consider using p≡p!\n"
);
}
if (max_major <= 0 || max_minor < 0) {
max_major = 1;
max_minor = 0;
}
// // I hate this. Wish it were extensible.
// // 2 to cover logs, one for period, one for null termination = + 4
// int buf_size = floor(log10(max_major)) + (max_minor == 0 ? 0 : floor(log10(max_minor))) + 4;
// char* msg_ver = (char*)calloc(buf_size, 1);
int buf_size = 100;
char msg_ver[100];
// if (!msg_ver)
// goto enomem;
snprintf(msg_ver, buf_size, "%d%s%d", max_major, ".", max_minor);
replace_opt_field(attachment, X_PEP_MSG_VER_KEY, msg_ver, true);
// free(msg_ver);
// 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 {
status = PEP_UNKNOWN_ERROR;
goto pEp_error;
}
if (!attachment->id || attachment->id[0] == '\0') {
free(attachment->id);
if (!_envelope->id) {
status = generate_message_id(_envelope);
if (status != PEP_STATUS_OK)
goto pEp_error;
}
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 pEp_error;
size_t message_len = strlen(message_text);
bloblist_t* message_blob = new_bloblist(message_text, message_len,
"message/rfc822", NULL);
if (!message_blob)
goto enomem;
_envelope->attachments = message_blob;
if (keep_orig_subject && attachment->shortmsg)
_envelope->shortmsg = strdup(attachment->shortmsg);
*new_message = _envelope;
return status;
enomem:
status = PEP_OUT_OF_MEMORY;
pEp_error:
if (!envelope) {
free_message(_envelope);
}
*new_message = NULL;
return status;
}
/**
* @internal
*
* <!-- encrypt_PGP_inline() -->
*
* @brief TODO
*
* @param[in] session PEP_SESSION
* @param[in] *src const message
* @param[in] *keys stringlist_t
* @param[in] *dst message
* @param[in] flags PEP_encrypt_flags_t
*
* @retval PEP_STATUS_OK
* @retval PEP_OUT_OF_MEMORY out of memory
* @retval any other value on error
*/
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 copied
if (src->shortmsg) {
dst->shortmsg = strdup(src->shortmsg);
assert(dst->shortmsg);
if (!dst->shortmsg)
return PEP_OUT_OF_MEMORY;
}
// id stays 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;
}
/**
* @internal
*
* <!-- encrypt_PGP_MIME() -->
*
* @brief TODO
*
* @param[in] session PEP_SESSION
* @param[in] *src const message
* @param[in] *keys stringlist_t
* @param[in] *dst message
* @param[in] flags PEP_encrypt_flags_t
* @param[in] wrap_type message_wrap_type
*
* @retval PEP_STATUS_OK
* @retval PEP_OUT_OF_MEMORY out of memory
* @retval any other value on error
*/
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;
}
*/
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);
}
/**
* @internal
*
* <!-- is_encrypted_attachment() -->
*
* @brief TODO
*
* @param[in] *blob const bloblist_t
*
* @retval bool
*/
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;
}
/**
* @internal
*
* <!-- is_encrypted_html_attachment() -->
*
* @brief TODO
*
* @param[in] *blob const bloblist_t
*
* @retval bool
*/
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;
}
/**
* @internal
*
* <!-- without_double_ending() -->
*
* @brief TODO
*
* @param[in] *filename const char
*
*/
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;
}
/**
* @internal
*
* <!-- decrypt_rating() -->
*
* @brief TODO
*
* @param[in] status PEP_STATUS
*
* @retval PEP_rating rating value for comm type ct
*/
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;
}
}
/**
* @internal
*
* <!-- key_rating() -->
*
* @brief TODO
*
* @param[in] session PEP_SESSION
* @param[in] *fpr const char
*
*/
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);
}
/**
* @internal
*
* <!-- worst_rating() -->
*
* @brief TODO
*
* @param[in] rating1 PEP_rating
* @param[in] rating2 PEP_rating
*
*/
static PEP_rating worst_rating(PEP_rating rating1, PEP_rating rating2) {
return ((rating1 < rating2) ? rating1 : rating2);
}
/**
* @internal
*
* <!-- keylist_rating() -->
*
* @brief TODO
*
* @param[in] session PEP_SESSION
* @param[in] *keylist stringlist_t
* @param[in] *sender_fpr char
* @param[in] sender_rating PEP_rating
*
*/
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.
/**
* @internal
*
* <!-- _get_comm_type() -->
*
* @brief TODO
*
* @param[in] session PEP_SESSION
* @param[in] max_comm_type PEP_comm_type
* @param[in] *ident pEp_identity
*
*/
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;
}
}
/**
* @internal
*
* <!-- _get_comm_type_preview() -->
*
* @brief TODO
*
* @param[in] session PEP_SESSION
* @param[in] max_comm_type PEP_comm_type
* @param[in] *ident pEp_identity
*
*/
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);
// }
// }
/**
* @internal
*
* <!-- is_key() -->
*
* @brief TODO
*
* @param[in] *bl const bloblist_t
*
* @retval bool
*/
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;
// }
// }
// }
/**
* @internal
*
* <!-- compare_first_n_bytes() -->
*
* @brief TODO
*
* @param[in] *first const char
* @param[in] *second const char
* @param[in] n size_t
*
*/
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;
}
// is_pEp_msg isn't available on the message yet usually when we get it here,
// so we need it as a parameter
/**
* @internal
*
* <!-- import_attached_keys() -->
*
* @brief TODO
*
* <!-- @param[in] session session handle
* @param[in] msg message*
* @param[in] is_pEp_msg bool
* @param[in] private_idents identity_list**
* @param[in] imported_key_list stringlist_t**
* @param[in] changed_keys uint64_t* -->
* @param[in,out] pEp_sender_key char**
*
* @retval bool
*/
bool import_attached_keys(
PEP_SESSION session,
message *msg,
bool is_pEp_msg,
identity_list **private_idents,
stringlist_t** imported_key_list,
uint64_t* changed_keys,
char** pEp_sender_key
)
{
assert(session);
assert(msg);
if (session == NULL || msg == NULL)
return false;
char* _sender_key_retval = NULL;
stringlist_t* _keylist = imported_key_list ? *imported_key_list : NULL;
bool remove = false;
int i = 0;
bloblist_t* prev __attribute__ ((__unused__)) = 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;
bool pEp_sender_key_found = false;
stringlist_t* last_fpr_ptr = _keylist ? stringlist_get_tail(_keylist) : NULL;
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;
bool single_import = 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,
&_keylist,
changed_keys);
if (_keylist) {
stringlist_t* added_keys = last_fpr_ptr ? last_fpr_ptr->next : _keylist;
if (stringlist_length(added_keys) == 1)
single_import = true;
last_fpr_ptr = stringlist_get_tail(last_fpr_ptr ? last_fpr_ptr : _keylist);
}
//bloblist_t* to_delete = NULL;
const char* uri = 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:
// N.B. Removed, at least, until trustsync is in
//
// 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;
uri = bl->filename;
if (pEp_sender_key && is_pEp_msg && !EMPTYSTR(uri)) {
if (strcmp(uri, "file://sender_key.asc") == 0) {
if (!pEp_sender_key_found) {
pEp_sender_key_found = true;
if (single_import && last_fpr_ptr && !EMPTYSTR(last_fpr_ptr->value))
_sender_key_retval = strdup(last_fpr_ptr->value);
}
else {
// BAD. Someone messed up. ONE sender_key.asc.
free(_sender_key_retval);
_sender_key_retval = NULL;
}
}
}
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;
}
}
if (pEp_sender_key)
*pEp_sender_key = _sender_key_retval;
if (imported_key_list) {
if (!(*imported_key_list))
*imported_key_list = _keylist;
}
else
free_stringlist(_keylist);
return remove;
}
/**
* @internal
*
* <!-- _attach_key() -->
*
* @brief TODO
*
* @param[in] session PEP_SESSION
* @param[in] *fpr const char
* @param[in] *msg message
*
* @retval PEP_STATUS_OK
* @retval PEP_ILLEGAL_VALUE illegal parameter values
* @retval PEP_KEY_NOT_FOUND key not found
* @retval PEP_OUT_OF_MEMORY out of memory
* @retval any other value on error
*/
PEP_STATUS _attach_key(PEP_SESSION session, const char* fpr, message *msg, const char* filename)
{
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);
if (EMPTYSTR(filename))
filename = "file://pEpkey.asc";
bloblist_t *bl = bloblist_add(msg->attachments, keydata, size, "application/pgp-keys",
filename);
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, "file://sender_key.asc") != 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, "file://revoked_key.asc");
}
}
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;
}
}
/**
* @internal
*
* <!-- _cleanup_src() -->
*
* @brief TODO
*
* @param[in] *src message
* @param[in] remove_attached_key bool
*
*/
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;
}
}
}
/**
* @internal
*
* <!-- id_list_set_enc_format() -->
*
* @brief TODO
*
* @param[in] session session handle
* @param[in] *id_list identity_list
* @param[in] enc_format PEP_enc_format
*
* @retval PEP_STATUS_OK
* @retval PEP_ILLEGAL_VALUE illegal parameter value
* @retval PEP_CANNOT_SET_IDENTITY
*/
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
/**
* @internal
*
* <!-- update_encryption_format() -->
*
* @brief TODO
*
* @param[in] *id_list identity_list
* @param[in] *enc_format PEP_enc_format
*
*/
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;
}
}
}
/**
* @internal
*
* <!-- failed_test() -->
*
* @brief returns true if status indicates failure
*
* @param[in] status PEP_STATUS
*
* @retval bool
*/
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
/**
* @internal
*
* <!-- _update_state_for_ident_list() -->
*
* @brief TODO
*
* @param[in] session PEP_SESSION
* @param[in] *from_ident pEp_identity
* @param[in] *ident_list identity_list
* @param[in] **keylist stringlist_t
* @param[in] *max_comm_type PEP_comm_type
* @param[in] *max_version_major unsignedint
* @param[in] *max_version_minor unsignedint
* @param[in] *has_pEp_user bool
* @param[in] *dest_keys_found bool
* @param[in] suppress_update_for_bcc bool
*
* @retval PEP_STATUS_OK
* @retval PEP_ILLEGAL_VALUE illegal parameter values
* @retval PEP_UNKNOWN_DB_ERROR;
* @retval any other value on error
*/
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);
}
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;
}
static bool message_is_from_Sync(const message *src)
{
// from must be set
if (!src->from || EMPTYSTR(src->from->address))
return false;
// first to must be set
if (!src->to || !src->to->ident || EMPTYSTR(src->to->ident->address))
return false;
// second to must not be set
if (src->to->next)
return false;
// cc must not be set
if (src->cc && src->cc->ident)
return false;
// bcc must not be set
if (src->bcc && src->bcc->ident)
return false;
// from and to must use the same address
if (strcmp(src->from->address, src->to->ident->address) != 0)
return false;
// this is a message from Sync
return true;
}
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;
// Reset the message rating before doing anything...
src->rating = PEP_rating_undefined;
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;
// This is only local, the caller will keep the keylist, but we don't want to
// allow extra keys for non-org (e.g. business) accounts, so we set it to NULL
// locally so as not to use it if it's a non-org account (cheaper than checks
// everywhere)
if (!(src->from->flags & PEP_idf_org_ident)) {
// if this is not from pEp Sync
if (!message_is_from_Sync(src))
extra = NULL;
}
// 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;
// Will be NULL if this is a private account
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;