diff --git a/src/keymanagement.c b/src/keymanagement.c index 8c735d25..21f3c1fd 100644 --- a/src/keymanagement.c +++ b/src/keymanagement.c @@ -641,7 +641,7 @@ DYNAMIC_API PEP_STATUS update_identity( PEP_SESSION session, pEp_identity * identity ) { - PEP_STATUS status; + PEP_STATUS status = PEP_STATUS_OK; assert(session); assert(identity); @@ -650,15 +650,27 @@ DYNAMIC_API PEP_STATUS update_identity( if (!(session && identity && !EMPTYSTR(identity->address))) return PEP_ILLEGAL_VALUE; + // + // Record some information about the input identity so that we don't keep + // evaluating it + bool is_own_user = identity->me; + bool input_has_user_id = !EMPTYSTR(identity->user_id); + bool input_has_username = !EMPTYSTR(identity->user_id); + bool input_has_real_id = input_has_user_id ? (strstr(identity->user_id, "TOFU_") != identity->user_id) : false; + char* default_own_id = NULL; - status = get_default_own_userid(session, &default_own_id); + pEp_identity* stored_ident = NULL; - bool is_own_user = identity->me; + status = get_default_own_userid(session, &default_own_id); + if (status == PEP_STATUS_OK || status == PEP_CANNOT_FIND_IDENTITY) + status = PEP_STATUS_OK; + else + goto pEp_free; // Is this me, temporary or not? If so, BAIL. if (!is_own_user) { if (default_own_id) { - if (!EMPTYSTR(identity->user_id)) { + if (input_has_user_id) { if (strcmp(default_own_id, identity->user_id) == 0) { is_own_user = true; } @@ -691,320 +703,184 @@ DYNAMIC_API PEP_STATUS update_identity( } // Otherwise, we don't even HAVE an own user yet, so we're ok. } - if (is_own_user) - { + if (is_own_user) { free(default_own_id); return PEP_ILLEGAL_VALUE; } // We have, at least, an address. // Retrieve stored identity information! - pEp_identity* stored_ident = NULL; - if (!EMPTYSTR(identity->user_id)) { + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // If we can get a starting identity from the database, do it. If we have a user_id (thank you, users), + // this is pretty simple. + // + // Otherwise, we double-check that someone didn't pass in an own address (hey, if you don't give us a + // user_id, we're have to guess somehow, and treating own identities like partner identities is dangerous). + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + if (input_has_user_id) { // (we're gonna update the trust/fpr anyway, so we use the no-fpr-from-trust-db variant) // * do get_identity() to retrieve stored identity information status = get_identity_without_trust_check(session, identity->address, identity->user_id, &stored_ident); - - // Before we start - if there was no stored identity, we should check to make sure we don't - // have a stored identity with a temporary user_id that differs from the input user_id. This - // happens in multithreaded environments sometimes. - if (!stored_ident) { - identity_list* id_list = NULL; - status = get_identities_by_address(session, identity->address, &id_list); - - if (id_list) { - identity_list* id_curr = id_list; - bool input_is_TOFU = (strstr(identity->user_id, "TOFU_") == identity->user_id); - while (id_curr) { - pEp_identity* this_id = id_curr->ident; - if (this_id) { - char* this_uid = this_id->user_id; - bool curr_is_TOFU = false; - // this_uid should never be NULL, as this is half of the ident - // DB primary key - assert(!EMPTYSTR(this_uid)); - - curr_is_TOFU = (strstr(this_uid, "TOFU_") == this_uid); - if (curr_is_TOFU && !input_is_TOFU) { - // FIXME: should we also be fixing pEp_own_userId in this - // function here? - - // if usernames match, we replace the userid. - // FIXME: do we need to create an address match function which - // matches the whole dot-and-case rigamarole from - if (EMPTYSTR(this_id->username) || - strcasecmp(this_id->username, this_id->address) == 0 || - (identity->username && - strcasecmp(identity->username, - this_id->username) == 0)) { - - // Ok, we have a temp ID. We have to replace this - // with the real ID. - status = replace_userid(session, - this_uid, - identity->user_id); - if (status != PEP_STATUS_OK) { - free_identity_list(id_list); - free(default_own_id); - return status; - } - - free(this_uid); - this_uid = NULL; - - // Reflect the change we just made to the DB - this_id->user_id = strdup(identity->user_id); - stored_ident = this_id; - // FIXME: free list. - break; - } - } - else if (input_is_TOFU && !curr_is_TOFU) { - // Replace ruthlessly - this is NOT supposed to happen. - // BAD APP BEHAVIOUR. - free(identity->user_id); - identity->user_id = strdup(this_id->user_id); - stored_ident = this_id; - // FIXME: free list. - break; - } - } - id_curr = id_curr->next; - } - } - } - - if (status == PEP_STATUS_OK && stored_ident) { - // * if identity available - // * patch it with username - // (note: this will happen when - // setting automatically below...) - // * elect valid key for identity - // * if valid key exists - // * set return value's fpr - status = prepare_updated_identity(session, - identity, - stored_ident, true); - } - // * else (identity unavailable) - else { - status = PEP_STATUS_OK; - - // FIXME: We may need to roll this back. - // FIXME: change docs if we don't - // if we only have user_id and address and identity not available - // * return error status (identity not found) - if (EMPTYSTR(identity->username)) { - free(identity->username); - identity->username = strdup(identity->address); - } - - // Otherwise, if we had user_id, address, and username: - // * create identity with user_id, address, username - // (this is the input id without the fpr + comm type!) - - elect_pubkey(session, identity, false); - - // * We've already checked and retrieved - // any applicable temporary identities above. If we're - // here, none of them fit. - // * call set_identity() to store - // FIXME: Do we set if we had to copy in the address? - adjust_pEp_trust_status(session, identity); - status = set_identity(session, identity); - // * Return: created identity - } } - else if (!EMPTYSTR(identity->username)) { - /* - * Temporary identity information with username supplied - * Input: address, username (no others) - */ - - // * See if there is an own identity that uses this address. If so, we'll - // prefer that - stored_ident = NULL; - + else { // see if we perhaps own this user if (default_own_id) { status = get_identity(session, identity->address, default_own_id, &stored_ident); } - // If there isn't an own identity, search for a non-temp stored ident - // with this address. - if (status == PEP_CANNOT_FIND_IDENTITY || !stored_ident) { - - identity_list* id_list = NULL; - status = get_identities_by_address(session, identity->address, &id_list); - - if (id_list) { - identity_list* id_curr = id_list; - while (id_curr) { - pEp_identity* this_id = id_curr->ident; - if (this_id) { - char* this_uid = this_id->user_id; - assert(!EMPTYSTR(this_uid)); - // Should never be NULL - DB primary key - - if (strstr(this_uid, "TOFU_") != this_uid) { - // if usernames match, we replace the userid. - if (identity->username && - strcasecmp(identity->username, - this_id->username) == 0) { - - // Ok, we have a real ID. Copy it! - identity->user_id = strdup(this_uid); - assert(identity->user_id); - if (!identity->user_id) - goto enomem; - - stored_ident = this_id; - - break; - } - } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // If we're unable to get a starting stored ID, we now need to try to get IDs which match the address. + // Should we find them, we go through the list and try to find an acceptable one by evaluating the + // following properties (not in order of priority, and not for every case - the logic here is a mess): + // + // 1. Did the input have a user_id? + // 2. Did the input hava a username? + // 3. Is the input user_id a real id? + // 4. Is the stored user_id a real id? + // 5. Does the stored user_id have a username? + // 6. Do the names match? + // + // Based on this, if we find an acceptable candidate, we do one: + // + // 1. Replace the global DB user_id with the input user_id and patch the stored identity's user_id + // (this may be different than 1, though in practice it seems we always do both) + // 2. Patch the output identity's user_id from the stored identity + // + // If we find none, in the case if the has-username-but-no-user_id input case, we'll try a TOFU id + // fetch before giving up on stored identity candidates. + // + // Acceptable candidates are then passed to prepare_update_identity which will patch usernames and + // find any applicable keys. + // + // Unacceptable candidates will then have minimal record information entered depending on how much + // came in in the input, TOFU user_ids created when needed, and a new record placed in the DB + // accordingly. + // + if (!stored_ident) { + identity_list* id_list = NULL; + status = get_identities_by_address(session, identity->address, &id_list); + if (id_list) { + identity_list* stored_curr = id_list; + + // Ok, here's where we search for stored identities and try to find a candidate. + while (stored_curr) { + // Ok, this is where the above code fun begins. Let's get some information about the identity. + pEp_identity* candidate = stored_curr->ident; + if (candidate) { + char* candidate_id = candidate->user_id; + + // this_uid should never be NULL, as this is half of the ident + // DB primary key + assert(!EMPTYSTR(candidate_id)); + + // grab some information about the stored identity + bool candidate_has_real_id = strstr(candidate_id, "TOFU_") != candidate_id; + bool candidate_has_username = !EMPTYSTR(candidate->username); + bool names_match = (input_has_username && candidate_has_username) && + (strcmp(identity->username, candidate->username) == 0); + + // This is where the optimisation gets a little weird: + // + // Decide whether to accept and patch the database and stored id from the input, + // Accept and patch the input id from the database, or reject and go to the next + // one in the list + // + + // This is unnecessary, but I think the terms need to be descriptive where possible + bool input_addr_only = !input_has_username && !input_has_user_id; + bool candidate_id_best = candidate_has_real_id && !input_has_real_id; + bool input_id_best = input_has_real_id && !candidate_has_real_id; + bool patch_input_id_conditions = input_has_user_id || names_match || !candidate_has_username; + + if (input_addr_only || (candidate_id_best && patch_input_id_conditions)) { + identity->user_id = strdup(candidate_id); + assert(identity->user_id); + if (!identity->user_id) + goto enomem; + + stored_ident = identity_dup(candidate); + break; } - id_curr = id_curr->next; - } - } - } - - if (stored_ident) { - status = prepare_updated_identity(session, - identity, - stored_ident, true); - } - else { - identity->user_id = calloc(1, strlen(identity->address) + 6); - if (!identity->user_id) - goto enomem; + else if (input_id_best && (names_match || (input_has_username && !candidate_has_username))) { + // Replace the TOFU db in the database with the input ID globally + status = replace_userid(session, + candidate_id, + identity->user_id); + if (status != PEP_STATUS_OK) { + free_identity_list(id_list); + free(default_own_id); + return status; + } - snprintf(identity->user_id, strlen(identity->address) + 6, - "TOFU_%s", identity->address); + free(candidate_id); + candidate_id = NULL; - status = get_identity(session, - identity->address, - identity->user_id, - &stored_ident); + // Reflect the change we just made to the DB + free(candidate->user_id); + candidate->user_id = strdup(identity->user_id); + stored_ident = identity_dup(candidate); - if (status == PEP_STATUS_OK && stored_ident) { - status = prepare_updated_identity(session, - identity, - stored_ident, true); - } - else { - - // * We've already checked and retrieved - // any applicable temporary identities above. If we're - // here, none of them fit. - - status = elect_pubkey(session, identity, false); - - // * call set_identity() to store - if (identity->fpr) { - // it is still possible we have DB information on this key. Better check. - status = get_trust(session, identity); - PEP_comm_type db_ct = identity->comm_type; - status = get_key_rating(session, identity->fpr, &identity->comm_type); - PEP_comm_type key_ct = identity->comm_type; - - if (status == PEP_STATUS_OK) { - switch (key_ct) { - case PEP_ct_key_expired: - if (db_ct == PEP_ct_key_expired_but_confirmed) - identity->comm_type = db_ct; - break; - default: - switch(db_ct) { - case PEP_ct_key_expired_but_confirmed: - if (key_ct >= PEP_ct_strong_but_unconfirmed) - identity->comm_type |= PEP_ct_confirmed; - break; - case PEP_ct_mistrusted: - case PEP_ct_compromised: - case PEP_ct_key_b0rken: - identity->comm_type = db_ct; - default: - break; - } - break; - } + break; } + // Else, we reject this candidate and try the next one, if there is one. + stored_curr = stored_curr->next; } - // * call set_identity() to store - adjust_pEp_trust_status(session, identity); - status = set_identity(session, identity); - } - } - } - else { - /* - * Input: address (no others) - * Temporary identity information without username suplied - */ - - // * Again, see if there is an own identity that uses this address. If so, we'll - // prefer that - stored_ident = NULL; - - if (default_own_id) { - status = get_identity(session, - identity->address, - default_own_id, - &stored_ident); - } - // If there isn't an own identity, search for a non-temp stored ident - // with this address. - if (status == PEP_CANNOT_FIND_IDENTITY || !stored_ident) { - - identity_list* id_list = NULL; - // * Search for identity with this address - status = get_identities_by_address(session, identity->address, &id_list); - - // Results are ordered by timestamp descending, so this covers - // both the one-result and multi-result cases - if (id_list) { - if (stored_ident) // unlikely - free_identity(stored_ident); - stored_ident = id_list->ident; + // Ok, we've checked all of the candidates, and if there's a stored identity, there's a duplicate. + // Freeeeeeee... + free_identity_list(id_list); } } - if (stored_ident) - status = prepare_updated_identity(session, identity, - stored_ident, false); - else { - // too little info. BUT. We see if we can find a key; if so, we create a - // temp identity, look for a key, and store. - - // create temporary identity, store it, and Return this - // This means TOFU_ user_id + // If, by here, there is no user id on the identity, we put one on there. + // We've found any non-TOFU one we're going to find, so if this is empty, + // We don't have a stored ident. + if (EMPTYSTR(identity->user_id)) { identity->user_id = calloc(1, strlen(identity->address) + 6); if (!identity->user_id) goto enomem; snprintf(identity->user_id, strlen(identity->address) + 6, - "TOFU_%s", identity->address); - + "TOFU_%s", identity->address); + + // Try one last time to see if there is an ident for us with a TOFU id, if there was no ID but there + // was a usernames + if (input_has_username) { + status = get_identity(session, + identity->address, + identity->user_id, + &stored_ident); + } + } + } + if (status == PEP_STATUS_OK && stored_ident) { + // * if identity available + // * patch it with username + // (note: this will happen when + // setting automatically below...) + // * get a valid default key (for ident or user) + // * if valid key exists + // * set return value's fpr + status = prepare_updated_identity(session, + identity, + stored_ident, true); + } + else { // No stored ident. We're done. + if (EMPTYSTR(identity->username)) { // currently, not after messing around + free(identity->username); identity->username = strdup(identity->address); - if (!identity->address) + if (!identity->username) goto enomem; - - free(identity->fpr); - identity->fpr = NULL; - identity->comm_type = PEP_ct_unknown; - - status = elect_pubkey(session, identity, false); - - if (identity->fpr) - status = get_key_rating(session, identity->fpr, &identity->comm_type); - - // * call set_identity() to store - adjust_pEp_trust_status(session, identity); - status = set_identity(session, identity); - } + + free(identity->fpr); + identity->fpr = NULL; + identity->comm_type = PEP_ct_unknown; + adjust_pEp_trust_status(session, identity); + status = set_identity(session, identity); } // FIXME: This is legacy. I presume it's a notification for the caller...