forked from pEp.foundation/pEpEngine
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.
5518 lines
179 KiB
5518 lines
179 KiB
// This file is under GNU General Public License 3.0
|
|
// see LICENSE.txt
|
|
|
|
#include "pEp_internal.h"
|
|
#include "message_api.h"
|
|
#include "pEpEngine.h"
|
|
|
|
#include "platform.h"
|
|
#include "mime.h"
|
|
#include "blacklist.h"
|
|
#include "baseprotocol.h"
|
|
#include "KeySync_fsm.h"
|
|
#include "base64.h"
|
|
#include "resource_id.h"
|
|
#include "internal_format.h"
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
|
|
|
|
// These are globals used in generating message IDs and should only be
|
|
// computed once, as they're either really constants or OS-dependent
|
|
|
|
int _pEp_rand_max_bits;
|
|
double _pEp_log2_36;
|
|
|
|
static bool is_a_pEpmessage(const message *msg)
|
|
{
|
|
for (stringpair_list_t *i = msg->opt_fields; i && i->value ; i=i->next) {
|
|
if (strcasecmp(i->value->key, "X-pEp-Version") == 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static char * keylist_to_string(const stringlist_t *keylist)
|
|
{
|
|
if (keylist) {
|
|
size_t size = stringlist_length(keylist);
|
|
|
|
const stringlist_t *_kl;
|
|
for (_kl = keylist; _kl && _kl->value; _kl = _kl->next) {
|
|
size += strlen(_kl->value);
|
|
}
|
|
|
|
char *result = calloc(size, 1);
|
|
if (result == NULL)
|
|
return NULL;
|
|
|
|
char *_r = result;
|
|
for (_kl = keylist; _kl && _kl->value; _kl = _kl->next) {
|
|
_r = stpcpy(_r, _kl->value);
|
|
if (_kl->next && _kl->next->value)
|
|
_r = stpcpy(_r, ",");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static const char * rating_to_string(PEP_rating rating)
|
|
{
|
|
switch (rating) {
|
|
case PEP_rating_cannot_decrypt:
|
|
return "cannot_decrypt";
|
|
case PEP_rating_have_no_key:
|
|
return "have_no_key";
|
|
case PEP_rating_unencrypted:
|
|
return "unencrypted";
|
|
case PEP_rating_unreliable:
|
|
return "unreliable";
|
|
case PEP_rating_reliable:
|
|
return "reliable";
|
|
case PEP_rating_trusted:
|
|
return "trusted";
|
|
case PEP_rating_trusted_and_anonymized:
|
|
return "trusted_and_anonymized";
|
|
case PEP_rating_fully_anonymous:
|
|
return "fully_anonymous";
|
|
case PEP_rating_mistrust:
|
|
return "mistrust";
|
|
case PEP_rating_b0rken:
|
|
return "b0rken";
|
|
case PEP_rating_under_attack:
|
|
return "under_attack";
|
|
default:
|
|
return "undefined";
|
|
}
|
|
}
|
|
|
|
bool _memnmemn(const char* needle,
|
|
size_t needle_size,
|
|
const char* haystack,
|
|
size_t haystack_size)
|
|
{
|
|
if (needle_size > haystack_size) {
|
|
return false;
|
|
}
|
|
else if (needle_size == 0) {
|
|
return true;
|
|
}
|
|
|
|
bool found = true;
|
|
const char* haystack_ptr = haystack;
|
|
unsigned int i = 0;
|
|
size_t remaining_hay = haystack_size;
|
|
for (i = 0; i < haystack_size && (remaining_hay >= needle_size); i++, haystack_ptr++) {
|
|
found = false;
|
|
const char* needle_ptr = needle;
|
|
if (*haystack_ptr == *needle) {
|
|
const char* haystack_tmp = haystack_ptr;
|
|
unsigned int j;
|
|
found = true;
|
|
for (j = 0; j < needle_size; j++) {
|
|
if (*needle_ptr++ != *haystack_tmp++) {
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
if (found)
|
|
break;
|
|
}
|
|
remaining_hay--;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
void add_opt_field(message *msg, const char *name, const char *value)
|
|
{
|
|
assert(msg && name && value);
|
|
|
|
if (msg && name && value) {
|
|
stringpair_t *pair = new_stringpair(name, value);
|
|
if (pair == NULL)
|
|
return;
|
|
|
|
stringpair_list_t *field = stringpair_list_add(msg->opt_fields, pair);
|
|
if (field == NULL)
|
|
{
|
|
free_stringpair(pair);
|
|
return;
|
|
}
|
|
|
|
if (msg->opt_fields == NULL)
|
|
msg->opt_fields = field;
|
|
}
|
|
}
|
|
|
|
void replace_opt_field(message *msg,
|
|
const char *name,
|
|
const char *value,
|
|
bool clobber)
|
|
{
|
|
assert(msg && name && value);
|
|
|
|
if (msg && name && value) {
|
|
stringpair_list_t* opt_fields = msg->opt_fields;
|
|
stringpair_t* pair = NULL;
|
|
|
|
if (opt_fields) {
|
|
while (opt_fields) {
|
|
pair = opt_fields->value;
|
|
if (pair && (strcmp(name, pair->key) == 0))
|
|
break;
|
|
|
|
pair = NULL;
|
|
opt_fields = opt_fields->next;
|
|
}
|
|
}
|
|
|
|
if (pair) {
|
|
if (clobber) {
|
|
free(pair->value);
|
|
pair->value = strdup(value);
|
|
}
|
|
}
|
|
else {
|
|
add_opt_field(msg, name, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void decorate_message(
|
|
message *msg,
|
|
PEP_rating rating,
|
|
stringlist_t *keylist,
|
|
bool add_version,
|
|
bool clobber
|
|
)
|
|
{
|
|
assert(msg);
|
|
|
|
if (add_version)
|
|
replace_opt_field(msg, "X-pEp-Version", PEP_VERSION, clobber);
|
|
|
|
if (rating != PEP_rating_undefined)
|
|
replace_opt_field(msg, "X-EncStatus", rating_to_string(rating), clobber);
|
|
|
|
if (keylist) {
|
|
char *_keylist = keylist_to_string(keylist);
|
|
replace_opt_field(msg, "X-KeyList", _keylist, clobber);
|
|
free(_keylist);
|
|
}
|
|
}
|
|
|
|
static char* _get_resource_ptr_noown(char* uri) {
|
|
char* uri_delim = strstr(uri, "://");
|
|
if (!uri_delim)
|
|
return uri;
|
|
else
|
|
return uri + 3;
|
|
}
|
|
|
|
static bool string_equality(const char *s1, const char *s2)
|
|
{
|
|
if (s1 == NULL || s2 == NULL)
|
|
return false;
|
|
|
|
assert(s1 && s2);
|
|
|
|
return strcmp(s1, s2) == 0;
|
|
}
|
|
|
|
static bool is_mime_type(const bloblist_t *bl, const char *mt)
|
|
{
|
|
assert(mt);
|
|
|
|
return bl && string_equality(bl->mime_type, mt);
|
|
}
|
|
|
|
//
|
|
// This function presumes the file ending is a proper substring of the
|
|
// filename (i.e. if bl->filename is "a.pgp" and fe is ".pgp", it will
|
|
// return true, but if bl->filename is ".pgp" and fe is ".pgp", it will
|
|
// return false. This is desired behaviour.
|
|
//
|
|
static bool is_fileending(const bloblist_t *bl, const char *fe)
|
|
{
|
|
assert(fe);
|
|
|
|
if (bl == NULL || bl->filename == NULL || fe == NULL || is_cid_uri(bl->filename))
|
|
return false;
|
|
|
|
assert(bl && bl->filename);
|
|
|
|
size_t fe_len = strlen(fe);
|
|
size_t fn_len = strlen(bl->filename);
|
|
|
|
if (fn_len <= fe_len)
|
|
return false;
|
|
|
|
assert(fn_len > fe_len);
|
|
|
|
return strcmp(bl->filename + (fn_len - fe_len), fe) == 0;
|
|
}
|
|
|
|
char * encapsulate_message_wrap_info(const char *msg_wrap_info, const char *longmsg)
|
|
{
|
|
assert(msg_wrap_info);
|
|
|
|
if (!msg_wrap_info) {
|
|
if (!longmsg)
|
|
return NULL;
|
|
else {
|
|
char *result = strdup(longmsg);
|
|
assert(result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (longmsg == NULL)
|
|
longmsg = "";
|
|
|
|
const char * const newlines = "\n\n";
|
|
const size_t NL_LEN = 2;
|
|
|
|
const size_t bufsize = PEP_MSG_WRAP_KEY_LEN + strlen(msg_wrap_info) + NL_LEN + strlen(longmsg) + 1;
|
|
char * ptext = calloc(bufsize, 1);
|
|
assert(ptext);
|
|
if (ptext == NULL)
|
|
return NULL;
|
|
|
|
strlcpy(ptext, PEP_MSG_WRAP_KEY, bufsize);
|
|
strlcat(ptext, msg_wrap_info, bufsize);
|
|
strlcat(ptext, newlines, bufsize);
|
|
strlcat(ptext, longmsg, bufsize);
|
|
|
|
return ptext;
|
|
}
|
|
|
|
static char * combine_short_and_long(const char *shortmsg, const char *longmsg)
|
|
{
|
|
assert(shortmsg);
|
|
|
|
unsigned char pEpstr[] = PEP_SUBJ_STRING;
|
|
|
|
// assert(strcmp(shortmsg, "pEp") != 0 && _unsigned_signed_strcmp(pEpstr, shortmsg, PEP_SUBJ_BYTELEN) != 0);
|
|
// in case encrypt_message() is called twice with a different passphrase this was done already
|
|
|
|
if (strcmp(shortmsg, "pEp") == 0 || _unsigned_signed_strcmp(pEpstr, shortmsg, PEP_SUBJ_BYTELEN) == 0) {
|
|
char *ptext = strdup(longmsg);
|
|
assert(ptext);
|
|
if (!ptext)
|
|
return NULL;
|
|
return ptext;
|
|
}
|
|
|
|
if (!shortmsg || strcmp(shortmsg, "pEp") == 0 ||
|
|
_unsigned_signed_strcmp(pEpstr, shortmsg, PEP_SUBJ_BYTELEN) == 0) {
|
|
if (!longmsg) {
|
|
return NULL;
|
|
}
|
|
else {
|
|
char *result = strdup(longmsg);
|
|
assert(result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (longmsg == NULL)
|
|
longmsg = "";
|
|
|
|
const char * const newlines = "\n\n";
|
|
const size_t NL_LEN = 2;
|
|
|
|
const size_t bufsize = PEP_SUBJ_KEY_LEN + strlen(shortmsg) + NL_LEN + strlen(longmsg) + 1;
|
|
char * ptext = calloc(bufsize, 1);
|
|
assert(ptext);
|
|
if (ptext == NULL)
|
|
return NULL;
|
|
|
|
strlcpy(ptext, PEP_SUBJ_KEY, bufsize);
|
|
strlcat(ptext, shortmsg, bufsize);
|
|
strlcat(ptext, newlines, bufsize);
|
|
strlcat(ptext, longmsg, bufsize);
|
|
|
|
return ptext;
|
|
}
|
|
|
|
static PEP_STATUS replace_subject(message* msg) {
|
|
unsigned char pEpstr[] = PEP_SUBJ_STRING;
|
|
if (msg->shortmsg && *(msg->shortmsg) != '\0') {
|
|
char* longmsg = combine_short_and_long(msg->shortmsg, msg->longmsg);
|
|
if (!longmsg)
|
|
return PEP_OUT_OF_MEMORY;
|
|
else {
|
|
free(msg->longmsg);
|
|
msg->longmsg = longmsg;
|
|
}
|
|
}
|
|
free(msg->shortmsg);
|
|
#ifdef WIN32
|
|
msg->shortmsg = strdup("pEp");
|
|
#else
|
|
msg->shortmsg = strdup((char*)pEpstr);
|
|
#endif
|
|
|
|
if (!msg->shortmsg)
|
|
return PEP_OUT_OF_MEMORY;
|
|
|
|
return PEP_STATUS_OK;
|
|
}
|
|
|
|
unsigned long long get_bitmask(int num_bits) {
|
|
if (num_bits <= 0)
|
|
return 0;
|
|
|
|
unsigned long long bitmask = 0;
|
|
int i;
|
|
for (i = 1; i < num_bits; i++) {
|
|
bitmask = bitmask << 1;
|
|
bitmask |= 1;
|
|
}
|
|
return bitmask;
|
|
}
|
|
|
|
static char* get_base_36_rep(unsigned long long value, int num_sig_bits) {
|
|
|
|
int bufsize = ((int) ceil((double) num_sig_bits / _pEp_log2_36)) + 1;
|
|
|
|
// based on
|
|
// https://en.wikipedia.org/wiki/Base36#C_implementation
|
|
// ok, we supposedly have a 64-bit kinda sorta random blob
|
|
const char base_36_symbols[37] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
char* retbuf = calloc(bufsize, 1);
|
|
assert(retbuf);
|
|
if (!retbuf)
|
|
return NULL;
|
|
|
|
int i = bufsize - 1; // (end index)
|
|
|
|
while (i > 0) {
|
|
retbuf[--i] = base_36_symbols[value % 36];
|
|
value /= 36;
|
|
}
|
|
|
|
return retbuf;
|
|
}
|
|
|
|
|
|
static char* message_id_prand_part(void) {
|
|
// RAND modulus
|
|
int num_bits = _pEp_rand_max_bits;
|
|
|
|
if (num_bits < 0)
|
|
return NULL;
|
|
|
|
const int DESIRED_BITS = 64;
|
|
|
|
num_bits = MIN(num_bits, DESIRED_BITS);
|
|
|
|
int i;
|
|
|
|
// at least 64 bits
|
|
unsigned long long bitmask = get_bitmask(num_bits);
|
|
|
|
unsigned long long output_value = 0;
|
|
|
|
i = DESIRED_BITS;
|
|
|
|
while (i > 0) {
|
|
int bitshift = 0;
|
|
int randval = rand();
|
|
unsigned long long temp_val = randval & bitmask;
|
|
|
|
output_value |= temp_val;
|
|
|
|
i -= MIN(num_bits, i);
|
|
|
|
bitshift = MIN(num_bits, i);
|
|
output_value <<= bitshift;
|
|
bitmask = get_bitmask(bitshift);
|
|
}
|
|
|
|
return get_base_36_rep(output_value, DESIRED_BITS);
|
|
}
|
|
|
|
static PEP_STATUS generate_message_id(message* msg) {
|
|
|
|
if (!msg || !msg->from || !msg->from->address)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
char* time_prefix = NULL;
|
|
char* random_id = NULL;
|
|
char* retval = NULL;
|
|
|
|
size_t buf_len = 2; // NUL + @
|
|
|
|
char* from_addr = msg->from->address;
|
|
char* domain_ptr = strstr(from_addr, "@");
|
|
if (!domain_ptr || *(domain_ptr + 1) == '\0')
|
|
domain_ptr = "localhost";
|
|
else
|
|
domain_ptr++;
|
|
|
|
buf_len += strlen(domain_ptr);
|
|
|
|
if (msg->id)
|
|
free(msg->id);
|
|
|
|
msg->id = NULL;
|
|
|
|
time_t curr_time = time(NULL);
|
|
|
|
time_prefix = get_base_36_rep(curr_time, (int) ceil(log2((double) curr_time)));
|
|
|
|
if (!time_prefix)
|
|
goto enomem;
|
|
|
|
buf_len += strlen(time_prefix);
|
|
|
|
random_id = message_id_prand_part();
|
|
|
|
if (!random_id)
|
|
goto enomem;
|
|
|
|
|
|
buf_len += strlen(random_id);
|
|
|
|
// make a new uuid - depending on rand() impl, time precision, etc,
|
|
// we may still not be unique. We'd better make sure. So.
|
|
char new_uuid[37];
|
|
pEpUUID uuid;
|
|
uuid_generate_random(uuid);
|
|
uuid_unparse_upper(uuid, new_uuid);
|
|
|
|
buf_len += strlen(new_uuid);
|
|
|
|
buf_len += 6; // "pEp" and 3 '.' chars
|
|
|
|
retval = calloc(buf_len, 1);
|
|
|
|
if (!retval)
|
|
goto enomem;
|
|
|
|
strlcpy(retval, "pEp.", buf_len);
|
|
strlcat(retval, time_prefix, buf_len);
|
|
strlcat(retval, ".", buf_len);
|
|
strlcat(retval, random_id, buf_len);
|
|
strlcat(retval, ".", buf_len);
|
|
strlcat(retval, new_uuid, buf_len);
|
|
strlcat(retval, "@", buf_len);
|
|
strlcat(retval, domain_ptr, buf_len);
|
|
|
|
msg->id = retval;
|
|
|
|
free(time_prefix);
|
|
free(random_id);
|
|
|
|
return PEP_STATUS_OK;
|
|
|
|
enomem:
|
|
free(time_prefix);
|
|
free(random_id);
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
/*
|
|
WARNING: For the moment, this only works for the first line of decrypted
|
|
plaintext because we don't need more. IF WE DO, THIS MUST BE EXPANDED, or
|
|
we need a delineated section to parse separately
|
|
|
|
Does case-insensitive compare of keys, so sending in a lower-cased
|
|
string constant saves a bit of computation
|
|
*/
|
|
static PEP_STATUS get_data_from_encapsulated_line(const char* plaintext, const char* key,
|
|
const size_t keylen, char** data,
|
|
char** modified_msg) {
|
|
char* _data = NULL;
|
|
char* _modified = NULL;
|
|
|
|
if (strncasecmp(plaintext, key, keylen) == 0) {
|
|
const char *line_end = strchr(plaintext, '\n');
|
|
|
|
if (line_end == NULL) {
|
|
_data = strdup(plaintext + keylen);
|
|
assert(_data);
|
|
if (_data == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
else {
|
|
size_t n = line_end - plaintext;
|
|
|
|
if (*(line_end - 1) == '\r')
|
|
_data = strndup(plaintext + keylen, n - (keylen + 1));
|
|
else
|
|
_data = strndup(plaintext + keylen, n - keylen);
|
|
assert(_data);
|
|
if (_data == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
|
|
while (*(plaintext + n) && (*(plaintext + n) == '\n' || *(plaintext + n) == '\r'))
|
|
++n;
|
|
|
|
if (*(plaintext + n)) {
|
|
_modified = strdup(plaintext + n);
|
|
assert(_modified);
|
|
if (_modified == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
}
|
|
*data = _data;
|
|
*modified_msg = _modified;
|
|
return PEP_STATUS_OK;
|
|
}
|
|
|
|
|
|
static int separate_short_and_long(const char *src, char **shortmsg, char** msg_wrap_info, char **longmsg)
|
|
{
|
|
char *_shortmsg = NULL;
|
|
char *_msg_wrap_info = NULL;
|
|
char *_longmsg = NULL;
|
|
|
|
assert(src);
|
|
assert(shortmsg);
|
|
assert(msg_wrap_info);
|
|
assert(longmsg);
|
|
|
|
if (src == NULL || shortmsg == NULL || msg_wrap_info == NULL || longmsg == NULL)
|
|
return -1;
|
|
|
|
*shortmsg = NULL;
|
|
*longmsg = NULL;
|
|
*msg_wrap_info = NULL;
|
|
|
|
// We generated the input here. If we ever need more than one header value to be
|
|
// encapsulated and hidden in the encrypted text, we will have to modify this.
|
|
// As is, we're either doing this with a version 1.0 client, in which case
|
|
// the only encapsulated header value is subject, or 2.0+, in which the
|
|
// message wrap info is the only encapsulated header value. If we need this
|
|
// to be more complex, we're going to have to do something more elegant
|
|
// and efficient.
|
|
PEP_STATUS status = get_data_from_encapsulated_line(src, PEP_SUBJ_KEY_LC,
|
|
PEP_SUBJ_KEY_LEN,
|
|
&_shortmsg, &_longmsg);
|
|
|
|
if (_shortmsg) {
|
|
if (status == PEP_STATUS_OK)
|
|
*shortmsg = _shortmsg;
|
|
else
|
|
goto enomem;
|
|
}
|
|
else {
|
|
status = get_data_from_encapsulated_line(src, PEP_MSG_WRAP_KEY_LC,
|
|
PEP_MSG_WRAP_KEY_LEN,
|
|
&_msg_wrap_info, &_longmsg);
|
|
if (_msg_wrap_info) {
|
|
if (status == PEP_STATUS_OK)
|
|
*msg_wrap_info = _msg_wrap_info;
|
|
else
|
|
goto enomem;
|
|
}
|
|
}
|
|
|
|
// If there was no secret data hiding in the first line...
|
|
if (!_shortmsg && !_msg_wrap_info) {
|
|
_longmsg = strdup(src);
|
|
assert(_longmsg);
|
|
if (_longmsg == NULL)
|
|
goto enomem;
|
|
}
|
|
|
|
*longmsg = _longmsg;
|
|
|
|
return 0;
|
|
|
|
enomem:
|
|
free(_shortmsg);
|
|
free(_msg_wrap_info);
|
|
free(_longmsg);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static PEP_STATUS copy_fields(message *dst, const message *src)
|
|
{
|
|
assert(dst);
|
|
assert(src);
|
|
|
|
if(!(dst && src))
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
free_timestamp(dst->sent);
|
|
dst->sent = NULL;
|
|
if (src->sent) {
|
|
dst->sent = timestamp_dup(src->sent);
|
|
if (dst->sent == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_timestamp(dst->recv);
|
|
dst->recv = NULL;
|
|
if (src->recv) {
|
|
dst->recv = timestamp_dup(src->recv);
|
|
if (dst->recv == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_identity(dst->from);
|
|
dst->from = NULL;
|
|
if (src->from) {
|
|
dst->from = identity_dup(src->from);
|
|
if (dst->from == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_identity_list(dst->to);
|
|
dst->to = NULL;
|
|
if (src->to && src->to->ident) {
|
|
dst->to = identity_list_dup(src->to);
|
|
if (dst->to == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_identity(dst->recv_by);
|
|
dst->recv_by = NULL;
|
|
if (src->recv_by) {
|
|
dst->recv_by = identity_dup(src->recv_by);
|
|
if (dst->recv_by == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_identity_list(dst->cc);
|
|
dst->cc = NULL;
|
|
if (src->cc && src->cc->ident) {
|
|
dst->cc = identity_list_dup(src->cc);
|
|
if (dst->cc == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_identity_list(dst->bcc);
|
|
dst->bcc = NULL;
|
|
if (src->bcc && src->bcc->ident) {
|
|
dst->bcc = identity_list_dup(src->bcc);
|
|
if (dst->bcc == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_identity_list(dst->reply_to);
|
|
dst->reply_to = NULL;
|
|
if (src->reply_to && src->reply_to->ident) {
|
|
dst->reply_to = identity_list_dup(src->reply_to);
|
|
if (dst->reply_to == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_stringlist(dst->in_reply_to);
|
|
dst->in_reply_to = NULL;
|
|
if (src->in_reply_to && src->in_reply_to->value) {
|
|
dst->in_reply_to = stringlist_dup(src->in_reply_to);
|
|
if (dst->in_reply_to == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_stringlist(dst->references);
|
|
dst->references = NULL;
|
|
if (src->references) {
|
|
dst->references = stringlist_dup(src->references);
|
|
if (dst->references == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_stringlist(dst->keywords);
|
|
dst->keywords = NULL;
|
|
if (src->keywords && src->keywords->value) {
|
|
dst->keywords = stringlist_dup(src->keywords);
|
|
if (dst->keywords == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free(dst->comments);
|
|
dst->comments = NULL;
|
|
if (src->comments) {
|
|
dst->comments = strdup(src->comments);
|
|
assert(dst->comments);
|
|
if (dst->comments == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
free_stringpair_list(dst->opt_fields);
|
|
dst->opt_fields = NULL;
|
|
if (src->opt_fields) {
|
|
dst->opt_fields = stringpair_list_dup(src->opt_fields);
|
|
if (dst->opt_fields == NULL)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return PEP_STATUS_OK;
|
|
}
|
|
|
|
// FIXME: error mem leakage
|
|
static message* extract_minimal_envelope(const message* src,
|
|
PEP_msg_direction direct) {
|
|
|
|
message* envelope = new_message(direct);
|
|
if (!envelope)
|
|
return NULL;
|
|
|
|
envelope->shortmsg = _pEp_subj_copy();
|
|
if (!envelope->shortmsg)
|
|
goto enomem;
|
|
|
|
if (src->from) {
|
|
envelope->from = identity_dup(src->from);
|
|
if (!envelope->from)
|
|
goto enomem;
|
|
}
|
|
|
|
if (src->to) {
|
|
envelope->to = identity_list_dup(src->to);
|
|
if (!envelope->to)
|
|
goto enomem;
|
|
}
|
|
|
|
if (src->cc) {
|
|
envelope->cc = identity_list_dup(src->cc);
|
|
if (!envelope->cc)
|
|
goto enomem;
|
|
}
|
|
|
|
if (src->bcc) {
|
|
envelope->bcc = identity_list_dup(src->bcc);
|
|
if (!envelope->bcc)
|
|
goto enomem;
|
|
}
|
|
|
|
// For Outlook Force-Encryption
|
|
// const char* pull_keys[] = {"pEp-auto-consume",
|
|
// "pEp-force-protection",
|
|
// "X-pEp-Never-Unsecure"};
|
|
// int pull_keys_len = 3; // UPDATE WHEN MORE ADDED ABOVE
|
|
//
|
|
// int i = 0;
|
|
// stringpair_t* opt_add = NULL;
|
|
// for( ; i < pull_keys_len; i++) {
|
|
// opt_add = search_optfields(src, pull_keys[i]);
|
|
// stringpair_list_t* add_ptr = NULL;
|
|
// if (opt_add) {
|
|
// add_ptr = stringpair_list_add(src->opt_fields, stringpair_dup(opt_add));
|
|
// if (!add_ptr)
|
|
// goto enomem;
|
|
// }
|
|
// opt_add = NULL;
|
|
// add_ptr = NULL;
|
|
// }
|
|
|
|
envelope->enc_format = src->enc_format;
|
|
|
|
return envelope;
|
|
|
|
enomem:
|
|
free(envelope);
|
|
return NULL;
|
|
}
|
|
|
|
static message * clone_to_empty_message(const message * src)
|
|
{
|
|
PEP_STATUS status;
|
|
message * msg = NULL;
|
|
|
|
assert(src);
|
|
if (src == NULL)
|
|
return NULL;
|
|
|
|
msg = calloc(1, sizeof(message));
|
|
assert(msg);
|
|
if (msg == NULL)
|
|
goto enomem;
|
|
|
|
msg->dir = src->dir;
|
|
|
|
status = copy_fields(msg, src);
|
|
if (status != PEP_STATUS_OK)
|
|
goto enomem;
|
|
|
|
return msg;
|
|
|
|
enomem:
|
|
free_message(msg);
|
|
return NULL;
|
|
}
|
|
|
|
static message* wrap_message_as_attachment(message* envelope,
|
|
message* attachment, message_wrap_type wrap_type,
|
|
bool keep_orig_subject, stringlist_t* extra_keys,
|
|
unsigned int max_major, unsigned int max_minor) {
|
|
|
|
if (!attachment)
|
|
return NULL;
|
|
|
|
message* _envelope = envelope;
|
|
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
|
|
replace_opt_field(attachment, "X-pEp-Version", PEP_VERSION, true);
|
|
|
|
if (extra_keys) {
|
|
char* ex_keystr = stringlist_to_string(extra_keys);
|
|
if (ex_keystr)
|
|
add_opt_field(attachment, "X-pEp-extra-keys", ex_keystr);
|
|
}
|
|
|
|
if (!_envelope && (wrap_type != PEP_message_transport)) {
|
|
_envelope = extract_minimal_envelope(attachment, PEP_dir_outgoing);
|
|
status = generate_message_id(_envelope);
|
|
|
|
if (status != PEP_STATUS_OK)
|
|
goto enomem;
|
|
|
|
const char* inner_type_string = "";
|
|
switch (wrap_type) {
|
|
case PEP_message_key_reset:
|
|
inner_type_string = "KEY_RESET";
|
|
break;
|
|
default:
|
|
inner_type_string = "INNER";
|
|
}
|
|
if (max_major < 2 || (max_major == 2 && max_minor == 0)) {
|
|
attachment->longmsg = encapsulate_message_wrap_info(inner_type_string, attachment->longmsg);
|
|
_envelope->longmsg = encapsulate_message_wrap_info("OUTER", _envelope->longmsg);
|
|
}
|
|
else {
|
|
_envelope->longmsg = strdup(
|
|
"This message was encrypted with p≡p (https://pep.software). If you are seeing this message,\n"
|
|
"your client does not support raising message attachments. Please click on the message attachment to\n"
|
|
"to view it, or better yet, consider using p≡p!\n"
|
|
);
|
|
}
|
|
// 2.1, to replace the above
|
|
add_opt_field(attachment, X_PEP_MSG_WRAP_KEY, inner_type_string);
|
|
}
|
|
else if (_envelope) {
|
|
// 2.1 - how do we peel this particular union when we get there?
|
|
_envelope->longmsg = encapsulate_message_wrap_info("TRANSPORT", _envelope->longmsg);
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
|
|
if (!attachment->id || attachment->id[0] == '\0') {
|
|
free(attachment->id);
|
|
if (!_envelope->id) {
|
|
status = generate_message_id(_envelope);
|
|
|
|
if (status != PEP_STATUS_OK)
|
|
goto enomem;
|
|
}
|
|
|
|
attachment->id = strdup(_envelope->id);
|
|
}
|
|
|
|
char* message_text = NULL;
|
|
|
|
/* prevent introduction of pEp in inner message */
|
|
|
|
if (!attachment->shortmsg) {
|
|
attachment->shortmsg = strdup("");
|
|
if (!attachment->shortmsg)
|
|
goto enomem;
|
|
}
|
|
|
|
/* add sender fpr to inner message */
|
|
add_opt_field(attachment,
|
|
"X-pEp-Sender-FPR",
|
|
(attachment->_sender_fpr ? attachment->_sender_fpr : "")
|
|
);
|
|
|
|
/* Turn message into a MIME-blob */
|
|
status = mime_encode_message(attachment, false, &message_text, false);
|
|
|
|
if (status != PEP_STATUS_OK)
|
|
goto enomem;
|
|
|
|
size_t message_len = strlen(message_text);
|
|
|
|
bloblist_t* message_blob = new_bloblist(message_text, message_len,
|
|
"message/rfc822", NULL);
|
|
|
|
_envelope->attachments = message_blob;
|
|
if (keep_orig_subject && attachment->shortmsg)
|
|
_envelope->shortmsg = strdup(attachment->shortmsg);
|
|
return _envelope;
|
|
|
|
enomem:
|
|
if (!envelope) {
|
|
free_message(_envelope);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static PEP_STATUS encrypt_PGP_inline(
|
|
PEP_SESSION session,
|
|
const message *src,
|
|
stringlist_t *keys,
|
|
message *dst,
|
|
PEP_encrypt_flags_t flags
|
|
)
|
|
{
|
|
char *ctext = NULL;
|
|
size_t csize = 0;
|
|
|
|
PEP_STATUS status = encrypt_and_sign(session, keys, src->longmsg,
|
|
strlen(src->longmsg), &ctext, &csize);
|
|
if (status)
|
|
return status;
|
|
|
|
dst->enc_format = src->enc_format;
|
|
|
|
// shortmsg is being copied
|
|
if (src->shortmsg) {
|
|
dst->shortmsg = strdup(src->shortmsg);
|
|
assert(dst->shortmsg);
|
|
if (!dst->shortmsg)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// id is staying the same
|
|
if (src->id) {
|
|
dst->id = strdup(src->id);
|
|
assert(dst->id);
|
|
if (!dst->id)
|
|
return PEP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
char *_ctext = realloc(ctext, csize + 1);
|
|
assert(_ctext);
|
|
if (!_ctext)
|
|
return PEP_OUT_OF_MEMORY;
|
|
_ctext[csize] = 0;
|
|
|
|
dst->longmsg = _ctext;
|
|
|
|
dst->attachments = new_bloblist(NULL, 0, NULL, NULL);
|
|
if (!dst->attachments)
|
|
return PEP_OUT_OF_MEMORY;
|
|
|
|
bloblist_t *ad = dst->attachments;
|
|
|
|
if (!EMPTYSTR(src->longmsg_formatted)) {
|
|
status = encrypt_and_sign(session, keys, src->longmsg_formatted,
|
|
strlen(src->longmsg_formatted), &ctext, &csize);
|
|
if (status)
|
|
return status;
|
|
|
|
char *_ctext = realloc(ctext, csize + 1);
|
|
assert(_ctext);
|
|
if (!_ctext)
|
|
return PEP_OUT_OF_MEMORY;
|
|
_ctext[csize] = 0;
|
|
|
|
ad = bloblist_add(ad, _ctext, csize + 1, "text/html", NULL);
|
|
if (!ad)
|
|
return PEP_OUT_OF_MEMORY;
|
|
|
|
ad->disposition = PEP_CONTENT_DISP_INLINE;
|
|
}
|
|
|
|
if (src->attachments && src->attachments->value) {
|
|
bloblist_t *as;
|
|
for (as = src->attachments; as && as->value; as = as->next) {
|
|
char *value = NULL;
|
|
size_t size = 0;
|
|
if (src->enc_format == PEP_enc_inline_EA) {
|
|
status = encode_internal(as->value, as->size, as->mime_type,
|
|
&value, &size);
|
|
if (status)
|
|
return status;
|
|
if (!value) {
|
|
value = as->value;
|
|
size = as->size;
|
|
}
|
|
}
|
|
else {
|
|
value = as->value;
|
|
size = as->size;
|
|
}
|
|
status = encrypt_and_sign(session, keys, value, size, &ctext,
|
|
&csize);
|
|
if (value != as->value)
|
|
free(value);
|
|
if (status)
|
|
return status;
|
|
|
|
char *_ctext = realloc(ctext, csize + 1);
|
|
assert(_ctext);
|
|
if (!_ctext)
|
|
return PEP_OUT_OF_MEMORY;
|
|
_ctext[csize] = 0;
|
|
|
|
size_t len = strlen(as->filename);
|
|
char *filename = malloc(len + 5);
|
|
assert(filename);
|
|
if (!filename)
|
|
return PEP_OUT_OF_MEMORY;
|
|
|
|
memcpy(filename, as->filename, len);
|
|
memcpy(filename + len, ".pgp", 5);
|
|
|
|
ad = bloblist_add(ad, _ctext, csize + 1, "application/octet-stream", filename);
|
|
free(filename);
|
|
filename = NULL;
|
|
if (!ad)
|
|
return PEP_OUT_OF_MEMORY;
|
|
|
|
ad->disposition = as->disposition;
|
|
}
|
|
}
|
|
|
|
return PEP_STATUS_OK;
|
|
}
|
|
|
|
static PEP_STATUS encrypt_PGP_MIME(
|
|
PEP_SESSION session,
|
|
const message *src,
|
|
stringlist_t *keys,
|
|
message *dst,
|
|
PEP_encrypt_flags_t flags,
|
|
message_wrap_type wrap_type
|
|
)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
bool free_ptext = false;
|
|
char *ptext = NULL;
|
|
char *ctext = NULL;
|
|
char *mimetext = NULL;
|
|
size_t csize;
|
|
assert(dst->longmsg == NULL);
|
|
dst->enc_format = PEP_enc_PGP_MIME;
|
|
|
|
if (src->shortmsg)
|
|
dst->shortmsg = strdup(src->shortmsg);
|
|
|
|
message *_src = calloc(1, sizeof(message));
|
|
assert(_src);
|
|
if (_src == NULL)
|
|
goto enomem;
|
|
// _src->longmsg = ptext;
|
|
_src->longmsg = src->longmsg;
|
|
_src->longmsg_formatted = src->longmsg_formatted;
|
|
_src->attachments = src->attachments;
|
|
_src->enc_format = PEP_enc_none;
|
|
|
|
bool wrapped = (wrap_type != PEP_message_unwrapped);
|
|
status = mime_encode_message(_src, true, &mimetext, wrapped);
|
|
assert(status == PEP_STATUS_OK);
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_error;
|
|
|
|
if (free_ptext){
|
|
free(ptext);
|
|
free_ptext=0;
|
|
}
|
|
free(_src);
|
|
_src = NULL;
|
|
assert(mimetext);
|
|
if (mimetext == NULL)
|
|
goto pEp_error;
|
|
|
|
if (flags & PEP_encrypt_flag_force_unsigned)
|
|
status = encrypt_only(session, keys, mimetext, strlen(mimetext),
|
|
&ctext, &csize);
|
|
else
|
|
status = encrypt_and_sign(session, keys, mimetext, strlen(mimetext),
|
|
&ctext, &csize);
|
|
free(mimetext);
|
|
if (ctext == NULL || status)
|
|
goto pEp_error;
|
|
|
|
dst->longmsg = strdup("this message was encrypted with p≡p "
|
|
"https://pEp-project.org");
|
|
assert(dst->longmsg);
|
|
if (dst->longmsg == NULL)
|
|
goto enomem;
|
|
|
|
char *v = strdup("Version: 1");
|
|
assert(v);
|
|
if (v == NULL)
|
|
goto enomem;
|
|
|
|
bloblist_t *_a = new_bloblist(v, strlen(v), "application/pgp-encrypted", NULL);
|
|
if (_a == NULL)
|
|
goto enomem;
|
|
dst->attachments = _a;
|
|
|
|
_a = bloblist_add(_a, ctext, csize, "application/octet-stream",
|
|
"file://msg.asc");
|
|
if (_a == NULL)
|
|
goto enomem;
|
|
|
|
return PEP_STATUS_OK;
|
|
|
|
enomem:
|
|
status = PEP_OUT_OF_MEMORY;
|
|
|
|
pEp_error:
|
|
if (free_ptext)
|
|
free(ptext);
|
|
free(_src);
|
|
free(ctext);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
static bool _has_PGP_MIME_format(message* msg) {
|
|
if (!msg || !msg->attachments || !msg->attachments->next)
|
|
return false;
|
|
if (msg->attachments->next->next)
|
|
return false;
|
|
if (!msg->attachments->mime_type)
|
|
return false;
|
|
if (strcmp(msg->attachments->mime_type, "application/pgp-encrypted") != 0)
|
|
return false;
|
|
if (!msg->attachments->next->mime_type ||
|
|
strcmp(msg->attachments->next->mime_type, "application/octet-stream") != 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
*/
|
|
|
|
static inline PEP_rating _rating(PEP_comm_type ct)
|
|
{
|
|
if (ct == PEP_ct_unknown)
|
|
return PEP_rating_undefined;
|
|
|
|
else if (ct == PEP_ct_key_not_found)
|
|
return PEP_rating_have_no_key;
|
|
|
|
else if (ct == PEP_ct_compromised)
|
|
return PEP_rating_under_attack;
|
|
|
|
else if (ct == PEP_ct_mistrusted)
|
|
return PEP_rating_mistrust;
|
|
|
|
if (ct == PEP_ct_no_encryption || ct == PEP_ct_no_encrypted_channel ||
|
|
ct == PEP_ct_my_key_not_included)
|
|
return PEP_rating_unencrypted;
|
|
|
|
if (ct >= PEP_ct_confirmed_enc_anon)
|
|
return PEP_rating_trusted_and_anonymized;
|
|
|
|
else if (ct >= PEP_ct_strong_encryption)
|
|
return PEP_rating_trusted;
|
|
|
|
else if (ct >= PEP_ct_strong_but_unconfirmed && ct < PEP_ct_confirmed)
|
|
return PEP_rating_reliable;
|
|
|
|
else
|
|
return PEP_rating_unreliable;
|
|
}
|
|
|
|
DYNAMIC_API PEP_rating rating_from_comm_type(PEP_comm_type ct)
|
|
{
|
|
return _rating(ct);
|
|
}
|
|
|
|
static bool is_encrypted_attachment(const bloblist_t *blob)
|
|
{
|
|
assert(blob);
|
|
|
|
if (blob == NULL || blob->filename == NULL || is_cid_uri(blob->filename))
|
|
return false;
|
|
|
|
char *ext = strrchr(blob->filename, '.');
|
|
if (ext == NULL)
|
|
return false;
|
|
|
|
if (strcmp(blob->mime_type, "application/octet-stream") == 0) {
|
|
if (strcmp(ext, ".pgp") == 0 || strcmp(ext, ".gpg") == 0)
|
|
return true;
|
|
}
|
|
if (strcmp(ext, ".asc") == 0 && blob->size > 0) {
|
|
const char* pubk_needle = "BEGIN PGP PUBLIC KEY";
|
|
size_t pubk_needle_size = strlen(pubk_needle);
|
|
const char* privk_needle = "BEGIN PGP PRIVATE KEY";
|
|
size_t privk_needle_size = strlen(privk_needle);
|
|
|
|
if (!(_memnmemn(pubk_needle, pubk_needle_size, blob->value, blob->size)) &&
|
|
!(_memnmemn(privk_needle, privk_needle_size, blob->value, blob->size)))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool is_encrypted_html_attachment(const bloblist_t *blob)
|
|
{
|
|
assert(blob);
|
|
assert(blob->filename);
|
|
if (blob == NULL || blob->filename == NULL || is_cid_uri(blob->filename))
|
|
return false;
|
|
|
|
const char* bare_filename_ptr = _get_resource_ptr_noown(blob->filename);
|
|
bare_filename_ptr += strlen(bare_filename_ptr) - 15;
|
|
if (strncmp(bare_filename_ptr, "PGPexch.htm.", 12) == 0) {
|
|
if (strcmp(bare_filename_ptr + 11, ".pgp") == 0 ||
|
|
strcmp(bare_filename_ptr + 11, ".asc") == 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static char * without_double_ending(const char *filename)
|
|
{
|
|
assert(filename);
|
|
if (filename == NULL || is_cid_uri(filename))
|
|
return NULL;
|
|
|
|
char *ext = strrchr(filename, '.');
|
|
if (ext == NULL)
|
|
return NULL;
|
|
|
|
char *result = strndup(filename, ext - filename);
|
|
assert(result);
|
|
return result;
|
|
}
|
|
|
|
static PEP_rating decrypt_rating(PEP_STATUS status)
|
|
{
|
|
switch (status) {
|
|
case PEP_UNENCRYPTED:
|
|
case PEP_VERIFIED:
|
|
case PEP_VERIFY_NO_KEY:
|
|
case PEP_VERIFIED_AND_TRUSTED:
|
|
return PEP_rating_unencrypted;
|
|
|
|
case PEP_DECRYPTED:
|
|
case PEP_VERIFY_SIGNER_KEY_REVOKED:
|
|
case PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH:
|
|
return PEP_rating_unreliable;
|
|
|
|
case PEP_DECRYPTED_AND_VERIFIED:
|
|
return PEP_rating_reliable;
|
|
|
|
case PEP_DECRYPT_NO_KEY:
|
|
return PEP_rating_have_no_key;
|
|
|
|
case PEP_DECRYPT_WRONG_FORMAT:
|
|
case PEP_CANNOT_DECRYPT_UNKNOWN:
|
|
return PEP_rating_cannot_decrypt;
|
|
|
|
default:
|
|
return PEP_rating_undefined;
|
|
}
|
|
}
|
|
|
|
static PEP_rating key_rating(PEP_SESSION session, const char *fpr)
|
|
{
|
|
|
|
assert(session);
|
|
assert(fpr);
|
|
|
|
if (session == NULL || fpr == NULL)
|
|
return PEP_rating_undefined;
|
|
|
|
|
|
PEP_comm_type bare_comm_type = PEP_ct_unknown;
|
|
PEP_comm_type resulting_comm_type = PEP_ct_unknown;
|
|
PEP_STATUS status = get_key_rating(session, fpr, &bare_comm_type);
|
|
if (status != PEP_STATUS_OK)
|
|
return PEP_rating_undefined;
|
|
|
|
PEP_comm_type least_comm_type = PEP_ct_unknown;
|
|
least_trust(session, fpr, &least_comm_type);
|
|
|
|
if (least_comm_type == PEP_ct_unknown) {
|
|
resulting_comm_type = bare_comm_type;
|
|
} else if (least_comm_type < PEP_ct_strong_but_unconfirmed ||
|
|
bare_comm_type < PEP_ct_strong_but_unconfirmed) {
|
|
// take minimum if anything bad
|
|
resulting_comm_type = least_comm_type < bare_comm_type ?
|
|
least_comm_type :
|
|
bare_comm_type;
|
|
} else {
|
|
resulting_comm_type = least_comm_type;
|
|
}
|
|
return _rating(resulting_comm_type);
|
|
}
|
|
|
|
static PEP_rating worst_rating(PEP_rating rating1, PEP_rating rating2) {
|
|
return ((rating1 < rating2) ? rating1 : rating2);
|
|
}
|
|
|
|
static PEP_rating keylist_rating(PEP_SESSION session, stringlist_t *keylist, char* sender_fpr, PEP_rating sender_rating)
|
|
{
|
|
PEP_rating rating = sender_rating;
|
|
|
|
assert(keylist && keylist->value);
|
|
if (keylist == NULL || keylist->value == NULL)
|
|
return PEP_rating_undefined;
|
|
|
|
stringlist_t *_kl;
|
|
for (_kl = keylist; _kl && _kl->value; _kl = _kl->next) {
|
|
|
|
// Ignore own fpr
|
|
if(_same_fpr(sender_fpr, strlen(sender_fpr), _kl->value, strlen(_kl->value)))
|
|
continue;
|
|
|
|
PEP_rating _rating_ = key_rating(session, _kl->value);
|
|
|
|
if (_rating_ <= PEP_rating_mistrust)
|
|
return _rating_;
|
|
|
|
rating = worst_rating(rating, _rating_);
|
|
}
|
|
|
|
return rating;
|
|
}
|
|
|
|
// KB: Fixme - the first statement below is probably unnecessary now.
|
|
// Internal function WARNING:
|
|
// Should be called on ident that might have its FPR set from retrieval!
|
|
// (or on one without an fpr)
|
|
// We do not want myself() setting the fpr here.
|
|
//
|
|
// Cannot return passphrase statuses. No keygen or renewal allowed here.
|
|
static PEP_comm_type _get_comm_type(
|
|
PEP_SESSION session,
|
|
PEP_comm_type max_comm_type,
|
|
pEp_identity *ident
|
|
)
|
|
{
|
|
if (!ident)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
|
|
if (max_comm_type == PEP_ct_compromised)
|
|
return PEP_ct_compromised;
|
|
|
|
if (max_comm_type == PEP_ct_mistrusted)
|
|
return PEP_ct_mistrusted;
|
|
|
|
if (!is_me(session, ident)) {
|
|
status = update_identity(session, ident);
|
|
}
|
|
else {
|
|
status = _myself(session, ident, false, false, false, true);
|
|
}
|
|
|
|
if (status == PEP_STATUS_OK) {
|
|
if (ident->comm_type == PEP_ct_compromised)
|
|
return PEP_ct_compromised;
|
|
else if (ident->comm_type == PEP_ct_mistrusted)
|
|
return PEP_ct_mistrusted;
|
|
else
|
|
return MIN(max_comm_type, ident->comm_type);
|
|
}
|
|
else {
|
|
return PEP_ct_unknown;
|
|
}
|
|
}
|
|
|
|
static PEP_comm_type _get_comm_type_preview(
|
|
PEP_SESSION session,
|
|
PEP_comm_type max_comm_type,
|
|
pEp_identity *ident
|
|
)
|
|
{
|
|
assert(session);
|
|
assert(ident);
|
|
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
|
|
if (max_comm_type == PEP_ct_compromised)
|
|
return PEP_ct_compromised;
|
|
|
|
if (max_comm_type == PEP_ct_mistrusted)
|
|
return PEP_ct_mistrusted;
|
|
|
|
PEP_comm_type comm_type = PEP_ct_unknown;
|
|
if (ident && !EMPTYSTR(ident->address) && !EMPTYSTR(ident->user_id)) {
|
|
pEp_identity *ident2;
|
|
status = get_identity(session, ident->address, ident->user_id, &ident2);
|
|
comm_type = ident2 ? ident2->comm_type : PEP_ct_unknown;
|
|
free_identity(ident2);
|
|
|
|
if (status == PEP_STATUS_OK) {
|
|
if (comm_type == PEP_ct_compromised)
|
|
comm_type = PEP_ct_compromised;
|
|
else if (comm_type == PEP_ct_mistrusted)
|
|
comm_type = PEP_ct_mistrusted;
|
|
else
|
|
comm_type = _MIN(max_comm_type, comm_type);
|
|
}
|
|
else {
|
|
comm_type = PEP_ct_unknown;
|
|
}
|
|
}
|
|
return comm_type;
|
|
}
|
|
|
|
// static void free_bl_entry(bloblist_t *bl)
|
|
// {
|
|
// if (bl) {
|
|
// free(bl->value);
|
|
// free(bl->mime_type);
|
|
// free(bl->filename);
|
|
// free(bl);
|
|
// }
|
|
// }
|
|
|
|
static bool is_key(const bloblist_t *bl)
|
|
{
|
|
return (// workaround for Apple Mail bugs
|
|
(is_mime_type(bl, "application/x-apple-msg-attachment") &&
|
|
is_fileending(bl, ".asc")) ||
|
|
// as binary, by file name
|
|
((bl->mime_type == NULL ||
|
|
is_mime_type(bl, "application/octet-stream")) &&
|
|
(is_fileending(bl, ".pgp") || is_fileending(bl, ".gpg") ||
|
|
is_fileending(bl, ".key") || is_fileending(bl, ".asc"))) ||
|
|
// explicit mime type
|
|
is_mime_type(bl, "application/pgp-keys") ||
|
|
// as text, by file name
|
|
(is_mime_type(bl, "text/plain") &&
|
|
(is_fileending(bl, ".pgp") || is_fileending(bl, ".gpg") ||
|
|
is_fileending(bl, ".key") || is_fileending(bl, ".asc")))
|
|
);
|
|
}
|
|
|
|
// static void remove_attached_keys(message *msg)
|
|
// {
|
|
// if (msg) {
|
|
// bloblist_t *last = NULL;
|
|
// for (bloblist_t *bl = msg->attachments; bl && bl->value; ) {
|
|
// bloblist_t *next = bl->next;
|
|
//
|
|
// if (is_key(bl)) {
|
|
// if (last) {
|
|
// last->next = next;
|
|
// }
|
|
// else {
|
|
// msg->attachments = next;
|
|
// }
|
|
// free_bl_entry(bl);
|
|
// }
|
|
// else {
|
|
// last = bl;
|
|
// }
|
|
// bl = next;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
static bool compare_first_n_bytes(const char* first, const char* second, size_t n) {
|
|
size_t i;
|
|
for (i = 0; i < n; i++) {
|
|
char num1 = *first;
|
|
char num2 = *second;
|
|
|
|
if (num1 != num2)
|
|
return false;
|
|
|
|
if (num1 == '\0') {
|
|
if (num2 == '\0')
|
|
return true;
|
|
}
|
|
first++;
|
|
second++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool import_attached_keys(
|
|
PEP_SESSION session,
|
|
message *msg,
|
|
identity_list **private_idents,
|
|
stringlist_t** imported_key_list,
|
|
uint64_t* changed_keys
|
|
)
|
|
{
|
|
assert(session);
|
|
assert(msg);
|
|
|
|
if (session == NULL || msg == NULL)
|
|
return false;
|
|
|
|
bool remove = false;
|
|
|
|
int i = 0;
|
|
|
|
bloblist_t* prev = NULL;
|
|
|
|
bool do_not_advance = false;
|
|
const char* pubkey_header = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
|
|
const char* privkey_header = "-----BEGIN PGP PRIVATE KEY BLOCK-----";
|
|
// Hate my magic numbers at your peril, but I don't want a strlen each time
|
|
const size_t PUBKEY_HSIZE = 36;
|
|
const size_t PRIVKEY_HSIZE = 37;
|
|
|
|
for (bloblist_t *bl = msg->attachments; i < MAX_KEYS_TO_IMPORT && bl && bl->value;
|
|
i++)
|
|
{
|
|
do_not_advance = false;
|
|
if (bl && bl->value && bl->size && bl->size < MAX_KEY_SIZE
|
|
&& is_key(bl))
|
|
{
|
|
char* blob_value = bl->value;
|
|
size_t blob_size = bl->size;
|
|
bool free_blobval = false;
|
|
|
|
if (is_encrypted_attachment(bl)) {
|
|
|
|
char* bl_ptext = NULL;
|
|
size_t bl_psize = 0;
|
|
stringlist_t* bl_keylist = NULL;
|
|
PEP_STATUS _status = decrypt_and_verify(session,
|
|
blob_value, blob_size,
|
|
NULL, 0,
|
|
&bl_ptext, &bl_psize,
|
|
&bl_keylist,
|
|
NULL);
|
|
free_stringlist(bl_keylist); // we don't care about key encryption as long as we decrypt
|
|
if (_status == PEP_DECRYPTED || _status == PEP_DECRYPTED_AND_VERIFIED) {
|
|
free_blobval = true;
|
|
blob_value = bl_ptext;
|
|
blob_size = bl_psize;
|
|
}
|
|
else {
|
|
// This is an encrypted attachment we can do nothing with.
|
|
// We shouldn't delete it or import it, because we can't
|
|
// do the latter.
|
|
free(bl_ptext);
|
|
prev = bl;
|
|
bl = bl->next;
|
|
continue;
|
|
}
|
|
}
|
|
identity_list *local_private_idents = NULL;
|
|
PEP_STATUS import_status = _import_key_with_fpr_return(
|
|
session, blob_value, blob_size,
|
|
&local_private_idents,
|
|
imported_key_list,
|
|
changed_keys);
|
|
bloblist_t* to_delete = NULL;
|
|
switch (import_status) {
|
|
case PEP_NO_KEY_IMPORTED:
|
|
break;
|
|
case PEP_KEY_IMPORT_STATUS_UNKNOWN:
|
|
// We'll delete armoured stuff, at least
|
|
if (blob_size <= PUBKEY_HSIZE)
|
|
break;
|
|
if ((!compare_first_n_bytes(pubkey_header, (const char*)blob_value, PUBKEY_HSIZE)) &&
|
|
(!compare_first_n_bytes(privkey_header, (const char*)blob_value, PRIVKEY_HSIZE)))
|
|
break;
|
|
// else fall through and delete
|
|
case PEP_KEY_IMPORTED:
|
|
case PEP_STATUS_OK:
|
|
to_delete = bl;
|
|
if (prev)
|
|
prev->next = bl->next;
|
|
else
|
|
msg->attachments = bl->next;
|
|
bl = bl->next;
|
|
to_delete->next = NULL;
|
|
free_bloblist(to_delete);
|
|
do_not_advance = true;
|
|
remove = true;
|
|
break;
|
|
default:
|
|
// bad stuff, but ok.
|
|
break;
|
|
}
|
|
if (private_idents && *private_idents == NULL && local_private_idents != NULL)
|
|
*private_idents = local_private_idents;
|
|
else
|
|
free_identity_list(local_private_idents);
|
|
if (free_blobval)
|
|
free(blob_value);
|
|
}
|
|
if (!do_not_advance) {
|
|
prev = bl;
|
|
bl = bl->next;
|
|
}
|
|
}
|
|
return remove;
|
|
}
|
|
|
|
|
|
PEP_STATUS _attach_key(PEP_SESSION session, const char* fpr, message *msg)
|
|
{
|
|
char *keydata = NULL;
|
|
size_t size = 0;
|
|
|
|
PEP_STATUS status = export_key(session, fpr, &keydata, &size);
|
|
assert(status == PEP_STATUS_OK);
|
|
if (status != PEP_STATUS_OK)
|
|
return status;
|
|
assert(size);
|
|
|
|
bloblist_t *bl = bloblist_add(msg->attachments, keydata, size, "application/pgp-keys",
|
|
"file://pEpkey.asc");
|
|
|
|
if (msg->attachments == NULL && bl)
|
|
msg->attachments = bl;
|
|
|
|
return PEP_STATUS_OK;
|
|
}
|
|
|
|
#define ONE_WEEK (7*24*3600)
|
|
|
|
void attach_own_key(PEP_SESSION session, message *msg)
|
|
{
|
|
assert(session);
|
|
assert(msg);
|
|
|
|
if (msg->dir == PEP_dir_incoming)
|
|
return;
|
|
|
|
assert(msg->from && msg->from->fpr);
|
|
if (msg->from == NULL || msg->from->fpr == NULL)
|
|
return;
|
|
|
|
if(_attach_key(session, msg->from->fpr, msg) != PEP_STATUS_OK)
|
|
return;
|
|
|
|
char *revoked_fpr = NULL;
|
|
uint64_t revocation_date = 0;
|
|
|
|
if(get_revoked(session, msg->from->fpr,
|
|
&revoked_fpr, &revocation_date) == PEP_STATUS_OK &&
|
|
revoked_fpr != NULL)
|
|
{
|
|
time_t now = time(NULL);
|
|
|
|
if (now < (time_t)revocation_date + ONE_WEEK)
|
|
{
|
|
_attach_key(session, revoked_fpr, msg);
|
|
}
|
|
}
|
|
free(revoked_fpr);
|
|
}
|
|
|
|
PEP_cryptotech determine_encryption_format(message *msg)
|
|
{
|
|
assert(msg);
|
|
|
|
if (is_PGP_message_text(msg->longmsg)) {
|
|
if (msg->enc_format != PEP_enc_inline_EA)
|
|
msg->enc_format = PEP_enc_inline;
|
|
return PEP_crypt_OpenPGP;
|
|
}
|
|
else if (msg->attachments && msg->attachments->next &&
|
|
is_mime_type(msg->attachments, "application/pgp-encrypted") &&
|
|
is_PGP_message_text(msg->attachments->next->value)
|
|
) {
|
|
msg->enc_format = PEP_enc_PGP_MIME;
|
|
return PEP_crypt_OpenPGP;
|
|
}
|
|
else if (msg->attachments && msg->attachments->next &&
|
|
is_mime_type(msg->attachments->next, "application/pgp-encrypted") &&
|
|
is_PGP_message_text(msg->attachments->value)
|
|
) {
|
|
msg->enc_format = PEP_enc_PGP_MIME_Outlook1;
|
|
return PEP_crypt_OpenPGP;
|
|
}
|
|
else {
|
|
msg->enc_format = PEP_enc_none;
|
|
return PEP_crypt_none;
|
|
}
|
|
}
|
|
|
|
static void _cleanup_src(message* src, bool remove_attached_key) {
|
|
assert(src);
|
|
|
|
if (!src)
|
|
return;
|
|
|
|
char* longmsg = NULL;
|
|
char* shortmsg = NULL;
|
|
char* msg_wrap_info = NULL;
|
|
if (src->longmsg)
|
|
separate_short_and_long(src->longmsg, &shortmsg, &msg_wrap_info,
|
|
&longmsg);
|
|
if (longmsg) {
|
|
free(src->longmsg);
|
|
free(shortmsg);
|
|
free(msg_wrap_info);
|
|
src->longmsg = longmsg;
|
|
}
|
|
if (remove_attached_key) {
|
|
// End of the attachment list
|
|
if (src->attachments) {
|
|
bloblist_t* tmp = src->attachments;
|
|
while (tmp->next && tmp->next->next) {
|
|
tmp = tmp->next;
|
|
}
|
|
free_bloblist(tmp->next);
|
|
tmp->next = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static PEP_STATUS id_list_set_enc_format(PEP_SESSION session, identity_list* id_list, PEP_enc_format enc_format) {
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
identity_list* id_list_curr = id_list;
|
|
for ( ; id_list_curr && id_list_curr->ident && status == PEP_STATUS_OK; id_list_curr = id_list_curr->next) {
|
|
status = set_ident_enc_format(session, id_list_curr->ident, enc_format);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// N.B.
|
|
// depends on update_identity and friends having already been called on list
|
|
static void update_encryption_format(identity_list* id_list, PEP_enc_format* enc_format) {
|
|
identity_list* id_list_curr;
|
|
for (id_list_curr = id_list; id_list_curr && id_list_curr->ident; id_list_curr = id_list_curr->next) {
|
|
PEP_enc_format format = id_list_curr->ident->enc_format;
|
|
if (format != PEP_enc_none) {
|
|
*enc_format = format;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
PEP_STATUS probe_encrypt(PEP_SESSION session, const char *fpr)
|
|
{
|
|
assert(session);
|
|
if (!session)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
if (EMPTYSTR(fpr))
|
|
return PEP_KEY_NOT_FOUND;
|
|
|
|
stringlist_t *keylist = new_stringlist(fpr);
|
|
if (!keylist)
|
|
return PEP_OUT_OF_MEMORY;
|
|
|
|
char *ctext = NULL;
|
|
size_t csize = 0;
|
|
PEP_STATUS status = encrypt_and_sign(session, keylist, "pEp", 4, &ctext, &csize);
|
|
free(ctext);
|
|
|
|
return status;
|
|
}
|
|
|
|
static bool failed_test(PEP_STATUS status)
|
|
{
|
|
if (status == PEP_OUT_OF_MEMORY ||
|
|
status == PEP_PASSPHRASE_REQUIRED ||
|
|
status == PEP_WRONG_PASSPHRASE ||
|
|
status == PEP_PASSPHRASE_FOR_NEW_KEYS_REQUIRED)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// CANNOT return PASSPHRASE errors, as no gen or renew allowed below
|
|
static PEP_STATUS _update_state_for_ident_list(
|
|
PEP_SESSION session,
|
|
pEp_identity* from_ident,
|
|
identity_list* ident_list,
|
|
stringlist_t** keylist,
|
|
PEP_comm_type* max_comm_type,
|
|
unsigned int* max_version_major,
|
|
unsigned int* max_version_minor,
|
|
bool* has_pEp_user,
|
|
bool* dest_keys_found,
|
|
bool suppress_update_for_bcc
|
|
)
|
|
{
|
|
if (!ident_list || !max_version_major || !max_version_minor
|
|
|| !has_pEp_user || !dest_keys_found
|
|
|| !keylist)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
|
|
identity_list* _il = ident_list;
|
|
|
|
for ( ; _il && _il->ident; _il = _il->next) {
|
|
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
|
|
if (!is_me(session, _il->ident)) {
|
|
status = update_identity(session, _il->ident);
|
|
|
|
if (status == PEP_CANNOT_FIND_IDENTITY) {
|
|
_il->ident->comm_type = PEP_ct_key_not_found;
|
|
status = PEP_STATUS_OK;
|
|
}
|
|
// 0 unless set, so safe.
|
|
|
|
if (!suppress_update_for_bcc) {
|
|
set_min_version( _il->ident->major_ver, _il->ident->minor_ver,
|
|
*max_version_major, *max_version_minor,
|
|
max_version_major, max_version_minor);
|
|
}
|
|
|
|
bool is_blacklisted = false;
|
|
if (_il->ident->fpr && IS_PGP_CT(_il->ident->comm_type)) {
|
|
status = blacklist_is_listed(session, _il->ident->fpr, &is_blacklisted);
|
|
if (status != PEP_STATUS_OK) {
|
|
// DB error
|
|
status = PEP_UNENCRYPTED;
|
|
goto pEp_done;
|
|
}
|
|
if (is_blacklisted) {
|
|
bool user_default, ident_default, address_default;
|
|
status = get_valid_pubkey(session, _il->ident,
|
|
&ident_default, &user_default,
|
|
&address_default,
|
|
true);
|
|
|
|
if (status != PEP_STATUS_OK || _il->ident->fpr == NULL) {
|
|
_il->ident->comm_type = PEP_ct_key_not_found;
|
|
status = PEP_STATUS_OK;
|
|
}
|
|
}
|
|
}
|
|
if (!(*has_pEp_user) && !EMPTYSTR(_il->ident->user_id))
|
|
is_pEp_user(session, _il->ident, has_pEp_user);
|
|
|
|
if (!suppress_update_for_bcc && from_ident) {
|
|
status = bind_own_ident_with_contact_ident(session, from_ident, _il->ident);
|
|
if (status != PEP_STATUS_OK) {
|
|
status = PEP_UNKNOWN_DB_ERROR;
|
|
goto pEp_done;
|
|
}
|
|
}
|
|
}
|
|
else // myself, but don't gen or renew
|
|
status = _myself(session, _il->ident, false, false, false, true);
|
|
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_done;
|
|
|
|
if (!EMPTYSTR(_il->ident->fpr)) {
|
|
*keylist = stringlist_add(*keylist, _il->ident->fpr);
|
|
if (*keylist == NULL) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto pEp_done;
|
|
}
|
|
*max_comm_type = _get_comm_type(session, *max_comm_type,
|
|
_il->ident);
|
|
}
|
|
else {
|
|
*dest_keys_found = false;
|
|
// ? status = PEP_KEY_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
pEp_done:
|
|
return status;
|
|
}
|
|
|
|
DYNAMIC_API PEP_STATUS encrypt_message(
|
|
PEP_SESSION session,
|
|
message *src,
|
|
stringlist_t * extra,
|
|
message **dst,
|
|
PEP_enc_format enc_format,
|
|
PEP_encrypt_flags_t flags
|
|
)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
message * msg = NULL;
|
|
stringlist_t * keys = NULL;
|
|
message* _src = src;
|
|
|
|
bool added_key_to_real_src = false;
|
|
|
|
assert(session);
|
|
assert(src && src->from);
|
|
assert(dst);
|
|
|
|
if (!(session && src && src->from && dst))
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
if (src->dir == PEP_dir_incoming)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
determine_encryption_format(src);
|
|
// TODO: change this for multi-encryption in message format 2.0
|
|
if (src->enc_format != PEP_enc_none)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
bool force_v_1 = flags & PEP_encrypt_flag_force_version_1;
|
|
|
|
*dst = NULL;
|
|
|
|
if (!src->from->user_id || src->from->user_id[0] == '\0') {
|
|
char* own_id = NULL;
|
|
status = get_default_own_userid(session, &own_id);
|
|
if (own_id) {
|
|
free(src->from->user_id);
|
|
src->from->user_id = own_id; // ownership transfer
|
|
}
|
|
}
|
|
|
|
status = myself(session, src->from);
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_error;
|
|
|
|
// is a passphrase needed?
|
|
status = probe_encrypt(session, src->from->fpr);
|
|
if (failed_test(status))
|
|
return status;
|
|
|
|
char* send_fpr = strdup(src->from->fpr ? src->from->fpr : "");
|
|
src->_sender_fpr = send_fpr;
|
|
|
|
keys = new_stringlist(send_fpr);
|
|
if (keys == NULL)
|
|
goto enomem;
|
|
|
|
stringlist_t *_k = keys;
|
|
|
|
if (extra) {
|
|
_k = stringlist_append(_k, extra);
|
|
if (_k == NULL)
|
|
goto enomem;
|
|
}
|
|
|
|
bool dest_keys_found = true;
|
|
bool has_pEp_user = false;
|
|
|
|
PEP_comm_type max_comm_type = PEP_ct_pEp;
|
|
unsigned int max_version_major = 0;
|
|
unsigned int max_version_minor = 0;
|
|
pEp_version_major_minor(PEP_VERSION, &max_version_major, &max_version_minor);
|
|
|
|
identity_list * _il = NULL;
|
|
|
|
//
|
|
// Update the identities and gather key and version information
|
|
// for sending
|
|
//
|
|
if (enc_format != PEP_enc_none && (_il = src->bcc) && _il->ident)
|
|
// BCC limited support:
|
|
{
|
|
// - App splits mails with BCC in multiple mails.
|
|
// - Each email is encrypted separately
|
|
if(_il->next || (src->to && src->to->ident) || (src->cc && src->cc->ident))
|
|
{
|
|
// Only one Bcc with no other recipient allowed for now
|
|
return PEP_ILLEGAL_VALUE;
|
|
}
|
|
|
|
// If you think this call is a beast, try the cut-and-pasted code 3 x
|
|
PEP_STATUS _status = _update_state_for_ident_list(
|
|
session, src->from, _il,
|
|
&_k,
|
|
&max_comm_type,
|
|
&max_version_major,
|
|
&max_version_minor,
|
|
&has_pEp_user,
|
|
&dest_keys_found,
|
|
true);
|
|
|
|
switch (_status) {
|
|
case PEP_PASSPHRASE_REQUIRED:
|
|
case PEP_PASSPHRASE_FOR_NEW_KEYS_REQUIRED:
|
|
case PEP_WRONG_PASSPHRASE:
|
|
status = _status;
|
|
goto pEp_error;
|
|
case PEP_STATUS_OK:
|
|
break;
|
|
default:
|
|
status = PEP_UNENCRYPTED;
|
|
goto pEp_error;
|
|
}
|
|
}
|
|
else // Non BCC
|
|
{
|
|
|
|
// If you think this call is a beast, try the cut-and-pasted code 3 x
|
|
PEP_STATUS _status = PEP_STATUS_OK;
|
|
|
|
if (src->to) {
|
|
_status = _update_state_for_ident_list(
|
|
session, src->from, src->to,
|
|
&_k,
|
|
&max_comm_type,
|
|
&max_version_major,
|
|
&max_version_minor,
|
|
&has_pEp_user,
|
|
&dest_keys_found,
|
|
false
|
|
);
|
|
switch (_status) {
|
|
case PEP_PASSPHRASE_REQUIRED:
|
|
case PEP_PASSPHRASE_FOR_NEW_KEYS_REQUIRED:
|
|
case PEP_WRONG_PASSPHRASE:
|
|
goto pEp_error;
|
|
case PEP_STATUS_OK:
|
|
break;
|
|
default:
|
|
status = PEP_UNENCRYPTED;
|
|
goto pEp_error;
|
|
}
|
|
}
|
|
if (src->cc) {
|
|
_status = _update_state_for_ident_list(
|
|
session, src->from, src->cc,
|
|
&_k,
|
|
&max_comm_type,
|
|
&max_version_major,
|
|
&max_version_minor,
|
|
&has_pEp_user,
|
|
&dest_keys_found,
|
|
false
|
|
);
|
|
switch (_status) {
|
|
case PEP_PASSPHRASE_REQUIRED:
|
|
case PEP_PASSPHRASE_FOR_NEW_KEYS_REQUIRED:
|
|
case PEP_WRONG_PASSPHRASE:
|
|
goto pEp_error;
|
|
case PEP_STATUS_OK:
|
|
break;
|
|
default:
|
|
status = PEP_UNENCRYPTED;
|
|
goto pEp_error;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (max_version_major < 2)
|
|
force_v_1 = true;
|
|
|
|
if (enc_format == PEP_enc_auto) {
|
|
update_encryption_format(src->to, &enc_format);
|
|
if (enc_format == PEP_enc_auto && src->cc)
|
|
update_encryption_format(src->cc, &enc_format);
|
|
if (enc_format == PEP_enc_auto && src->bcc)
|
|
update_encryption_format(src->bcc, &enc_format);
|
|
if (enc_format == PEP_enc_auto)
|
|
enc_format = PEP_enc_PEP;
|
|
}
|
|
else if (enc_format != PEP_enc_none) {
|
|
status = id_list_set_enc_format(session, src->to, enc_format);
|
|
status = ((status != PEP_STATUS_OK || !(src->cc)) ? status : id_list_set_enc_format(session, src->cc, enc_format));
|
|
status = ((status != PEP_STATUS_OK || !(src->bcc)) ? status : id_list_set_enc_format(session, src->bcc, enc_format));
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_error;
|
|
}
|
|
|
|
if (enc_format == PEP_enc_none || !dest_keys_found ||
|
|
stringlist_length(keys) == 0 ||
|
|
_rating(max_comm_type) < PEP_rating_reliable)
|
|
{
|
|
free_stringlist(keys);
|
|
if ((has_pEp_user || !session->passive_mode) &&
|
|
!(flags & PEP_encrypt_flag_force_no_attached_key)) {
|
|
attach_own_key(session, src);
|
|
added_key_to_real_src = true;
|
|
}
|
|
decorate_message(src, PEP_rating_undefined, NULL, true, true);
|
|
return PEP_UNENCRYPTED;
|
|
}
|
|
else {
|
|
// First, dedup the keylist
|
|
if (keys && keys->next)
|
|
dedup_stringlist(keys->next);
|
|
|
|
// FIXME - we need to deal with transport types (via flag)
|
|
message_wrap_type wrap_type = PEP_message_unwrapped;
|
|
if ((enc_format != PEP_enc_inline) && (enc_format != PEP_enc_inline_EA) && (!force_v_1) && ((max_comm_type | PEP_ct_confirmed) == PEP_ct_pEp)) {
|
|
wrap_type = ((flags & PEP_encrypt_flag_key_reset_only) ? PEP_message_key_reset : PEP_message_default);
|
|
_src = wrap_message_as_attachment(NULL, src, wrap_type, false, extra, max_version_major, max_version_minor);
|
|
if (!_src)
|
|
goto pEp_error;
|
|
}
|
|
else {
|
|
// hide subject
|
|
if (enc_format != PEP_enc_inline && enc_format != PEP_enc_inline_EA) {
|
|
status = replace_subject(_src);
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto enomem;
|
|
}
|
|
if (!(flags & PEP_encrypt_flag_force_no_attached_key))
|
|
added_key_to_real_src = true;
|
|
}
|
|
if (!(flags & PEP_encrypt_flag_force_no_attached_key))
|
|
attach_own_key(session, _src);
|
|
|
|
msg = clone_to_empty_message(_src);
|
|
if (msg == NULL)
|
|
goto enomem;
|
|
|
|
switch (enc_format) {
|
|
case PEP_enc_PGP_MIME:
|
|
case PEP_enc_PEP: // BUG: should be implemented extra
|
|
status = encrypt_PGP_MIME(session, _src, keys, msg, flags, wrap_type);
|
|
break;
|
|
|
|
case PEP_enc_inline:
|
|
case PEP_enc_inline_EA:
|
|
_src->enc_format = enc_format;
|
|
status = encrypt_PGP_inline(session, _src, keys, msg, flags);
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
status = PEP_ILLEGAL_VALUE;
|
|
goto pEp_error;
|
|
}
|
|
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto enomem;
|
|
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_error;
|
|
}
|
|
|
|
free_stringlist(keys);
|
|
|
|
if (msg && msg->shortmsg == NULL) {
|
|
msg->shortmsg = strdup("");
|
|
assert(msg->shortmsg);
|
|
if (msg->shortmsg == NULL)
|
|
goto enomem;
|
|
}
|
|
|
|
if (msg) {
|
|
decorate_message(msg, PEP_rating_undefined, NULL, true, true);
|
|
if (_src->id) {
|
|
msg->id = strdup(_src->id);
|
|
assert(msg->id);
|
|
if (msg->id == NULL)
|
|
goto enomem;
|
|
}
|
|
}
|
|
|
|
*dst = msg;
|
|
|
|
// ??? FIXME: Check to be sure we don't have references btw _src and msg.
|
|
// I don't think we do.
|
|
if (_src && _src != src)
|
|
free_message(_src);
|
|
|
|
// Do similar for extra key list...
|
|
_cleanup_src(src, added_key_to_real_src);
|
|
|
|
return status;
|
|
|
|
enomem:
|
|
status = PEP_OUT_OF_MEMORY;
|
|
|
|
pEp_error:
|
|
free_stringlist(keys);
|
|
free_message(msg);
|
|
if (_src && _src != src)
|
|
free_message(_src);
|
|
|
|
_cleanup_src(src, added_key_to_real_src);
|
|
|
|
return status;
|
|
}
|
|
|
|
DYNAMIC_API PEP_STATUS encrypt_message_and_add_priv_key(
|
|
PEP_SESSION session,
|
|
message *src,
|
|
message **dst,
|
|
const char* to_fpr,
|
|
PEP_enc_format enc_format,
|
|
PEP_encrypt_flags_t flags
|
|
)
|
|
{
|
|
assert(session);
|
|
assert(src);
|
|
assert(dst);
|
|
assert(to_fpr);
|
|
|
|
if (!session || !src || !dst || !to_fpr)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
if (enc_format == PEP_enc_none)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
if (src->cc || src->bcc)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
if (!src->to || src->to->next)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
if (!src->from->address || !src->to->ident || !src->to->ident->address)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
if (strcasecmp(src->from->address, src->to->ident->address) != 0)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
stringlist_t* keys = NULL;
|
|
|
|
char* own_id = NULL;
|
|
char* default_id = NULL;
|
|
|
|
pEp_identity* own_identity = NULL;
|
|
char* own_private_fpr = NULL;
|
|
|
|
char* priv_key_data = NULL;
|
|
|
|
PEP_STATUS status = get_default_own_userid(session, &own_id);
|
|
|
|
if (!own_id)
|
|
return PEP_UNKNOWN_ERROR; // Probably a DB error at this point
|
|
|
|
if (src->from->user_id) {
|
|
if (strcmp(src->from->user_id, own_id) != 0) {
|
|
status = get_userid_alias_default(session, src->from->user_id, &default_id);
|
|
if (status != PEP_STATUS_OK || !default_id || strcmp(default_id, own_id) != 0) {
|
|
status = PEP_ILLEGAL_VALUE;
|
|
goto pEp_free;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ok, we are at least marginally sure the initial stuff is ok.
|
|
|
|
// Let's get our own, normal identity
|
|
own_identity = identity_dup(src->from);
|
|
status = myself(session, own_identity);
|
|
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_free;
|
|
|
|
// is a passphrase needed?
|
|
status = probe_encrypt(session, own_identity->fpr);
|
|
if (failed_test(status))
|
|
goto pEp_free;
|
|
|
|
// Ok, now we know the address is an own address. All good. Then...
|
|
own_private_fpr = own_identity->fpr;
|
|
own_identity->fpr = strdup(to_fpr);
|
|
|
|
status = get_trust(session, own_identity);
|
|
|
|
if (status != PEP_STATUS_OK) {
|
|
if (status == PEP_CANNOT_FIND_IDENTITY)
|
|
status = PEP_ILLEGAL_VALUE;
|
|
goto pEp_free;
|
|
}
|
|
|
|
if ((own_identity->comm_type & PEP_ct_confirmed) != PEP_ct_confirmed) {
|
|
status = PEP_ILLEGAL_VALUE;
|
|
goto pEp_free;
|
|
}
|
|
|
|
// Ok, so all the things are now allowed.
|
|
// So let's get our own private key and roll with it.
|
|
size_t priv_key_size = 0;
|
|
|
|
status = export_secret_key(session, own_private_fpr, &priv_key_data,
|
|
&priv_key_size);
|
|
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_free;
|
|
|
|
if (!priv_key_data) {
|
|
status = PEP_CANNOT_EXPORT_KEY;
|
|
goto pEp_free;
|
|
}
|
|
|
|
// Ok, fine... let's encrypt yon blob
|
|
keys = new_stringlist(own_private_fpr);
|
|
if (!keys) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto pEp_free;
|
|
}
|
|
|
|
stringlist_add(keys, to_fpr);
|
|
|
|
char* encrypted_key_text = NULL;
|
|
size_t encrypted_key_size = 0;
|
|
|
|
if (flags & PEP_encrypt_flag_force_unsigned)
|
|
status = encrypt_only(session, keys, priv_key_data, priv_key_size,
|
|
&encrypted_key_text, &encrypted_key_size);
|
|
else
|
|
status = encrypt_and_sign(session, keys, priv_key_data, priv_key_size,
|
|
&encrypted_key_text, &encrypted_key_size);
|
|
|
|
if (status == PEP_PASSPHRASE_REQUIRED || status == PEP_WRONG_PASSPHRASE) {
|
|
free(encrypted_key_text);
|
|
goto pEp_free;
|
|
}
|
|
else if (!encrypted_key_text) {
|
|
status = PEP_UNKNOWN_ERROR;
|
|
goto pEp_free;
|
|
}
|
|
else if (status != PEP_STATUS_OK) {
|
|
free(encrypted_key_text);
|
|
goto pEp_free; // FIXME - we need an error return overall
|
|
}
|
|
|
|
// We will have to delete this before returning, as we allocated it.
|
|
bloblist_t* created_bl = NULL;
|
|
bloblist_t* created_predecessor = NULL;
|
|
|
|
bloblist_t* old_head = NULL;
|
|
|
|
if (!src->attachments || src->attachments->value == NULL) {
|
|
if (src->attachments && src->attachments->value == NULL) {
|
|
old_head = src->attachments;
|
|
src->attachments = NULL;
|
|
}
|
|
src->attachments = new_bloblist(encrypted_key_text, encrypted_key_size,
|
|
"application/octet-stream",
|
|
"file://pEpkey.asc.pgp");
|
|
created_bl = src->attachments;
|
|
}
|
|
else {
|
|
bloblist_t* tmp = src->attachments;
|
|
while (tmp && tmp->next) {
|
|
tmp = tmp->next;
|
|
}
|
|
created_predecessor = tmp;
|
|
created_bl = bloblist_add(tmp,
|
|
encrypted_key_text, encrypted_key_size,
|
|
"application/octet-stream",
|
|
"file://pEpkey.asc.pgp");
|
|
}
|
|
|
|
if (!created_bl) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto pEp_free;
|
|
}
|
|
|
|
// Ok, it's in there. Let's do this.
|
|
status = encrypt_message(session, src, keys, dst, enc_format, flags);
|
|
|
|
// Delete what we added to src
|
|
free_bloblist(created_bl);
|
|
if (created_predecessor)
|
|
created_predecessor->next = NULL;
|
|
else {
|
|
if (old_head)
|
|
src->attachments = old_head;
|
|
else
|
|
src->attachments = NULL;
|
|
}
|
|
|
|
pEp_free:
|
|
free(own_id);
|
|
free(default_id);
|
|
free(own_private_fpr);
|
|
free(priv_key_data);
|
|
free_identity(own_identity);
|
|
free_stringlist(keys);
|
|
return status;
|
|
}
|
|
|
|
|
|
DYNAMIC_API PEP_STATUS encrypt_message_for_self(
|
|
PEP_SESSION session,
|
|
pEp_identity* target_id,
|
|
message *src,
|
|
stringlist_t* extra,
|
|
message **dst,
|
|
PEP_enc_format enc_format,
|
|
PEP_encrypt_flags_t flags
|
|
)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
message * msg = NULL;
|
|
stringlist_t * keys = NULL;
|
|
message* _src = src;
|
|
|
|
assert(session);
|
|
assert(target_id);
|
|
assert(src);
|
|
assert(dst);
|
|
assert(enc_format != PEP_enc_none);
|
|
|
|
if (!(session && target_id && src && dst && enc_format != PEP_enc_none))
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
// if (src->dir == PEP_dir_incoming)
|
|
// return PEP_ILLEGAL_VALUE;
|
|
|
|
determine_encryption_format(src);
|
|
if (src->enc_format != PEP_enc_none)
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
if (!target_id->user_id || target_id->user_id[0] == '\0') {
|
|
char* own_id = NULL;
|
|
status = get_default_own_userid(session, &own_id);
|
|
if (own_id) {
|
|
free(target_id->user_id);
|
|
target_id->user_id = own_id; // ownership transfer
|
|
}
|
|
}
|
|
|
|
if (!target_id->user_id || target_id->user_id[0] == '\0')
|
|
return PEP_CANNOT_FIND_IDENTITY;
|
|
|
|
if (target_id->address) {
|
|
status = myself(session, target_id);
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_error;
|
|
}
|
|
else if (!target_id->fpr) {
|
|
return PEP_ILLEGAL_VALUE;
|
|
}
|
|
|
|
*dst = NULL;
|
|
|
|
// PEP_STATUS _status = update_identity(session, target_id);
|
|
// if (_status != PEP_STATUS_OK) {
|
|
// status = _status;
|
|
// goto pEp_error;
|
|
// }
|
|
|
|
char* target_fpr = target_id->fpr;
|
|
if (!target_fpr)
|
|
return PEP_KEY_NOT_FOUND; // FIXME: Error condition
|
|
|
|
// is a passphrase needed?
|
|
status = probe_encrypt(session, target_fpr);
|
|
if (failed_test(status))
|
|
return status;
|
|
|
|
keys = new_stringlist(target_fpr);
|
|
|
|
stringlist_t *_k = keys;
|
|
|
|
if (extra) {
|
|
_k = stringlist_append(_k, extra);
|
|
if (_k == NULL)
|
|
goto enomem;
|
|
}
|
|
|
|
/* KG: did we ever do this??? */
|
|
// if (!(flags & PEP_encrypt_flag_force_no_attached_key))
|
|
// _attach_key(session, target_fpr, src);
|
|
|
|
unsigned int major_ver, minor_ver;
|
|
pEp_version_major_minor(PEP_VERSION, &major_ver, &minor_ver);
|
|
_src = wrap_message_as_attachment(NULL, src, PEP_message_default, false, extra, major_ver, minor_ver);
|
|
if (!_src)
|
|
goto pEp_error;
|
|
|
|
msg = clone_to_empty_message(_src);
|
|
if (msg == NULL)
|
|
goto enomem;
|
|
|
|
switch (enc_format) {
|
|
case PEP_enc_PGP_MIME:
|
|
case PEP_enc_PEP: // BUG: should be implemented extra
|
|
status = encrypt_PGP_MIME(session, _src, keys, msg, flags, PEP_message_default);
|
|
if (status == PEP_STATUS_OK || (src->longmsg && strstr(src->longmsg, "INNER")))
|
|
_cleanup_src(src, false);
|
|
break;
|
|
|
|
case PEP_enc_inline:
|
|
case PEP_enc_inline_EA:
|
|
_src->enc_format = enc_format;
|
|
status = encrypt_PGP_inline(session, _src, keys, msg, flags);
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
status = PEP_ILLEGAL_VALUE;
|
|
goto pEp_error;
|
|
}
|
|
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto enomem;
|
|
|
|
if (status != PEP_STATUS_OK)
|
|
goto pEp_error;
|
|
|
|
if (msg) {
|
|
if (!src->shortmsg) {
|
|
free(msg->shortmsg);
|
|
msg->shortmsg = _pEp_subj_copy();
|
|
assert(msg->shortmsg);
|
|
if (msg->shortmsg == NULL)
|
|
goto enomem;
|
|
}
|
|
else {
|
|
if (session->unencrypted_subject && (flags & PEP_encrypt_reencrypt)) {
|
|
free(msg->shortmsg);
|
|
msg->shortmsg = strdup(src->shortmsg);
|
|
}
|
|
}
|
|
|
|
if (_src->id) {
|
|
msg->id = strdup(_src->id);
|
|
assert(msg->id);
|
|
if (msg->id == NULL)
|
|
goto enomem;
|
|
}
|
|
decorate_message(msg, PEP_rating_undefined, NULL, true, true);
|
|
}
|
|
|
|
*dst = msg;
|
|
|
|
if (src != _src)
|
|
free_message(_src);
|
|
|
|
return status;
|
|
|
|
enomem:
|
|
status = PEP_OUT_OF_MEMORY;
|
|
|
|
pEp_error:
|
|
free_stringlist(keys);
|
|
free_message(msg);
|
|
if (src != _src)
|
|
free_message(_src);
|
|
|
|
return status;
|
|
}
|
|
|
|
// static PEP_STATUS _update_identity_for_incoming_message(
|
|
// PEP_SESSION session,
|
|
// const message *src
|
|
// )
|
|
// {
|
|
// PEP_STATUS status;
|
|
//
|
|
// if (src->from && src->from->address) {
|
|
// if (!is_me(session, src->from))
|
|
// status = update_identity(session, src->from);
|
|
// else
|
|
// status = myself(session, src->from);
|
|
// if (status == PEP_STATUS_OK
|
|
// && is_a_pEpmessage(src)
|
|
// && src->from->comm_type >= PEP_ct_OpenPGP_unconfirmed
|
|
// && src->from->comm_type != PEP_ct_pEp_unconfirmed
|
|
// && src->from->comm_type != PEP_ct_pEp)
|
|
// {
|
|
// src->from->comm_type |= PEP_ct_pEp_unconfirmed;
|
|
|