summaryrefslogtreecommitdiff
path: root/plugins/MirOTR/libotr-3.2.0/src/message.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/MirOTR/libotr-3.2.0/src/message.c')
-rw-r--r--plugins/MirOTR/libotr-3.2.0/src/message.c1446
1 files changed, 1446 insertions, 0 deletions
diff --git a/plugins/MirOTR/libotr-3.2.0/src/message.c b/plugins/MirOTR/libotr-3.2.0/src/message.c
new file mode 100644
index 0000000000..b385c1f4f1
--- /dev/null
+++ b/plugins/MirOTR/libotr-3.2.0/src/message.c
@@ -0,0 +1,1446 @@
+/*
+ * Off-the-Record Messaging library
+ * Copyright (C) 2004-2008 Ian Goldberg, Chris Alexander, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* system headers */
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+/* libgcrypt headers */
+#include <gcrypt.h>
+
+/* libotr headers */
+#include "privkey.h"
+#include "proto.h"
+#include "auth.h"
+#include "message.h"
+#include "sm.h"
+
+/* 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
+
+/* 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 this 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. Call otrl_message_free(*messagep) 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, const char *message, OtrlTLV *tlvs,
+ char **messagep,
+ void (*add_appdata)(void *data, ConnContext *context),
+ void *data)
+{
+ struct context * context;
+ char * msgtosend;
+ gcry_error_t err;
+ OtrlPolicy policy = OTRL_POLICY_DEFAULT;
+ int context_added = 0;
+
+ *messagep = NULL;
+
+ if (!accountname || !protocol || !recipient || !message || !messagep)
+ return gcry_error(GPG_ERR_NO_ERROR);
+
+ /* See if we have a fingerprint for this user */
+ context = otrl_context_find(us, recipient, accountname, protocol,
+ 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);
+ }
+
+ /* 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 gcry_error(GPG_ERR_NO_ERROR);
+ }
+
+ /* If this is an OTR Query message, don't encrypt it. */
+ if (otrl_proto_message_type(message) == OTRL_MSGTYPE_QUERY) {
+ /* Replace the "?OTR?" with a custom message */
+ char *bettermsg = otrl_proto_default_query_msg(accountname, policy);
+ if (bettermsg) {
+ *messagep = bettermsg;
+ }
+ return gcry_error(GPG_ERR_NO_ERROR);
+ }
+
+ /* 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->display_otr_message) ||
+ ops->display_otr_message(opdata, accountname,
+ protocol, recipient, "Attempting to start a "
+ "private conversation...")) && ops->notify) {
+ const char *format = "You attempted to send an "
+ "unencrypted message to %s";
+ char *primary = malloc(strlen(format) +
+ strlen(recipient) - 1);
+ if (primary) {
+ sprintf(primary, format, recipient);
+ ops->notify(opdata, OTRL_NOTIFY_WARNING, accountname,
+ protocol, recipient, "OTR Policy Violation",
+ primary,
+ "Unencrypted messages to this recipient are "
+ "not allowed. Attempting to start a private "
+ "conversation.\n\nYour message will be "
+ "retransmitted when the private conversation "
+ "starts.");
+ free(primary);
+ }
+ }
+ context->lastmessage = gcry_malloc_secure(strlen(message) + 1);
+ if (context->lastmessage) {
+ char *bettermsg = otrl_proto_default_query_msg(accountname,
+ policy);
+ strcpy(context->lastmessage, message);
+ context->lastsent = time(NULL);
+ context->may_retransmit = 2;
+ if (bettermsg) {
+ *messagep = bettermsg;
+ } else {
+ return gcry_error(GPG_ERR_ENOMEM);
+ }
+ }
+ } 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(message);
+ 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;
+ char *taggedmsg = malloc(msglen + basetaglen + v1taglen
+ +v2taglen + 1);
+ if (taggedmsg) {
+ strcpy(taggedmsg, message);
+ 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);
+ }
+ *messagep = taggedmsg;
+ if (context) {
+ context->otr_offer = OFFER_SENT;
+ }
+ }
+ }
+ }
+ break;
+ case OTRL_MSGSTATE_ENCRYPTED:
+ /* Create the new, encrypted message */
+ err = otrl_proto_create_data(&msgtosend, context, message, tlvs,
+ 0);
+ if (!err) {
+ context->lastsent = time(NULL);
+ *messagep = msgtosend;
+ } else {
+ /* Uh, oh. Whatever we do, *don't* send the message in the
+ * clear. */
+ *messagep = _strdup("?OTR Error: Error occurred encrypting "
+ "message");
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata, accountname,
+ protocol, recipient, "An error occurred when "
+ "encrypting your message. The message was not "
+ "sent.")) && ops->notify) {
+ ops->notify(opdata, OTRL_NOTIFY_ERROR,
+ accountname, protocol, recipient,
+ "Error encrypting message",
+ "An error occurred when encrypting your message",
+ "The message was not sent.");
+ }
+ if (!(*messagep)) {
+ return gcry_error(GPG_ERR_ENOMEM);
+ }
+ }
+ break;
+ case OTRL_MSGSTATE_FINISHED:
+ *messagep = _strdup("");
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata, accountname,
+ protocol, recipient, "Your message was not sent. "
+ "Either end your private conversation, or restart "
+ "it.")) && ops->notify) {
+ const char *fmt = "%s has already closed his/her private "
+ "connection to you";
+ char *primary = malloc(strlen(fmt) + strlen(recipient) - 1);
+ if (primary) {
+ sprintf(primary, fmt, recipient);
+ ops->notify(opdata, OTRL_NOTIFY_ERROR,
+ accountname, protocol, recipient,
+ "Private connection closed", primary,
+ "Your message was not sent. Either close your "
+ "private connection to him, or refresh it.");
+ }
+ }
+ if (!(*messagep)) {
+ return gcry_error(GPG_ERR_ENOMEM);
+ }
+ break;
+ }
+
+ return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* 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)
+{
+ if (!err) {
+ const char *msg = context->auth.lastauthmsg;
+ if (msg && *msg) {
+ otrl_message_fragment_and_send(ops, opdata, context, msg, OTRL_FRAGMENT_SEND_ALL, NULL);
+ /*if (ops->inject_message) {
+ ops->inject_message(opdata, context->accountname,
+ context->protocol, context->username, msg);
+ }*/
+ }
+ } else {
+ const char *buf_format = "Error setting up private conversation: %s";
+ const char *strerr;
+ char *buf;
+
+ switch(gcry_err_code(err)) {
+ case GPG_ERR_INV_VALUE:
+ strerr = "Malformed message received";
+ break;
+ default:
+ strerr = gcry_strerror(err);
+ break;
+ }
+ buf = malloc(strlen(buf_format) + strlen(strerr) - 1);
+ if (buf) {
+ sprintf(buf, buf_format, strerr);
+ }
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata, context->accountname,
+ context->protocol, context->username, buf))
+ && ops->notify) {
+ ops->notify(opdata, OTRL_NOTIFY_ERROR, context->accountname,
+ context->protocol, context->username, "OTR error",
+ buf, NULL);
+ }
+ free(buf);
+ }
+ 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->display_otr_message) ||
+ edata->ops->display_otr_message(edata->opdata,
+ edata->context->accountname, edata->context->protocol,
+ edata->context->username,
+ "We are receiving our own OTR messages. "
+ "You are either trying to talk to yourself, "
+ "or someone is reflecting your messages back "
+ "at you.")) && edata->ops->notify) {
+ edata->ops->notify(edata->opdata, OTRL_NOTIFY_ERROR,
+ edata->context->accountname, edata->context->protocol,
+ edata->context->username, "OTR Error",
+ "We are receiving our own OTR messages.",
+ "You are either trying to talk to yourself, "
+ "or someone is reflecting your messages back "
+ "at you.");
+ }
+ 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->our_keyid - 1 == edata->context->auth.our_keyid &&
+ !gcry_mpi_cmp(edata->context->our_old_dh_key.pub,
+ edata->context->auth.our_dh.pub) &&
+ ((edata->context->their_keyid > 0 &&
+ edata->context->their_keyid ==
+ edata->context->auth.their_keyid &&
+ !gcry_mpi_cmp(edata->context->their_y,
+ edata->context->auth.their_pub)) ||
+ (edata->context->their_keyid > 1 &&
+ edata->context->their_keyid - 1 ==
+ edata->context->auth.their_keyid &&
+ edata->context->their_old_y != NULL &&
+ !gcry_mpi_cmp(edata->context->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->their_keyid = edata->context->auth.their_keyid;
+ gcry_mpi_release(edata->context->their_y);
+ gcry_mpi_release(edata->context->their_old_y);
+ edata->context->their_y = gcry_mpi_copy(edata->context->auth.their_pub);
+ edata->context->their_old_y = NULL;
+
+ if (edata->context->our_keyid - 1 != edata->context->auth.our_keyid ||
+ gcry_mpi_cmp(edata->context->our_old_dh_key.pub,
+ edata->context->auth.our_dh.pub)) {
+ otrl_dh_keypair_free(&(edata->context->our_dh_key));
+ otrl_dh_keypair_free(&(edata->context->our_old_dh_key));
+ otrl_dh_keypair_copy(&(edata->context->our_old_dh_key),
+ &(edata->context->auth.our_dh));
+ otrl_dh_gen_keypair(edata->context->our_old_dh_key.groupid,
+ &(edata->context->our_dh_key));
+ edata->context->our_keyid = edata->context->auth.our_keyid + 1;
+ }
+
+ /* Create the session keys from the DH keys */
+ otrl_dh_session_free(&(edata->context->sesskeys[0][0]));
+ err = otrl_dh_session(&(edata->context->sesskeys[0][0]),
+ &(edata->context->our_dh_key), edata->context->their_y);
+ if (err) return err;
+ otrl_dh_session_free(&(edata->context->sesskeys[1][0]));
+ err = otrl_dh_session(&(edata->context->sesskeys[1][0]),
+ &(edata->context->our_old_dh_key), edata->context->their_y);
+ if (err) return err;
+
+ edata->context->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->lastmessage != NULL &&
+ edata->context->may_retransmit &&
+ edata->context->lastsent >= (now - RESEND_INTERVAL)) {
+ char *resendmsg;
+ int resending = (edata->context->may_retransmit == 1);
+
+ /* Re-encrypt the message with the new keys */
+ err = otrl_proto_create_data(&resendmsg,
+ edata->context, edata->context->lastmessage, NULL, 0);
+ if (!err) {
+ const char *format = "<b>The last message "
+ "to %s was resent.</b>";
+ char *buf;
+
+ /* Resend the message */
+ otrl_message_fragment_and_send(edata->ops, edata->opdata, edata->context, resendmsg, OTRL_FRAGMENT_SEND_ALL, NULL);
+ free(resendmsg);
+ edata->context->lastsent = now;
+
+ if (!resending) {
+ /* We're actually just sending it
+ * for the first time. */
+ edata->ignore_message = 1;
+ } else {
+ /* Let the user know we resent it */
+ buf = malloc(strlen(format) +
+ strlen(edata->context->username) - 1);
+ if (buf) {
+ sprintf(buf, format, edata->context->username);
+ if (edata->ops->display_otr_message) {
+ if (!edata->ops->display_otr_message(
+ edata->opdata, edata->context->accountname,
+ edata->context->protocol,
+ edata->context->username, buf)) {
+ edata->ignore_message = 1;
+ }
+ }
+ if (edata->ignore_message != 1) {
+ *(edata->messagep) = buf;
+ edata->ignore_message = 0;
+ } else {
+ free(buf);
+ }
+ }
+ }
+ }
+ }
+}
+
+/* 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);
+ if (!err) {
+ /* Send it, and set the next expected message to the
+ * logical response */
+ err = otrl_message_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);
+ if (!err) {
+ /* Send the abort signal so our buddy knows we've stopped */
+ err = otrl_message_fragment_and_send(ops, opdata, context,
+ sendsmp, OTRL_FRAGMENT_SEND_ALL, NULL);
+ }
+ free(sendsmp);
+ otrl_tlv_free(sendtlv);
+}
+
+/* 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 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,
+ void (*add_appdata)(void *data, ConnContext *context),
+ void *data)
+{
+ ConnContext *context;
+ OtrlMessageType msgtype;
+ int context_added = 0;
+ OtrlMessageState msgstate;
+ OtrlPolicy policy = OTRL_POLICY_DEFAULT;
+ int fragment_assembled = 0;
+ char *unfragmessage = NULL;
+ EncrData edata;
+
+ if (!accountname || !protocol || !sender || !message || !newmessagep)
+ return 0;
+
+ *newmessagep = NULL;
+ if (tlvsp) *tlvsp = NULL;
+
+ /* Find our context and state with this correspondent */
+ context = otrl_context_find(us, sender, accountname,
+ protocol, 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);
+ }
+
+ /* 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;
+ }
+
+ /* See if we have a fragment */
+ 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. */
+ fragment_assembled = 1;
+ message = unfragmessage;
+ 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);
+ msgstate = context->msgstate;
+
+ /* 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;
+ }
+ }
+
+ 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;
+ gcry_error_t err;
+ 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->our_old_dh_key);
+ our_keyid = context->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 2:
+ err = otrl_auth_start_v2(&(context->auth));
+ send_or_error_auth(ops, opdata, err, context);
+ 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);
+ }
+ 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:
+ if ((policy & OTRL_POLICY_ALLOW_V2)) {
+ err = otrl_auth_handle_commit(&(context->auth), message);
+ send_or_error_auth(ops, opdata, err, context);
+ }
+
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
+
+ case OTRL_MSGTYPE_DH_KEY:
+ if ((policy & OTRL_POLICY_ALLOW_V2)) {
+ /* 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), message,
+ &haveauthmsg, privkey);
+ if (err || haveauthmsg) {
+ send_or_error_auth(ops, opdata, err, context);
+ }
+ }
+ }
+
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
+
+ case OTRL_MSGTYPE_REVEALSIG:
+ if ((policy & OTRL_POLICY_ALLOW_V2)) {
+ /* 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),
+ message, &haveauthmsg, privkey, go_encrypted,
+ &edata);
+ if (err || haveauthmsg) {
+ send_or_error_auth(ops, opdata, err, context);
+ maybe_resend(&edata);
+ }
+ }
+ }
+
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
+
+ case OTRL_MSGTYPE_SIGNATURE:
+ if ((policy & OTRL_POLICY_ALLOW_V2)) {
+ err = otrl_auth_handle_signature(&(context->auth),
+ message, &haveauthmsg, go_encrypted, &edata);
+ if (err || haveauthmsg) {
+ send_or_error_auth(ops, opdata, err, context);
+ maybe_resend(&edata);
+ }
+ }
+
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
+
+ case OTRL_MSGTYPE_V1_KEYEXCH:
+ if ((policy & OTRL_POLICY_ALLOW_V1)) {
+ /* See if we should use an existing DH keypair, or generate
+ * a fresh one. */
+ if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+ our_dh = &(context->our_old_dh_key);
+ our_keyid = context->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);
+ 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 *format;
+ const char *displayaccountname;
+ 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;
+ }
+
+ /* Don't use g_strdup_printf here, because someone
+ * (not us) is going to free() the *newmessagep pointer,
+ * not g_free() it. */
+ format = "<b>The encrypted message received from %s is "
+ "unreadable, as you are not currently communicating "
+ "privately.</b>";
+ buf = malloc(strlen(format) + strlen(context->username)
+ - 1); /* Remove "%s", add username + '\0' */
+ if (buf) {
+ sprintf(buf, format, context->username);
+ if (ops->display_otr_message) {
+ if (!ops->display_otr_message(opdata, accountname,
+ protocol, sender, buf)) {
+ edata.ignore_message = 1;
+ }
+ }
+ if (edata.ignore_message != 1) {
+ *newmessagep = buf;
+ edata.ignore_message = 0;
+ } else {
+ free(buf);
+ }
+ }
+ format = "?OTR Error: You sent encrypted "
+ "data to %s, who wasn't expecting it.";
+ if (otrl_api_version >= 0x00030100 &&
+ ops->account_name) {
+ displayaccountname = ops->account_name(opdata,
+ context->accountname, protocol);
+ } else {
+ displayaccountname = NULL;
+ }
+ buf = malloc(strlen(format) + strlen(displayaccountname ?
+ displayaccountname : context->accountname)
+ - 1);
+ if (buf) {
+ sprintf(buf, format, displayaccountname ?
+ displayaccountname : context->accountname);
+ if (ops->inject_message) {
+ ops->inject_message(opdata, accountname, protocol,
+ sender, buf);
+ }
+ free(buf);
+ }
+ if (displayaccountname && otrl_api_version >= 0x00030100 &&
+ ops->account_name_free) {
+ ops->account_name_free(opdata, displayaccountname);
+ }
+
+ break;
+
+ case OTRL_MSGSTATE_ENCRYPTED:
+ err = otrl_proto_accept_data(&plaintext, &tlvs, context,
+ message, &flags);
+ if (err) {
+ int is_conflict =
+ (gpg_err_code(err) == GPG_ERR_CONFLICT);
+ if ((flags & OTRL_MSGFLAGS_IGNORE_UNREADABLE)) {
+ edata.ignore_message = 1;
+ break;
+ }
+ format = is_conflict ? "We received an unreadable "
+ "encrypted message from %s." :
+ "We received a malformed data message from %s.";
+ buf = malloc(strlen(format) + strlen(sender) - 1);
+ if (buf) {
+ sprintf(buf, format, sender);
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata,
+ accountname, protocol, sender,
+ buf)) && ops->notify) {
+ ops->notify(opdata, OTRL_NOTIFY_ERROR,
+ accountname, protocol, sender,
+ "OTR Error", buf, NULL);
+ }
+ free(buf);
+ }
+ if (ops->inject_message) {
+ ops->inject_message(opdata, accountname, protocol,
+ sender, is_conflict ? "?OTR Error: "
+ "You transmitted an unreadable "
+ "encrypted message." :
+ "?OTR Error: You transmitted "
+ "a malformed data message");
+ }
+ 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 TLVs contain SMP data, process it */
+ nextMsg = context->smstate->nextExpected;
+ tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q);
+ if (tlv && nextMsg == OTRL_SMP_EXPECT1) {
+ /* 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);
+ }
+ tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1);
+ if (tlv && 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);
+ }
+ tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2);
+ if (tlv && nextMsg == OTRL_SMP_EXPECT2) {
+ unsigned char* nextmsg;
+ int nextmsglen;
+ OtrlTLV *sendtlv;
+ char *sendsmp;
+ 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);
+ if (!err) {
+ err = otrl_message_fragment_and_send(ops,
+ opdata, context, sendsmp,
+ OTRL_FRAGMENT_SEND_ALL, NULL);
+ }
+ free(sendsmp);
+ otrl_tlv_free(sendtlv);
+ }
+ free(nextmsg);
+ }
+ tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3);
+ if (tlv && nextMsg == OTRL_SMP_EXPECT3) {
+ unsigned char* nextmsg;
+ int nextmsglen;
+ OtrlTLV *sendtlv;
+ char *sendsmp;
+ 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);
+ if (!err) {
+ err = otrl_message_fragment_and_send(ops,
+ opdata, context, sendsmp,
+ OTRL_FRAGMENT_SEND_ALL, NULL);
+ }
+ free(sendsmp);
+ otrl_tlv_free(sendtlv);
+ }
+ free(nextmsg);
+ }
+ tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4);
+ if (tlv && 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)));
+ }
+ tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT);
+ if (tlv) {
+ context->smstate->nextExpected = OTRL_SMP_EXPECT1;
+ }
+ if (plaintext[0] == '\0') {
+ /* If it's a heartbeat (an empty message), don't
+ * display it to the user, but log a debug message. */
+ format = "Heartbeat received from %s.\n";
+ buf = malloc(strlen(format) + strlen(sender) - 1);
+ if (buf) {
+ sprintf(buf, format, sender);
+ if (ops->log_message) {
+ ops->log_message(opdata, buf);
+ }
+ free(buf);
+ }
+ edata.ignore_message = 1;
+ } else if (edata.ignore_message == 0 &&
+ context->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->lastsent < (now - HEARTBEAT_INTERVAL)) {
+ char *heartbeat;
+
+ /* Create the heartbeat message */
+ err = otrl_proto_create_data(&heartbeat,
+ context, "", NULL,
+ OTRL_MSGFLAGS_IGNORE_UNREADABLE);
+ if (!err) {
+ /* Send it, and log a debug message */
+ if (ops->inject_message) {
+ ops->inject_message(opdata, accountname,
+ protocol, sender, heartbeat);
+ }
+ free(heartbeat);
+
+ context->lastsent = now;
+
+ /* Log a debug message */
+ format = "Heartbeat sent to %s.\n";
+ buf = malloc(strlen(format) + strlen(sender)
+ - 1);
+ if (buf) {
+ sprintf(buf, format, sender);
+ if (ops->log_message) {
+ ops->log_message(opdata, buf);
+ }
+ free(buf);
+ }
+ }
+ }
+ }
+
+ /* 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) {
+ *newmessagep = plaintext;
+ edata.ignore_message = 0;
+ } 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->may_retransmit = 1;
+ }
+
+ /* In any event, display the error message, with the
+ * display_otr_message callback, if possible */
+ if (ops->display_otr_message) {
+ const char *otrerror = strstr(message, "?OTR Error:");
+ if (otrerror) {
+ /* Skip the leading '?' */
+ ++otrerror;
+ } else {
+ otrerror = message;
+ }
+ if (!ops->display_otr_message(opdata, accountname, protocol,
+ sender, otrerror)) {
+ 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 2:
+ err = otrl_auth_start_v2(&(context->auth));
+ send_or_error_auth(ops, opdata, err, context);
+ 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);
+ }
+ break;
+ default:
+ /* Don't start the AKE */
+ break;
+ }
+ }
+
+ /* FALLTHROUGH */
+ case OTRL_MSGTYPE_NOTOTR:
+ if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT ||
+ (policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) {
+ /* Not fine. Let the user know. */
+
+ /* Don't use g_strdup_printf here, because someone
+ * (not us) is going to free() the *message pointer,
+ * not g_free() it. */
+ const char *plainmsg = (*newmessagep) ? *newmessagep : message;
+ const char *format = "The following message received "
+ "from %s was not encrypted: [</b>%s<b>]";
+ char *buf = malloc(strlen(format) + strlen(context->username)
+ + strlen(plainmsg) - 3);
+ /* Remove "%s%s", add username + message + '\0' */
+ if (buf) {
+ sprintf(buf, format, context->username, plainmsg);
+ if (ops->display_otr_message) {
+ if (!ops->display_otr_message(opdata, accountname,
+ protocol, sender, buf)) {
+ free(*newmessagep);
+ *newmessagep = NULL;
+ edata.ignore_message = 1;
+ }
+ }
+ if (edata.ignore_message != 1) {
+ free(*newmessagep);
+ *newmessagep = buf;
+ edata.ignore_message = 0;
+ } else {
+ free(buf);
+ }
+ }
+ }
+ break;
+
+ case OTRL_MSGTYPE_UNKNOWN:
+ /* We received an OTR message we didn't recognize. Ignore
+ * it, but make a log entry. */
+ if (ops->log_message) {
+ const char *format = "Unrecognized OTR message received "
+ "from %s.\n";
+ char *buf = malloc(strlen(format) + strlen(sender) - 1);
+ if (buf) {
+ sprintf(buf, format, sender);
+ ops->log_message(opdata, buf);
+ free(buf);
+ }
+ }
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
+ }
+
+ /* If we reassembled a fragmented message, we need to free the
+ * allocated memory now. */
+ if (fragment_assembled) {
+ free(unfragmessage);
+ }
+
+ if (edata.ignore_message == -1) edata.ignore_message = 0;
+ return edata.ignore_message;
+}
+
+/* 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. */
+gcry_error_t otrl_message_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 (otrl_api_version >= 0x030100 && 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 fragment_count = ((msglen - 1) / (mms -19)) + 1;
+ /* like ceil(msglen/(mms - 19)) */
+
+ err = otrl_proto_fragment_create(mms, fragment_count, &fragments,
+ 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. */
+ int l = strlen(message) + 1;
+ *returnFragment = malloc(sizeof(char)*l);
+ strcpy(*returnFragment, message);
+ }
+ }
+ }
+ return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* 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. */
+void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
+ void *opdata, const char *accountname, const char *protocol,
+ const char *username)
+{
+ ConnContext *context = otrl_context_find(us, username, accountname,
+ protocol, 0, NULL, NULL, NULL);
+
+ if (!context) return;
+
+ if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
+ context->their_keyid > 0 &&
+ ops->is_logged_in &&
+ ops->is_logged_in(opdata, accountname, protocol, 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);
+ if (!err) {
+ ops->inject_message(opdata, accountname, protocol,
+ username, encmsg);
+ }
+ free(encmsg);
+ }
+ }
+
+ otrl_context_force_plaintext(context);
+ if (ops->update_context_list) {
+ ops->update_context_list(opdata);
+ }
+}