diff options
author | SoniEx2 <endermoneymod@gmail.com> | 2021-04-09 07:19:03 -0300 |
---|---|---|
committer | SoniEx2 <endermoneymod@gmail.com> | 2021-04-09 07:19:03 -0300 |
commit | 0e752a6e215aee21dc73da097c3225495d54a5b6 (patch) | |
tree | b81be02cbf2f06aebf322ac4a5d014b44176bba5 /libotr/libotr-4.1.1/src/message.c | |
parent | 7754076c715285173311a1b6811ce377950e18a6 (diff) |
Add libotr/etc sources
Diffstat (limited to 'libotr/libotr-4.1.1/src/message.c')
-rw-r--r-- | libotr/libotr-4.1.1/src/message.c | 2058 |
1 files changed, 2058 insertions, 0 deletions
diff --git a/libotr/libotr-4.1.1/src/message.c b/libotr/libotr-4.1.1/src/message.c new file mode 100644 index 0000000..c44ce7b --- /dev/null +++ b/libotr/libotr-4.1.1/src/message.c @@ -0,0 +1,2058 @@ +/* + * Off-the-Record Messaging library + * Copyright (C) 2004-2015 Ian Goldberg, David Goulet, Rob Smits, + * Chris Alexander, Willy Lew, Lisa Du, + * Nikita Borisov + * <otr@cypherpunks.ca> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General + * Public License as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* system headers */ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +/* libgcrypt headers */ +#include <gcrypt.h> + +/* libotr headers */ +#include "privkey.h" +#include "userstate.h" +#include "proto.h" +#include "auth.h" +#include "message.h" +#include "sm.h" +#include "instag.h" + +#if OTRL_DEBUGGING +#include <stdio.h> + +/* If OTRL_DEBUGGING is on, and the user types this string, the current + * context and its siblings will be dumped to stderr. */ +const char *OTRL_DEBUGGING_DEBUGSTR = "?OTR!"; + +void otrl_context_all_dump(FILE *f, OtrlUserState us); +void otrl_context_siblings_dump(FILE *f, const ConnContext *context); +#endif + +/* The API version */ +extern unsigned int otrl_api_version; + +/* How long after sending a packet should we wait to send a heartbeat? */ +#define HEARTBEAT_INTERVAL 60 + +/* How old are messages allowed to be in order to be candidates for + * resending in response to a rekey? */ +#define RESEND_INTERVAL 60 + +/* How long should we wait for the last of the logged-in instances of + * our buddy to respond before marking our private key as a candidate + * for wiping (in seconds)? */ +#define MAX_AKE_WAIT_TIME 60 + +/* How frequently should we check our ConnContexts for wipeable private + * keys (and wipe them) (in seconds)? */ +#define POLL_DEFAULT_INTERVAL 70 + +/* Send a message to the network, fragmenting first if necessary. + * All messages to be sent to the network should go through this + * method immediately before they are sent, ie after encryption. */ +static gcry_error_t fragment_and_send(const OtrlMessageAppOps *ops, + void *opdata, ConnContext *context, const char *message, + OtrlFragmentPolicy fragPolicy, char **returnFragment) +{ + int mms = 0; + + if (message && ops->inject_message) { + int msglen; + + if (ops->max_message_size) { + mms = ops->max_message_size(opdata, context); + } + msglen = strlen(message); + + /* Don't incur overhead of fragmentation unless necessary */ + if(mms != 0 && msglen > mms) { + char **fragments; + gcry_error_t err; + int i; + int headerlen = context->protocol_version == 3 ? 37 : 19; + /* Like ceil(msglen/(mms - headerlen)) */ + int fragment_count = ((msglen - 1) / (mms - headerlen)) + 1; + + err = otrl_proto_fragment_create(mms, fragment_count, &fragments, + context, message); + if (err) { + return err; + } + + /* Determine which fragments to send and which to return + * based on given Fragment Policy. If the first fragment + * should be returned instead of sent, store it. */ + if (fragPolicy == OTRL_FRAGMENT_SEND_ALL_BUT_FIRST) { + *returnFragment = strdup(fragments[0]); + } else { + ops->inject_message(opdata, context->accountname, + context->protocol, context->username, fragments[0]); + } + for (i=1; i<fragment_count-1; i++) { + ops->inject_message(opdata, context->accountname, + context->protocol, context->username, fragments[i]); + } + /* If the last fragment should be stored instead of sent, + * store it */ + if (fragPolicy == OTRL_FRAGMENT_SEND_ALL_BUT_LAST) { + *returnFragment = strdup(fragments[fragment_count-1]); + } else { + ops->inject_message(opdata, context->accountname, + context->protocol, context->username, + fragments[fragment_count-1]); + } + /* Now free all fragment memory */ + otrl_proto_fragment_free(&fragments, fragment_count); + + } else { + /* No fragmentation necessary */ + if (fragPolicy == OTRL_FRAGMENT_SEND_ALL) { + ops->inject_message(opdata, context->accountname, + context->protocol, context->username, message); + } else { + /* Copy and return the entire given message. */ + *returnFragment = strdup(message); + } + } + } + + return gcry_error(GPG_ERR_NO_ERROR); +} + +static void populate_context_instag(OtrlUserState us, const OtrlMessageAppOps + *ops, void *opdata, const char *accountname, const char *protocol, + ConnContext *context) { + OtrlInsTag *p_instag; + + p_instag = otrl_instag_find(us, accountname, protocol); + if ((!p_instag) && ops->create_instag) { + ops->create_instag(opdata, accountname, protocol); + p_instag = otrl_instag_find(us, accountname, protocol); + } + + if (p_instag && p_instag->instag >= OTRL_MIN_VALID_INSTAG) { + context->our_instance = p_instag->instag; + } else { + context->our_instance = otrl_instag_get_new(); + } +} + +/* Deallocate a message allocated by other otrl_message_* routines. */ +void otrl_message_free(char *message) +{ + free(message); +} + +/* Handle a message about to be sent to the network. It is safe to pass + * all messages about to be sent to this routine. add_appdata is a + * function that will be called in the event that a new ConnContext is + * created. It will be passed the data that you supplied, as well as a + * pointer to the new ConnContext. You can use this to add + * application-specific information to the ConnContext using the + * "context->app" field, for example. If you don't need to do this, you + * can pass NULL for the last two arguments of otrl_message_sending. + * + * tlvs is a chain of OtrlTLVs to append to the private message. It is + * usually correct to just pass NULL here. + * + * If non-NULL, ops->convert_msg will be called just before encrypting a + * message. + * + * "instag" specifies the instance tag of the buddy (protocol version 3 only). + * Meta-instances may also be specified (e.g., OTRL_INSTAG_MOST_SECURE). + * If "contextp" is not NULL, it will be set to the ConnContext used for + * sending the message. + * + * If no fragmentation or msg injection is wanted, use OTRL_FRAGMENT_SEND_SKIP + * as the OtrlFragmentPolicy. In this case, this function will assign *messagep + * with the encrypted msg. If the routine returns non-zero, then the library + * tried to encrypt the message, but for some reason failed. DO NOT send the + * message in the clear in that case. If *messagep gets set by the call to + * something non-NULL, then you should replace your message with the contents + * of *messagep, and send that instead. + * + * Other fragmentation policies are OTRL_FRAGMENT_SEND_ALL, + * OTRL_FRAGMENT_SEND_ALL_BUT_LAST, or OTRL_FRAGMENT_SEND_ALL_BUT_FIRST. In + * these cases, the appropriate fragments will be automatically sent. For the + * last two policies, the remaining fragment will be passed in *original_msg. + * + * Call otrl_message_free(*messagep) if you don't need *messagep or when you're + * done with it. */ +gcry_error_t otrl_message_sending(OtrlUserState us, + const OtrlMessageAppOps *ops, + void *opdata, const char *accountname, const char *protocol, + const char *recipient, otrl_instag_t their_instag, + const char *original_msg, OtrlTLV *tlvs, char **messagep, + OtrlFragmentPolicy fragPolicy, ConnContext **contextp, + void (*add_appdata)(void *data, ConnContext *context), + void *data) +{ + ConnContext * context = NULL; + char * msgtosend; + const char * err_msg; + gcry_error_t err_code, err; + OtrlPolicy policy = OTRL_POLICY_DEFAULT; + int context_added = 0; + int convert_called = 0; + char *converted_msg = NULL; + + if (messagep) { + *messagep = NULL; + } + + err = gcry_error(GPG_ERR_NO_ERROR); /* Default to no error */ + + if (contextp) { + *contextp = NULL; + } + + if (!accountname || !protocol || !recipient || + !original_msg || !messagep) { + err = gcry_error(GPG_ERR_INV_VALUE); + goto fragment; + } + + /* See if we have a fingerprint for this user */ + context = otrl_context_find(us, recipient, accountname, protocol, + their_instag, 1, &context_added, add_appdata, data); + + /* Update the context list if we added one */ + if (context_added && ops->update_context_list) { + ops->update_context_list(opdata); + } + + /* Find or generate the instance tag if needed */ + if (!context->our_instance) { + populate_context_instag(us, ops, opdata, accountname, protocol, + context); + } + + if (contextp) { + *contextp = context; + } + + /* Check the policy */ + if (ops->policy) { + policy = ops->policy(opdata, context); + } + + /* Should we go on at all? */ + if ((policy & OTRL_POLICY_VERSION_MASK) == 0) { + err = gcry_error(GPG_ERR_NO_ERROR); + goto fragment; + } + +#if OTRL_DEBUGGING + /* If the user typed the magic debug string, dump this context and + * its siblings. */ + { + const char *debugtag = strstr(original_msg, OTRL_DEBUGGING_DEBUGSTR); + + if (debugtag) { + const char *debugargs = + debugtag + strlen(OTRL_DEBUGGING_DEBUGSTR); + if (debugargs[0] == '!') { /* typed ?OTR!! */ + otrl_context_all_dump(stderr, us); + } else { /* typed ?OTR! without extra command chars */ + otrl_context_siblings_dump(stderr, context); + } + + /* Don't actually send the message */ + *messagep = strdup(""); + if (!(*messagep)) { + err = gcry_error(GPG_ERR_ENOMEM); + } + goto fragment; + } + } +#endif + + /* If this is an OTR Query message, don't encrypt it. */ + if (otrl_proto_message_type(original_msg) == OTRL_MSGTYPE_QUERY) { + /* Replace the "?OTR?" with a custom message */ + char *bettermsg = otrl_proto_default_query_msg(accountname, policy); + if (bettermsg) { + *messagep = bettermsg; + } + context->otr_offer = OFFER_SENT; + err = gcry_error(GPG_ERR_NO_ERROR); + goto fragment; + } + + /* What is the current message disposition? */ + switch(context->msgstate) { + + case OTRL_MSGSTATE_PLAINTEXT: + if ((policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) { + /* We're trying to send an unencrypted message with a policy + * that disallows that. Don't do that, but try to start + * up OTR instead. */ + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_ENCRYPTION_REQUIRED, + context, NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + + context->context_priv->lastmessage = + gcry_malloc_secure(strlen(original_msg) + 1); + if (context->context_priv->lastmessage) { + char *bettermsg = otrl_proto_default_query_msg(accountname, + policy); + strcpy(context->context_priv->lastmessage, original_msg); + context->context_priv->lastsent = time(NULL); + otrl_context_update_recent_child(context, 1); + context->context_priv->may_retransmit = 2; + if (bettermsg) { + *messagep = bettermsg; + context->otr_offer = OFFER_SENT; + } else { + err = gcry_error(GPG_ERR_ENOMEM); + goto fragment; + } + } + } else { + if ((policy & OTRL_POLICY_SEND_WHITESPACE_TAG) && + context->otr_offer != OFFER_REJECTED) { + /* See if this user can speak OTR. Append the + * OTR_MESSAGE_TAG to the plaintext message, and see + * if he responds. */ + size_t msglen = strlen(original_msg); + size_t basetaglen = strlen(OTRL_MESSAGE_TAG_BASE); + size_t v1taglen = (policy & OTRL_POLICY_ALLOW_V1) ? + strlen(OTRL_MESSAGE_TAG_V1) : 0; + size_t v2taglen = (policy & OTRL_POLICY_ALLOW_V2) ? + strlen(OTRL_MESSAGE_TAG_V2) : 0; + size_t v3taglen = (policy & OTRL_POLICY_ALLOW_V3) ? + strlen(OTRL_MESSAGE_TAG_V3) : 0; + char *taggedmsg = malloc(msglen + basetaglen + v1taglen + + v2taglen + v3taglen + 1); + if (taggedmsg) { + strcpy(taggedmsg, original_msg); + strcpy(taggedmsg + msglen, OTRL_MESSAGE_TAG_BASE); + if (v1taglen) { + strcpy(taggedmsg + msglen + basetaglen, + OTRL_MESSAGE_TAG_V1); + } + if (v2taglen) { + strcpy(taggedmsg + msglen + basetaglen + v1taglen, + OTRL_MESSAGE_TAG_V2); + } + if (v3taglen) { + strcpy(taggedmsg + msglen + basetaglen + v1taglen + + v2taglen, OTRL_MESSAGE_TAG_V3); + } + *messagep = taggedmsg; + context->otr_offer = OFFER_SENT; + } + } + } + break; + case OTRL_MSGSTATE_ENCRYPTED: + /* convert the original message if necessary */ + if (ops->convert_msg) { + ops->convert_msg(opdata, context, OTRL_CONVERT_SENDING, + &converted_msg, original_msg); + + if (converted_msg) { + convert_called = 1; + } + } + + /* Create the new, encrypted message */ + if (convert_called) { + err_code = otrl_proto_create_data(&msgtosend, context, + converted_msg, tlvs, 0, NULL); + + if (ops->convert_free) { + ops->convert_free(opdata, context, converted_msg); + converted_msg = NULL; + } + } else { + err_code = otrl_proto_create_data(&msgtosend, context, + original_msg, tlvs, 0, NULL); + } + if (!err_code) { + context->context_priv->lastsent = time(NULL); + otrl_context_update_recent_child(context, 1); + *messagep = msgtosend; + } else { + /* Uh, oh. Whatever we do, *don't* send the message in the + * clear. */ + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_ENCRYPTION_ERROR, + context, NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + if (ops->otr_error_message) { + err_msg = ops->otr_error_message(opdata, context, + OTRL_ERRCODE_ENCRYPTION_ERROR); + *messagep = malloc(strlen(OTR_ERROR_PREFIX) + + strlen(err_msg) + 1); + if (*messagep) { + strcpy(*messagep, OTR_ERROR_PREFIX); + strcat(*messagep, err_msg); + } + if (ops->otr_error_message_free) { + ops->otr_error_message_free(opdata, err_msg); + } + if (!(*messagep)) { + err = gcry_error(GPG_ERR_ENOMEM); + goto fragment; + } + } + } + break; + case OTRL_MSGSTATE_FINISHED: + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, OTRL_MSGEVENT_CONNECTION_ENDED, + context, NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + *messagep = strdup(""); + if (!(*messagep)) { + err = gcry_error(GPG_ERR_ENOMEM); + goto fragment; + } + break; + } + +fragment: + if (fragPolicy == OTRL_FRAGMENT_SEND_SKIP ) { + /* Do not fragment/inject. Default behaviour of libotr3.2.0 */ + return err; + } else { + /* Fragment and send according to policy */ + if (!err && messagep && *messagep) { + if (context) { + char *rmessagep = NULL; + err = fragment_and_send(ops, opdata, context, *messagep, + fragPolicy, &rmessagep); + if (rmessagep) { + /* Free the current message pointer and return back the + * returned fragmented one. */ + free(*messagep); + *messagep = rmessagep; + } + } + } + return err; + } +} + +/* If err == 0, send the last auth message for the given context to the + * appropriate user. Otherwise, display an appripriate error dialog. + * Return the value of err that was passed. */ +static gcry_error_t send_or_error_auth(const OtrlMessageAppOps *ops, + void *opdata, gcry_error_t err, ConnContext *context, + OtrlUserState us) +{ + if (!err) { + const char *msg = context->auth.lastauthmsg; + if (msg && *msg) { + fragment_and_send(ops, opdata, context, msg, + OTRL_FRAGMENT_SEND_ALL, NULL); + time_t now = time(NULL); + /* Update the "last sent" fields, unless this is a version 3 + * message typing to update the master context (as happens + * when sending a v3 COMMIT message, for example). */ + if (context != context->m_context || + context->auth.protocol_version != 3) { + context->context_priv->lastsent = now; + otrl_context_update_recent_child(context, 1); + } + + /* If this is a master context, and we're sending a v3 COMMIT + * message, update the commit_sent_time timestamp, so we can + * expire it. */ + if (context == context->m_context && + context->auth.authstate == OTRL_AUTHSTATE_AWAITING_DHKEY && + context->auth.protocol_version == 3) { + context->auth.commit_sent_time = now; + /* If there's not already a timer running to clean up + * this private key, try to start one. */ + if (us->timer_running == 0 && ops && ops->timer_control) { + ops->timer_control(opdata, POLL_DEFAULT_INTERVAL); + us->timer_running = 1; + } + } + } + } else { + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, OTRL_MSGEVENT_SETUP_ERROR, + context, NULL, err); + } + } + return err; +} + +typedef struct { + int gone_encrypted; + OtrlUserState us; + const OtrlMessageAppOps *ops; + void *opdata; + ConnContext *context; + int ignore_message; + char **messagep; +} EncrData; + +static gcry_error_t go_encrypted(const OtrlAuthInfo *auth, void *asdata) +{ + EncrData *edata = asdata; + gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR); + Fingerprint *found_print = NULL; + int fprint_added = 0; + OtrlMessageState oldstate = edata->context->msgstate; + Fingerprint *oldprint = edata->context->active_fingerprint; + + /* See if we're talking to ourselves */ + if (!gcry_mpi_cmp(auth->their_pub, auth->our_dh.pub)) { + /* Yes, we are. */ + if (edata->ops->handle_msg_event) { + edata->ops->handle_msg_event(edata->opdata, + OTRL_MSGEVENT_MSG_REFLECTED, edata->context, + NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + edata->ignore_message = 1; + return gcry_error(GPG_ERR_NO_ERROR); + } + + found_print = otrl_context_find_fingerprint(edata->context, + edata->context->auth.their_fingerprint, 1, &fprint_added); + + if (fprint_added) { + /* Inform the user of the new fingerprint */ + if (edata->ops->new_fingerprint) { + edata->ops->new_fingerprint(edata->opdata, edata->us, + edata->context->accountname, edata->context->protocol, + edata->context->username, + edata->context->auth.their_fingerprint); + } + /* Arrange that the new fingerprint be written to disk */ + if (edata->ops->write_fingerprints) { + edata->ops->write_fingerprints(edata->opdata); + } + } + + /* Is this a new session or just a refresh of an existing one? */ + if (edata->context->msgstate == OTRL_MSGSTATE_ENCRYPTED && + oldprint == found_print && + edata->context->context_priv->our_keyid - 1 == + edata->context->auth.our_keyid && + !gcry_mpi_cmp(edata->context->context_priv->our_old_dh_key.pub, + edata->context->auth.our_dh.pub) && + ((edata->context->context_priv->their_keyid > 0 && + edata->context->context_priv->their_keyid == + edata->context->auth.their_keyid && + !gcry_mpi_cmp(edata->context->context_priv->their_y, + edata->context->auth.their_pub)) || + (edata->context->context_priv->their_keyid > 1 && + edata->context->context_priv->their_keyid - 1 == + edata->context->auth.their_keyid && + edata->context->context_priv->their_old_y != NULL && + !gcry_mpi_cmp(edata->context->context_priv->their_old_y, + edata->context->auth.their_pub)))) { + /* This is just a refresh of the existing session. */ + if (edata->ops->still_secure) { + edata->ops->still_secure(edata->opdata, edata->context, + edata->context->auth.initiated); + } + edata->ignore_message = 1; + return gcry_error(GPG_ERR_NO_ERROR); + } + + /* Copy the information from the auth into the context */ + memmove(edata->context->sessionid, + edata->context->auth.secure_session_id, 20); + edata->context->sessionid_len = + edata->context->auth.secure_session_id_len; + edata->context->sessionid_half = + edata->context->auth.session_id_half; + edata->context->protocol_version = + edata->context->auth.protocol_version; + + edata->context->context_priv->their_keyid = + edata->context->auth.their_keyid; + gcry_mpi_release(edata->context->context_priv->their_y); + gcry_mpi_release(edata->context->context_priv->their_old_y); + edata->context->context_priv->their_y = + gcry_mpi_copy(edata->context->auth.their_pub); + edata->context->context_priv->their_old_y = NULL; + + if (edata->context->context_priv->our_keyid - 1 != + edata->context->auth.our_keyid || + gcry_mpi_cmp(edata->context->context_priv->our_old_dh_key.pub, + edata->context->auth.our_dh.pub)) { + otrl_dh_keypair_free(&(edata->context->context_priv->our_dh_key)); + otrl_dh_keypair_free(&(edata->context->context_priv->our_old_dh_key)); + otrl_dh_keypair_copy(&(edata->context->context_priv->our_old_dh_key), + &(edata->context->auth.our_dh)); + otrl_dh_gen_keypair( + edata->context->context_priv->our_old_dh_key.groupid, + &(edata->context->context_priv->our_dh_key)); + edata->context->context_priv->our_keyid = edata->context->auth.our_keyid + + 1; + } + + /* Create the session keys from the DH keys */ + otrl_dh_session_free(&(edata->context->context_priv->sesskeys[0][0])); + err = otrl_dh_session(&(edata->context->context_priv->sesskeys[0][0]), + &(edata->context->context_priv->our_dh_key), + edata->context->context_priv->their_y); + if (err) return err; + otrl_dh_session_free(&(edata->context->context_priv->sesskeys[1][0])); + err = otrl_dh_session(&(edata->context->context_priv->sesskeys[1][0]), + &(edata->context->context_priv->our_old_dh_key), + edata->context->context_priv->their_y); + if (err) return err; + + edata->context->context_priv->generation++; + edata->context->active_fingerprint = found_print; + edata->context->msgstate = OTRL_MSGSTATE_ENCRYPTED; + + if (edata->ops->update_context_list) { + edata->ops->update_context_list(edata->opdata); + } + if (oldstate == OTRL_MSGSTATE_ENCRYPTED && oldprint == found_print) { + if (edata->ops->still_secure) { + edata->ops->still_secure(edata->opdata, edata->context, + edata->context->auth.initiated); + } + } else { + if (edata->ops->gone_secure) { + edata->ops->gone_secure(edata->opdata, edata->context); + } + } + + edata->gone_encrypted = 1; + + return gpg_error(GPG_ERR_NO_ERROR); +} + +static void maybe_resend(EncrData *edata) +{ + gcry_error_t err; + time_t now; + + if (!edata->gone_encrypted) return; + + /* See if there's a message we sent recently that should be resent. */ + now = time(NULL); + if (edata->context->context_priv->lastmessage != NULL && + edata->context->context_priv->may_retransmit && + edata->context->context_priv->lastsent >= (now - RESEND_INTERVAL)) { + char *resendmsg; + char *msg_to_send; + int resending = (edata->context->context_priv->may_retransmit == 1); + + /* Initialize msg_to_send */ + if (resending) { + const char *resent_prefix; + int used_ops_resentmp = 1; + resent_prefix = edata->ops->resent_msg_prefix ? + edata->ops->resent_msg_prefix(edata->opdata, + edata->context) : NULL; + if (!resent_prefix) { + resent_prefix = "[resent]"; /* Assign default prefix */ + used_ops_resentmp = 0; + } + msg_to_send = malloc( + strlen(edata->context->context_priv->lastmessage) + + strlen(resent_prefix) + 2); + if (msg_to_send) { + strcpy(msg_to_send, resent_prefix); + strcat(msg_to_send, " "); + strcat(msg_to_send, edata->context->context_priv->lastmessage); + } else { + return; /* Out of memory; don't try to resend */ + } + if (used_ops_resentmp) { + edata->ops->resent_msg_prefix_free(edata->opdata, + resent_prefix); + } + } else { + msg_to_send = edata->context->context_priv->lastmessage; + } + + /* Re-encrypt the message with the new keys */ + err = otrl_proto_create_data(&resendmsg, + edata->context, msg_to_send, NULL, 0, NULL); + if (resending) { + free(msg_to_send); + } + if (!err) { + /* Resend the message */ + fragment_and_send(edata->ops, edata->opdata, edata->context, + resendmsg, OTRL_FRAGMENT_SEND_ALL, NULL); + free(resendmsg); + edata->context->context_priv->lastsent = now; + otrl_context_update_recent_child(edata->context, 1); + if (resending) { + /* We're not sending it for the first time; let the user + * know we resent it */ + if (edata->ops->handle_msg_event) { + edata->ops->handle_msg_event(edata->opdata, + OTRL_MSGEVENT_MSG_RESENT, edata->context, + NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + } + edata->ignore_message = 1; + } + } +} + +/* Set the trust level based on the result of the SMP */ +static void set_smp_trust(const OtrlMessageAppOps *ops, void *opdata, + ConnContext *context, int trusted) +{ + otrl_context_set_trust(context->active_fingerprint, trusted ? "smp" : ""); + + /* Write the new info to disk, redraw the ui, and redraw the + * OTR buttons. */ + if (ops->write_fingerprints) { + ops->write_fingerprints(opdata); + } +} + +static void init_respond_smp(OtrlUserState us, const OtrlMessageAppOps *ops, + void *opdata, ConnContext *context, const char *question, + const unsigned char *secret, size_t secretlen, int initiating) +{ + unsigned char *smpmsg = NULL; + int smpmsglen; + unsigned char combined_secret[SM_DIGEST_SIZE]; + gcry_error_t err; + unsigned char our_fp[20]; + unsigned char *combined_buf; + size_t combined_buf_len; + OtrlTLV *sendtlv; + char *sendsmp = NULL; + + if (!context || context->msgstate != OTRL_MSGSTATE_ENCRYPTED) return; + + /* + * Construct the combined secret as a SHA256 hash of: + * Version byte (0x01), Initiator fingerprint (20 bytes), + * responder fingerprint (20 bytes), secure session id, input secret + */ + otrl_privkey_fingerprint_raw(us, our_fp, context->accountname, + context->protocol); + + combined_buf_len = 41 + context->sessionid_len + secretlen; + combined_buf = malloc(combined_buf_len); + combined_buf[0] = 0x01; + if (initiating) { + memmove(combined_buf + 1, our_fp, 20); + memmove(combined_buf + 21, + context->active_fingerprint->fingerprint, 20); + } else { + memmove(combined_buf + 1, + context->active_fingerprint->fingerprint, 20); + memmove(combined_buf + 21, our_fp, 20); + } + memmove(combined_buf + 41, context->sessionid, + context->sessionid_len); + memmove(combined_buf + 41 + context->sessionid_len, + secret, secretlen); + gcry_md_hash_buffer(SM_HASH_ALGORITHM, combined_secret, combined_buf, + combined_buf_len); + free(combined_buf); + + if (initiating) { + otrl_sm_step1(context->smstate, combined_secret, SM_DIGEST_SIZE, + &smpmsg, &smpmsglen); + } else { + otrl_sm_step2b(context->smstate, combined_secret, SM_DIGEST_SIZE, + &smpmsg, &smpmsglen); + } + + /* If we've got a question, attach it to the smpmsg */ + if (question != NULL) { + size_t qlen = strlen(question); + unsigned char *qsmpmsg = malloc(qlen + 1 + smpmsglen); + if (!qsmpmsg) { + free(smpmsg); + return; + } + strcpy((char *)qsmpmsg, question); + memmove(qsmpmsg + qlen + 1, smpmsg, smpmsglen); + free(smpmsg); + smpmsg = qsmpmsg; + smpmsglen += qlen + 1; + } + + /* Send msg with next smp msg content */ + sendtlv = otrl_tlv_new(initiating ? + (question != NULL ? OTRL_TLV_SMP1Q : OTRL_TLV_SMP1) + : OTRL_TLV_SMP2, + smpmsglen, smpmsg); + err = otrl_proto_create_data(&sendsmp, context, "", sendtlv, + OTRL_MSGFLAGS_IGNORE_UNREADABLE, NULL); + if (!err) { + /* Send it, and set the next expected message to the + * logical response */ + err = fragment_and_send(ops, opdata, context, + sendsmp, OTRL_FRAGMENT_SEND_ALL, NULL); + context->smstate->nextExpected = + initiating ? OTRL_SMP_EXPECT2 : OTRL_SMP_EXPECT3; + } + free(sendsmp); + otrl_tlv_free(sendtlv); + free(smpmsg); +} + +/* Initiate the Socialist Millionaires' Protocol */ +void otrl_message_initiate_smp(OtrlUserState us, const OtrlMessageAppOps *ops, + void *opdata, ConnContext *context, const unsigned char *secret, + size_t secretlen) +{ + init_respond_smp(us, ops, opdata, context, NULL, secret, secretlen, 1); +} + +/* Initiate the Socialist Millionaires' Protocol and send a prompt + * question to the buddy */ +void otrl_message_initiate_smp_q(OtrlUserState us, + const OtrlMessageAppOps *ops, void *opdata, ConnContext *context, + const char *question, const unsigned char *secret, size_t secretlen) +{ + init_respond_smp(us, ops, opdata, context, question, secret, secretlen, 1); +} + +/* Respond to a buddy initiating the Socialist Millionaires' Protocol */ +void otrl_message_respond_smp(OtrlUserState us, const OtrlMessageAppOps *ops, + void *opdata, ConnContext *context, const unsigned char *secret, + size_t secretlen) +{ + init_respond_smp(us, ops, opdata, context, NULL, secret, secretlen, 0); +} + +/* Abort the SMP. Called when an unexpected SMP message breaks the + * normal flow. */ +void otrl_message_abort_smp(OtrlUserState us, const OtrlMessageAppOps *ops, + void *opdata, ConnContext *context) +{ + OtrlTLV *sendtlv = otrl_tlv_new(OTRL_TLV_SMP_ABORT, 0, + (const unsigned char *)""); + char *sendsmp = NULL; + gcry_error_t err; + + context->smstate->nextExpected = OTRL_SMP_EXPECT1; + + err = otrl_proto_create_data(&sendsmp, + context, "", sendtlv, + OTRL_MSGFLAGS_IGNORE_UNREADABLE, NULL); + if (!err) { + /* Send the abort signal so our buddy knows we've stopped */ + err = fragment_and_send(ops, opdata, context, + sendsmp, OTRL_FRAGMENT_SEND_ALL, NULL); + } + free(sendsmp); + otrl_tlv_free(sendtlv); +} + +static void message_malformed(const OtrlMessageAppOps *ops, + void *opdata, ConnContext *context) { + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, OTRL_MSGEVENT_RCVDMSG_MALFORMED, context, + NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + + if (ops->inject_message && ops->otr_error_message) { + const char *err_msg = ops->otr_error_message(opdata, context, + OTRL_ERRCODE_MSG_MALFORMED); + + if (err_msg) { + char *buf = malloc(strlen(OTR_ERROR_PREFIX) + strlen(err_msg) + 1); + + if (buf) { + strcpy(buf, OTR_ERROR_PREFIX); + strcat(buf, err_msg); + ops->inject_message(opdata, context->accountname, + context->protocol, context->username, buf); + free(buf); + } + + if (ops->otr_error_message_free) { + ops->otr_error_message_free(opdata, err_msg); + } + } + } +} + + +/* Handle a message just received from the network. It is safe to pass + * all received messages to this routine. add_appdata is a function + * that will be called in the event that a new ConnContext is created. + * It will be passed the data that you supplied, as well as + * a pointer to the new ConnContext. You can use this to add + * application-specific information to the ConnContext using the + * "context->app" field, for example. If you don't need to do this, you + * can pass NULL for the last two arguments of otrl_message_receiving. + * + * If non-NULL, ops->convert_msg will be called after a data message is + * decrypted. + * + * If "contextp" is not NULL, it will be set to the ConnContext used for + * receiving the message. + * + * If otrl_message_receiving returns 1, then the message you received + * was an internal protocol message, and no message should be delivered + * to the user. + * + * If it returns 0, then check if *messagep was set to non-NULL. If + * so, replace the received message with the contents of *messagep, and + * deliver that to the user instead. You must call + * otrl_message_free(*messagep) when you're done with it. If tlvsp is + * non-NULL, *tlvsp will be set to a chain of any TLVs that were + * transmitted along with this message. You must call + * otrl_tlv_free(*tlvsp) when you're done with those. + * + * If otrl_message_receiving returns 0 and *messagep is NULL, then this + * was an ordinary, non-OTR message, which should just be delivered to + * the user without modification. */ +int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops, + void *opdata, const char *accountname, const char *protocol, + const char *sender, const char *message, char **newmessagep, + OtrlTLV **tlvsp, ConnContext **contextp, + void (*add_appdata)(void *data, ConnContext *context), + void *data) +{ + ConnContext *context, *m_context, *best_context; + OtrlMessageType msgtype; + int context_added = 0; + OtrlPolicy policy = OTRL_POLICY_DEFAULT; + char *unfragmessage = NULL, *otrtag = NULL; + EncrData edata; + otrl_instag_t our_instance = 0, their_instance = 0; + int version; + gcry_error_t err; + + if (!accountname || !protocol || !sender || !message || !newmessagep) + return 0; + + *newmessagep = NULL; + if (tlvsp) *tlvsp = NULL; + + if (contextp) { + *contextp = NULL; + } + + /* Find the master context and state with this correspondent */ + m_context = otrl_context_find(us, sender, accountname, + protocol, OTRL_INSTAG_MASTER, 1, &context_added, add_appdata, data); + context = m_context; + + /* Update the context list if we added one */ + if (context_added && ops->update_context_list) { + ops->update_context_list(opdata); + } + + best_context = otrl_context_find(us, sender, accountname, + protocol, OTRL_INSTAG_BEST, 0, NULL, add_appdata, data); + + /* Find or generate the instance tag if needed */ + if (!context->our_instance) { + populate_context_instag(us, ops, opdata, accountname, protocol, + context); + } + + + /* Check the policy */ + if (ops->policy) { + policy = ops->policy(opdata, context); + } + + /* Should we go on at all? */ + if ((policy & OTRL_POLICY_VERSION_MASK) == 0) { + return 0; + } + + otrtag = strstr(message, "?OTR"); + if (otrtag) { + /* See if we have a V3 fragment. The '4' in the next line is + * strlen("?OTR"). otrtag[4] is the character immediately after + * the "?OTR", and is guaranteed to exist, because in the worst + * case, it is the NUL terminating 'message'. */ + if (otrtag[4] == '|') { + /* Get the instance tag from fragment header*/ + sscanf(otrtag, "?OTR|%x|%x,", &their_instance, &our_instance); + /* Ignore message if it is intended for a different instance */ + if (our_instance && context->our_instance != our_instance) { + + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE, + m_context, NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + return 1; + } + /* Get the context for this instance */ + if (their_instance >= OTRL_MIN_VALID_INSTAG) { + context = otrl_context_find(us, sender, accountname, + protocol, their_instance, 1, &context_added, + add_appdata, data); + } else { + message_malformed(ops, opdata, context); + return 1; + } + } + switch(otrl_proto_fragment_accumulate(&unfragmessage, + context, message)) { + case OTRL_FRAGMENT_UNFRAGMENTED: + /* Do nothing */ + break; + case OTRL_FRAGMENT_INCOMPLETE: + /* We've accumulated this fragment, but we don't have a + * complete message yet */ + return 1; + case OTRL_FRAGMENT_COMPLETE: + /* We've got a new complete message, in unfragmessage. */ + message = unfragmessage; + otrtag = strstr(message, "?OTR"); + break; + } + } + + /* What type of message is it? Note that this just checks the + * header; it's not necessarily a _valid_ message of this type. */ + msgtype = otrl_proto_message_type(message); + version = otrl_proto_message_version(message); + + /* See if they responded to our OTR offer */ + if ((policy & OTRL_POLICY_SEND_WHITESPACE_TAG)) { + if (msgtype != OTRL_MSGTYPE_NOTOTR) { + context->otr_offer = OFFER_ACCEPTED; + } else if (context->otr_offer == OFFER_SENT) { + context->otr_offer = OFFER_REJECTED; + } + } + + /* Check that this version is allowed by the policy */ + if (((version == 3) && !(policy & OTRL_POLICY_ALLOW_V3)) + || ((version == 2) && !(policy & OTRL_POLICY_ALLOW_V2)) + || ((version == 1) && !(policy & OTRL_POLICY_ALLOW_V1))) { + edata.ignore_message = 1; + goto end; + } + /* Check the to and from instance tags */ + if (version == 3) { + err = gcry_error(GPG_ERR_INV_VALUE); + if (otrtag) { + err = otrl_proto_instance(otrtag, &their_instance, &our_instance); + } + if (!err) { + if ((msgtype == OTRL_MSGTYPE_DH_COMMIT && our_instance && + context->our_instance != our_instance) || + (msgtype != OTRL_MSGTYPE_DH_COMMIT && + context->our_instance != our_instance)) { + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE, + m_context, NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + /* ignore message intended for a different instance */ + edata.ignore_message = 1; + goto end; + } + + if (their_instance >= OTRL_MIN_VALID_INSTAG) { + context = otrl_context_find(us, sender, accountname, + protocol, their_instance, 1, &context_added, + add_appdata, data); + } + } + + if (err || their_instance < OTRL_MIN_VALID_INSTAG) { + message_malformed(ops, opdata, context); + edata.ignore_message = 1; + goto end; + } + + if (context_added) { + /* Context added because of new instance (either here or when + * accumulating fragments */ + /* Copy information from m_context to the new instance context */ + context->auth.protocol_version = 3; + context->protocol_version = 3; + context->msgstate = m_context->msgstate; + + if (m_context->context_priv->may_retransmit) { + gcry_free(context->context_priv->lastmessage); + context->context_priv->lastmessage = m_context->context_priv->lastmessage; + m_context->context_priv->lastmessage = NULL; + context->context_priv->may_retransmit = m_context->context_priv->may_retransmit; + m_context->context_priv->may_retransmit = 0; + } + + if (msgtype == OTRL_MSGTYPE_DH_KEY) { + otrl_auth_copy_on_key(&(m_context->auth), &(context->auth)); + } else if (msgtype != OTRL_MSGTYPE_DH_COMMIT) { + edata.ignore_message = 1; + goto end; + } + + /* Update the context list */ + if (ops->update_context_list) { + ops->update_context_list(opdata); + } + } else if (m_context != context) { + /* Switching from m_context to existing instance context */ + if (msgtype == OTRL_MSGTYPE_DH_KEY && m_context->auth.authstate + == OTRL_AUTHSTATE_AWAITING_DHKEY && + !(context->auth.authstate == + OTRL_AUTHSTATE_AWAITING_DHKEY)) { + context->msgstate = m_context->msgstate; + context->auth.protocol_version = 3; + context->protocol_version = 3; + otrl_auth_copy_on_key(&(m_context->auth), &(context->auth)); + } + } + } + + if (contextp) { + *contextp = context; + } + + /* update time of last received message */ + context->context_priv->lastrecv = time(NULL); + otrl_context_update_recent_child(context, 0); + + edata.gone_encrypted = 0; + edata.us = us; + edata.context = context; + edata.ops = ops; + edata.opdata = opdata; + edata.ignore_message = -1; + edata.messagep = newmessagep; + + switch(msgtype) { + unsigned int bestversion; + const char *startwhite, *endwhite; + DH_keypair *our_dh; + unsigned int our_keyid; + OtrlPrivKey *privkey; + int haveauthmsg; + + case OTRL_MSGTYPE_QUERY: + /* See if we should use an existing DH keypair, or generate + * a fresh one. */ + if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + our_dh = &(context->context_priv->our_old_dh_key); + our_keyid = context->context_priv->our_keyid - 1; + } else { + our_dh = NULL; + our_keyid = 0; + } + + /* Find the best version of OTR that we both speak */ + switch(otrl_proto_query_bestversion(message, policy)) { + case 3: + err = otrl_auth_start_v23(&(context->auth), 3); + send_or_error_auth(ops, opdata, err, context, us); + break; + case 2: + err = otrl_auth_start_v23(&(context->auth), 2); + send_or_error_auth(ops, opdata, err, context, us); + break; + case 1: + /* Get our private key */ + privkey = otrl_privkey_find(us, context->accountname, + context->protocol); + if (privkey == NULL) { + /* We've got no private key! */ + if (ops->create_privkey) { + ops->create_privkey(opdata, context->accountname, + context->protocol); + privkey = otrl_privkey_find(us, + context->accountname, context->protocol); + } + } + if (privkey) { + err = otrl_auth_start_v1(&(context->auth), our_dh, + our_keyid, privkey); + send_or_error_auth(ops, opdata, err, context, us); + } + break; + default: + /* Just ignore this message */ + break; + } + /* Don't display the Query message to the user. */ + if (edata.ignore_message == -1) edata.ignore_message = 1; + break; + + case OTRL_MSGTYPE_DH_COMMIT: + err = otrl_auth_handle_commit(&(context->auth), otrtag, version); + send_or_error_auth(ops, opdata, err, context, us); + + if (edata.ignore_message == -1) edata.ignore_message = 1; + break; + + case OTRL_MSGTYPE_DH_KEY: + /* Get our private key */ + privkey = otrl_privkey_find(us, context->accountname, + context->protocol); + if (privkey == NULL) { + /* We've got no private key! */ + if (ops->create_privkey) { + ops->create_privkey(opdata, context->accountname, + context->protocol); + privkey = otrl_privkey_find(us, + context->accountname, context->protocol); + } + } + if (privkey) { + err = otrl_auth_handle_key(&(context->auth), otrtag, + &haveauthmsg, privkey); + if (err || haveauthmsg) { + send_or_error_auth(ops, opdata, err, context, us); + } + } + + if (edata.ignore_message == -1) edata.ignore_message = 1; + break; + + case OTRL_MSGTYPE_REVEALSIG: + /* Get our private key */ + privkey = otrl_privkey_find(us, context->accountname, + context->protocol); + if (privkey == NULL) { + /* We've got no private key! */ + if (ops->create_privkey) { + ops->create_privkey(opdata, context->accountname, + context->protocol); + privkey = otrl_privkey_find(us, + context->accountname, context->protocol); + } + } + if (privkey) { + err = otrl_auth_handle_revealsig(&(context->auth), + otrtag, &haveauthmsg, privkey, go_encrypted, + &edata); + if (err || haveauthmsg) { + send_or_error_auth(ops, opdata, err, context, us); + maybe_resend(&edata); + } + } + + if (edata.ignore_message == -1) edata.ignore_message = 1; + break; + + case OTRL_MSGTYPE_SIGNATURE: + err = otrl_auth_handle_signature(&(context->auth), + otrtag, &haveauthmsg, go_encrypted, &edata); + if (err || haveauthmsg) { + send_or_error_auth(ops, opdata, err, context, us); + maybe_resend(&edata); + } + + if (edata.ignore_message == -1) edata.ignore_message = 1; + break; + + case OTRL_MSGTYPE_V1_KEYEXCH: + /* See if we should use an existing DH keypair, or generate + * a fresh one. */ + if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + our_dh = &(context->context_priv->our_old_dh_key); + our_keyid = context->context_priv->our_keyid - 1; + } else { + our_dh = NULL; + our_keyid = 0; + } + + /* Get our private key */ + privkey = otrl_privkey_find(us, context->accountname, + context->protocol); + if (privkey == NULL) { + /* We've got no private key! */ + if (ops->create_privkey) { + ops->create_privkey(opdata, context->accountname, + context->protocol); + privkey = otrl_privkey_find(us, context->accountname, + context->protocol); + } + } + if (privkey) { + err = otrl_auth_handle_v1_key_exchange(&(context->auth), + message, &haveauthmsg, privkey, our_dh, our_keyid, + go_encrypted, &edata); + if (err || haveauthmsg) { + send_or_error_auth(ops, opdata, err, context, us); + maybe_resend(&edata); + } + } + + if (edata.ignore_message == -1) edata.ignore_message = 1; + break; + + case OTRL_MSGTYPE_DATA: + switch(context->msgstate) { + gcry_error_t err; + OtrlTLV *tlvs, *tlv; + char *plaintext; + char *buf; + const char *err_msg; + unsigned char *extrakey; + unsigned char flags; + NextExpectedSMP nextMsg; + + case OTRL_MSGSTATE_PLAINTEXT: + case OTRL_MSGSTATE_FINISHED: + /* See if we're supposed to ignore this message in + * the event it's unreadable. */ + err = otrl_proto_data_read_flags(message, &flags); + if ((flags & OTRL_MSGFLAGS_IGNORE_UNREADABLE)) { + edata.ignore_message = 1; + break; + } + + if(best_context && best_context != context && + best_context->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE, + m_context, NULL, + gcry_error(GPG_ERR_NO_ERROR)); + } + } else if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE, + context, NULL, + gcry_error(GPG_ERR_NO_ERROR)); + } + edata.ignore_message = 1; + + /* We don't actually want to send anything in this case, + since this could just be a message intended for another + v2 instance. We still notify the local user though */ + break; + + case OTRL_MSGSTATE_ENCRYPTED: + extrakey = gcry_malloc_secure(OTRL_EXTRAKEY_BYTES); + err = otrl_proto_accept_data(&plaintext, &tlvs, context, + message, &flags, extrakey); + if (err) { + int is_conflict = + (gpg_err_code(err) == GPG_ERR_CONFLICT); + if ((flags & OTRL_MSGFLAGS_IGNORE_UNREADABLE)) { + edata.ignore_message = 1; + break; + } + if (is_conflict) { + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_UNREADABLE, + context, NULL, + gcry_error(GPG_ERR_NO_ERROR)); + } + } else { + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_MALFORMED, + context, NULL, + gcry_error(GPG_ERR_NO_ERROR)); + } + } + if (ops->inject_message && ops->otr_error_message) { + err_msg = ops->otr_error_message(opdata, + context, + is_conflict ? + OTRL_ERRCODE_MSG_UNREADABLE : + OTRL_ERRCODE_MSG_MALFORMED); + if (err_msg) { + buf = malloc(strlen(OTR_ERROR_PREFIX) + + strlen(err_msg) + 1); + if (buf) { + strcpy(buf, OTR_ERROR_PREFIX); + strcat(buf, err_msg); + ops->inject_message(opdata, + accountname, protocol, + sender, buf); + free(buf); + } + } + if (ops->otr_error_message_free) { + ops->otr_error_message_free(opdata, + err_msg); + } + } + edata.ignore_message = 1; + break; + } + + /* If the other side told us he's disconnected his + * private connection, make a note of that so we + * don't try sending anything else to him. */ + if (otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED)) { + otrl_context_force_finished(context); + } + + /* If the other side told us to use the current + * extra symmetric key, let the application know. */ + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SYMKEY); + if (tlv && otrl_api_version >= 0x040000) { + if (ops->received_symkey && tlv->len >= 4) { + unsigned char *bufp = tlv->data; + unsigned int use = + (bufp[0] << 24) | (bufp[1] << 16) | + (bufp[2] << 8) | bufp[3]; + ops->received_symkey(opdata, context, use, + bufp+4, tlv->len - 4, extrakey); + } + } + gcry_free(extrakey); + extrakey = NULL; + + /* If TLVs contain SMP data, process it */ + nextMsg = context->smstate->nextExpected; + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q); + if (tlv) { + if (nextMsg == OTRL_SMP_EXPECT1 && tlv->len > 0) { + /* We can only do the verification half now. + * We must wait for the secret to be entered + * to continue. */ + char *question = (char *)tlv->data; + char *qend = memchr(question, '\0', tlv->len - 1); + size_t qlen = qend ? (qend - question + 1) : + tlv->len; + otrl_sm_step2a(context->smstate, tlv->data + qlen, + tlv->len - qlen, 1); + + if (context->smstate->sm_prog_state != + OTRL_SMP_PROG_CHEATED) { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_ASK_FOR_ANSWER, + context, 25, question); + } + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_CHEATED, context, + 0, NULL); + } + context->smstate->nextExpected = + OTRL_SMP_EXPECT1; + context->smstate->sm_prog_state = + OTRL_SMP_PROG_OK; + } + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_ERROR, context, + 0, NULL); + } + } + } + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1); + if (tlv) { + if (nextMsg == OTRL_SMP_EXPECT1) { + /* We can only do the verification half now. + * We must wait for the secret to be entered + * to continue. */ + otrl_sm_step2a(context->smstate, tlv->data, + tlv->len, 0); + if (context->smstate->sm_prog_state != + OTRL_SMP_PROG_CHEATED) { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_ASK_FOR_SECRET, + context, 25, NULL); + } + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_CHEATED, + context, 0, NULL); + } + context->smstate->nextExpected = + OTRL_SMP_EXPECT1; + context->smstate->sm_prog_state = + OTRL_SMP_PROG_OK; + } + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_ERROR, context, + 0, NULL); + } + } + } + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2); + if (tlv) { + if (nextMsg == OTRL_SMP_EXPECT2) { + unsigned char* nextmsg; + int nextmsglen; + OtrlTLV *sendtlv; + char *sendsmp = NULL; + otrl_sm_step3(context->smstate, tlv->data, + tlv->len, &nextmsg, &nextmsglen); + + if (context->smstate->sm_prog_state != + OTRL_SMP_PROG_CHEATED) { + /* Send msg with next smp msg content */ + sendtlv = otrl_tlv_new(OTRL_TLV_SMP3, + nextmsglen, nextmsg); + err = otrl_proto_create_data(&sendsmp, + context, "", sendtlv, + OTRL_MSGFLAGS_IGNORE_UNREADABLE, + NULL); + if (!err) { + err = fragment_and_send(ops, + opdata, context, sendsmp, + OTRL_FRAGMENT_SEND_ALL, NULL); + } + free(sendsmp); + otrl_tlv_free(sendtlv); + + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_IN_PROGRESS, + context, 60, NULL); + } + context->smstate->nextExpected = + OTRL_SMP_EXPECT4; + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_CHEATED, + context, 0, NULL); + } + context->smstate->nextExpected = + OTRL_SMP_EXPECT1; + context->smstate->sm_prog_state = + OTRL_SMP_PROG_OK; + } + free(nextmsg); + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_ERROR, context, + 0, NULL); + } + } + } + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3); + if (tlv) { + if (nextMsg == OTRL_SMP_EXPECT3) { + unsigned char* nextmsg; + int nextmsglen; + OtrlTLV *sendtlv; + char *sendsmp = NULL; + err = otrl_sm_step4(context->smstate, tlv->data, + tlv->len, &nextmsg, &nextmsglen); + /* Set trust level based on result */ + if (context->smstate->received_question == 0) { + set_smp_trust(ops, opdata, context, + (err == gcry_error(GPG_ERR_NO_ERROR))); + } + + if (context->smstate->sm_prog_state != + OTRL_SMP_PROG_CHEATED) { + /* Send msg with next smp msg content */ + sendtlv = otrl_tlv_new(OTRL_TLV_SMP4, + nextmsglen, nextmsg); + err = otrl_proto_create_data(&sendsmp, + context, "", sendtlv, + OTRL_MSGFLAGS_IGNORE_UNREADABLE, + NULL); + if (!err) { + err = fragment_and_send(ops, + opdata, context, sendsmp, + OTRL_FRAGMENT_SEND_ALL, NULL); + } + free(sendsmp); + otrl_tlv_free(sendtlv); + + if (ops->handle_smp_event) { + OtrlSMPEvent succorfail = + context->smstate->sm_prog_state == + OTRL_SMP_PROG_SUCCEEDED ? + OTRL_SMPEVENT_SUCCESS : + OTRL_SMPEVENT_FAILURE; + ops->handle_smp_event(opdata, succorfail, + context, 100, NULL); + } + context->smstate->nextExpected = + OTRL_SMP_EXPECT1; + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_CHEATED, + context, 0, NULL); + } + context->smstate->nextExpected = + OTRL_SMP_EXPECT1; + context->smstate->sm_prog_state = + OTRL_SMP_PROG_OK; + } + free(nextmsg); + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_ERROR, context, + 0, NULL); + } + } + } + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4); + if (tlv) { + if (nextMsg == OTRL_SMP_EXPECT4) { + err = otrl_sm_step5(context->smstate, tlv->data, + tlv->len); + /* Set trust level based on result */ + set_smp_trust(ops, opdata, context, + (err == gcry_error(GPG_ERR_NO_ERROR))); + + if (context->smstate->sm_prog_state != + OTRL_SMP_PROG_CHEATED) { + if (ops->handle_smp_event) { + OtrlSMPEvent succorfail = + context->smstate->sm_prog_state == + OTRL_SMP_PROG_SUCCEEDED ? + OTRL_SMPEVENT_SUCCESS : + OTRL_SMPEVENT_FAILURE; + ops->handle_smp_event(opdata, succorfail, + context, 100, NULL); + } + context->smstate->nextExpected = + OTRL_SMP_EXPECT1; + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_CHEATED, + context, 0, NULL); + } + context->smstate->nextExpected = + OTRL_SMP_EXPECT1; + context->smstate->sm_prog_state = + OTRL_SMP_PROG_OK; + } + } else { + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, + OTRL_SMPEVENT_ERROR, context, + 0, NULL); + } + } + } + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT); + if (tlv) { + context->smstate->nextExpected = OTRL_SMP_EXPECT1; + if (ops->handle_smp_event) { + ops->handle_smp_event(opdata, OTRL_SMPEVENT_ABORT, + context, 0, NULL); + } + } + + if (plaintext[0] == '\0') { + /* If it's a heartbeat (an empty message), don't + * display it to the user, but signal an event. */ + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD, + context, NULL, + gcry_error(GPG_ERR_NO_ERROR)); + } + edata.ignore_message = 1; + } else if (edata.ignore_message != 1 && + context->context_priv->their_keyid > 0) { + /* If it's *not* a heartbeat, and we haven't + * sent anything in a while, also send a + * heartbeat. */ + time_t now = time(NULL); + if (context->context_priv->lastsent < + (now - HEARTBEAT_INTERVAL)) { + char *heartbeat; + + /* Create the heartbeat message */ + err = otrl_proto_create_data(&heartbeat, + context, "", NULL, + OTRL_MSGFLAGS_IGNORE_UNREADABLE, + NULL); + if (!err) { + /* Send it, and inject a debug message */ + if (ops->inject_message) { + ops->inject_message(opdata, accountname, + protocol, sender, heartbeat); + } + free(heartbeat); + + context->context_priv->lastsent = now; + otrl_context_update_recent_child(context, 1); + + /* Signal an event for the heartbeat message */ + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_LOG_HEARTBEAT_SENT, + context, NULL, + gcry_error(GPG_ERR_NO_ERROR)); + } + } + } + } + + /* Return the TLVs even if ignore_message == 1 so + * that we can attach TLVs to heartbeats. */ + if (tlvsp) { + *tlvsp = tlvs; + } else { + otrl_tlv_free(tlvs); + } + + if (edata.ignore_message != 1) { + char *converted_msg = NULL; + + *newmessagep = plaintext; + edata.ignore_message = 0; + + /* convert the plaintext message if necessary */ + if (ops->convert_msg) { + ops->convert_msg(opdata, context, + OTRL_CONVERT_RECEIVING, &converted_msg, + plaintext); + + if (converted_msg) { + free(plaintext); + plaintext = NULL; + *newmessagep = strdup(converted_msg); + + if (ops->convert_free) { + ops->convert_free(opdata, context, + converted_msg); + } + } + } + } else { + free(plaintext); + } + break; + } + break; + + case OTRL_MSGTYPE_ERROR: + if ((policy & OTRL_POLICY_ERROR_START_AKE)) { + char *msgtosend = otrl_proto_default_query_msg( + context->accountname, policy); + if (msgtosend && ops->inject_message) { + ops->inject_message(opdata, context->accountname, + context->protocol, context->username, + msgtosend); + } + free(msgtosend); + } + + if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + /* Mark the last message we sent as eligible for + * retransmission */ + context->context_priv->may_retransmit = 1; + } + + /* In any event, display the error message, with the + * display_otr_message callback, if possible */ + if (ops->handle_msg_event) { + /* Remove the OTR error prefix and pass the msg */ + const char *just_err_msg = strstr(message, OTR_ERROR_PREFIX); + if (!just_err_msg) { + just_err_msg = message; + } else { + just_err_msg += (strlen(OTR_ERROR_PREFIX)); + if (*just_err_msg == ' ') { + /* Advance pointer to skip the space character */ + just_err_msg++; + } + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR, + context, just_err_msg, + gcry_error(GPG_ERR_NO_ERROR)); + edata.ignore_message = 1; + } + } + break; + + case OTRL_MSGTYPE_TAGGEDPLAINTEXT: + /* Strip the tag from the message */ + bestversion = otrl_proto_whitespace_bestversion(message, + &startwhite, &endwhite, policy); + if (startwhite && endwhite) { + size_t restlen = strlen(endwhite); + char *strippedmsg = strdup(message); + + if (strippedmsg) { + memmove(strippedmsg + (startwhite - message), + strippedmsg + (endwhite - message), restlen+1); + *newmessagep = strippedmsg; + edata.ignore_message = 0; + } + } + if (bestversion && context->msgstate != OTRL_MSGSTATE_ENCRYPTED + && (policy & OTRL_POLICY_WHITESPACE_START_AKE)) { + switch(bestversion) { + case 3: + err = otrl_auth_start_v23(&(context->auth), 3); + send_or_error_auth(ops, opdata, err, context, us); + break; + case 2: + err = otrl_auth_start_v23(&(context->auth), 2); + send_or_error_auth(ops, opdata, err, context, us); + break; + case 1: + /* Get our private key */ + privkey = otrl_privkey_find(us, context->accountname, + context->protocol); + if (privkey == NULL) { + /* We've got no private key! */ + if (ops->create_privkey) { + ops->create_privkey(opdata, + context->accountname, + context->protocol); + privkey = otrl_privkey_find(us, + context->accountname, + context->protocol); + } + } + if (privkey) { + err = otrl_auth_start_v1(&(context->auth), NULL, 0, + privkey); + send_or_error_auth(ops, opdata, err, context, us); + } + break; + default: + /* Don't start the AKE */ + break; + } + } + + /* FALLTHROUGH */ + case OTRL_MSGTYPE_NOTOTR: + if (best_context->msgstate != OTRL_MSGSTATE_PLAINTEXT || + (policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) { + /* Not fine. Let the user know. */ + const char *plainmsg = (*newmessagep) ? *newmessagep : message; + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED, + context, plainmsg, gcry_error(GPG_ERR_NO_ERROR)); + free(*newmessagep); + *newmessagep = NULL; + edata.ignore_message = 1; + } + } + break; + + case OTRL_MSGTYPE_UNKNOWN: + /* We received an OTR message we didn't recognize. Ignore + * it, and signal an event. */ + if (ops->handle_msg_event) { + ops->handle_msg_event(opdata, + OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED, + context, NULL, gcry_error(GPG_ERR_NO_ERROR)); + } + if (edata.ignore_message == -1) edata.ignore_message = 1; + break; + } + +end: + /* If we reassembled a fragmented message, we need to free the + * allocated memory now. */ + free(unfragmessage); + + if (edata.ignore_message == -1) edata.ignore_message = 0; + return edata.ignore_message; +} + +/* Put a connection into the PLAINTEXT state, first sending the + * other side a notice that we're doing so if we're currently ENCRYPTED, + * and we think he's logged in. Affects only the specified context. */ +static void disconnect_context(OtrlUserState us, const OtrlMessageAppOps *ops, + void *opdata, ConnContext *context) +{ + if (!context) return; + + if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED && + context->context_priv->their_keyid > 0 && + ops->is_logged_in && + ops->is_logged_in(opdata, context->accountname, context->protocol, + context->username) == 1) { + if (ops->inject_message) { + char *encmsg = NULL; + gcry_error_t err; + OtrlTLV *tlv = otrl_tlv_new(OTRL_TLV_DISCONNECTED, 0, NULL); + + err = otrl_proto_create_data(&encmsg, context, "", tlv, + OTRL_MSGFLAGS_IGNORE_UNREADABLE, NULL); + if (!err) { + ops->inject_message(opdata, context->accountname, + context->protocol, context->username, encmsg); + } + free(encmsg); + otrl_tlv_free(tlv); + } + } + + otrl_context_force_plaintext(context); + if (ops->update_context_list) { + ops->update_context_list(opdata); + } +} + + +/* Put a connection into the PLAINTEXT state, first sending the + * other side a notice that we're doing so if we're currently ENCRYPTED, + * and we think he's logged in. Affects only the specified instance. */ +void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops, + void *opdata, const char *accountname, const char *protocol, + const char *username, otrl_instag_t instance) +{ + ConnContext *context = otrl_context_find(us, username, accountname, + protocol, instance, 0, NULL, NULL, NULL); + + if (!context) return; + + disconnect_context(us, ops, opdata, context); +} + +/* Put a connection into the PLAINTEXT state, first sending the + * other side a notice that we're doing so if we're currently ENCRYPTED, + * and we think he's logged in. Affects all matching instances. */ +void otrl_message_disconnect_all_instances(OtrlUserState us, + const OtrlMessageAppOps *ops, void *opdata, const char *accountname, + const char *protocol, const char *username) +{ + ConnContext * c_iter; + ConnContext *context; + + if (!username || !accountname || !protocol) return; + + context = otrl_context_find(us, username, accountname, + protocol, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); + + if (!context) return; + + for (c_iter = context; c_iter && c_iter->m_context == context->m_context; + c_iter = c_iter->next) { + disconnect_context(us, ops, opdata, c_iter); + } +} + +/* Get the current extra symmetric key (of size OTRL_EXTRAKEY_BYTES + * bytes) and let the other side know what we're going to use it for. + * The key is stored in symkey, which must already be allocated + * and OTRL_EXTRAKEY_BYTES bytes long. */ +gcry_error_t otrl_message_symkey(OtrlUserState us, + const OtrlMessageAppOps *ops, void *opdata, ConnContext *context, + unsigned int use, const unsigned char *usedata, size_t usedatalen, + unsigned char *symkey) +{ + if (!context || (usedatalen > 0 && !usedata)) { + return gcry_error(GPG_ERR_INV_VALUE); + } + + if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED && + context->context_priv->their_keyid > 0) { + unsigned char *tlvdata = malloc(usedatalen+4); + char *encmsg = NULL; + gcry_error_t err; + OtrlTLV *tlv; + + tlvdata[0] = (use >> 24) & 0xff; + tlvdata[1] = (use >> 16) & 0xff; + tlvdata[2] = (use >> 8) & 0xff; + tlvdata[3] = (use) & 0xff; + if (usedatalen > 0) { + memmove(tlvdata+4, usedata, usedatalen); + } + + tlv = otrl_tlv_new(OTRL_TLV_SYMKEY, usedatalen+4, tlvdata); + free(tlvdata); + + err = otrl_proto_create_data(&encmsg, context, "", tlv, + OTRL_MSGFLAGS_IGNORE_UNREADABLE, symkey); + if (!err && ops->inject_message) { + ops->inject_message(opdata, context->accountname, + context->protocol, context->username, encmsg); + } + free(encmsg); + otrl_tlv_free(tlv); + + return err; + } + + /* We weren't in an encrypted session. */ + return gcry_error(GPG_ERR_INV_VALUE); +} + +/* If you do _not_ define a timer_control callback function, set a timer + * to go off every definterval = + * otrl_message_poll_get_default_interval(userstate) seconds, and call + * otrl_message_poll every time the timer goes off. */ +unsigned int otrl_message_poll_get_default_interval(OtrlUserState us) +{ + return POLL_DEFAULT_INTERVAL; +} + +/* Call this function every so often, either as directed by the + * timer_control callback, or every definterval = + * otrl_message_poll_get_default_interval(userstate) seconds if you have + * no timer_control callback. This function must be called from the + * main libotr thread.*/ +void otrl_message_poll(OtrlUserState us, const OtrlMessageAppOps *ops, + void *opdata) +{ + /* Wipe private keys last sent before this time */ + time_t expire_before = time(NULL) - MAX_AKE_WAIT_TIME; + + ConnContext *contextp; + + /* Is there a context still waiting for a DHKEY message, even after + * we wipe the stale ones? */ + int still_waiting = 0; + + if (us == NULL) return; + + for (contextp = us->context_root; contextp; contextp = contextp->next) { + /* If this is a master context, and it's still waiting for a + * v3 DHKEY message, see if it's waited long enough. */ + if (contextp->m_context == contextp && + contextp->auth.authstate == OTRL_AUTHSTATE_AWAITING_DHKEY && + contextp->auth.protocol_version == 3 && + contextp->auth.commit_sent_time > 0) { + if (contextp->auth.commit_sent_time < expire_before) { + otrl_auth_clear(&contextp->auth); + } else { + /* Not yet expired */ + still_waiting = 1; + } + } + } + + /* If there's nothing more to wait for, stop the timer, if possible. */ + if (still_waiting == 0 && ops && ops->timer_control) { + ops->timer_control(opdata, 0); + us->timer_running = 0; + } +} |