/*
* 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;
}
}