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

2447 lines
77 KiB
C

/**
* @file group.c
* @brief Implementation of in-memory objects and functions for representation of groups
* @license This file is under GNU General Public License 3.0 - see LICENSE.txt
*/
#include "group.h"
#include "group_internal.h"
#include "pEp_internal.h"
#include "message_api.h"
#include "message_api_internal.h"
#include "distribution_codec.h"
#include "map_asn1.h"
#include "baseprotocol.h"
#include "sync_api.h"
// ** Static functions
/******************************************************************************************
* STATIC FUNCTIONS
******************************************************************************************/
/******************************************************************************************
*
* @internal
*
* <!-- * _build_managed_group_message_payload -->
*
* @brief TODO
*
* @param session
* @param group_identity
* @param manager
* @param data
* @param size
* @param managed_group_msg_type
* @retval PEP_STATUS_OK
* @retval any other on error
*/
static PEP_STATUS _build_managed_group_message_payload(PEP_SESSION session,
const pEp_identity* group_identity,
const pEp_identity* manager,
char** data, size_t* size,
ManagedGroup_PR managed_group_msg_type
) {
PEP_STATUS status = PEP_STATUS_OK;
char *_data = NULL;
size_t _size = 0;
// Ok, let's get the payload set up
Distribution_t *outdist = (Distribution_t *) calloc(1, sizeof(Distribution_t));
if (!outdist)
return PEP_OUT_OF_MEMORY;
*data = NULL;
*size = 0;
outdist->present = Distribution_PR_managedgroup;
outdist->choice.managedgroup.present = managed_group_msg_type;
Identity_t* group_identity_Ident = NULL;
Identity_t* other_identity_Ident = NULL;
switch (managed_group_msg_type) {
case ManagedGroup_PR_groupInvite:
group_identity_Ident = &(outdist->choice.managedgroup.choice.groupInvite.groupIdentity);
other_identity_Ident = &(outdist->choice.managedgroup.choice.groupInvite.manager);
break;
case ManagedGroup_PR_groupDissolve:
group_identity_Ident = &(outdist->choice.managedgroup.choice.groupDissolve.groupIdentity);
other_identity_Ident = &(outdist->choice.managedgroup.choice.groupDissolve.manager);
break;
case ManagedGroup_PR_groupAdopted:
group_identity_Ident = &(outdist->choice.managedgroup.choice.groupAdopted.groupIdentity);
other_identity_Ident = &(outdist->choice.managedgroup.choice.groupAdopted.member);
break;
default:
status = PEP_ILLEGAL_VALUE;
goto pEp_error;
}
// We don't free anything here because PEP_OUT_OF_MEMORY is always fatal up the stack
if (!Identity_from_Struct(group_identity, group_identity_Ident))
goto enomem;
if (!Identity_from_Struct(manager, other_identity_Ident))
goto enomem;
// Man, I hope this is it.
status = encode_Distribution_message(outdist, &_data, &_size);
if (status != PEP_STATUS_OK)
goto pEp_error;
*data = _data;
*size = _size;
free(outdist);
return status;
enomem:
status = PEP_OUT_OF_MEMORY;
pEp_error:
free(_data);
free(outdist);
return status;
}
/******************************************************************************************
*
* @internal
*
* <!-- _create_and_send_managed_group_message -->
*
* @brief TODO
*
* @param session
* @param from
* @param recip
* @param data
* @param size
* @param attachments
* @return
*/
static PEP_STATUS _create_and_send_managed_group_message(PEP_SESSION session,
pEp_identity* from,
pEp_identity* recip,
char* data,
size_t size,
bloblist_t* attachments
) {
if (!session || !from || !recip || EMPTYSTR(from->user_id) || EMPTYSTR(from->address) ||
EMPTYSTR(recip->user_id) || EMPTYSTR(recip->address))
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(from->fpr))
return PEP_ILLEGAL_VALUE;
message* msg = NULL;
message* enc_msg = NULL;
PEP_STATUS status = base_prepare_message(session, from, recip, BASE_DISTRIBUTION,
data, size, from->fpr, &msg);
if (status != PEP_STATUS_OK)
goto pEp_error;
// Fatal, bail
if (!msg)
return PEP_OUT_OF_MEMORY;
if (!msg->attachments) {
status = PEP_UNKNOWN_ERROR;
goto pEp_error;
}
if (attachments)
msg->attachments = bloblist_join(msg->attachments, attachments);
// encrypt this baby and get out
// extra keys???
status = encrypt_message(session, msg, NULL, &enc_msg, PEP_enc_auto, 0); // FIXME
if (status != PEP_STATUS_OK)
goto pEp_error;
_add_auto_consume(enc_msg);
// insert into queue
status = session->messageToSend(enc_msg);
if (status != PEP_STATUS_OK)
goto pEp_error;
free_message(msg);
msg = NULL;
return status;
pEp_error:
free_message(msg);
free_message(enc_msg);
return status;
}
/******************************************************************************************
*
* @param session
* @param group
* @param message_type
* @return
*/
static PEP_STATUS _send_managed_group_message_to_list(PEP_SESSION session,
pEp_group* group,
ManagedGroup_PR message_type) {
char *_data = NULL;
size_t _size = 0;
char* key_material_priv = NULL;
size_t key_material_size = 0;
bloblist_t* keycopyblob = NULL;
if (!session->messageToSend)
return PEP_SEND_FUNCTION_NOT_REGISTERED;
if (!session || !group || !group->group_identity || !group->manager)
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(group->group_identity->user_id) ||
EMPTYSTR(group->group_identity->address) ||
EMPTYSTR(group->manager->user_id) ||
EMPTYSTR(group->manager->address)) {
return PEP_ILLEGAL_VALUE;
}
if (!is_me(session, group->manager))
return PEP_ILLEGAL_VALUE;
// Ok, let's get the payload set up, because we can duplicate this for each message.
PEP_STATUS status = _build_managed_group_message_payload(session, group->group_identity,
group->manager, &_data, &_size,
message_type);
if (status != PEP_STATUS_OK)
goto pEp_error;
// Let's also get the private key for the group we want to distribute and set up a bloblist element to
// dup
status = export_secret_key(session, group->group_identity->fpr, &key_material_priv, &key_material_size);
if (status != PEP_STATUS_OK)
goto pEp_error;
if (key_material_size == 0 || !key_material_priv) {
status = PEP_UNKNOWN_ERROR;
goto pEp_error;
}
keycopyblob = new_bloblist(key_material_priv, key_material_size,
"application/pgp-keys",
"file://pEpkey_group_priv.asc");
// Ok, for every member in the member list, send away.
// (We'll copy in for now. It's small and quick.)
member_list* curr_member = NULL;
for (curr_member = group->members; curr_member && curr_member->member && curr_member->member->ident; curr_member = curr_member->next) {
pEp_identity* recip = curr_member->member->ident; // This will be duped in base_prepare_message
PEP_rating recip_rating;
status = identity_rating(session, recip, &recip_rating);
if (status != PEP_STATUS_OK)
goto pEp_error;
if (recip_rating < PEP_rating_reliable)
continue;
// Copy the data to send in as an argument
char* data_copy = (char*)malloc(_size);
if (!data_copy)
goto enomem;
memcpy(data_copy, _data, _size);
bloblist_t* key_attachment = bloblist_dup(keycopyblob);
// encrypt and send this baby and get out
status = _create_and_send_managed_group_message(session, group->manager, recip, data_copy, _size, key_attachment);
if (status != PEP_STATUS_OK)
goto pEp_error;
}
return status;
enomem:
status = PEP_OUT_OF_MEMORY;
pEp_error:
free(key_material_priv);
free(_data);
return status;
}
/******************************************************************************************
*
* @param session
* @param group_identity
* @param as_member
* @return
*/
static PEP_STATUS _set_own_status_joined(PEP_SESSION session,
pEp_identity* group_identity,
pEp_identity* as_member) {
int result = 0;
sqlite3_reset(session->group_join);
sqlite3_bind_text(session->group_join, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_join, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_join, 3, as_member->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_join, 4, as_member->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->group_join);
sqlite3_reset(session->group_join);
if (result != SQLITE_DONE)
return PEP_CANNOT_CREATE_GROUP;
return PEP_STATUS_OK;
}
/******************************************************************************************
*
* @param session
* @param group_identity
* @param as_member
* @return
*/
static PEP_STATUS _remove_member_from_group(PEP_SESSION session,
pEp_identity* group_identity,
pEp_identity* member) {
int result = 0;
sqlite3_reset(session->group_delete_member);
sqlite3_bind_text(session->group_delete_member, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_delete_member, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_delete_member, 3, member->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_delete_member, 4, member->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->group_delete_member);
sqlite3_reset(session->group_delete_member);
if (result != SQLITE_DONE)
return PEP_UNKNOWN_ERROR;
return PEP_STATUS_OK;
}
/******************************************************************************************
*
* @param session
* @param group_identity
* @param leaver
* @return
*/
static PEP_STATUS _set_leave_group_status(PEP_SESSION session, pEp_identity* group_identity, pEp_identity* leaver) {
sqlite3_reset(session->leave_group);
sqlite3_bind_text(session->leave_group, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->leave_group, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->leave_group, 3, leaver->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->leave_group, 4, leaver->address, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->leave_group);
sqlite3_reset(session->leave_group);
if (result != SQLITE_DONE)
return PEP_CANNOT_LEAVE_GROUP;
else
return PEP_STATUS_OK;
}
/******************************************************************************************
*
* @param session
* @param group_identity
* @return
*/
static PEP_STATUS _set_group_as_disabled(PEP_SESSION session, pEp_identity* group_identity) {
int result = 0;
sqlite3_reset(session->disable_group);
sqlite3_bind_text(session->disable_group, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->disable_group, 2, group_identity->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->disable_group);
sqlite3_reset(session->disable_group);
if (result != SQLITE_DONE)
return PEP_CANNOT_DISABLE_GROUP;
else
return PEP_STATUS_OK;
}
/******************************************************************************************
*
* @param session
* @param group_identity
* @param memberlist
* @return
*/
static PEP_STATUS _retrieve_own_membership_info_for_group(PEP_SESSION session, pEp_identity* group_identity,
member_list** memberlist) {
PEP_STATUS status = PEP_STATUS_OK;
int result = 0;
member_list* _mbr_list_head = NULL;
member_list** _mbr_list_next = &_mbr_list_head;
pEp_identity* ident = NULL;
pEp_member* member = NULL;
sqlite3_reset(session->retrieve_own_membership_info_for_group);
sqlite3_bind_text(session->retrieve_own_membership_info_for_group, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->retrieve_own_membership_info_for_group, 2, group_identity->address, -1,
SQLITE_STATIC);
while ((result = sqlite3_step(session->retrieve_own_membership_info_for_group)) == SQLITE_ROW) {
ident = new_identity((const char *) sqlite3_column_text(session->retrieve_own_membership_info_for_group, 1),
NULL,(const char *) sqlite3_column_text(session->retrieve_own_membership_info_for_group, 0),
NULL);
if (ident == NULL)
goto enomem;
member = new_member(ident);
ident = NULL; // prevent double-free on error
if (!member)
goto enomem;
member->joined = sqlite3_column_int(session->retrieve_own_membership_info_for_group, 2);
*_mbr_list_next = new_memberlist(member);
member = NULL; // prevent double-free on error
if (!(*_mbr_list_next))
goto enomem;
_mbr_list_next = &((*_mbr_list_next)->next);
}
if (result != SQLITE_DONE) {
status = PEP_CANNOT_DISABLE_GROUP;
goto pEp_error;
}
sqlite3_reset(session->retrieve_own_membership_info_for_group);
*memberlist = _mbr_list_head;
return PEP_STATUS_OK;
enomem:
status = PEP_OUT_OF_MEMORY;
pEp_error:
sqlite3_reset(session->retrieve_own_membership_info_for_group);
if (ident) // Only true if problem allocating member
free_identity(ident);
if (member) // Only true if problem allocating memberlist node
free_member(member);
free_memberlist(_mbr_list_head);
return status;
}
/******************************************************************************************
*
* @param session
* @param group_identity
* @param member
* @param is_member
* @return
*/
static PEP_STATUS is_invited_group_member(PEP_SESSION session, pEp_identity* group_identity,
pEp_identity* member, bool* is_member) {
PEP_STATUS status = PEP_STATUS_OK;
if (!session || !is_member)
return PEP_ILLEGAL_VALUE;
if (!group_identity || EMPTYSTR(group_identity->user_id) || EMPTYSTR(group_identity->address))
return PEP_ILLEGAL_VALUE;
if (!member || EMPTYSTR(member->user_id) || EMPTYSTR(member->address))
return PEP_ILLEGAL_VALUE;
sqlite3_bind_text(session->is_invited_group_member, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->is_invited_group_member, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->is_invited_group_member, 3, member->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->is_invited_group_member, 4, member->address, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->is_invited_group_member);
if (result != SQLITE_ROW)
status = PEP_UNKNOWN_DB_ERROR;
else
*is_member = sqlite3_column_int(session->is_invited_group_member, 0);
sqlite3_reset(session->is_invited_group_member);
return status;
}
/******************************************************************************************
* UTILITY FUNCTIONS
******************************************************************************************/
identity_list* member_list_to_identity_list(member_list* memberlist) {
member_list* curr_mem = memberlist;
identity_list* head = NULL;
identity_list** id_list_curr_ptr = &head;
for ( ; curr_mem && curr_mem->member && curr_mem->member->ident; curr_mem = curr_mem->next,
id_list_curr_ptr = &((*id_list_curr_ptr)->next)) {
*id_list_curr_ptr = new_identity_list(identity_dup(curr_mem->member->ident));
if (!(*id_list_curr_ptr))
return NULL; // Out of memory - FIXME: can we be cleaner here?
}
return head;
}
member_list* identity_list_to_memberlist(identity_list* ident_list) {
identity_list* curr_ident = ident_list;
member_list* head = NULL;
member_list** mem_list_curr_ptr = &head;
pEp_identity* tmp_ident = NULL;
pEp_member* member = NULL;
member_list* new_node = NULL;
for ( ; curr_ident && curr_ident->ident; curr_ident = curr_ident->next,
mem_list_curr_ptr = &((*mem_list_curr_ptr)->next)) {
tmp_ident = identity_dup(curr_ident->ident);
if (!tmp_ident)
goto enomem;
pEp_member* member = new_member(tmp_ident);
if (!member)
goto enomem;
tmp_ident = NULL;
new_node = new_memberlist(member);
if (!new_node)
goto enomem;
member = NULL;
*mem_list_curr_ptr = new_node;
}
return head;
enomem:
if (!member)
free(tmp_ident); // is probably NULL anyway
else {
free_member(member);
}
free_memberlist(head);
return NULL;
}
// Exposed for testing.
PEP_STATUS set_membership_status(PEP_SESSION session,
pEp_identity* group_identity,
pEp_identity* as_member,
bool active) {
int result = 0;
sqlite3_reset(session->set_group_member_status);
sqlite3_bind_int(session->set_group_member_status, 1, active);
sqlite3_bind_text(session->set_group_member_status, 2, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->set_group_member_status, 3, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->set_group_member_status, 4, as_member->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->set_group_member_status, 5, as_member->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->set_group_member_status);
sqlite3_reset(session->set_group_member_status);
if (result != SQLITE_DONE)
return PEP_CANNOT_CREATE_GROUP;
return PEP_STATUS_OK;
}
PEP_STATUS get_group_manager(PEP_SESSION session,
pEp_identity* group_identity,
pEp_identity** manager) {
if (!session || !group_identity || !manager ||
EMPTYSTR(group_identity->user_id) || EMPTYSTR(group_identity->address))
return PEP_ILLEGAL_VALUE;
PEP_STATUS status = PEP_STATUS_OK;
sqlite3_reset(session->get_group_manager);
sqlite3_bind_text(session->get_group_manager, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->get_group_manager, 2, group_identity->address, -1,
SQLITE_STATIC);
*manager = NULL;
int result = sqlite3_step(session->get_group_manager);
if (result != SQLITE_ROW)
status = PEP_GROUP_NOT_FOUND;
else {
*manager = new_identity((const char *) sqlite3_column_text(session->get_group_manager, 1),
NULL, (const char *) sqlite3_column_text(session->get_group_manager, 0),
NULL);
if (!*manager)
return PEP_OUT_OF_MEMORY;
}
sqlite3_reset(session->get_group_manager);
return status;
}
PEP_STATUS is_group_active(PEP_SESSION session, pEp_identity* group_identity, bool* active) {
if (!group_identity || EMPTYSTR(group_identity->address) || EMPTYSTR(group_identity->user_id) || !active)
return PEP_ILLEGAL_VALUE;
PEP_STATUS status = PEP_STATUS_OK;
*active = false;
sqlite3_reset(session->is_group_active);
sqlite3_bind_text(session->is_group_active, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->is_group_active, 2, group_identity->address, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->is_group_active);
switch (result) {
case SQLITE_ROW:
*active = (sqlite3_column_int(session->is_group_active, 0) != 0);
break;
default:
status = PEP_UNKNOWN_DB_ERROR;
}
sqlite3_reset(session->is_group_active);
return status;
}
PEP_STATUS is_group_mine(PEP_SESSION session, pEp_identity* group_identity, bool* own_manager) {
if (!own_manager)
return PEP_ILLEGAL_VALUE;
*own_manager = false;
// Ok, we have a group ident. Someone ensure I'm the manager...
pEp_identity* manager = NULL;
PEP_STATUS status = get_group_manager(session, group_identity, &manager);
if (status != PEP_STATUS_OK)
return status;
if (!manager)
return PEP_GROUP_NOT_FOUND;
if (is_me(session, manager))
*own_manager = true;
free_identity(manager);
return PEP_STATUS_OK;
}
// group_identity MUST have been myself'd.
// Called only from create_group and PRESUMES group, group->identity (user_id and address),
// group->manager (user_id and address) are there AND VALIDATED. This is JUST the DB call factored out.
PEP_STATUS create_group_entry(PEP_SESSION session,
pEp_group* group) {
pEp_identity* group_identity = group->group_identity;
pEp_identity* manager = group->manager;
int result = 0;
sqlite3_reset(session->create_group);
sqlite3_bind_text(session->create_group, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->create_group, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->create_group, 3, manager->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->create_group, 4, manager->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->create_group);
sqlite3_reset(session->create_group);
if (result != SQLITE_DONE)
return PEP_CANNOT_CREATE_GROUP;
return PEP_STATUS_OK;
}
// This presumes these values have been checked!!!!!!!!
PEP_STATUS add_own_membership_entry(PEP_SESSION session,
pEp_identity* group_identity,
pEp_identity* manager,
pEp_identity* own_identity_recip) {
if (!session || !group_identity || !manager || !own_identity_recip)
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(group_identity->user_id) || EMPTYSTR(group_identity->address))
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(manager->user_id) || EMPTYSTR(manager->address))
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(own_identity_recip->user_id) || EMPTYSTR(own_identity_recip->address))
return PEP_ILLEGAL_VALUE;
int result = 0;
sqlite3_bind_text(session->add_own_membership_entry, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->add_own_membership_entry, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->add_own_membership_entry, 3, own_identity_recip->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->add_own_membership_entry, 4, own_identity_recip->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->add_own_membership_entry);
sqlite3_reset(session->add_own_membership_entry);
if (result != SQLITE_DONE)
return PEP_CANNOT_CREATE_GROUP;
return PEP_STATUS_OK;
}
PEP_STATUS get_own_membership_status(PEP_SESSION session,
pEp_identity* group_identity,
pEp_identity* own_identity,
bool* have_joined) {
PEP_STATUS status = PEP_STATUS_OK;
sqlite3_reset(session->get_own_membership_status);
sqlite3_bind_text(session->get_own_membership_status, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->get_own_membership_status, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->get_own_membership_status, 3, own_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->get_own_membership_status, 4, own_identity->address, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->get_own_membership_status);
switch (result) {
case SQLITE_ROW:
*have_joined = sqlite3_column_int(session->get_own_membership_status, 0);
break;
default:
status = PEP_NO_MEMBERSHIP_STATUS_FOUND;
}
sqlite3_reset(session->get_own_membership_status);
return status;
}
PEP_STATUS retrieve_own_membership_info_for_group_and_identity(PEP_SESSION session,
pEp_group* group,
pEp_identity* own_identity) {
PEP_STATUS status = PEP_STATUS_OK;
if (!group)
return PEP_ILLEGAL_VALUE;
if (!own_identity || EMPTYSTR(own_identity->user_id) || EMPTYSTR(own_identity->address))
return PEP_ILLEGAL_VALUE;
const pEp_identity* group_identity = group->group_identity;
if (!group_identity || EMPTYSTR(group_identity->user_id) || EMPTYSTR(group_identity->address))
return PEP_ILLEGAL_VALUE;
pEp_identity* my_member_ident = NULL;
pEp_member* me_mem = NULL;
member_list* memberlist = NULL;
sqlite3_reset(session->retrieve_own_membership_info_for_group_and_ident);
sqlite3_bind_text(session->retrieve_own_membership_info_for_group_and_ident, 1, group->group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->retrieve_own_membership_info_for_group_and_ident, 2, group->group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->retrieve_own_membership_info_for_group_and_ident, 3, own_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->retrieve_own_membership_info_for_group_and_ident, 4, own_identity->address, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->retrieve_own_membership_info_for_group_and_ident);
switch (result) {
case SQLITE_ROW: {
my_member_ident = identity_dup(own_identity);
if (!my_member_ident)
goto enomem;
me_mem = new_member(my_member_ident);
if (!me_mem)
goto enomem;
me_mem->joined = sqlite3_column_int(session->retrieve_own_membership_info_for_group_and_ident, 0);
memberlist = new_memberlist(me_mem);
if (!memberlist)
goto enomem;
group->members = memberlist;
group->manager = new_identity((const char *) sqlite3_column_text(session->retrieve_own_membership_info_for_group_and_ident, 2),
NULL,
(const char *) sqlite3_column_text(session->retrieve_own_membership_info_for_group_and_ident, 1),
NULL);
if (!group->manager)
goto enomem;
group->active = sqlite3_column_int(session->retrieve_own_membership_info_for_group_and_ident, 3);
break;
}
default:
status = PEP_NO_MEMBERSHIP_STATUS_FOUND;
}
return status;
enomem:
if (!memberlist) {
if (!me_mem)
free_identity(my_member_ident);
else
free_member(me_mem);
}
else
free_memberlist(memberlist);
return PEP_OUT_OF_MEMORY;
}
PEP_STATUS leave_group(
PEP_SESSION session,
pEp_identity *group_identity,
pEp_identity *member_identity
) {
if (!session || !group_identity || !member_identity)
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(group_identity->user_id) || EMPTYSTR(member_identity->user_id) ||
EMPTYSTR(group_identity->address) || EMPTYSTR(member_identity->address)) {
return PEP_ILLEGAL_VALUE;
}
// get our status, if there is any
bool am_member = false;
PEP_STATUS status = get_own_membership_status(session, group_identity, member_identity, &am_member);
if (status == PEP_NO_MEMBERSHIP_STATUS_FOUND)
return PEP_STATUS_OK;
if (status != PEP_STATUS_OK)
return status;
if (!am_member)
return PEP_STATUS_OK;
// Ok, we clearly joined the group at some point.
int result = 0;
sqlite3_reset(session->leave_group);
sqlite3_bind_text(session->leave_group, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->leave_group, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->leave_group, 3, member_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->leave_group, 4, member_identity->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->leave_group);
sqlite3_reset(session->leave_group);
if (result != SQLITE_DONE)
return PEP_CANNOT_CREATE_GROUP;
return PEP_STATUS_OK;
}
PEP_STATUS group_enable(
PEP_SESSION session,
pEp_identity *group_identity
) {
bool exists = false;
PEP_STATUS status = exists_group(session, group_identity, &exists);
if (status != PEP_STATUS_OK)
return status;
if (!exists)
return PEP_GROUP_NOT_FOUND;
int result = 0;
sqlite3_reset(session->enable_group);
sqlite3_bind_text(session->enable_group, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->enable_group, 2, group_identity->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->enable_group);
sqlite3_reset(session->enable_group);
if (result != SQLITE_DONE)
status = PEP_CANNOT_ENABLE_GROUP;
return status;
}
PEP_STATUS exists_group(
PEP_SESSION session,
pEp_identity* group_identity,
bool* exists
) {
PEP_STATUS status = PEP_STATUS_OK;
sqlite3_reset(session->exists_group_entry);
sqlite3_bind_text(session->exists_group_entry, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->exists_group_entry, 2, group_identity->address, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->exists_group_entry);
switch (result) {
case SQLITE_ROW: {
// yeah yeah, I know, we could be lazy here, but it looks bad.
*exists = (sqlite3_column_int(session->exists_group_entry, 0) != 0);
break;
}
default:
status = PEP_UNKNOWN_DB_ERROR;
}
sqlite3_reset(session->exists_group_entry);
return status;
}
// N.B. Call update_identity first!!
PEP_STATUS group_add_member(
PEP_SESSION session,
pEp_identity *group_identity,
pEp_identity *group_member
) {
bool exists = false;
PEP_STATUS status = exists_group(session, group_identity, &exists);
if (status != PEP_STATUS_OK)
return status;
if (!exists)
return PEP_GROUP_NOT_FOUND;
int result = 0;
sqlite3_reset(session->group_add_member);
sqlite3_bind_text(session->group_add_member, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_add_member, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_add_member, 3, group_member->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->group_add_member, 4, group_member->address, -1,
SQLITE_STATIC);
result = sqlite3_step(session->group_add_member);
sqlite3_reset(session->group_add_member);
if (result != SQLITE_DONE)
status = PEP_CANNOT_ADD_GROUP_MEMBER;
return status;
}
PEP_STATUS retrieve_full_group_membership(
PEP_SESSION session,
pEp_identity* group_identity,
member_list** members)
{
PEP_STATUS status = PEP_STATUS_OK;
if (!session || !group_identity || !members)
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(group_identity->user_id) || EMPTYSTR(group_identity->address))
return PEP_ILLEGAL_VALUE;
*members = NULL;
sqlite3_reset(session->get_all_members);
sqlite3_bind_text(session->get_all_members, 1, group_identity->user_id, -1, SQLITE_STATIC);
sqlite3_bind_text(session->get_all_members, 2, group_identity->address, -1, SQLITE_STATIC);
int result;
member_list* retval = NULL;
member_list** member_list_next = &retval;
while ((result = sqlite3_step(session->get_all_members)) == SQLITE_ROW) {
pEp_identity *ident = new_identity((const char *) sqlite3_column_text(session->get_all_members, 1),
NULL,(const char *) sqlite3_column_text(session->get_all_members, 0),
NULL);
assert(ident);
if (ident == NULL)
goto enomem;
pEp_member* new_mem = new_member(ident);
new_mem->joined = sqlite3_column_int(session->get_all_members, 2);
member_list* new_node = new_memberlist(new_mem);
if (!new_node)
goto enomem;
*member_list_next = new_node;
member_list_next = &(new_node->next);
}
member_list* curr = retval;
for ( ; curr ; curr = curr->next) {
if (!(curr->member && curr->member->ident))
goto enomem;
status = update_identity(session, curr->member->ident);
}
sqlite3_reset(session->get_all_members);
*members = retval;
return PEP_STATUS_OK;
enomem:
status = PEP_OUT_OF_MEMORY;
//pEp_error: // Uncomment if other errors are valid
sqlite3_reset(session->get_all_members);
free_memberlist(retval);
return status;
}
PEP_STATUS retrieve_active_member_list(
PEP_SESSION session,
pEp_identity* group_identity,
member_list** mbr_list)
{
PEP_STATUS status = PEP_STATUS_OK;
if (!session || !group_identity || !mbr_list)
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(group_identity->user_id) || EMPTYSTR(group_identity->address))
return PEP_ILLEGAL_VALUE;
*mbr_list = NULL;
sqlite3_reset(session->get_active_members);
sqlite3_bind_text(session->get_active_members, 1, group_identity->user_id, -1, SQLITE_STATIC);
sqlite3_bind_text(session->get_active_members, 2, group_identity->address, -1, SQLITE_STATIC);
int result;
member_list* retval = NULL;
member_list** mbr_list_next = &retval;
while ((result = sqlite3_step(session->get_active_members)) == SQLITE_ROW) {
pEp_identity *ident = new_identity((const char *) sqlite3_column_text(session->get_active_members, 1),
NULL,(const char *) sqlite3_column_text(session->get_active_members, 0),
NULL);
assert(ident);
if (ident == NULL)
goto enomem;
pEp_member* member = new_member(ident);
if (!member)
goto enomem;
member_list* new_node = new_memberlist(member);
if (!new_node) {
free_member(member);
goto enomem;
}
new_node->member->joined = true;
*mbr_list_next = new_node;
mbr_list_next = &(new_node->next);
}
member_list* curr = retval;
for ( ; curr && curr->member && curr->member->ident; curr = curr->next) {
if (!curr->member->ident)
goto enomem;
status = update_identity(session, curr->member->ident);
}
sqlite3_reset(session->get_active_members);
*mbr_list = retval;
return PEP_STATUS_OK;
enomem:
status = PEP_OUT_OF_MEMORY;
//pEp_error: // Uncomment if other errors are valid
sqlite3_reset(session->get_active_members);
free_memberlist(retval);
return status;
}
PEP_STATUS retrieve_group_info(PEP_SESSION session, pEp_identity* group_identity, pEp_group** group_info) {
if (!session || !group_identity || EMPTYSTR(group_identity->address) || !group_info)
return PEP_ILLEGAL_VALUE;
pEp_group* group = NULL;
pEp_identity* manager = NULL;
member_list* members = NULL;
PEP_STATUS status = PEP_STATUS_OK;
*group_info = NULL;
pEp_identity* stored_identity = NULL;
status = get_identity(session, group_identity->address, group_identity->user_id, &stored_identity);
if (status != PEP_STATUS_OK)
return status;
if (!stored_identity)
return PEP_CANNOT_FIND_IDENTITY;
status = retrieve_full_group_membership(session, stored_identity, &members);
if (status != PEP_STATUS_OK)
goto pEp_error;
status = get_group_manager(session, stored_identity, &manager);
if (status != PEP_STATUS_OK)
goto pEp_error;
group = new_group(stored_identity, manager, members);
if (!group)
goto enomem;
bool active = false;
status = is_group_active(session, group_identity, &active);
if (status != PEP_STATUS_OK)
goto pEp_error;
group->active = active;
*group_info = group;
return status;
enomem:
status = PEP_OUT_OF_MEMORY;
pEp_error:
if (!group) {
free_memberlist(members);
free_identity(manager);
}
else {
free_group(group);
}
return status;
}
PEP_STATUS send_GroupAdopted(PEP_SESSION session, pEp_identity* group_identity, pEp_identity* from) {
PEP_STATUS status = PEP_STATUS_OK;
pEp_identity* manager = NULL;
if (!session->messageToSend)
return PEP_SEND_FUNCTION_NOT_REGISTERED;
if (!session || !group_identity || !from)
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(group_identity->user_id) ||
EMPTYSTR(group_identity->address) ||
EMPTYSTR(from->user_id) ||
EMPTYSTR(from->address)) {
return PEP_ILLEGAL_VALUE;
}
if (!is_me(session, group_identity))
status = update_identity(session, group_identity);
else
status = _myself(session, group_identity, false, false, false, true);
if (status != PEP_STATUS_OK)
return status;
else if (!(is_me(session, group_identity) && group_identity->flags & PEP_idf_group_ident)) {
return PEP_ILLEGAL_VALUE;
}
// Get the manager
status = get_group_manager(session, group_identity, &manager);
if (status != PEP_STATUS_OK)
return status;
else if (!manager)
return PEP_OUT_OF_MEMORY;
else if (EMPTYSTR(manager->address) || EMPTYSTR(manager->user_id)) {
status = PEP_UNKNOWN_ERROR;
goto pEp_error;
}
// ?? Is this really necessary? It's an internal call. Well, anything can mess up, no?
if (is_me(session, manager))
return PEP_ILLEGAL_VALUE;
status = update_identity(session, manager);
if (status != PEP_STATUS_OK)
goto pEp_error;
// ??
char* _data = NULL;
size_t _size = 0;
// Ok, let's get the payload set up, because we can duplicate this for each message.
status = _build_managed_group_message_payload(session, group_identity,
from, &_data, &_size,
ManagedGroup_PR_groupAdopted);
if (status != PEP_STATUS_OK)
return status;
pEp_identity* recip = manager; // This will be duped in base_prepare_message
PEP_rating recip_rating;
status = identity_rating(session, recip, &recip_rating);
if (status != PEP_STATUS_OK)
goto pEp_error;
if (recip_rating < PEP_rating_reliable)
return PEP_NO_TRUST; // ??? FIXME
// encrypt and send this baby and get out
status = _create_and_send_managed_group_message(session, from, recip, _data, _size, NULL);
if (status != PEP_STATUS_OK)
goto pEp_error;
return status;
pEp_error:
free_identity(manager);
return status;
}
PEP_STATUS receive_GroupInvite(PEP_SESSION session, message* msg, PEP_rating rating, GroupInvite_t* gc) {
PEP_STATUS status = PEP_STATUS_OK;
if (rating < PEP_rating_reliable)
return PEP_NO_TRUST; // Find better error
if (!msg)
return PEP_ILLEGAL_VALUE;
// Make sure everything's there are enforce exactly one recip
if (!gc || !msg->to || !msg->to->ident || msg->to->next)
return PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
pEp_identity* member_ident = NULL;
pEp_identity* group_identity = NULL;
pEp_identity* manager = NULL;
identity_list* list = NULL;
pEp_group* group = NULL;
char* own_id = NULL;
stringlist_t* keylist = NULL;
// FIXME: this will be hard without address aliases.
// We will probably always have to do this, but if something changes externally we need this check.
if (!msg->to->ident->me) {
status = update_identity(session, msg->to->ident);
if (status != PEP_STATUS_OK)
return status;
}
if (!is_me(session, msg->to->ident))
return PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
group_identity = Identity_to_Struct(&(gc->groupIdentity), NULL);
if (!group_identity)
return PEP_UNKNOWN_ERROR; // we really don't know why
manager = Identity_to_Struct(&(gc->manager), NULL);
if (!manager)
return PEP_UNKNOWN_ERROR;
free(manager->user_id);
manager->user_id = NULL;
free(group_identity->user_id);
group_identity->user_id = NULL;
status = update_identity(session, manager);
if (!manager->fpr) {// at some point, we can require this to be the sender fpr I think - FIXME
status = PEP_KEY_NOT_FOUND;
goto pEp_free;
}
if (status != PEP_STATUS_OK)
goto pEp_free;
// If we are the manager of this group, we should ignore this message - Volker, fixme if groupsync should be different here
// when you implement it
if (is_me(session, manager))
goto pEp_free;
// Ok then - let's do this:
// First, we need to ensure the group_ident has an own ident instead
status = get_default_own_userid(session, &own_id);
if (status != PEP_STATUS_OK) {
free(own_id); // Just in case
goto pEp_free;
}
if (!own_id) {
status = PEP_NO_OWN_USERID_FOUND;
goto pEp_free;
}
// Takes ownership here, which is why we DON'T free own_id at the end
group_identity->user_id = own_id;
own_id = NULL; // avoid double-free;
// Ok, let's ensure we HAVE the key for this group:
status = find_private_keys(session, group_identity->fpr, &keylist);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (!keylist) {
status = PEP_KEY_NOT_FOUND;
goto pEp_free;
}
status = set_own_key(session, group_identity, group_identity->fpr);
if (status != PEP_STATUS_OK)
goto pEp_free;
member_ident = identity_dup(msg->to->ident);
if (!member_ident) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
list = new_identity_list(member_ident);
if (!list) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
status = group_create(session, group_identity, manager, list, &group);
if (status != PEP_STATUS_OK)
goto pEp_free;
status = add_own_membership_entry(session, group_identity, manager, msg->to->ident);
// Ok, we did all we have to do and it worked out. Notify the app.
if (status == PEP_STATUS_OK && session->notifyHandshake) {
// identities go to the callee, so we have to dup them here because the normal ones belong
// to the returned group. #notmyspec ;)
pEp_identity* grp = identity_dup(group_identity);
pEp_identity* mgr = identity_dup(manager);
status = session->notifyHandshake(grp, mgr, SYNC_NOTIFY_GROUP_INVITATION);
}
pEp_free:
if (!group) {
if (!list)
free_identity(member_ident);
else
free_identity_list(list);
free_identity(manager);
free_identity(group_identity);
}
else
free_group(group);
free_stringlist(keylist);
return status;
}
PEP_STATUS receive_GroupDissolve(PEP_SESSION session, message* msg, PEP_rating rating, GroupDissolve_t* gd) {
PEP_STATUS status = PEP_STATUS_OK;
if (rating < PEP_rating_reliable)
return PEP_NO_TRUST; // Find better error
if (!msg)
return PEP_ILLEGAL_VALUE;
if (!msg->_sender_fpr) // We'll never be able to verify. Reject
return PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
// Make sure everything's there are enforce exactly one recip
if (!gd || !msg->to || !msg->to->ident || msg->to->next)
return PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
// We will probably always have to do this, but if something changes externally we need this check.
if (!msg->to->ident->me) {
status = update_identity(session, msg->to->ident);
if (status != PEP_STATUS_OK)
return status;
}
// this will be hard without address aliases
if (!is_me(session, msg->to->ident))
return PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
stringlist_t* keylist = NULL;
pEp_identity* manager = NULL;
pEp_identity* group_identity = NULL;
pEp_identity* tmp_ident = NULL;
pEp_group* group = NULL;
pEp_identity* own_identity = msg->to->ident;
group_identity = Identity_to_Struct(&(gd->groupIdentity), NULL);
if (!group_identity)
return PEP_UNKNOWN_ERROR; // we really don't know why, and nothing is allocated yet
manager = Identity_to_Struct(&(gd->manager), NULL);
if (!manager) {
status = PEP_UNKNOWN_ERROR;
goto pEp_free;
}
free(manager->user_id);
manager->user_id = NULL;
status = update_identity(session, manager);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (is_me(session, manager)) {
// if this is from me, I should never have received this message, so we ignore it
status = PEP_STATUS_OK;
goto pEp_free;
}
if (!manager->fpr) {
status = PEP_KEY_NOT_FOUND;
goto pEp_free;
}
// N.B. This check is sort of a placeholder - this will change once it is possible
// for the signer to NOT be the manager of the group. For now, we check the claim against
// the sender and the known database manager against the sender.
// It would be stupid to lie here, but form and all.. FIXME: Change when signature delivery is
// implemented as in https://dev.pep.foundation/Engine/GroupEncryption#design - this will no longer
// be sufficient or entirely correct
// FIXME - we'll have to check that it matches A sender key, not "THE" sender key.
// FIXME AGAIN - actually, this is a problem. It could match a GREEN sender key, sure.
// but yellow won't do (I guess unless it was once a default?)
// 1. is manager->fpr the same as the sender key? If not, look for other previous defaults for this user...
if (strcmp(manager->fpr, msg->_sender_fpr) != 0) {
// 2. See if there exists a trust entry in the DB for this key and user
pEp_identity* tmp_ident = identity_dup(manager);
if (!tmp_ident) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
free(tmp_ident->fpr);
tmp_ident->fpr = strdup(msg->_sender_fpr);
if (!tmp_ident->fpr) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
tmp_ident->comm_type = PEP_ct_unknown;
status = get_trust(session, tmp_ident);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (tmp_ident->comm_type < PEP_ct_strong_but_unconfirmed) {
status = PEP_MESSAGE_IGNORE; // Should this be "ignore" or "OK"?
goto pEp_free;
}
}
if (strcmp(manager->address, msg->from->address) != 0) {// FIXME: aliases...
status = PEP_DISTRIBUTION_ILLEGAL_MESSAGE; // FIXME: Do we just ignore it?
goto pEp_free;
}
// Update group identity id
char* own_id = NULL;
status = get_default_own_userid(session, &own_id);
if (status != PEP_STATUS_OK) {
free(own_id);
goto pEp_free;
}
if (EMPTYSTR(own_id)) {
status = PEP_NO_OWN_USERID_FOUND;
goto pEp_free;
}
free(group_identity->user_id);
// Takes ownership
group_identity->user_id = own_id;
own_id = NULL; // PREVENT DOUBLE-FREE!
// Note: at this point, group_identity ownership goes to the group object. pEp_free takes account of this.
status = retrieve_group_info(session, group_identity, &group);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (!group) {
status = PEP_UNKNOWN_ERROR;
goto pEp_free;
}
status = retrieve_own_membership_info_for_group_and_identity(session, group, own_identity);
if (status != PEP_STATUS_OK)
goto pEp_free;
// If none of these are true, then we don't know about it. FIXME: check against above
// This shouldn't be fatal - "receive group we don't know about? Ignore"
if (!group->members || !group->members->member || !group->members->member->ident)
goto pEp_free; // status is PEP_STATUS_OK
// Ok, so we have a group with this manager and we have received info about it from our own
// membership info. We've at least been invited.
// Ok then - let's do this:
// set group to inactive and our own membership to non-participant
status = group_dissolve(session, group_identity, manager);
// Ok, database is set. Now for the keys:
// Ok, let's ensure we HAVE the key for this group:
status = find_private_keys(session, group_identity->fpr, &keylist);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (!keylist) {
status = PEP_KEY_NOT_FOUND;
goto pEp_free;
}
// It should have been revoked on message import. Was it?
bool revoked = false;
status = key_revoked(session, group_identity->fpr, &revoked);
if (!revoked) {
status = PEP_UNKNOWN_ERROR;
goto pEp_free;
}
pEp_free:
free_identity(manager);
free_stringlist(keylist);
free_identity(tmp_ident);
if (group)
free_group(group);
else
free_identity(group_identity);
return status;
}
PEP_STATUS receive_GroupAdopted(PEP_SESSION session, message* msg, PEP_rating rating, GroupAdopted_t* ga) {
PEP_STATUS status = PEP_STATUS_OK;
pEp_identity* db_group_ident = NULL;
char* own_id = NULL;
pEp_identity* group_identity = NULL;
pEp_identity* member = NULL;
if (rating < PEP_rating_reliable)
return PEP_NO_TRUST; // Find better error
if (!msg)
return PEP_ILLEGAL_VALUE;
// Make sure everything's there are enforce exactly one recip
if (!ga || !msg->to || !msg->to->ident || msg->to->next)
return PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
// We will probably always have to do this, but if something changes externally we need this check.
if (!msg->to->ident->me) {
status = update_identity(session, msg->to->ident);
if (status != PEP_STATUS_OK)
goto pEp_free;
}
// this will be hard without address aliases
if (!is_me(session, msg->to->ident)) {
status = PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
goto pEp_free;
}
// FIXME: is there a check we need to do here?
// pEp_identity* own_identity = msg->to->ident;
group_identity = Identity_to_Struct(&(ga->groupIdentity), NULL);
if (!group_identity) {
status = PEP_UNKNOWN_ERROR; // we really don't know why
goto pEp_free;
}
member = Identity_to_Struct(&(ga->member), NULL);
if (!member) {
status = PEP_UNKNOWN_ERROR; // we really don't know why
goto pEp_free;
}
status = get_default_own_userid(session, &own_id);
if (status != PEP_STATUS_OK || EMPTYSTR(own_id)) {
if (status == PEP_STATUS_OK)
status = PEP_UNKNOWN_ERROR;
goto pEp_free;
}
// is this even our group? If not, ignore.
status = get_identity(session, group_identity->address, own_id, &db_group_ident);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (!db_group_ident) {
status = PEP_CANNOT_FIND_IDENTITY;
goto pEp_free;
}
// There's nothing in the group_identity we care about actually other than the address, so free and replace
free_identity(group_identity);
group_identity = db_group_ident;
db_group_ident = NULL; // prevent double-free!!
bool is_mine = NULL;
status = is_group_mine(session, group_identity, &is_mine);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (!is_mine) // If it's not my group, I don't care and will ignore it.
goto pEp_free;
// is this even someone we invited? If not, ignore.
bool invited = false;
// Ok, first off, the user_id will be wrong.
free(member->user_id);
member->user_id = NULL;
status = update_identity(session, member);
if (status != PEP_STATUS_OK)
goto pEp_free;
status = is_invited_group_member(session, group_identity, member, &invited);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (!invited)
goto pEp_free; // Nice try, NSA Bob! But we ignore it.
// Ok. So. Do we need to check sender's FPR? I think we do.
// It would be stupid to lie here, but form and all.. FIXME: Change when signature delivery is
// implemented as in https://dev.pep.foundation/Engine/GroupEncryption#design - this will no longer
// be sufficient or entirely correct
if ((strcmp(member->fpr, msg->_sender_fpr) != 0) || (strcmp(member->address, msg->from->address) != 0)) {
status = PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
goto pEp_free;
}
// Ok, we invited them. Set their status to "joined".
status = set_membership_status(session, group_identity, member, true);
pEp_free:
free_identity(db_group_ident);
free(own_id);
free_identity(group_identity);
free_identity(member);
return status;
}
PEP_STATUS receive_managed_group_message(PEP_SESSION session, message* msg, PEP_rating rating, Distribution_t* dist) {
if (!session || !msg || !msg->_sender_fpr || !dist)
return PEP_ILLEGAL_VALUE;
switch (dist->choice.managedgroup.present) {
case ManagedGroup_PR_groupInvite:
return receive_GroupInvite(session, msg, rating, &(dist->choice.managedgroup.choice.groupInvite));
case ManagedGroup_PR_groupDissolve:
return receive_GroupDissolve(session, msg, rating, &(dist->choice.managedgroup.choice.groupDissolve));
case ManagedGroup_PR_groupAdopted:
return receive_GroupAdopted(session, msg, rating, &(dist->choice.managedgroup.choice.groupAdopted));
break;
default:
return PEP_DISTRIBUTION_ILLEGAL_MESSAGE;
}
return PEP_STATUS_OK;
}
PEP_STATUS is_own_group_identity(PEP_SESSION session, pEp_identity* group_identity, bool* is_own) {
if (!session || !group_identity || EMPTYSTR(group_identity->user_id) || EMPTYSTR(group_identity->address))
return PEP_ILLEGAL_VALUE;
*is_own = false;
pEp_identity* manager = NULL;
PEP_STATUS status = get_group_manager(session, group_identity, &manager);
if (status == PEP_STATUS_OK && manager) {
if (is_me(session, manager))
*is_own = true;
}
free(manager);
return status;
}
/******************************************************************************************
* API FUNCTIONS
******************************************************************************************/
DYNAMIC_API pEp_member *new_member(pEp_identity *ident) {
if (!ident)
return NULL;
pEp_member* member = (pEp_member*)calloc(1, sizeof(pEp_member));
member->ident = ident;
return member;
}
DYNAMIC_API void free_member(pEp_member *member) {
if (member) {
free_identity(member->ident);
free(member);
}
}
DYNAMIC_API member_list *new_memberlist(pEp_member *member) {
member_list* retval = (member_list*)(calloc(1, sizeof(member_list)));
if (!retval)
return NULL;
retval->member = member;
return retval;
}
DYNAMIC_API void free_memberlist(member_list *list) {
member_list* curr = list;
while (curr) {
member_list *next = curr->next;
free_member(curr->member);
free(curr);
curr = next;
}
}
DYNAMIC_API member_list *memberlist_add(member_list *list, pEp_member *member) {
if (!list)
return new_memberlist(member);
if (!list->member) {
list->member = member;
return list;
}
member_list* curr = list;
member_list** last_ptr = NULL;
while (curr) {
last_ptr = &(curr->next);
curr = curr->next;
}
*last_ptr = new_memberlist(member);
return *last_ptr;
}
DYNAMIC_API pEp_group *new_group(
pEp_identity *group_identity,
pEp_identity *manager,
member_list *memberlist
) {
if (!group_identity)
return NULL;
pEp_group* retval = (pEp_group*)calloc(1, sizeof(pEp_group));
if (!retval)
return NULL;
retval->group_identity = group_identity;
retval->manager = manager;
retval->members = memberlist;
return retval;
}
DYNAMIC_API void free_group(pEp_group *group) {
free_identity(group->group_identity);
free_identity(group->manager);
free_memberlist(group->members);
}
static PEP_STATUS _validate_member_ident(PEP_SESSION session, pEp_identity* ident) {
if (!ident || EMPTYSTR(ident->address) || EMPTYSTR(ident->username) || EMPTYSTR(ident->fpr))
return PEP_ILLEGAL_VALUE;
if (_rating(ident->comm_type) < PEP_rating_reliable)
return PEP_KEY_UNSUITABLE;
return PEP_STATUS_OK;
}
static PEP_STATUS _validate_member_identities(PEP_SESSION session, identity_list* member_idents) {
if (!session)
return PEP_ILLEGAL_VALUE;
identity_list* curr = member_idents;
for ( ; curr && curr->ident; curr = curr->next) {
pEp_identity* the_id = curr->ident;
if (_validate_member_ident(session, the_id) != PEP_STATUS_OK)
return PEP_CANNOT_ADD_GROUP_MEMBER;
}
return PEP_STATUS_OK;
}
DYNAMIC_API PEP_STATUS group_create(
PEP_SESSION session,
pEp_identity *group_identity,
pEp_identity *manager,
identity_list *member_ident_list,
pEp_group **group
) {
if (!session || !group_identity || !manager)
return PEP_ILLEGAL_VALUE;
if (!group_identity->address || !manager->address)
return PEP_ILLEGAL_VALUE;
PEP_STATUS status = _validate_member_identities(session, member_ident_list);
if (status != PEP_STATUS_OK)
return status;
pEp_group* _group = NULL;
pEp_identity* group_ident_clone = NULL;
pEp_identity* manager_clone = NULL;
member_list* memberlist = NULL;
if (!group_identity->user_id || !is_me(session, group_identity)) {
char* own_id = NULL;
status = get_default_own_userid(session, &own_id);
if (status != PEP_STATUS_OK)
return status;
free(group_identity->user_id);
group_identity->user_id = own_id;
}
// We have an address, create a key for the group if needed
status = myself(session, group_identity);
if (status != PEP_STATUS_OK)
return status;
// Do we already have this group?
bool already_exists = false;
status = exists_group(session, group_identity, &already_exists);
if (already_exists)
return PEP_GROUP_EXISTS;
// set it as a group_identity
status = set_identity_flags(session, group_identity, group_identity->flags | PEP_idf_group_ident);
if (status != PEP_STATUS_OK)
goto pEp_error;
// ALLOCATIONS BEGIN HERE - after this, stuff has to be freed.
// from here on out: group_identity still belongs to the caller. So we dup it.
group_ident_clone = identity_dup(group_identity);
manager_clone = identity_dup(manager);
if (!group_ident_clone || !manager_clone) {
status = PEP_OUT_OF_MEMORY;
goto pEp_error;
}
// update the manager ident
if (is_me(session, manager))
status = myself(session, manager);
else
status = update_identity(session, manager);
if (status != PEP_STATUS_OK)
goto pEp_error;
if (manager->flags & PEP_idf_group_ident) {
status = PEP_ILLEGAL_VALUE;
goto pEp_error;
}
// Ok, we're ready to do DB stuff. Do some allocation:
memberlist = identity_list_to_memberlist(member_ident_list);
// FIXME: double-check empty list head in function above
if (member_ident_list && member_ident_list->ident && !memberlist) {
status = PEP_OUT_OF_MEMORY;
goto pEp_error;
}
// memberlist fully belongs to us and will now go to the group
// All fields here belong to us; if the group fails, they'll
// be freed individually in the error block
_group = new_group(group_ident_clone, manager_clone, memberlist);
if (!_group) {
status = PEP_OUT_OF_MEMORY;
goto pEp_error;
}
// Before we start doing database stuff, also get current member info (yes, I realise
// we're traversing the list twice.)
member_list* curr_member = NULL;
// Will bail if adding fails.
for (curr_member = memberlist; curr_member && curr_member->member && curr_member->member->ident && status == PEP_STATUS_OK;
curr_member = curr_member->next) {
pEp_identity* member = curr_member->member->ident;
if (is_me(session, member))
status = myself(session, member);
else
status = update_identity(session, member);
if (status != PEP_STATUS_OK)
goto pEp_error; // We can do this because we are BEFORE the start of the transaction!!!
}
sqlite3_exec(session->db, "BEGIN TRANSACTION ;", NULL, NULL, NULL);
status = create_group_entry(session, _group);
if (status == PEP_STATUS_OK) {
status = group_enable(session, group_ident_clone);
}
if (status == PEP_STATUS_OK) {
curr_member = NULL;
// Will bail if adding fails.
for (curr_member = memberlist; curr_member && curr_member->member && status == PEP_STATUS_OK;
curr_member = curr_member->next) {
if (!curr_member->member->ident)
status = PEP_ILLEGAL_VALUE;
else {
pEp_identity *member = curr_member->member->ident;
if (EMPTYSTR(member->user_id) || EMPTYSTR(member->address)) {
status = PEP_ILLEGAL_VALUE;
} else {
status = group_add_member(session, group_ident_clone, member);
if (status == PEP_STATUS_OK) {
if (is_me(session, member)) {
status = add_own_membership_entry(session, group_ident_clone, manager, member);
if (status == PEP_STATUS_OK && is_me(session, manager))
status = group_join(session, group_ident_clone, member);
}
}
}
}
if (status != PEP_STATUS_OK)
break; // will cause rollback and goto pEp_error
}
}
if (status != PEP_STATUS_OK) {
sqlite3_exec(session->db, "ROLLBACK ;", NULL, NULL, NULL);
goto pEp_error;
}
sqlite3_exec(session->db, "COMMIT ;", NULL, NULL, NULL);
// Ok, mail em.
if (is_me(session, manager))
status = _send_managed_group_message_to_list(session, _group, ManagedGroup_PR_groupInvite);
if (group)
*group = _group;
else
free_group(_group);
return status;
pEp_error:
if (!_group) {
free_memberlist(memberlist);
free_identity(group_ident_clone);
free_identity(manager_clone);
}
else
free_group(_group);
*group = NULL;
return status;
}
DYNAMIC_API PEP_STATUS group_join(
PEP_SESSION session,
pEp_identity *group_identity,
pEp_identity *as_member
) {
if (!session || !group_identity || !as_member)
return PEP_ILLEGAL_VALUE;
if (EMPTYSTR(group_identity->user_id) || EMPTYSTR(as_member->user_id) ||
EMPTYSTR(group_identity->address) || EMPTYSTR(as_member->address)) {
return PEP_ILLEGAL_VALUE;
}
// get our status, if there is any
bool am_member = false;
// FIXME: differentiate between no records and DB error in call
PEP_STATUS status = get_own_membership_status(session, group_identity, as_member, &am_member);
if (status != PEP_STATUS_OK)
return status;
if (am_member)
return PEP_STATUS_OK; // FIXME: ask Volker if there is a reason to do this, like the message wasn't sent
// Ok, group invite exists. Do it.
status = send_GroupAdopted(session, group_identity, as_member);
if (status != PEP_STATUS_OK)
return status;
status = _set_own_status_joined(session, group_identity, as_member);
return PEP_STATUS_OK;
}
DYNAMIC_API PEP_STATUS group_dissolve(
PEP_SESSION session,
pEp_identity *group_identity,
pEp_identity *manager
) {
if (!session || !group_identity || !manager)
return PEP_ILLEGAL_VALUE;
pEp_identity* stored_manager = NULL;
member_list* list = NULL;
pEp_group* group = NULL;
bool exists = false;
PEP_STATUS status = exists_group(session, group_identity, &exists);
if (status != PEP_STATUS_OK)
return status;
if (!exists)
return PEP_GROUP_NOT_FOUND;
// Ok, do we have the right manager? If not, don't do it.
status = get_group_manager(session, group_identity, &stored_manager);
if (status != PEP_STATUS_OK)
return status;
if (!stored_manager)
return PEP_OUT_OF_MEMORY;
if (!stored_manager->user_id || !stored_manager->address) {
status = PEP_UNKNOWN_DB_ERROR;
goto pEp_free;
}
if ((strcmp(stored_manager->user_id, manager->user_id) != 0) ||
(strcmp(stored_manager->address, manager->address) != 0)) {
status = PEP_CANNOT_DISABLE_GROUP;
goto pEp_free;
}
status = _set_group_as_disabled(session, group_identity);
if (status != PEP_STATUS_OK)
goto pEp_free;
// If I'm the manager, then I have to send out the dissolution stuff and deactivate
if (is_me(session, manager)) {
status = revoke_key(session, group_identity->fpr, NULL);
if (status != PEP_STATUS_OK)
goto pEp_free;
// You'd better get all the group info here
status = retrieve_active_member_list(session, group_identity, &list);
if (status != PEP_STATUS_OK)
return status;
group = new_group(group_identity, manager, list);
if (!group) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
status = _send_managed_group_message_to_list(session, group, ManagedGroup_PR_groupDissolve);
if (status != PEP_STATUS_OK)
goto pEp_free; // fixme ??
// deactivate members
member_list* memb = group->members;
while (memb && memb->member && memb->member->ident) {
pEp_identity* memb_ident = memb->member->ident;
if (EMPTYSTR(memb_ident->user_id) || EMPTYSTR(memb_ident->address)) {
status = PEP_UNKNOWN_ERROR;
goto pEp_free;
}
status = set_membership_status(session, group_identity, memb->member->ident, false);
memb = memb->next;
}
}
else {
// I'm not the manager. So I need to find the identities I have that
// know about or have joined this group and tell them the fun is over
member_list* my_group_idents = NULL;
status = _set_group_as_disabled(session, group_identity);
if (status != PEP_STATUS_OK)
goto pEp_free;
// Ok, group is now not usable. Let's get our membership straight.
status = _retrieve_own_membership_info_for_group(session, group_identity, &my_group_idents);
if (status != PEP_STATUS_OK)
goto pEp_free;
member_list* curr_member = my_group_idents;
while (curr_member) {
if (!curr_member->member)
break; // ??
pEp_identity* ident = curr_member->member->ident;
if (!ident)
break; // er... this should probably be an error, but given how we do lists, it's acceptable
status = _set_leave_group_status(session, group_identity, ident);
if (status != PEP_STATUS_OK)
goto pEp_free;
curr_member = curr_member->next;
}
}
pEp_free:
free_identity(stored_manager);
free_memberlist(list);
if (group) {
// We don't own the group_ident or manager that comes in here, so we need to set them to NULL.
group->group_identity = NULL;
group->manager = NULL;
group->members = NULL; // already freed
free_group(group);
}
return status;
}
DYNAMIC_API PEP_STATUS group_invite_member(
PEP_SESSION session,
pEp_identity *group_identity,
pEp_identity *group_member
) {
if (!session || !group_identity || !group_member)
return PEP_ILLEGAL_VALUE;
PEP_STATUS status = PEP_STATUS_OK;
pEp_identity* manager = NULL;
char* data = NULL;
size_t size = 0;
char* key_material_priv = NULL;
size_t key_material_size = 0;
bloblist_t* key_attachment = NULL;
if (_validate_member_ident(session, group_member) != PEP_STATUS_OK)
return PEP_CANNOT_ADD_GROUP_MEMBER;
status = get_group_manager(session, group_identity, &manager);
if (status != PEP_STATUS_OK)
return status;
if (!manager)
return PEP_UNKNOWN_ERROR;
// Trying to be sneaky
if (!is_me(session, manager))
return PEP_ILLEGAL_VALUE;
status = myself(session, manager);
if (status != PEP_STATUS_OK)
goto pEp_free;
status = group_add_member(session, group_identity, group_member);
if (status == PEP_STATUS_OK) {
if (is_me(session, group_member)) {
status = add_own_membership_entry(session, group_identity, manager, group_member);
if (status == PEP_STATUS_OK)
status = group_join(session, group_identity, group_member);
else {
status = PEP_UNKNOWN_ERROR;
goto pEp_free;
}
}
else {
status = _build_managed_group_message_payload(session, group_identity,
manager, &data, &size,
ManagedGroup_PR_groupInvite);
if (status != PEP_STATUS_OK)
goto pEp_free;
// Let's also get the private key for the group we want to distribute
status = export_secret_key(session, group_identity->fpr, &key_material_priv, &key_material_size);
if (status != PEP_STATUS_OK)
return status;
if (key_material_size == 0 || !key_material_priv)
return PEP_UNKNOWN_ERROR;
bloblist_t* key_attachment = new_bloblist(key_material_priv, key_material_size,
"application/pgp-keys",
"file://pEpkey_group_priv.asc");
if (key_attachment == NULL) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
key_material_priv = NULL; // belongs to attachment now
PEP_rating recip_rating;
status = identity_rating(session, group_member, &recip_rating);
if (status == PEP_STATUS_OK) {
if (recip_rating < PEP_rating_reliable) {
status = PEP_CANNOT_ADD_GROUP_MEMBER;
goto pEp_free;
}
// encrypt and send this baby and get out
// FIXME: mem? - it gets freed IF all goes well, but if not?
status = _create_and_send_managed_group_message(session, manager, group_member, data, size,
key_attachment);
// FIXME
data = NULL; // avoid double-free - check this
}
}
}
pEp_free:
free_identity(manager);
free(data);
if (!key_attachment)
free(key_material_priv);
else
free_bloblist(key_attachment);
return status;
}
DYNAMIC_API PEP_STATUS group_remove_member(
PEP_SESSION session,
pEp_identity *group_identity,
pEp_identity *group_member
) {
bool exists = false;
pEp_identity* manager = NULL;
char* group_key_to_revoke = NULL;
PEP_STATUS status = exists_group(session, group_identity, &exists);
if (status != PEP_STATUS_OK)
return status;
if (!exists)
return PEP_GROUP_NOT_FOUND;
// Make sure this person was ever invited to the group
bool is_invited = false;
status = is_invited_group_member(session, group_identity, group_member, &is_invited);
if (!is_invited)
return PEP_NO_MEMBERSHIP_STATUS_FOUND;
status = _remove_member_from_group(session, group_identity, group_member);
if (status != PEP_STATUS_OK)
return status;
status = get_group_manager(session, group_identity, &manager);
if (status != PEP_STATUS_OK)
goto pEp_free;
else if (!manager)
return PEP_UNKNOWN_ERROR; // nothing to deallocate at the moment
// We dup this because I'm not sure about ownership on the group identity fpr in key reset.
// FIXME: check this against key reset and revise if necessarily
group_key_to_revoke = strdup(group_identity->fpr);
if (!group_key_to_revoke) {
status = PEP_OUT_OF_MEMORY;
goto pEp_free;
}
status = key_reset(session, group_key_to_revoke, group_identity);
pEp_free:
free(group_key_to_revoke);
free_identity(manager);
return status;
}
DYNAMIC_API PEP_STATUS group_rating(
PEP_SESSION session,
pEp_identity *group_identity,
pEp_identity *manager,
PEP_rating *rating
) {
PEP_STATUS status = PEP_STATUS_OK;
if (!is_me(session, manager))
return identity_rating(session, group_identity, rating);
member_list* active_members = NULL;
status = retrieve_active_member_list(session, group_identity, &active_members);
if (status != PEP_STATUS_OK)
goto pEp_free;
PEP_rating _rating = PEP_rating_fully_anonymous;
if (active_members) {
member_list* curr;
for (curr = active_members; curr; curr = curr->next) {
if (!(curr->member) && curr->next != NULL) {
status = PEP_ILLEGAL_VALUE;
goto pEp_free;
}
if (!(curr->member->ident)) {
status = PEP_ILLEGAL_VALUE;
goto pEp_free;
}
PEP_rating tmp_rating = PEP_rating_undefined;
status = identity_rating(session, curr->member->ident, &tmp_rating);
if (status != PEP_STATUS_OK)
goto pEp_free;
if (tmp_rating < _rating)
_rating = tmp_rating;
}
}
else {
_rating = PEP_rating_undefined; // ??? or group_identity? I dunno.
}
*rating = _rating;
status = PEP_STATUS_OK;
pEp_free:
free_memberlist(active_members);
return status;
}
PEP_STATUS is_active_group_member(PEP_SESSION session, pEp_identity* group_identity,
pEp_identity* member, bool* is_active) {
if (!session || !is_active)
return PEP_ILLEGAL_VALUE;
if (!group_identity || EMPTYSTR(group_identity->user_id) || EMPTYSTR(group_identity->address))
return PEP_ILLEGAL_VALUE;
if (!member || EMPTYSTR(member->user_id) || EMPTYSTR(member->address))
return PEP_ILLEGAL_VALUE;
PEP_STATUS status = PEP_STATUS_OK;
sqlite3_reset(session->is_active_group_member);
sqlite3_bind_text(session->is_active_group_member, 1, group_identity->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->is_active_group_member, 2, group_identity->address, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->is_active_group_member, 3, member->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->is_active_group_member, 4, member->address, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->is_active_group_member);
if (result == SQLITE_ROW)
*is_active = sqlite3_column_int(session->is_active_group_member, 0);
else if (result == SQLITE_DONE)
status = PEP_NO_MEMBERSHIP_STATUS_FOUND;
else
status = PEP_UNKNOWN_DB_ERROR;
sqlite3_reset(session->is_active_group_member);
return status;
}