summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2014-05-02 14:09:11 -0400
committerXavier Claessens <xavier.claessens@collabora.com>2014-05-09 12:15:56 -0400
commitd55e4a1b36275ac3cb1bd5ff39fa7704e1b74a5d (patch)
tree76e7039fd50ff9449b023d76ff35f837a555fde0
parent9f01937eaa66f21298cc55bf36751ff6569239e1 (diff)
GabbleIMChannel: Add optional OTR support
The extra DBus channel interface is implemented using GDBus so it needs to be exported on a different bus name. In Telepathy 1.0 it will really be an extra interface on the channel object.
-rw-r--r--.gitignore1
-rw-r--r--configure.ac29
-rw-r--r--src/Channel_Interface_OTR1.xml97
-rw-r--r--src/Makefile.am28
-rw-r--r--src/im-channel-otr.c784
-rw-r--r--src/im-channel-otr.h37
-rw-r--r--src/im-channel.c29
7 files changed, 1002 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index bb2a5bed8..2c50029ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ FIXME.out
Makefile
Makefile.in
_gen
+_gdbus
/aclocal.m4
/autom4te.cache
/build-aux
diff --git a/configure.ac b/configure.ac
index 2d4f11897..eeb29cf16 100644
--- a/configure.ac
+++ b/configure.ac
@@ -222,6 +222,17 @@ PKG_CHECK_MODULES(GMODULE, [gmodule-2.0 >= 2.32])
AC_DEFINE(GLIB_VERSION_MIN_REQUIRED, GLIB_VERSION_2_32, [Ignore post 2.32 deprecations])
AC_DEFINE(GLIB_VERSION_MAX_ALLOWED, GLIB_VERSION_2_32, [Prevent post 2.32 APIs])
+dnl Check for GIO-Unix
+PKG_CHECK_MODULES(GIO_UNIX, [gio-unix-2.0],
+ have_gio_unix=yes, have_gio_unix=no)
+
+AS_IF([test "x$have_gio_unix" = "xyes"],
+ [AC_DEFINE(HAVE_GIO_UNIX, [], [Define if GIO-Unix is available])
+
+ GLIB_CFLAGS="$GLIB_CFLAGS $GIO_UNIX_CFLAGS"
+ GLIB_LIBS="$GLIB_LIBS $GIO_UNIX_LIBS"
+ ])
+
AC_SUBST(GLIB_CFLAGS)
AC_SUBST(GLIB_LIBS)
@@ -408,6 +419,22 @@ AC_SUBST(NICE_CFLAGS)
AC_SUBST(NICE_LIBS)
AM_CONDITIONAL([ENABLE_JINGLE_FILE_TRANSFER], [test "x$enable_jingle_ft" = xyes])
+AC_ARG_ENABLE(otr,
+ AC_HELP_STRING([--disable-otr],
+ [disable OTR support]),
+ [enable_otr=$enableval], [enable_otr=yes])
+
+if test x$enable_otr = xyes; then
+ PKG_CHECK_MODULES(OTR, libotr >= 4.0.0)
+ AM_PATH_LIBGCRYPT(1:1.2.0,,AC_MSG_ERROR(libgcrypt 1.2.0 or newer is required.))
+ AC_DEFINE(ENABLE_OTR, [], [Enable OTR])
+fi
+AM_CONDITIONAL([ENABLE_OTR], [test "x$enable_otr" = xyes])
+
+AC_SUBST(OTR_CFLAGS)
+AC_SUBST(OTR_LIBS)
+AM_CONDITIONAL([ENABLE_OTR], [test "x$enable_otr" = xyes])
+
AC_CHECK_FUNCS(getifaddrs memset select strndup setresuid setreuid strerror)
AC_OUTPUT( Makefile \
@@ -454,5 +481,5 @@ Configure summary:
File transfer support.......: ${enable_ft}
Jingle file transfer support: ${enable_jingle_ft}
VoIP support................: ${enable_voip}
-
+ OTR support.................: ${enable_otr}
"
diff --git a/src/Channel_Interface_OTR1.xml b/src/Channel_Interface_OTR1.xml
new file mode 100644
index 000000000..fbf6a8ec5
--- /dev/null
+++ b/src/Channel_Interface_OTR1.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_OTR1" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2014 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>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.</p>
+
+ <p>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.</p>
+ </tp:license>
+
+ <interface name="im.telepathy.v1.Channel.Interface.OTR1"
+ tp:causes-havoc="experimental">
+ <tp:added version="Gabble 0.UNRELEASED">(Gabble-specific)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal"
+ value="true"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A simple D-Bus API <a
+ href="https://otr.cypherpunks.ca/">Off The Record</a>.
+ </tp:docstring>
+
+ <property name="TrustLevel"
+ tp:name-for-bindings="Trust_Level"
+ type="u" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current trust level of this channel:
+ 0=TRUST_NOT_PRIVATE, 1=TRUST_UNVERIFIED, 2=TRUST_PRIVATE,
+ 3=TRUST_FINISHED</p>
+ <p>Clients MUST listen to PropertiesChanged to update UI when trust
+ level changes.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="LocalFingerprint"
+ tp:name-for-bindings="Local_Fingerprint"
+ type="(say)" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>User's current fingerprint. The first element is a human readable
+ fingerprint that can be displayed to the user so he can communicate it
+ to the other end by other means so he can trust it. The 2nd element is
+ the fingerprint raw data.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="RemoteFingerprint"
+ tp:name-for-bindings="Fingerprint"
+ type="(say)" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current fingerprint of the remote contact. Should be displayed
+ to the user to update its trust level. The first element of the tuple
+ is the fingerprint formatted to be displayed. The 2nd element is the
+ fingerprint raw data that can be passed to TrustFingerprint</p>
+ </tp:docstring>
+ </property>
+
+ <method name="TrustFingerprint"
+ tp:name-for-bindings="Trust_Fingerprint">
+ <tp:docstring>
+ <p>Set whether or not the user trusts the given fingerprint.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="Fingerprint" type="ay">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ <tp:docstring>
+ The fingerprint.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Trust" type="b">
+ <tp:docstring>
+ %TRUE if trusted, %FALSE otherwise.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="Initialize"
+ tp:name-for-bindings="Initialize">
+ <tp:docstring>
+ <p>Start an OTR session for this channel if the remote end supports it
+ has well.</p>
+ </tp:docstring>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/src/Makefile.am b/src/Makefile.am
index 6f5b47c71..e42bb9908 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -171,6 +171,31 @@ libgabble_convenience_la_SOURCES += \
gtalk-file-collection.h
endif
+if ENABLE_OTR
+libgabble_convenience_la_SOURCES += \
+ im-channel-otr.c \
+ im-channel-otr.h \
+ _gdbus/Channel_Interface_OTR1.c \
+ _gdbus/Channel_Interface_OTR1.h \
+ $(NULL)
+
+BUILT_SOURCES += \
+ _gdbus/Channel_Interface_OTR1.c \
+ _gdbus/Channel_Interface_OTR1.h
+endif
+
+_gdbus/%.c: _gdbus/%-stamp
+ @:
+_gdbus/%.h: _gdbus/%-stamp
+ @:
+_gdbus/%-stamp: Makefile %.xml
+ $(MKDIR_P) _gdbus
+ gdbus-codegen --interface-prefix im.telepathy.v1. \
+ --generate-c-code _gdbus/$* \
+ --c-namespace GabbleGDBus \
+ $*.xml
+ touch $@
+
enumtype_sources = \
$(top_srcdir)/src/connection.h \
$(top_srcdir)/src/room-config.h \
@@ -231,6 +256,7 @@ AM_CFLAGS = $(ERROR_CFLAGS) -I$(top_srcdir) -I$(top_builddir) \
@DBUS_CFLAGS@ @GLIB_CFLAGS@ @WOCKY_CFLAGS@ \
@TP_GLIB_CFLAGS@ \
@SOUP_CFLAGS@ @NICE_CFLAGS@ @GMODULE_CFLAGS@ \
+ @OTR_CFLAGS@ @LIBGCRYPT_CFLAGS@ \
-I $(top_srcdir)/lib -I $(top_builddir)/lib \
-DG_LOG_DOMAIN=\"gabble\" \
-DPLUGIN_DIR=\"$(pluginexecdir)\"
@@ -241,7 +267,7 @@ if WINDOWS
endif
ALL_LIBS = @DBUS_LIBS@ @GLIB_LIBS@ @WOCKY_LIBS@ @TP_GLIB_LIBS@ \
- @SOUP_LIBS@ @NICE_LIBS@ @GMODULE_LIBS@
+ @SOUP_LIBS@ @NICE_LIBS@ @GMODULE_LIBS@ @OTR_LIBS@ @LIBGCRYPT_LIBS@
# build gibber first
all: gibber
diff --git a/src/im-channel-otr.c b/src/im-channel-otr.c
new file mode 100644
index 000000000..263810f58
--- /dev/null
+++ b/src/im-channel-otr.c
@@ -0,0 +1,784 @@
+/*
+ * Copyright (C) 2014 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "im-channel-otr.h"
+
+#include <glib/gi18n.h>
+
+#include <libotr/proto.h>
+#include <libotr/message.h>
+#include <libotr/privkey.h>
+
+#include "_gdbus/Channel_Interface_OTR1.h"
+
+#define DEBUG_FLAG GABBLE_DEBUG_IM
+#include "connection.h"
+#include "debug.h"
+#include "presence-cache.h"
+#include "util.h"
+
+#define OTR_PRIV_KEY "otr-priv"
+#define GET_PRIV(self) g_object_get_data (G_OBJECT (self), OTR_PRIV_KEY)
+
+typedef struct
+{
+ otrl_instag_t instag;
+ OtrlMessageEvent last_msg_event;
+ GabbleGDBusChannelInterfaceOTR1 *skeleton;
+} OtrPrivate;
+
+static OtrlUserState userstate = NULL;
+static OtrlMessageAppOps *ui_ops_p = NULL;
+
+static void
+otr_private_free (OtrPrivate *priv)
+{
+ g_object_unref (priv->skeleton);
+ g_slice_free (OtrPrivate, priv);
+}
+
+static gchar *
+dup_filename (const gchar *basename)
+{
+ return g_build_filename (g_get_user_data_dir (), "telepathy", basename, NULL);
+}
+
+static gchar *
+dup_instag_filename (void)
+{
+ return dup_filename ("otr-instag");
+}
+
+static gchar *
+dup_privkey_filename (void)
+{
+ return dup_filename ("otr-privkey");
+}
+static gchar *
+dup_fingerprint_filename (void)
+{
+ return dup_filename ("otr-fingerprint");
+}
+
+static const gchar *
+get_id (TpBaseConnection *base_conn,
+ TpHandle contact)
+{
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ return tp_handle_inspect (contact_repo, contact);
+}
+
+static const gchar *
+get_self_id (GabbleIMChannel *self)
+{
+ TpBaseChannel *base_chan = (TpBaseChannel *) self;
+ TpBaseConnection *base_conn = tp_base_channel_get_connection (base_chan);
+
+ return get_id (base_conn, tp_base_connection_get_self_handle (base_conn));
+}
+
+static const gchar *
+get_target_id (GabbleIMChannel *self)
+{
+ TpBaseChannel *base_chan = (TpBaseChannel *) self;
+ TpBaseConnection *base_conn = tp_base_channel_get_connection (base_chan);
+
+ return get_id (base_conn, tp_base_channel_get_target_handle (base_chan));
+}
+
+static void
+inject_message (GabbleIMChannel *self,
+ const gchar *message)
+{
+ TpBaseChannel *base_chan = (TpBaseChannel *) self;
+ TpBaseConnection *base_conn = tp_base_channel_get_connection (base_chan);
+ WockyPorter *porter;
+ WockyStanza *stanza;
+ WockyNode *node;
+ gchar *id;
+
+ id = gabble_generate_id ();
+ stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_CHAT,
+ NULL, get_target_id (self),
+ '@', "id", id,
+ '*', &node,
+ NULL);
+ g_free (id);
+
+ wocky_node_add_child_with_content (node, "body", message);
+
+ porter = gabble_connection_dup_porter ((GabbleConnection *) base_conn);
+ wocky_porter_send_async (porter, stanza, NULL, NULL, NULL);
+ g_object_unref (porter);
+ g_object_unref (stanza);
+}
+
+static void notify (GabbleIMChannel *self,
+ const gchar *format, ...) G_GNUC_PRINTF (2, 3);
+
+static void
+notify (GabbleIMChannel *self,
+ const gchar *format,
+ ...)
+{
+ TpBaseChannel *base_chan = (TpBaseChannel *) self;
+ TpBaseConnection *base_conn = tp_base_channel_get_connection (base_chan);
+ va_list args;
+ gchar *text;
+
+ va_start (args, format);
+ text = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ /* FIXME: There should be no sender for a notification, but setting handle to
+ * 0 makes empathy crash atm. */
+ tp_message_mixin_take_received (G_OBJECT (self),
+ tp_cm_message_new_text (base_conn,
+ tp_base_channel_get_target_handle (base_chan),
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE, text));
+
+ g_free (text);
+}
+
+typedef enum
+{
+ TRUST_LEVEL_NOT_PRIVATE,
+ TRUST_LEVEL_UNVERIFIED,
+ TRUST_LEVEL_PRIVATE,
+ TRUST_LEVEL_FINISHED
+} TrustLevel;
+
+static GVariant *
+fp_raw_to_variant (guchar *fp_raw)
+{
+ if (fp_raw != NULL && fp_raw[0] != '\0')
+ {
+ gchar display_fp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
+
+ otrl_privkey_hash_to_human (display_fp, fp_raw);
+ return g_variant_new ("(s@ay)", display_fp,
+ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, fp_raw, 20,
+ sizeof (guchar)));
+ }
+
+ return g_variant_new ("(say)", "", NULL);
+}
+
+static GVariant *
+fp_to_variant (Fingerprint *fp)
+{
+ return fp_raw_to_variant (fp != NULL ? fp->fingerprint : NULL);
+}
+
+static void
+update_properties (GabbleIMChannel *self)
+{
+ OtrPrivate *priv = GET_PRIV (self);
+ ConnContext *context;
+ TrustLevel level = TRUST_LEVEL_NOT_PRIVATE;
+ Fingerprint *their_fp = NULL;
+ guchar our_fp_raw[20];
+
+ context = otrl_context_find (userstate, get_target_id (self),
+ get_self_id (self), "xmpp", priv->instag, 0, NULL, NULL, NULL);
+
+ if (context != NULL)
+ {
+ if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED)
+ {
+ their_fp = context->active_fingerprint;
+
+ if (otrl_context_is_fingerprint_trusted (their_fp))
+ level = TRUST_LEVEL_PRIVATE;
+ else
+ level = TRUST_LEVEL_UNVERIFIED;
+ }
+ else if (context->msgstate == OTRL_MSGSTATE_FINISHED)
+ {
+ level = TRUST_LEVEL_FINISHED;
+ }
+ }
+
+ otrl_privkey_fingerprint_raw (userstate, our_fp_raw, get_self_id (self),
+ "xmpp");
+
+ gabble_gdbus_channel_interface_otr1_set_trust_level (priv->skeleton, level);
+ gabble_gdbus_channel_interface_otr1_set_remote_fingerprint (priv->skeleton,
+ fp_to_variant (their_fp));
+ gabble_gdbus_channel_interface_otr1_set_local_fingerprint (priv->skeleton,
+ fp_raw_to_variant (our_fp_raw));
+}
+
+static OtrlPolicy
+otr_policy (void *opdata,
+ ConnContext *context)
+{
+ return OTRL_POLICY_MANUAL;
+}
+
+static void
+otr_create_privkey (void *opdata,
+ const gchar *accountname,
+ const gchar *protocol)
+{
+ gchar *filename;
+
+ filename = dup_privkey_filename ();
+ otrl_privkey_generate (userstate, filename, accountname, protocol);
+ g_free (filename);
+}
+
+static gint
+otr_is_logged_in (void *opdata,
+ const gchar *accountname,
+ const gchar *protocol,
+ const gchar *recipient)
+{
+ GabbleIMChannel *self = opdata;
+ TpBaseChannel *base_chan = (TpBaseChannel *) self;
+ TpBaseConnection *base_conn = tp_base_channel_get_connection (base_chan);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ GabbleConnection *conn = (GabbleConnection *) base_conn;
+ GabblePresence *presence;
+ TpHandle contact;
+
+ contact = tp_handle_lookup (contact_repo, recipient, NULL, NULL);
+ if (contact == 0)
+ return -1;
+
+ presence = gabble_presence_cache_get (conn->presence_cache, contact);
+ if (presence == NULL)
+ return -1;
+
+ return (presence->status > GABBLE_PRESENCE_LAST_UNAVAILABLE) ? 1 : 0;
+}
+
+static void
+otr_inject_message (void *opdata,
+ const gchar *accountname,
+ const gchar *protocol,
+ const gchar *recipient,
+ const gchar *message)
+{
+ inject_message (opdata, message);
+}
+
+static void
+otr_update_context_list (void *opdata)
+{
+ update_properties (opdata);
+}
+
+static void
+otr_new_fingerprint (void *opdata,
+ OtrlUserState us,
+ const gchar *accountname,
+ const gchar *protocol,
+ const gchar *username,
+ guchar fingerprint[20])
+{
+ update_properties (opdata);
+}
+
+static void
+otr_write_fingerprints (void *opdata)
+{
+ gchar *filename;
+
+ filename = dup_fingerprint_filename ();
+ otrl_privkey_write_fingerprints (userstate, filename);
+ g_free (filename);
+}
+
+static void
+otr_gone_secure (void *opdata,
+ ConnContext *context)
+{
+ update_properties (opdata);
+}
+
+static void
+otr_gone_insecure (void *opdata,
+ ConnContext *context)
+{
+ update_properties (opdata);
+}
+
+static void
+otr_still_secure (void *opdata,
+ ConnContext *context,
+ gint is_reply)
+{
+ update_properties (opdata);
+}
+
+static gint
+otr_max_message_size (void *opdata,
+ ConnContext *context)
+{
+ return 0;
+}
+
+static const gchar *
+otr_error_message (void *opdata,
+ ConnContext *context,
+ OtrlErrorCode err_code)
+{
+ gchar *err_msg = NULL;
+
+ switch (err_code)
+ {
+ case OTRL_ERRCODE_NONE:
+ break;
+ case OTRL_ERRCODE_ENCRYPTION_ERROR:
+ err_msg = g_strdup(_("Error occurred encrypting message."));
+ break;
+ case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE:
+ if (context)
+ {
+ err_msg = g_strdup_printf (_("You sent encrypted data to %s, who"
+ " wasn't expecting it."), context->accountname);
+ }
+ break;
+ case OTRL_ERRCODE_MSG_UNREADABLE:
+ err_msg = g_strdup (_("You transmitted an unreadable encrypted message."));
+ break;
+ case OTRL_ERRCODE_MSG_MALFORMED:
+ err_msg = g_strdup (_("You transmitted a malformed data message."));
+ break;
+ }
+
+ return err_msg;
+}
+
+static void
+otr_error_message_free (void *opdata,
+ const gchar *err_msg)
+{
+ g_free ((gchar *) err_msg);
+}
+
+static const gchar *
+otr_resent_msg_prefix (void *opdata,
+ ConnContext *context)
+{
+ return g_strdup (_("[resent]"));
+}
+
+static void
+otr_resent_msg_prefix_free (void *opdata,
+ const gchar *prefix)
+{
+ g_free ((gchar *) prefix);
+}
+
+static void
+otr_handle_smp_event (void *opdata,
+ OtrlSMPEvent smp_event,
+ ConnContext *context,
+ unsigned short progress_percent,
+ gchar *question)
+{
+ DEBUG ("UNIMPLEMENTED\n");
+}
+
+static void
+otr_handle_msg_event (void *opdata,
+ OtrlMessageEvent msg_event,
+ ConnContext *context,
+ const gchar *message,
+ gcry_error_t err)
+{
+ GabbleIMChannel *self = opdata;
+ OtrPrivate *priv = GET_PRIV (self);
+
+ switch (msg_event)
+ {
+ case OTRL_MSGEVENT_NONE:
+ break;
+
+ case OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
+ notify (self, _("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."));
+ break;
+
+ case OTRL_MSGEVENT_ENCRYPTION_ERROR:
+ notify (self, _("An error occurred when encrypting your message and "
+ "not sent."));
+ break;
+
+ case OTRL_MSGEVENT_CONNECTION_ENDED:
+ notify (self, _("Your message was not sent because %s closed their "
+ "connection. Either close your private connection, or refresh it."),
+ context->username);
+ break;
+
+ case OTRL_MSGEVENT_SETUP_ERROR:
+ if (!err)
+ err = GPG_ERR_INV_VALUE;
+
+ switch (gcry_err_code (err))
+ {
+ case GPG_ERR_INV_VALUE:
+ notify (self, _("Error setting up private conversation: "
+ "Malformed message received"));
+ break;
+ default:
+ notify (self, _("Error setting up private conversation: %s"),
+ gcry_strerror (err));
+ break;
+ }
+ break;
+
+ case OTRL_MSGEVENT_MSG_REFLECTED:
+ notify (self, _("You are either trying to talk to yourself, "
+ "or someone is reflecting your messages back "
+ "at you."));
+ break;
+
+ case OTRL_MSGEVENT_MSG_RESENT:
+ notify (self, _("The last message to %s was resent."),
+ context->username);
+ break;
+
+ case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE:
+ notify (self, _("The encrypted message received from %s is unreadable, "
+ "as you are not currently communicating privately."),
+ context->username);
+ break;
+
+ case OTRL_MSGEVENT_RCVDMSG_UNREADABLE:
+ notify (self, _("We received an unreadable encrypted message from %s."),
+ context->username);
+ break;
+
+ case OTRL_MSGEVENT_RCVDMSG_MALFORMED:
+ notify (self, _("We received a malformed data message from %s."),
+ context->username);
+ break;
+
+ case OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD:
+ DEBUG ("Heartbeat received from %s", context->username);
+ break;
+
+ case OTRL_MSGEVENT_LOG_HEARTBEAT_SENT:
+ DEBUG ("Heartbeat sent to %s", context->accountname);
+ break;
+
+ case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
+ notify (self, _("OTR Error: %s"), message);
+ break;
+
+ case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
+ notify (self, _("The following message received from %s was *not* "
+ "encrypted: %s"), context->username, message);
+ break;
+
+ case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED:
+ DEBUG ("Unrecognized OTR message received from %s.", context->username);
+ break;
+
+ case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
+ if (priv->last_msg_event == msg_event)
+ break;
+
+ notify (self, _("%s has sent a message intended for a different "
+ "session. If you are logged in multiple times, another session may "
+ "have received the message."), context->username);
+ break;
+ }
+
+ priv->last_msg_event = msg_event;
+}
+
+static void
+otr_create_instag (void *opdata,
+ const gchar *accountname,
+ const gchar *protocol)
+{
+ gchar *filename;
+
+ filename = dup_instag_filename ();
+ otrl_instag_generate (userstate, filename, accountname, protocol);
+ g_free (filename);
+}
+
+static gboolean
+timeout_cb (gpointer user_data)
+{
+ otrl_message_poll (userstate, ui_ops_p, NULL);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+otr_timer_control (void *opdata,
+ guint interval)
+{
+ static guint timeout_id = 0;
+
+ if (timeout_id != 0)
+ {
+ g_source_remove (timeout_id);
+ timeout_id = 0;
+ }
+
+ if (interval > 0)
+ timeout_id = g_timeout_add_seconds (interval, timeout_cb, NULL);
+}
+
+static OtrlMessageAppOps ui_ops =
+{
+ otr_policy,
+ otr_create_privkey,
+ otr_is_logged_in,
+ otr_inject_message,
+ otr_update_context_list,
+ otr_new_fingerprint,
+ otr_write_fingerprints,
+ otr_gone_secure,
+ otr_gone_insecure,
+ otr_still_secure,
+ otr_max_message_size,
+ NULL, /* account_name */
+ NULL, /* account_name_free */
+ NULL, /* received_symkey */
+ otr_error_message,
+ otr_error_message_free,
+ otr_resent_msg_prefix,
+ otr_resent_msg_prefix_free,
+ otr_handle_smp_event,
+ otr_handle_msg_event,
+ otr_create_instag,
+ NULL, /* convert_data */
+ NULL, /* convert_data_free */
+ otr_timer_control
+};
+
+static void
+global_init (void)
+{
+ gchar *filename;
+
+ if (userstate != NULL)
+ return;
+
+ OTRL_INIT;
+ ui_ops_p = &ui_ops;
+
+ userstate = otrl_userstate_create ();
+
+ filename = dup_filename (NULL);
+ g_mkdir_with_parents (filename, 0700);
+ g_free (filename);
+
+ filename = dup_privkey_filename ();
+ otrl_privkey_read (userstate, filename);
+ g_free (filename);
+
+ filename = dup_instag_filename ();
+ otrl_instag_read (userstate, filename);
+ g_free (filename);
+
+ filename = dup_fingerprint_filename ();
+ otrl_privkey_read_fingerprints (userstate, filename, NULL, NULL);
+ g_free (filename);
+}
+
+static gboolean
+handle_initialize_cb (GabbleGDBusChannelInterfaceOTR1 *skeleton,
+ GDBusMethodInvocation *invocation,
+ GabbleIMChannel *self)
+{
+ gchar *msg;
+
+ msg = otrl_proto_default_query_msg (get_self_id (self), OTRL_POLICY_DEFAULT);
+ inject_message (self, msg);
+ free (msg);
+
+ gabble_gdbus_channel_interface_otr1_complete_initialize (skeleton,
+ invocation);
+
+ return TRUE;
+}
+
+static gboolean
+handle_trust_fingerprint_cb (GabbleGDBusChannelInterfaceOTR1 *skeleton,
+ GDBusMethodInvocation *invocation,
+ GVariant *fp_variant,
+ gboolean trust,
+ GabbleIMChannel *self)
+{
+ OtrPrivate *priv = GET_PRIV (self);
+ ConnContext *context;
+ const guchar *fp_data;
+ Fingerprint *fp;
+
+ context = otrl_context_find (userstate, get_target_id (self),
+ get_self_id (self), "xmpp", priv->instag, 0, NULL, NULL, NULL);
+ if (context == NULL)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED, "Couldn't find OTR context");
+ return TRUE;
+ }
+
+ fp_data = g_variant_get_data (fp_variant);
+ fp = otrl_context_find_fingerprint (context, (guchar *) fp_data, 0, NULL);
+ if (fp == NULL)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS, "Couldn't find fingerprint");
+ return TRUE;
+ }
+
+ otrl_context_set_trust (fp, trust ? "verified" : "");
+ otr_write_fingerprints (self);
+ update_properties (self);
+
+ gabble_gdbus_channel_interface_otr1_complete_trust_fingerprint (skeleton,
+ invocation);
+
+ return TRUE;
+}
+
+static void
+unown_name_id (gpointer user_data)
+{
+ g_bus_unown_name (GPOINTER_TO_UINT (user_data));
+}
+
+static void
+ensure_own_name (TpBaseConnection *base_conn,
+ GDBusConnection *dbus)
+{
+ guint id;
+ gchar *bus_name;
+
+ /* This is a hack that will go away in tp1.0: we need to own a name with that
+ * GDBusConnection.
+ */
+
+ id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (base_conn),
+ "otr-own-name-id"));
+ if (id != 0)
+ return;
+
+ bus_name = g_strconcat (tp_base_connection_get_bus_name (base_conn),
+ ".OTR", NULL);
+ id = g_bus_own_name_on_connection (dbus, bus_name,
+ G_BUS_NAME_OWNER_FLAGS_NONE, NULL, NULL, NULL, NULL);
+ g_object_set_data_full (G_OBJECT (base_conn), "otr-own-name-id",
+ GUINT_TO_POINTER (id), unown_name_id);
+ g_free (bus_name);
+}
+
+void
+gabble_im_channel_otr_init (GabbleIMChannel *self)
+{
+ TpBaseChannel *base_chan = (TpBaseChannel *) self;
+ TpBaseConnection *base_conn = tp_base_channel_get_connection (base_chan);
+ OtrPrivate *priv;
+ GDBusConnection *dbus;
+
+ global_init ();
+
+ priv = g_slice_new0 (OtrPrivate);
+ priv->instag = OTRL_INSTAG_BEST;
+ priv->last_msg_event = OTRL_MSGEVENT_NONE;
+ priv->skeleton = gabble_gdbus_channel_interface_otr1_skeleton_new ();
+ g_object_set_data_full (G_OBJECT (self), OTR_PRIV_KEY, priv,
+ (GDestroyNotify) otr_private_free);
+
+ g_signal_connect (priv->skeleton, "handle-initialize",
+ G_CALLBACK (handle_initialize_cb), self);
+ g_signal_connect (priv->skeleton, "handle-trust-fingerprint",
+ G_CALLBACK (handle_trust_fingerprint_cb), self);
+ update_properties (self);
+
+ dbus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (priv->skeleton),
+ dbus, tp_base_channel_get_object_path (base_chan), NULL);
+ ensure_own_name (base_conn, dbus);
+ g_object_unref (dbus);
+}
+
+gboolean
+gabble_im_channel_otr_sending (GabbleIMChannel *self,
+ WockyStanza *stanza,
+ GError **error)
+{
+ OtrPrivate *priv = GET_PRIV (self);
+ WockyNode *node;
+ const gchar *content;
+ gchar *new_content;
+ gcry_error_t err;
+
+ node = wocky_stanza_get_top_node (stanza);
+ content = wocky_node_get_content_from_child (node, "body");
+
+ err = otrl_message_sending (userstate, ui_ops_p, self,
+ get_self_id (self), "xmpp", get_target_id (self),
+ priv->instag, content, NULL, &new_content,
+ OTRL_FRAGMENT_SEND_ALL_BUT_LAST, NULL,
+ NULL, NULL);
+
+ if (err)
+ {
+ g_set_error_literal (error, TP_ERROR, TP_ERROR_ENCRYPTION_ERROR,
+ gcry_strerror (err));
+ return FALSE;
+ }
+
+ if (new_content != NULL)
+ {
+ node = wocky_node_get_child (node, "body");
+ wocky_node_set_content (node, new_content);
+ }
+
+ otrl_message_free (new_content);
+
+ return TRUE;
+}
+
+gchar *
+gabble_im_channel_otr_receiving (GabbleIMChannel *self,
+ const gchar *content)
+{
+ gchar *new_content;
+ gchar *ret = NULL;
+ gboolean ignore;
+
+ ignore = otrl_message_receiving (userstate, ui_ops_p, self,
+ get_self_id (self), "xmpp", get_target_id (self), content,
+ &new_content, NULL, NULL, NULL, NULL);
+
+ if (!ignore)
+ ret = g_strdup ((new_content != NULL) ? new_content : content);
+
+ otrl_message_free (new_content);
+
+ return ret;
+}
diff --git a/src/im-channel-otr.h b/src/im-channel-otr.h
new file mode 100644
index 000000000..742fe741f
--- /dev/null
+++ b/src/im-channel-otr.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GABBLE_IM_CHANNEL_OTR_H__
+#define __GABBLE_IM_CHANNEL_OTR_H__
+
+#include "im-channel.h"
+
+G_BEGIN_DECLS
+
+void gabble_im_channel_otr_init (GabbleIMChannel *self);
+
+gboolean gabble_im_channel_otr_sending (GabbleIMChannel *self,
+ WockyStanza *stanza,
+ GError **error);
+
+gchar *gabble_im_channel_otr_receiving (GabbleIMChannel *self,
+ const gchar *content);
+
+G_END_DECLS
+
+#endif
diff --git a/src/im-channel.c b/src/im-channel.c
index 9cbdbfaab..8bf9ae6d0 100644
--- a/src/im-channel.c
+++ b/src/im-channel.c
@@ -39,6 +39,10 @@
#include "roster.h"
#include "util.h"
+#ifdef ENABLE_OTR
+#include "im-channel-otr.h"
+#endif
+
static void destroyable_iface_init (gpointer, gpointer);
G_DEFINE_TYPE_WITH_CODE (GabbleIMChannel, gabble_im_channel,
@@ -157,6 +161,10 @@ gabble_im_channel_constructed (GObject *obj)
priv->chat_states_supported = CHAT_STATES_UNKNOWN;
tp_message_mixin_implement_send_chat_state (obj,
_gabble_im_channel_send_chat_state);
+
+#ifdef ENABLE_OTR
+ gabble_im_channel_otr_init (self);
+#endif
}
static void gabble_im_channel_dispose (GObject *object);
@@ -390,6 +398,16 @@ _gabble_im_channel_send_message (GObject *object,
flags = 0;
}
+#ifdef ENABLE_OTR
+ if (!gabble_im_channel_otr_sending (self, stanza, &error))
+ {
+ tp_message_mixin_sent (object, message, 0, NULL, error);
+ g_clear_error (&error);
+ g_object_unref (stanza);
+ return;
+ }
+#endif
+
porter = gabble_connection_dup_porter (gabble_conn);
context = g_slice_new0 (_GabbleIMSendMessageCtx);
context->channel = g_object_ref (base);
@@ -407,7 +425,6 @@ _gabble_im_channel_send_message (GObject *object,
g_error_free (error);
}
-
if (priv->send_nick)
priv->send_nick = FALSE;
}
@@ -502,12 +519,20 @@ _gabble_im_channel_receive (GabbleIMChannel *chan,
TpBaseChannel *base_chan;
TpHandle peer;
TpMessage *msg;
+ gchar *text_dup = NULL;
g_assert (GABBLE_IS_IM_CHANNEL (chan));
priv = chan->priv;
base_chan = (TpBaseChannel *) chan;
peer = tp_base_channel_get_target_handle (base_chan);
+#ifdef ENABLE_OTR
+ /* If it returns NULL it means that was an internal OTR protocol message */
+ text = text_dup = gabble_im_channel_otr_receiving (chan, text);
+ if (text == NULL)
+ return;
+#endif
+
/* update peer's full JID if it's changed */
if (tp_strdiff (from, priv->peer_jid))
{
@@ -529,6 +554,8 @@ _gabble_im_channel_receive (GabbleIMChannel *chan,
tp_message_mixin_take_received (G_OBJECT (chan), msg);
maybe_send_delivery_report (chan, message, from, id);
+
+ g_free (text_dup);
}
void