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.
 
 
 
 

4864 lines
150 KiB

// This file is under GNU General Public License 3.0
// see LICENSE.txt
#include "pEp_internal.h"
#include "message_api.h"
#include "platform.h"
#include "mime.h"
#include "blacklist.h"
#include "baseprotocol.h"
#include "KeySync_fsm.h"
#include "base64.h"
#include "resource_id.h"
#include <assert.h>
#include <string.h>
#include <stdlib.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 bool is_wrapper(message* src)
{
bool retval = false;
if (src) {
unsigned char pEpstr[] = PEP_SUBJ_STRING;
if (is_a_pEpmessage(src) || (src->shortmsg == NULL || strcmp(src->shortmsg, "pEp") == 0 ||
_unsigned_signed_strcmp(pEpstr, src->shortmsg, PEP_SUBJ_BYTELEN) == 0) ||
(strcmp(src->shortmsg, "p=p") == 0)) {
char* plaintext = src->longmsg;
if (plaintext) {
const char *line_end = strchr(plaintext, '\n');
if (line_end != NULL) {
size_t n = line_end - plaintext;
char* copycat = calloc(n + 1, 1);
if (copycat) {
strlcpy(copycat, plaintext, n+1);
if (strstr(copycat, PEP_MSG_WRAP_KEY) && strstr(copycat, "OUTER"))
retval = true;
free(copycat);
}
}
}
}
}
return retval;
}
/*
* static stringpair_t* search_optfields(const message* msg, const char* key) {
* if (msg && key) {
* stringpair_list_t* opt_fields = msg->opt_fields;
*
* const stringpair_list_t* curr;
*
* for (curr = opt_fields; curr && curr->value; curr = curr->next) {
* if (curr->value->key) {
* if (strcasecmp(curr->value->key, key) == 0)
* return curr->value;
* }
* }
* }
* return NULL;
* }
*/
static char * keylist_to_string(const stringlist_t *keylist)
{
if (keylist) {
size_t size = stringlist_length(keylist);
const stringlist_t *_kl;
for (_kl = keylist; _kl && _kl->value; _kl = _kl->next) {
size += strlen(_kl->value);
}
char *result = calloc(size, 1);
if (result == NULL)
return NULL;
char *_r = result;
for (_kl = keylist; _kl && _kl->value; _kl = _kl->next) {
_r = stpcpy(_r, _kl->value);
if (_kl->next && _kl->next->value)
_r = stpcpy(_r, ",");
}
return result;
}
else {
return NULL;
}
}
static const char * rating_to_string(PEP_rating rating)
{
switch (rating) {
case PEP_rating_cannot_decrypt:
return "cannot_decrypt";
case PEP_rating_have_no_key:
return "have_no_key";
case PEP_rating_unencrypted:
return "unencrypted";
case PEP_rating_unencrypted_for_some: // don't use this any more
return "undefined";
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);
}
}
}
static 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);
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) {
if (!attachment)
return NULL;
message* _envelope = envelope;
PEP_STATUS status = PEP_STATUS_OK;
replace_opt_field(attachment, "X-pEp-Version", PEP_VERSION, true);
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";
}
attachment->longmsg = encapsulate_message_wrap_info(inner_type_string, attachment->longmsg);
_envelope->longmsg = encapsulate_message_wrap_info("OUTER", _envelope->longmsg);
}
else if (_envelope) {
_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;
}
/* Turn message into a MIME-blob */
status = _mime_encode_message_internal(attachment, false, &message_text, true);
if (status != PEP_STATUS_OK)
goto enomem;
size_t message_len = strlen(message_text);
bloblist_t* message_blob = new_bloblist(message_text, message_len,
"message/rfc822", NULL);
_envelope->attachments = message_blob;
if (keep_orig_subject && attachment->shortmsg)
_envelope->shortmsg = strdup(attachment->shortmsg);
return _envelope;
enomem:
if (!envelope) {
free_message(_envelope);
}
return NULL;
}
static PEP_STATUS update_identity_recip_list(PEP_SESSION session,
identity_list* list) {
PEP_STATUS status = PEP_STATUS_OK;
if (!session)
return PEP_UNKNOWN_ERROR;
identity_list* id_list_ptr = NULL;
for (id_list_ptr = list; id_list_ptr; id_list_ptr = id_list_ptr->next) {
pEp_identity* curr_identity = id_list_ptr->ident;
if (curr_identity) {
if (!is_me(session, curr_identity)) {
char* name_bak = curr_identity->username;
curr_identity->username = NULL;
status = update_identity(session, curr_identity);
if (name_bak &&
(EMPTYSTR(curr_identity->username) || strcmp(name_bak, curr_identity->username) != 0)) {
free(curr_identity->username);
curr_identity->username = name_bak;
}
}
else
status = _myself(session, curr_identity, false, false, true);
if (status == PEP_ILLEGAL_VALUE || status == PEP_OUT_OF_MEMORY)
return status;
}
}
return PEP_STATUS_OK;
}
static PEP_STATUS encrypt_PGP_MIME(
PEP_SESSION session,
const message *src,
stringlist_t *keys,
message *dst,
PEP_encrypt_flags_t flags
)
{
PEP_STATUS status = PEP_STATUS_OK;
bool free_ptext = false;
char *ptext = NULL;
char *ctext = NULL;
char *mimetext = NULL;
size_t csize;
assert(dst->longmsg == NULL);
dst->enc_format = PEP_enc_PGP_MIME;
if (src->shortmsg)
dst->shortmsg = strdup(src->shortmsg);
message *_src = calloc(1, sizeof(message));
assert(_src);
if (_src == NULL)
goto enomem;
// _src->longmsg = ptext;
_src->longmsg = src->longmsg;
_src->longmsg_formatted = src->longmsg_formatted;
_src->attachments = src->attachments;
_src->enc_format = PEP_enc_none;
bool mime_encode = !is_wrapper(_src);
status = _mime_encode_message_internal(_src, true, &mimetext, mime_encode);
assert(status == PEP_STATUS_OK);
if (status != PEP_STATUS_OK)
goto pEp_error;
if (free_ptext){
free(ptext);
free_ptext=0;
}
free(_src);
_src = NULL;
assert(mimetext);
if (mimetext == NULL)
goto pEp_error;
if (flags & PEP_encrypt_flag_force_unsigned)
status = encrypt_only(session, keys, mimetext, strlen(mimetext),
&ctext, &csize);
else
status = encrypt_and_sign(session, keys, mimetext, strlen(mimetext),
&ctext, &csize);
free(mimetext);
if (ctext == NULL)
goto pEp_error;
dst->longmsg = strdup("this message was encrypted with p≡p "
"https://pEp-project.org");
assert(dst->longmsg);
if (dst->longmsg == NULL)
goto enomem;
char *v = strdup("Version: 1");
assert(v);
if (v == NULL)
goto enomem;
bloblist_t *_a = new_bloblist(v, strlen(v), "application/pgp-encrypted", NULL);
if (_a == NULL)
goto enomem;
dst->attachments = _a;
_a = bloblist_add(_a, ctext, csize, "application/octet-stream",
"file://msg.asc");
if (_a == NULL)
goto enomem;
return PEP_STATUS_OK;
enomem:
status = PEP_OUT_OF_MEMORY;
pEp_error:
if (free_ptext)
free(ptext);
free(_src);
free(ctext);
return status;
}
static 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 ||
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 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;
}
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_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;
}
// Internal function WARNING:
// Only call this on an ident that might have its FPR set from retrieval!
// (or on one without an fpr)
// We do not want myself() setting the fpr here.
static PEP_comm_type _get_comm_type(
PEP_SESSION session,
PEP_comm_type max_comm_type,
pEp_identity *ident
)
{
PEP_STATUS status = PEP_STATUS_OK;
if (max_comm_type == PEP_ct_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, 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) {
int 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
)
{
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(session, blob_value, blob_size, &local_private_idents);
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:
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;
PEP_STATUS status = export_key(session, fpr, &keydata, &size);
assert(status == PEP_STATUS_OK);
if (status != PEP_STATUS_OK)
return status;
assert(size);
bloblist_t *bl = bloblist_add(msg->attachments, keydata, size, "application/pgp-keys",
"file://pEpkey.asc");
if (msg->attachments == NULL && bl)
msg->attachments = bl;
return PEP_STATUS_OK;
}
#define ONE_WEEK (7*24*3600)
void attach_own_key(PEP_SESSION session, message *msg)
{
assert(session);
assert(msg);
if (msg->dir == PEP_dir_incoming)
return;
assert(msg->from && msg->from->fpr);
if (msg->from == NULL || msg->from->fpr == NULL)
return;
if(_attach_key(session, msg->from->fpr, msg) != PEP_STATUS_OK)
return;
char *revoked_fpr = NULL;
uint64_t revocation_date = 0;
if(get_revoked(session, msg->from->fpr,
&revoked_fpr, &revocation_date) == PEP_STATUS_OK &&
revoked_fpr != NULL)
{
time_t now = time(NULL);
if (now < (time_t)revocation_date + ONE_WEEK)
{
_attach_key(session, revoked_fpr, msg);
}
}
free(revoked_fpr);
}
PEP_cryptotech determine_encryption_format(message *msg)
{
assert(msg);
if (is_PGP_message_text(msg->longmsg)) {
msg->enc_format = PEP_enc_pieces;
return PEP_crypt_OpenPGP;
}
else if (msg->attachments && msg->attachments->next &&
is_mime_type(msg->attachments, "application/pgp-encrypted") &&
is_PGP_message_text(msg->attachments->next->value)
) {
msg->enc_format = PEP_enc_PGP_MIME;
return PEP_crypt_OpenPGP;
}
else if (msg->attachments && msg->attachments->next &&
is_mime_type(msg->attachments->next, "application/pgp-encrypted") &&
is_PGP_message_text(msg->attachments->value)
) {
msg->enc_format = PEP_enc_PGP_MIME_Outlook1;
return PEP_crypt_OpenPGP;
}
else {
msg->enc_format = PEP_enc_none;
return PEP_crypt_none;
}
}
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;
}
}
}
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;
keys = new_stringlist(src->from->fpr);
if (keys == NULL)
goto enomem;
stringlist_t *_k = keys;
if (extra) {
_k = stringlist_append(_k, extra);
if (_k == NULL)
goto enomem;
}
bool dest_keys_found = true;
bool has_pEp_user = false;
PEP_comm_type max_comm_type = PEP_ct_pEp;
identity_list * _il = NULL;
if (enc_format != PEP_enc_none && (_il = src->bcc) && _il->ident)
// BCC limited support:
{
// - App splits mails with BCC in multiple mails.
// - Each email is encrypted separately
if(_il->next || (src->to && src->to->ident) || (src->cc && src->cc->ident))
{
// Only one Bcc with no other recipient allowed for now
return PEP_ILLEGAL_VALUE;
}
PEP_STATUS _status = PEP_STATUS_OK;
if (!is_me(session, _il->ident)) {
_status = update_identity(session, _il->ident);
if (_status == PEP_CANNOT_FIND_IDENTITY) {
_il->ident->comm_type = PEP_ct_key_not_found;
_status = PEP_STATUS_OK;
}
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_error;
}
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);
}
else
_status = myself(session, _il->ident);
if (_status != PEP_STATUS_OK) {
status = PEP_UNENCRYPTED;
goto pEp_error;
}
if (_il->ident->fpr && _il->ident->fpr[0]) {
_k = stringlist_add(_k, _il->ident->fpr);
if (_k == NULL)
goto enomem;
max_comm_type = _get_comm_type(session, max_comm_type,
_il->ident);
}
else {
dest_keys_found = false;
status = PEP_KEY_NOT_FOUND;
}
}
else // Non BCC
{
for (_il = src->to; _il && _il->ident; _il = _il->next) {
PEP_STATUS _status = PEP_STATUS_OK;
if (!is_me(session, _il->ident)) {
_status = update_identity(session, _il->ident);
if (_status == PEP_CANNOT_FIND_IDENTITY) {
_il->ident->comm_type = PEP_ct_key_not_found;
_status = PEP_STATUS_OK;
}
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_error;
}
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);
_status = bind_own_ident_with_contact_ident(session, src->from, _il->ident);
if (_status != PEP_STATUS_OK) {
status = PEP_UNKNOWN_DB_ERROR;
goto pEp_error;
}
}
else
_status = myself(session, _il->ident);
if (_status != PEP_STATUS_OK) {
status = PEP_UNENCRYPTED;
goto pEp_error;
}
if (_il->ident->fpr && _il->ident->fpr[0]) {
_k = stringlist_add(_k, _il->ident->fpr);
if (_k == NULL)
goto enomem;
max_comm_type = _get_comm_type(session, max_comm_type,
_il->ident);
}
else {
dest_keys_found = false;
status = PEP_KEY_NOT_FOUND;
}
}
for (_il = src->cc; _il && _il->ident; _il = _il->next) {
PEP_STATUS _status = PEP_STATUS_OK;
if (!is_me(session, _il->ident)) {
_status = update_identity(session, _il->ident);
if (_status == PEP_CANNOT_FIND_IDENTITY) {
_il->ident->comm_type = PEP_ct_key_not_found;
_status = PEP_STATUS_OK;
}
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_error;
}
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);
}
else
_status = myself(session, _il->ident);
if (_status != PEP_STATUS_OK)
{
status = PEP_UNENCRYPTED;
goto pEp_error;
}
if (_il->ident->fpr && _il->ident->fpr[0]) {
_k = stringlist_add(_k, _il->ident->fpr);
if (_k == NULL)
goto enomem;
max_comm_type = _get_comm_type(session, max_comm_type,
_il->ident);
}
else {
dest_keys_found = false;
}
}
}
if (enc_format == PEP_enc_none || !dest_keys_found ||
stringlist_length(keys) == 0 ||
_rating(max_comm_type) < PEP_rating_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 {
// FIXME - we need to deal with transport types (via flag)
if ((!force_v_1) && ((max_comm_type | PEP_ct_confirmed) == PEP_ct_pEp)) {
message_wrap_type 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);
if (!_src)
goto pEp_error;
}
else {
// hide subject
if (!session->unencrypted_subject) {
status = replace_subject(_src);
if (status == PEP_OUT_OF_MEMORY)
goto enomem;
}
if (!(flags & PEP_encrypt_flag_force_no_attached_key))
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);
break;
/* case PEP_enc_PEP:
// TODO: implement
NOT_IMPLEMENTED */
default:
assert(0);
status = PEP_ILLEGAL_VALUE;
goto pEp_error;
}
if (status == PEP_OUT_OF_MEMORY)
goto enomem;
if (status != PEP_STATUS_OK)
goto pEp_error;
}
free_stringlist(keys);
if (msg && msg->shortmsg == NULL) {
msg->shortmsg = strdup("");
assert(msg->shortmsg);
if (msg->shortmsg == NULL)
goto enomem;
}
if (msg) {
decorate_message(msg, PEP_rating_undefined, NULL, true, 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);
_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;
// 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 (!encrypted_key_text) {
status = PEP_UNKNOWN_ERROR;
goto pEp_free;
}
// 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
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);
_src = wrap_message_as_attachment(NULL, src, PEP_message_default, false);
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);
if (status == PEP_STATUS_OK || (src->longmsg && strstr(src->longmsg, "INNER")))
_cleanup_src(src, false);
break;
default:
assert(0);
status = PEP_ILLEGAL_VALUE;
goto pEp_error;
}
if (status == PEP_OUT_OF_MEMORY)
goto enomem;
if (status != PEP_STATUS_OK)
goto pEp_error;
if (msg && msg->shortmsg == NULL) {
msg->shortmsg = _pEp_subj_copy();
assert(msg->shortmsg);
if (msg->shortmsg == NULL)
goto enomem;
}
if (msg) {
if (_src->id) {
msg->id = strdup(_src->id);
assert(msg->id);
if (msg->id == NULL)
goto enomem;
}
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;
// status = set_identity(session, src->from);
// }
// return status;
// }
// return PEP_ILLEGAL_VALUE;
// }
static PEP_STATUS _get_detached_signature(message* msg,
bloblist_t** signature_blob) {
bloblist_t* attach_curr = msg->attachments;
*signature_blob = NULL;
while (attach_curr) {
if (attach_curr->mime_type &&
(strcasecmp(attach_curr->mime_type, "application/pgp-signature") == 0)) {
*signature_blob = attach_curr;
break;
}
attach_curr = attach_curr->next;
}
return PEP_STATUS_OK;
}
static PEP_STATUS _get_signed_text(const char* ptext, const size_t psize,
char** stext, size_t* ssize) {
char* signed_boundary = NULL;
char* signpost = strstr(ptext, "Content-Type: multipart/signed");
*ssize = 0;
*stext = NULL;
if (!signpost)
return PEP_UNKNOWN_ERROR;
char* curr_line = signpost;
// const char* end_text = ptext + psize;
const char* boundary_key = "boundary=";
const size_t BOUNDARY_KEY_SIZE = 9;
char* start_boundary = strstr(curr_line, boundary_key);
if (!start_boundary)
return PEP_UNKNOWN_ERROR;
start_boundary += BOUNDARY_KEY_SIZE;
bool quoted = (*start_boundary == '"');
if (quoted)
start_boundary++;
char* end_boundary = (quoted ? strstr(start_boundary, "\"") : strstr(start_boundary, ";")); // FIXME: third possiblity is CRLF, or?
if (!end_boundary)
return PEP_UNKNOWN_ERROR;
// Add space for the "--"
size_t boundary_strlen = (end_boundary - start_boundary) + 2;
signed_boundary = calloc(boundary_strlen + 1, 1);
assert(signed_boundary);
if (!signed_boundary)
return PEP_OUT_OF_MEMORY;
strlcpy(signed_boundary, "--", boundary_strlen + 1);
strlcat(signed_boundary, start_boundary, boundary_strlen + 1);
start_boundary = strstr(end_boundary, signed_boundary);
if (!start_boundary)
return PEP_UNKNOWN_ERROR;
start_boundary += boundary_strlen;
if (*start_boundary == '\r') {
if (*(start_boundary + 1) == '\n')
start_boundary += 2;
}
else if (*start_boundary == '\n')
start_boundary++;
end_boundary = strstr(start_boundary + boundary_strlen, signed_boundary);
if (!end_boundary)
return PEP_UNKNOWN_ERROR;
// See RFC3156 section 5...
end_boundary--;
if (*(end_boundary - 1) == '\r')
end_boundary--;
*ssize = end_boundary - start_boundary;
*stext = start_boundary;
free(signed_boundary);
return PEP_STATUS_OK;
}
static PEP_STATUS combine_keylists(PEP_SESSION session, stringlist_t** verify_in,
stringlist_t** keylist_in_out,
pEp_identity* from) {
if (!verify_in || !(*verify_in)) // this isn't really a problem.
return PEP_STATUS_OK;
stringlist_t* orig_verify = *verify_in;
stringlist_t* verify_curr = NULL;
stringlist_t* from_keys = NULL;
/* FIXME: what to do if head needs to be null */
PEP_STATUS status = find_keys(session, from->address, &from_keys);
stringlist_t* from_fpr_node = NULL;
stringlist_t* from_curr;
for (from_curr = from_keys; from_curr; from_curr = from_curr->next) {
for (verify_curr = orig_verify; verify_curr; verify_curr = verify_curr->next) {
if (from_curr->value && verify_curr->value &&
_same_fpr(from_curr->value, strlen(from_curr->value),
verify_curr->value, strlen(verify_curr->value))) {
from_fpr_node = from_curr;
break;
}
}
}
if (!from_fpr_node) {
status = PEP_KEY_NOT_FOUND;
goto free;
}
verify_curr = orig_verify;
/* put "from" signer at the beginning of the list */
if (!_same_fpr(orig_verify->value, strlen(orig_verify->value),
from_fpr_node->value, strlen(from_fpr_node->value))) {
orig_verify = stringlist_delete(orig_verify, from_fpr_node->value);
verify_curr = new_stringlist(from_fpr_node->value);
verify_curr->next = orig_verify;
}
if (keylist_in_out) {
/* append keylist to signers */
if (*keylist_in_out && (*keylist_in_out)->value) {
stringlist_t** tail_pp = &verify_curr->next;
while (*tail_pp) {
tail_pp = &((*tail_pp)->next);
}
stringlist_t* second_list = *keylist_in_out;
if (second_list) {
char* listhead_val = second_list->value;
if (!listhead_val || listhead_val[0] == '\0') {
/* remove head, basically. This can happen when,
for example, the signature is detached and
verification is not seen directly after
decryption, so no signer is presumed in
the first construction of the keylist */
*keylist_in_out = (*keylist_in_out)->next;
second_list->next = NULL;
free_stringlist(second_list);
}
}
*tail_pp = *keylist_in_out;
}
*keylist_in_out = verify_curr;
}
status = PEP_STATUS_OK;
free:
free_stringlist(from_keys);
return status;
}
static PEP_STATUS amend_rating_according_to_sender_and_recipients(
PEP_SESSION session,
PEP_rating *rating,
pEp_identity *sender,
stringlist_t *recipients) {
PEP_STATUS status = PEP_STATUS_OK;
if (*rating > PEP_rating_mistrust) {
if (recipients == NULL) {
*rating = PEP_rating_undefined;
return PEP_STATUS_OK;
}
char *fpr = recipients->value;
if (!(sender && sender->user_id && sender->user_id[0] && fpr && fpr[0])) {
*rating = PEP_rating_unreliable;
}
else {
pEp_identity *_sender = new_identity(sender->address, fpr,
sender->user_id, sender->username);
if (_sender == NULL)
return PEP_OUT_OF_MEMORY;
status = get_trust(session, _sender);
if (_sender->comm_type == PEP_ct_unknown) {
get_key_rating(session, fpr, &_sender->comm_type);
}
if (_sender->comm_type != PEP_ct_unknown) {
*rating = keylist_rating(session, recipients,