1
0
Fork 0

Add OTR support.

This patch adds support for the OTR protocol to irssi. This is an import
of the external irssi-otr project that we are now taking over
maintership for.

Major thanks to the original authors of Irssi-OTR: Uli Meis and David
Goulet. Thanks to the OTR community in #OTR on OFTC, thanks to everyone
who have helped testing the patches and submitted UI suggestions.
This commit is contained in:
Alexander Færøy 2015-01-04 01:57:21 +01:00 committed by Alexander Færøy
parent ad4324d242
commit 016b42baea
25 changed files with 3262 additions and 41 deletions

View File

@ -27,17 +27,23 @@ addons:
packages:
- libperl-dev
- elinks
- libgcrypt11-dev
before_install:
- perl -V
- ./autogen.sh --with-proxy --with-bot --with-perl=module
- wget https://otr.cypherpunks.ca/libotr-4.1.1.tar.gz
- tar zxf libotr-*.tar.gz
- cd libotr-*
- ./configure --prefix $HOME/otr-prefix && make && make install
- cd ..
- ./autogen.sh --with-proxy --with-bot --with-perl=module --with-otr=yes --with-libotr-prefix=$HOME/otr-prefix/lib --with-libotr-inc-prefix=$HOME/otr-prefix/include
- make dist
- cd ..
- tar xaf */irssi-*.tar.*
- cd irssi-*
install:
- ./configure --with-proxy --with-bot --with-perl=module --prefix=$HOME/irssi-build
- ./configure --with-proxy --with-bot --with-perl=module --with-otr=yes --with-libotr-prefix=$HOME/otr-prefix/lib --with-libotr-inc-prefix=$HOME/otr-prefix/include --prefix=$HOME/irssi-build
$( $UNITTESTS && echo --enable-always-build-tests )
- make CFLAGS="-Wall -Werror -Werror=declaration-after-statement"
- make install

View File

@ -27,6 +27,8 @@ Large feature patches by:
Heikki Orsila : DCC SEND queueing
Mark Trumbull : DCC SERVER
Francesco Fracassi : Passive DCC
Uli Meis : OTR support
David Goulet : OTR support
Other patches (grep for "patch" in ChangeLog) by:

View File

@ -148,6 +148,21 @@ AC_ARG_WITH(perl,
fi,
want_perl=static)
AC_ARG_WITH(otr,
[ --with-otr[=yes|no|module] Build with OTR support - also specifies
if it should be built into the main irssi
binary or as a module (default)],
if test x$withval = xyes; then
want_otr=module
elif test x$withval = xstatic; then
want_otr=static
elif test x$withval = xmodule; then
want_otr=module
else
want_otr=no
fi,
want_otr=no)
AC_ARG_ENABLE(true-color,
[ --enable-true-color Build with true color support in terminal],
if test x$enableval = xno ; then
@ -527,6 +542,48 @@ if test "x$want_capsicum" = "xyes"; then
])
fi
dnl **
dnl ** OTR checks
dnl **
have_otr=no
if test "x$want_otr" != "xno"; then
AM_PATH_LIBGCRYPT(1:1.2.0, [], [AC_ERROR(libgcrypt 1.2.0 or newer is required.)])
AM_PATH_LIBOTR(4.1.0, [], [AC_ERROR([libotr 4.1.0 or newer is required.])])
OTR_CFLAGS="$LIBOTR_CFLAGS $LIBGCRYPT_CFLAGS"
OTR_LDFLAGS="$LIBOTR_LIBS $LIBGCRYPT_LIBS"
AC_SUBST(otr_module_lib)
AC_SUBST(otr_static_lib)
if test "x$want_otr" != "xno"; then
if test "x$want_otr" = "xstatic"; then
otr_module_lib=
otr_static_lib=libotr_core_static.la
OTR_LINK_LIBS="../otr/libotr_core_static.la"
OTR_LINK_FLAGS="$OTR_LDFLAGS"
AC_DEFINE(HAVE_STATIC_OTR)
else
otr_module_lib=libotr_core.la
otr_static_lib=
fi
fi
AC_SUBST(otr_module_lib)
AC_SUBST(otr_static_lib)
AC_SUBST(OTR_CFLAGS)
AC_SUBST(OTR_LDFLAGS)
AC_SUBST(OTR_LINK_LIBS)
AC_SUBST(OTR_LINK_FLAGS)
have_otr=yes
fi
dnl ** check what we want to build
AM_CONDITIONAL(BUILD_TEXTUI, test "$want_textui" = "yes")
AM_CONDITIONAL(BUILD_IRSSIBOT, test "$want_irssibot" = "yes")
@ -535,6 +592,7 @@ AM_CONDITIONAL(BUILD_IRSSIPROXY, test "$want_irssiproxy" = "yes")
AM_CONDITIONAL(HAVE_PERL, test "$want_perl" != "no")
AM_CONDITIONAL(HAVE_CAPSICUM, test "x$want_capsicum" = "xyes")
AM_CONDITIONAL(USE_GREGEX, test "x$want_gregex" = "xyes")
AM_CONDITIONAL(HAVE_OTR, test "x$have_otr" != "xno")
# move LIBS to PROG_LIBS so they're not tried to be used when linking eg. perl libraries
PROG_LIBS=$LIBS
@ -629,6 +687,7 @@ fi
AH_TEMPLATE(HAVE_GMODULE)
AH_TEMPLATE(HAVE_SOCKS_H, [misc..])
AH_TEMPLATE(HAVE_STATIC_PERL)
AH_TEMPLATE(HAVE_STATIC_OTR)
AH_TEMPLATE(PRIuUOFF_T, [printf()-format for uoff_t, eg. "u" or "lu" or "llu"])
AH_TEMPLATE(UOFF_T_INT, [What type should be used for uoff_t])
AH_TEMPLATE(UOFF_T_LONG)
@ -662,6 +721,7 @@ src/perl/common/Makefile.PL
src/perl/irc/Makefile.PL
src/perl/ui/Makefile.PL
src/perl/textui/Makefile.PL
src/otr/Makefile
scripts/Makefile
scripts/examples/Makefile
tests/Makefile
@ -755,5 +815,14 @@ echo "Building with true color support.. : $want_truecolor"
echo "Building with GRegex ............. : $want_gregex"
echo "Building with Capsicum ........... : $want_capsicum"
if test "x$want_otr" = "xstatic"; then
echo "Building with OTR support ........ : static (in irssi binary)"
elif test "x$want_otr" = "xmodule"; then
echo "Building with OTR support ........ : module"
else
echo "Building with OTR support ........ : no"
fi
echo
echo "If there are any problems, read the INSTALL file."

111
docs/help/in/otr.in Normal file
View File

@ -0,0 +1,111 @@
OTR %|[OPTION]
Command to control the OTR module. Without an option, this help is printed.
This help contains three sections which are %9options, quickstart and files.%n
To add the OTR status bar (highly recommended):
%9/statusbar window add otr%n
%9Options:%n
AUTH <secret>
Start or respond to an authentication process.
AUTHQ <question> <secret>
Start a SMP authentication process.
Example: %9/otr authq "My question is" "this is the secret"%n
AUTHABORT
Abort an ongoing authentication process.
CONTEXTS
List known contexts which basically list the known fingerprints and their
state.
DEBUG
Turn on debugging.
DISTRUST <fingerprint>
Distrust a specific fingerprint. This command can be done inside a private
window for which the current fingerprint of the other person will be used
or else set fp to a human readable OTR fingerprint available with the above
contexts command.
Examples: %9/otr distrust 487FFADA 5073FEDD C5AB5C14 5BB6C1FF 6D40D48A%n
FINISH
End the OTR session. This MUST be done inside a private conversation
window.
FORGET <fingerprint>
Forget a specific fingerprint (deleted from the known fingerprints). The
behavior is the same as the distrust command explained above.
GENKEY <name>
Generate OTR keys for a given account name. This is done automatically
if someone tries to establish a secure session.
This process is done in a background worker and can take an arbitrary
amount of time. The completion is checked when another irssi event is
catched.
HELP
Print this help.
INFO
Display the OTR fingerprint(s) of all your account(s).
INIT
Initialize an OTR conversation within a private conversation window.
TRUST <fingerprint>
Trust a specific fingerprint. The behavior is the same as the forget and
distrust commands explained above.
VERSION
Print the version of the OTR module.
%9Quickstart:%n
Start a private conversation with the person you want to initiate a secure
session. Once in the private message window:
%9/otr init%n
Key generation should start if no key is found for your account name. Once the
process is done, either type a message which should automatically start the
session or redo the init command.
Time to authenticate the person. Either use a shared secret exchange through
phone or GPG-signed email or use the socialist millionaire problem mechanism
(SMP) which is basically to ask a question for which the answer can only be
known by the other person.
%9/otr auth <shared-secret>%n OR %9/otr authq "A question" <shared-secret>%n
Or to respond to an authentication:
%9/otr auth <secret>%n
%9Files:%n
This otr modules creates a directory in %9$HOME/.irssi/otr%n and creates three
files:
* %9otr.key%n
Contains your OTR private key(s). NEVER shared this directory with someone
else unless you know what you are doing.
* %9otr.fp%n
The known fingerprints with their _trust_ status.
* %9otr.instag
Instance tag of the libotr. This should NEVER be copied to an other
computer. If unsure, just ignore this file.
For more information on OTR, see https://otr.cypherpunks.ca/

View File

@ -350,3 +350,9 @@ Perl
----
"script error", PERL_SCRIPT_REC, char *errormsg
OTR Core
--------
otr.c:
"otr event", SERVER_REC, char *nick, char *status

134
m4/libotr.m4 Normal file
View File

@ -0,0 +1,134 @@
dnl
dnl Off-the-Record Messaging library
dnl Copyright (C) 2004-2007 Ian Goldberg, Chris Alexander, Nikita Borisov
dnl <otr@cypherpunks.ca>
dnl
dnl This library is free software; you can redistribute it and/or
dnl modify it under the terms of version 2.1 of the GNU Lesser General
dnl Public License as published by the Free Software Foundation.
dnl
dnl This library is distributed in the hope that it will be useful,
dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
dnl Lesser General Public License for more details.
dnl
dnl You should have received a copy of the GNU Lesser General Public
dnl License along with this library; if not, write to the Free Software
dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
dnl
dnl AM_PATH_LIBOTR([MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]])
dnl Test for libotr, and define LIBOTR_CFLAGS and LIBOTR_LIBS as appropriate.
dnl enables arguments --with-libotr-prefix=
dnl --with-libotr-inc-prefix=
dnl
dnl You must already have found libgcrypt with AM_PATH_LIBGCRYPT
dnl
dnl Adapted from alsa.m4, originally by
dnl Richard Boulton <richard-alsa@tartarus.org>
dnl Christopher Lansdown <lansdoct@cs.alfred.edu>
dnl Jaroslav Kysela <perex@suse.cz>
AC_DEFUN([AM_PATH_LIBOTR],
[dnl Save the original CFLAGS, LDFLAGS, and LIBS
libotr_save_CFLAGS="$CFLAGS"
libotr_save_LDFLAGS="$LDFLAGS"
libotr_save_LIBS="$LIBS"
libotr_found=yes
dnl
dnl Get the cflags and libraries for libotr
dnl
AC_ARG_WITH(libotr-prefix,
[ --with-libotr-prefix=PFX Prefix where libotr is installed(optional)],
[libotr_prefix="$withval"], [libotr_prefix=""])
AC_ARG_WITH(libotr-inc-prefix,
[ --with-libotr-inc-prefix=PFX Prefix where libotr includes are (optional)],
[libotr_inc_prefix="$withval"], [libotr_inc_prefix=""])
dnl Add any special include directories
AC_MSG_CHECKING(for libotr CFLAGS)
if test "$libotr_inc_prefix" != "" ; then
LIBOTR_CFLAGS="$LIBOTR_CFLAGS -I$libotr_inc_prefix"
CFLAGS="$CFLAGS $LIBOTR_CFLAGS"
fi
AC_MSG_RESULT($LIBOTR_CFLAGS)
dnl add any special lib dirs
AC_MSG_CHECKING(for libotr LIBS)
if test "$libotr_prefix" != "" ; then
LIBOTR_LIBS="$LIBOTR_LIBS -L$libotr_prefix"
LDFLAGS="$LDFLAGS $LIBOTR_LIBS"
fi
dnl add the libotr library
LIBOTR_LIBS="$LIBOTR_LIBS -lotr"
LIBS="$LIBOTR_LIBS $LIBS"
AC_MSG_RESULT($LIBOTR_LIBS)
dnl Check for a working version of libotr that is of the right version.
min_libotr_version=ifelse([$1], ,3.0.0,$1)
no_libotr=""
libotr_min_major_version=`echo $min_libotr_version | \
sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
libotr_min_minor_version=`echo $min_libotr_version | \
sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
libotr_min_sub_version=`echo $min_libotr_version | \
sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
AC_MSG_CHECKING(for libotr headers version $libotr_min_major_version.x >= $min_libotr_version)
AC_LANG_SAVE
AC_LANG_C
AC_TRY_COMPILE([
#include <stdlib.h>
#include <libotr/version.h>
], [
# if(OTRL_VERSION_MAJOR != $libotr_min_major_version)
# error not present
# else
# if(OTRL_VERSION_MINOR > $libotr_min_minor_version)
exit(0);
# else
# if(OTRL_VERSION_MINOR < $libotr_min_minor_version)
# error not present
# endif
# if(OTRL_VERSION_SUB < $libotr_min_sub_version)
# error not present
# endif
# endif
# endif
exit(0);
],
[AC_MSG_RESULT(found.)],
[AC_MSG_RESULT(not present.)
ifelse([$3], , [AC_MSG_ERROR(Sufficiently new version of libotr not found.)])
libotr_found=no]
)
AC_LANG_RESTORE
dnl Now that we know that we have the right version, let's see if we have the library and not just the headers.
AC_CHECK_LIB([otr], [otrl_message_receiving],,
[ifelse([$3], , [AC_MSG_ERROR(No linkable libotr was found.)])
libotr_found=no],
$LIBGCRYPT_LIBS
)
LDFLAGS="$libotr_save_LDFLAGS"
LIBS="$libotr_save_LIBS"
if test "x$libotr_found" = "xyes" ; then
ifelse([$2], , :, [$2])
else
LIBOTR_CFLAGS=""
LIBOTR_LIBS=""
ifelse([$3], , :, [$3])
fi
dnl That should be it. Now just export our symbols:
AC_SUBST(LIBOTR_CFLAGS)
AC_SUBST(LIBOTR_LIBS)
])

View File

@ -14,8 +14,12 @@ if HAVE_PERL
PERLDIR=perl
endif
if HAVE_OTR
OTRDIR=otr
endif
pkginc_srcdir=$(pkgincludedir)/src
pkginc_src_HEADERS = \
common.h
SUBDIRS = lib-config core irc fe-common $(PERLDIR) $(TEXTUI) $(BOTUI) $(FUZZERUI)
SUBDIRS = lib-config core irc fe-common $(PERLDIR) $(OTRDIR) $(TEXTUI) $(BOTUI) $(FUZZERUI)

View File

@ -35,41 +35,6 @@ typedef struct {
int tag;
} SIMPLE_THREAD_REC;
static int g_io_channel_write_block(GIOChannel *channel, void *data, int len)
{
gsize ret;
int sent;
GIOStatus status;
sent = 0;
do {
status = g_io_channel_write_chars(channel, (char *) data + sent,
len-sent, &ret, NULL);
sent += ret;
} while (sent < len && status != G_IO_STATUS_ERROR);
return sent < len ? -1 : 0;
}
static int g_io_channel_read_block(GIOChannel *channel, void *data, int len)
{
time_t maxwait;
gsize ret;
int received;
GIOStatus status;
maxwait = time(NULL)+2;
received = 0;
do {
status = g_io_channel_read_chars(channel, (char *) data + received,
len-received, &ret, NULL);
received += ret;
} while (received < len && time(NULL) < maxwait &&
status != G_IO_STATUS_ERROR && status != G_IO_STATUS_EOF);
return received < len ? -1 : 0;
}
/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
written to pipe when found PID of the resolver child is returned */
int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe,

View File

@ -48,6 +48,39 @@ GIOChannel *g_io_channel_new(int handle)
return chan;
}
int g_io_channel_write_block(GIOChannel *channel, void *data, int len)
{
gsize ret;
int sent;
GIOStatus status;
sent = 0;
do {
status = g_io_channel_write_chars(channel, (char *) data + sent, len - sent, &ret, NULL);
sent += ret;
} while (sent < len && status != G_IO_STATUS_ERROR);
return sent < len ? -1 : 0;
}
int g_io_channel_read_block(GIOChannel *channel, void *data, int len)
{
time_t maxwait;
gsize ret;
int received;
GIOStatus status;
maxwait = time(NULL)+2;
received = 0;
do {
status = g_io_channel_read_chars(channel, (char *) data + received, len - received, &ret, NULL);
received += ret;
} while (received < len && time(NULL) < maxwait &&
status != G_IO_STATUS_ERROR && status != G_IO_STATUS_EOF);
return received < len ? -1 : 0;
}
IPADDR ip4_any = {
AF_INET,
#if defined(IN6ADDR_ANY_INIT)

View File

@ -36,6 +36,8 @@ GIOChannel *g_io_channel_new(int handle);
/* Returns 1 if IPADDRs are the same. */
/* Deprecated since it is unused. It will be deleted in a later release. */
int net_ip_compare(IPADDR *ip1, IPADDR *ip2) G_GNUC_DEPRECATED;
int g_io_channel_write_block(GIOChannel *channel, void *data, int len);
int g_io_channel_read_block(GIOChannel *channel, void *data, int len);
int net_connect_ip_handle(const IPADDR *ip, int port, const IPADDR *my_ip);

View File

@ -9,7 +9,8 @@ AM_CPPFLAGS = \
irssi_DEPENDENCIES = \
@COMMON_LIBS@ \
@PERL_LINK_LIBS@ \
@PERL_FE_LINK_LIBS@
@PERL_FE_LINK_LIBS@ \
@OTR_LINK_LIBS@
irssi_LDFLAGS = -export-dynamic
@ -17,6 +18,8 @@ irssi_LDADD = \
@COMMON_LIBS@ \
@PERL_LINK_LIBS@ \
@PERL_FE_LINK_LIBS@ \
@OTR_LINK_LIBS@ \
@OTR_LINK_FLAGS@ \
@PERL_LINK_FLAGS@ \
@PROG_LIBS@ \
@TEXTUI_LIBS@

View File

@ -54,6 +54,11 @@ void fe_perl_init(void);
void fe_perl_deinit(void);
#endif
#ifdef HAVE_STATIC_OTR
void otr_core_init(void);
void otr_core_deinit(void);
#endif
void irc_init(void);
void irc_deinit(void);
@ -183,6 +188,10 @@ static void textui_finish_init(void)
fe_perl_init();
#endif
#ifdef HAVE_STATIC_OTR
otr_core_init();
#endif
dirty_check();
fe_common_core_finish_init();
@ -221,8 +230,12 @@ static void textui_deinit(void)
module_unload(modules->data);
#ifdef HAVE_STATIC_PERL
perl_core_deinit();
fe_perl_deinit();
perl_core_deinit();
fe_perl_deinit();
#endif
#ifdef HAVE_STATIC_OTR
otr_core_deinit();
#endif
dirty_check(); /* one last time to print any quit messages */

42
src/otr/Makefile.am Normal file
View File

@ -0,0 +1,42 @@
moduledir = $(libdir)/irssi/modules
module_LTLIBRARIES = $(otr_module_lib)
noinst_LTLIBRARIES = $(otr_static_lib)
EXTRA_LTLIBRARIES = \
libotr_core.la \
libotr_core_static.la
AM_CPPFLAGS = \
-I$(top_srcdir)/src \
-I$(top_srcdir)/src/core/ \
-I$(top_srcdir)/src/irc/core/ \
-I$(top_srcdir)/src/fe-common/core/ \
-I$(top_srcdir)/src/fe-text/ \
$(GLIB_CFLAGS) \
$(OTR_CFLAGS)
libotr_core_la_LDFLAGS = -module -avoid-version -rpath $(moduledir)
libotr_core_la_LIBADD = $(OTR_LDFLAGS)
otr_sources = \
key.c \
module.c \
otr-formats.c \
otr-ops.c \
otr-fe.c \
otr.c
libotr_core_la_SOURCES = \
$(otr_sources)
libotr_core_static_la_SOURCES = \
$(otr_sources)
noinst_HEADERS = \
irssi-otr.h \
key.h \
module.h \
otr-formats.h \
otr-fe.h \
otr.h

39
src/otr/irssi-otr.h Normal file
View File

@ -0,0 +1,39 @@
/*
* Off-the-Record Messaging (OTR) module for the irssi IRC client
*
* Copyright (C) 2008 - Uli Meis <a.sporto+bee@gmail.com>
* 2012 - David Goulet <dgoulet@ev0ke.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#ifndef IRSSI_IRSSI_OTR_H
#define IRSSI_IRSSI_OTR_H
/* Ease our life a bit. */
#define OTR_IRSSI_MSG_PREFIX "%9OTR%9: "
/*
* Irssi macros for printing text to console.
*/
#define IRSSI_OTR_DEBUG(fmt, ...) \
do { \
if (otr_debug_get()) { \
printtext(NULL, NULL, MSGLEVEL_MSGS, OTR_IRSSI_MSG_PREFIX fmt, \
## __VA_ARGS__); \
} \
} while (0)
#endif /* IRSSI_IRSSI_OTR_H */

402
src/otr/key.c Normal file
View File

@ -0,0 +1,402 @@
/*
* Off-the-Record Messaging (OTR) modules for IRC
*
* Copyright (C) 2008 - Uli Meis <a.sporto+bee@gmail.com>
* 2012 - David Goulet <dgoulet@ev0ke.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#define _GNU_SOURCE
#include <glib.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/poll.h>
#include <signal.h>
#include <unistd.h>
#include "key.h"
#include "levels.h"
#include "network.h"
#include "pidwait.h"
#include "printtext.h"
#include "irssi-otr.h"
#include "otr-formats.h"
/*
* Status of key generation.
*/
enum key_gen_status {
KEY_GEN_IDLE = 0,
KEY_GEN_RUNNING = 1,
KEY_GEN_FINISHED = 2,
KEY_GEN_ERROR = 3,
};
/*
* Data of the state of key generation.
*/
struct key_gen_data {
struct otr_user_state *ustate;
char *account_name;
char *key_file_path;
enum key_gen_status status;
gcry_error_t gcry_error;
};
/*
* Event from the key generation process.
*/
struct key_gen_event {
enum key_gen_status status;
gcry_error_t error;
};
/*
* Key generation process.
*/
struct key_gen_worker {
int tag;
GIOChannel *pipes[2];
};
/*
* Key generation data for the thread in charge of creating the key.
*/
static struct key_gen_data key_gen_state = {
.status = KEY_GEN_IDLE,
.gcry_error = GPG_ERR_NO_ERROR,
};
/*
* Build file path concatenate to the irssi config dir.
*/
static char *file_path_build(const char *path)
{
g_return_val_if_fail(path != NULL, NULL);
/* Either NULL or the filename is returned here which is valid. */
return g_strdup_printf("%s/%s", get_irssi_dir(), path);
}
/*
* Emit a key generation status event.
*/
static void emit_event(GIOChannel *pipe, enum key_gen_status status, gcry_error_t error)
{
struct key_gen_event event;
g_return_if_fail(pipe != NULL);
event.status = status;
event.error = error;
g_io_channel_write_block(pipe, &event, sizeof(event));
}
/*
* Reset key generation state and status is IDLE.
*/
static void reset_key_gen_state(void)
{
/* Safety. */
g_free(key_gen_state.key_file_path);
g_free(key_gen_state.account_name);
/* Nullify everything. */
memset(&key_gen_state, 0, sizeof(key_gen_state));
key_gen_state.status = KEY_GEN_IDLE;
key_gen_state.gcry_error = GPG_ERR_NO_ERROR;
}
/*
* Read status event from key generation worker.
*/
static void read_key_gen_status(struct key_gen_worker *worker, GIOChannel *pipe)
{
struct key_gen_event event;
gcry_error_t err;
g_return_if_fail(worker != NULL);
fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
if (g_io_channel_read_block(pipe, &event, sizeof(event)) == -1) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
TXT_OTR_KEYGEN_FAILED,
key_gen_state.account_name,
g_strerror(errno));
return;
}
key_gen_state.status = event.status;
key_gen_state.gcry_error = event.error;
if (event.status == KEY_GEN_FINISHED || event.status == KEY_GEN_ERROR) {
/* Worker is done. */
g_source_remove(worker->tag);
g_io_channel_shutdown(worker->pipes[0], TRUE, NULL);
g_io_channel_unref(worker->pipes[0]);
g_io_channel_shutdown(worker->pipes[1], TRUE, NULL);
g_io_channel_unref(worker->pipes[1]);
g_free(worker);
if (event.status == KEY_GEN_ERROR) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
TXT_OTR_KEYGEN_FAILED,
key_gen_state.account_name,
gcry_strerror(key_gen_state.gcry_error));
reset_key_gen_state();
return;
}
err = otrl_privkey_read(key_gen_state.ustate->otr_state, key_gen_state.key_file_path);
if (err != GPG_ERR_NO_ERROR) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
TXT_OTR_KEYGEN_FAILED,
key_gen_state.account_name,
gcry_strerror(key_gen_state.gcry_error));
} else {
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
TXT_OTR_KEYGEN_COMPLETED,
key_gen_state.account_name);
}
reset_key_gen_state();
}
}
/*
* Run key generation in a seperate process (takes ages). The other process
* will rewrite the key file, we shouldn't change anything till it's done and
* we've reloaded the keys.
*/
void key_gen_run(struct otr_user_state *ustate, const char *account_name)
{
struct key_gen_worker *worker;
int fd[2];
gcry_error_t err;
pid_t pid;
g_return_if_fail(ustate != NULL);
g_return_if_fail(account_name != NULL);
if (key_gen_state.status != KEY_GEN_IDLE) {
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_OTR_KEYGEN_RUNNING, key_gen_state.account_name);
return;
}
/* Make sure the pointer does not go away during the proess. */
key_gen_state.account_name = strdup(account_name);
key_gen_state.ustate = ustate;
/* Creating key file path. */
key_gen_state.key_file_path = file_path_build(OTR_KEYFILE);
if (key_gen_state.key_file_path == NULL) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
TXT_OTR_KEYGEN_FAILED,
key_gen_state.account_name,
g_strerror(errno));
reset_key_gen_state();
return;
}
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_OTR_KEYGEN_STARTED, key_gen_state.account_name);
if (pipe(fd) != 0) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
TXT_OTR_KEYGEN_FAILED,
key_gen_state.account_name,
g_strerror(errno));
reset_key_gen_state();
return;
}
worker = g_new0(struct key_gen_worker, 1);
if (worker == NULL) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
TXT_OTR_KEYGEN_FAILED,
key_gen_state.account_name,
g_strerror(errno));
reset_key_gen_state();
return;
}
worker->pipes[0] = g_io_channel_new(fd[0]);
worker->pipes[1] = g_io_channel_new(fd[1]);
pid = fork();
if (pid > 0) {
/* Parent process */
pidwait_add(pid);
worker->tag = g_input_add(worker->pipes[0], G_INPUT_READ, (GInputFunction)read_key_gen_status, worker);
return;
}
if (pid != 0) {
/* error */
g_warning("Key generation failed: %s", g_strerror(errno));
g_source_remove(worker->tag);
g_io_channel_shutdown(worker->pipes[0], TRUE, NULL);
g_io_channel_unref(worker->pipes[0]);
g_io_channel_shutdown(worker->pipes[1], TRUE, NULL);
g_io_channel_unref(worker->pipes[1]);
g_free(worker);
return;
}
/* Child process */
key_gen_state.status = KEY_GEN_RUNNING;
emit_event(worker->pipes[1], KEY_GEN_RUNNING, GPG_ERR_NO_ERROR);
err = otrl_privkey_generate(key_gen_state.ustate->otr_state, key_gen_state.key_file_path, key_gen_state.account_name, OTR_PROTOCOL_ID);
if (err != GPG_ERR_NO_ERROR) {
emit_event(worker->pipes[1], KEY_GEN_ERROR, err);
_exit(99);
return;
}
emit_event(worker->pipes[1], KEY_GEN_FINISHED, GPG_ERR_NO_ERROR);
_exit(99);
}
/*
* Write fingerprints to file.
*/
void key_write_fingerprints(struct otr_user_state *ustate)
{
gcry_error_t err;
char *filename;
g_return_if_fail(ustate != NULL);
filename = file_path_build(OTR_FINGERPRINTS_FILE);
g_return_if_fail(filename != NULL);
err = otrl_privkey_write_fingerprints(ustate->otr_state, filename);
if (err == GPG_ERR_NO_ERROR) {
IRSSI_OTR_DEBUG("Fingerprints saved to %9%s%9", filename);
} else {
IRSSI_OTR_DEBUG("Error writing fingerprints: %d (%d)",
gcry_strerror(err), gcry_strsource(err));
}
g_free(filename);
}
/*
* Write instance tags to file.
*/
void key_write_instags(struct otr_user_state *ustate)
{
gcry_error_t err;
char *filename;
g_return_if_fail(ustate != NULL);
filename = file_path_build(OTR_INSTAG_FILE);
g_return_if_fail(filename != NULL);
err = otrl_instag_write(ustate->otr_state, filename);
if (err == GPG_ERR_NO_ERROR) {
IRSSI_OTR_DEBUG("Instance tags saved in %9%s%9", filename);
} else {
IRSSI_OTR_DEBUG("Error saving instance tags: %d (%d)",
gcry_strerror(err), gcry_strsource(err));
}
g_free(filename);
}
/*
* Load private keys.
*/
void key_load(struct otr_user_state *ustate)
{
int ret;
gcry_error_t err;
char *filename;
g_return_if_fail(ustate != NULL);
filename = file_path_build(OTR_KEYFILE);
g_return_if_fail(filename != NULL);
ret = access(filename, F_OK);
if (ret < 0) {
IRSSI_OTR_DEBUG("No private keys found in %9%s%9", filename);
g_free(filename);
return;
}
err = otrl_privkey_read(ustate->otr_state, filename);
if (err == GPG_ERR_NO_ERROR) {
IRSSI_OTR_DEBUG("Private keys loaded from %9%s%9", filename);
} else {
IRSSI_OTR_DEBUG("Error loading private keys: %d (%d)",
gcry_strerror(err), gcry_strsource(err));
}
g_free(filename);
}
/*
* Load fingerprints.
*/
void key_load_fingerprints(struct otr_user_state *ustate)
{
int ret;
gcry_error_t err;
char *filename;
g_return_if_fail(ustate != NULL);
filename = file_path_build(OTR_FINGERPRINTS_FILE);
g_return_if_fail(filename != NULL);
ret = access(filename, F_OK);
if (ret < 0) {
IRSSI_OTR_DEBUG("No fingerprints found in %9%s%9", filename);
g_free(filename);
return;
}
err = otrl_privkey_read_fingerprints(ustate->otr_state, filename, NULL,
NULL);
if (err == GPG_ERR_NO_ERROR) {
IRSSI_OTR_DEBUG("Fingerprints loaded from %9%s%9", filename);
} else {
IRSSI_OTR_DEBUG("Error loading fingerprints: %d (%d)",
gcry_strerror(err), gcry_strsource(err));
}
g_free(filename);
}

35
src/otr/key.h Normal file
View File

@ -0,0 +1,35 @@
/*
* Off-the-Record Messaging (OTR) modules for IRC
*
* Copyright (C) 2012 - David Goulet <dgoulet@ev0ke.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#ifndef IRSSI_OTR_KEY_H
#define IRSSI_OTR_KEY_H
#include "common.h"
#include "servers.h"
#include "otr.h"
void key_gen_run(struct otr_user_state *ustate, const char *account_name);
void key_load(struct otr_user_state *ustate);
void key_load_fingerprints(struct otr_user_state *ustate);
void key_write_fingerprints(struct otr_user_state *ustate);
void key_write_instags(struct otr_user_state *ustate);
#endif /* IRSSI_OTR_KEY_H */

261
src/otr/module.c Normal file
View File

@ -0,0 +1,261 @@
/*
* Off-the-Record Messaging (OTR) module for the irssi IRC client
*
* Copyright (C) 2008 Uli Meis <a.sporto+bee@gmail.com>
* 2012 David Goulet <dgoulet@ev0ke.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#define _GNU_SOURCE
#include <glib.h>
#include "module.h"
#include "signals.h"
#include "queries.h"
#include "commands.h"
#include "irc.h"
#include "irc-servers.h"
#include "irc-queries.h"
#include "irc-commands.h"
#include "key.h"
#include "otr.h"
#include "otr-formats.h"
#include "otr-fe.h"
#include "misc.h"
/*
* Global state for the user. Init when the module loads.
*/
struct otr_user_state *user_state_global;
/*
* Pipes all outgoing private messages through OTR
*/
static void sig_server_sendmsg(SERVER_REC *server, const char *target,
const char *msg, void *target_type_p)
{
char *otrmsg = NULL;
if (GPOINTER_TO_INT(target_type_p) != SEND_TARGET_NICK) {
otrl_message_free(otrmsg);
return;
}
/* Critical section. On error, message MUST NOT be sent */
if (otr_send(server, msg, target, &otrmsg)) {
signal_stop();
otrl_message_free(otrmsg);
return;
}
if (otrmsg == NULL) {
/* Send original message */
signal_continue(4, server, target, msg, target_type_p);
} else {
/* Send encrypted message */
signal_continue(4, server, target, otrmsg, target_type_p);
}
otrl_message_free(otrmsg);
}
/*
* Pipes all incoming private messages through OTR
*/
void sig_message_private(SERVER_REC *server, const char *msg, const char *nick, const char *address)
{
char *new_msg = NULL;
if (otr_receive(server, msg, nick, &new_msg)) {
signal_stop();
otrl_message_free(new_msg);
return;
}
if (new_msg == NULL) {
/* This message was not OTR */
signal_continue(4, server, msg, nick, address);
} else {
/*
* Check for /me IRC marker and if so, handle it so the user does not
* receive a message beginning with /me but rather let irssi handle it
* as a IRC action.
*/
if (strncmp(new_msg, OTR_IRC_MARKER_ME, OTR_IRC_MARKER_ME_LEN) == 0) {
signal_stop();
signal_emit("message irc action", 5, server, new_msg + OTR_IRC_MARKER_ME_LEN, nick, address, nick);
} else {
/* OTR received message */
signal_continue(4, server, new_msg, nick, address);
}
}
otrl_message_free(new_msg);
}
/*
* Finish an OTR conversation when its query is closed.
*/
static void sig_query_destroyed(QUERY_REC *query)
{
if (query && query->server && query->server->connrec) {
otr_finish(query->server, query->name);
}
}
/*
* Handle /me IRC command.
*/
static void cmd_me(const char *data, IRC_SERVER_REC *server,
WI_ITEM_REC *item)
{
const char *target;
char *msg, *otrmsg = NULL;
QUERY_REC *query;
query = QUERY(item);
if (query == NULL || query->server == NULL) {
return;
}
CMD_IRC_SERVER(server);
if (!IS_IRC_QUERY(query)) {
return;
}
if (server == NULL || !server->connected) {
cmd_return_error(CMDERR_NOT_CONNECTED);
}
target = window_item_get_target(item);
msg = g_strdup_printf(OTR_IRC_MARKER_ME "%s", data);
g_return_if_fail(msg != NULL);
/* Critical section. On error, message MUST NOT be sent */
otr_send(query->server, msg, target, &otrmsg);
g_free(msg);
if (otrmsg == NULL) {
return;
}
signal_stop();
if (otrmsg) {
/* Send encrypted message */
otr_send_message(SERVER(server), target, otrmsg);
otrl_message_free(otrmsg);
}
signal_emit("message irc own_action", 3, server, data, item->visible_name);
}
/*
* Optionally finish conversations on /quit. We're already doing this on unload
* but the quit handler terminates irc connections before unloading.
*/
static void cmd_quit(const char *data, void *server, WI_ITEM_REC *item)
{
otr_finishall(user_state_global);
}
/*
* Create otr module directory if none exists.
*/
static void create_module_dir(void)
{
char *dir_path = NULL;
struct stat statbuf;
/* Create ~/.irssi/otr directory. */
dir_path = g_strdup_printf("%s/%s", get_irssi_dir(), OTR_DIR);
g_return_if_fail(dir_path != NULL);
if (stat(dir_path, &statbuf) != 0) {
if (g_mkdir_with_parents(dir_path, 0700) != 0)
g_warning("Unable to create OTR directory path.");
} else if (!S_ISDIR(statbuf.st_mode)) {
g_warning("%s is not a directory.", dir_path);
g_warning("You should remove it with command: rm %s", dir_path);
}
g_free(dir_path);
}
void otr_send_message(SERVER_REC *server, const char *recipient, const char *msg)
{
/*
* Apparently, there are cases where the server record is NULL which has
* been reported with the irssi xmpp plugin. In that case, just return an
* do nothing.
*/
g_return_if_fail(server != NULL);
server->send_message(server, recipient, msg, GPOINTER_TO_INT(SEND_TARGET_NICK));
}
/*
* irssi init()
*/
void otr_core_init(void)
{
module_register("otr", "core");
create_module_dir();
otr_lib_init();
user_state_global = otr_init_user_state();
g_return_if_fail(user_state_global != NULL);
signal_add_first("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
signal_add("query destroyed", (SIGNAL_FUNC) sig_query_destroyed);
command_bind_first("quit", NULL, (SIGNAL_FUNC) cmd_quit);
command_bind_irc_first("me", NULL, (SIGNAL_FUNC) cmd_me);
otr_fe_init();
}
/*
* irssi deinit()
*/
void otr_core_deinit(void)
{
signal_remove("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
signal_remove("query destroyed", (SIGNAL_FUNC) sig_query_destroyed);
otr_fe_deinit();
command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
command_unbind("me", (SIGNAL_FUNC) cmd_me);
otr_finishall(user_state_global);
/* Remove glib timer if any. */
otr_control_timer(0, NULL);
otr_free_user_state(user_state_global);
otr_lib_uninit();
}

29
src/otr/module.h Normal file
View File

@ -0,0 +1,29 @@
/*
* Off-the-Record Messaging (OTR) module for the irssi IRC client
*
* Copyright (C) 2012 - David Goulet <dgoulet@ev0ke.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#ifndef IRSSI_OTR_MODULE
#define IRSSI_OTR_MODULE
#include "common.h"
#include "servers.h"
void sig_message_private(SERVER_REC *server, const char *msg, const char *nick, const char *address);
#endif /* IRSSI_OTR_MODULE */

344
src/otr/otr-fe.c Normal file
View File

@ -0,0 +1,344 @@
/*
* Off-the-Record Messaging (OTR) module for the irssi IRC client
*
* Copyright (C) 2008 Uli Meis <a.sporto+bee@gmail.com>
* 2012 David Goulet <dgoulet@ev0ke.net>
* 2014 Alexander Færøy <ahf@0x90.dk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#include "common.h"
#include "levels.h"
#include "printtext.h"
#include "commands.h"
#include "irc.h"
#include "irc-servers.h"
#include "irc-queries.h"
#include "statusbar-item.h"
#include "otr.h"
#include "otr-formats.h"
#include "key.h"
static void cmd_otr(const char *data, SERVER_REC *server, void *item)
{
if (*data == '\0')
data = "info"; // FIXME(ahf): Is this really what we want as default?
command_runsub("otr", data, server, item);
// We always redraw the OTR statusbar, just in case.
statusbar_items_redraw("otr");
}
static void cmd_otr_debug(const char *data)
{
otr_debug_toggle();
if (otr_debug_get())
printtext(NULL, NULL, MSGLEVEL_CRAP, "OTR debugging enabled");
else
printtext(NULL, NULL, MSGLEVEL_CRAP, "OTR debugging disabled");
}
static void cmd_otr_init(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
QUERY_REC *query;
char *target;
ConnContext *ctx;
g_return_if_fail(server != NULL);
if (!server->connected)
cmd_return_error(CMDERR_NOT_CONNECTED);
if (!IS_QUERY(item))
cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
query = QUERY(item);
target = query->name;
ctx = otr_find_context(server, target, FALSE);
if (ctx && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
printformat(server, target, MSGLEVEL_CRAP, TXT_OTR_SESSION_ALREADY_SECURED, ctx->accountname);
return;
}
printformat(server, target, MSGLEVEL_CRAP, TXT_OTR_SESSION_INITIATING);
/*
* Irssi does not handle well the HTML tag in the default OTR query message
* so just send the OTR tag instead. Contact me for a better fix! :)
*/
otr_send_message(server, target, "?OTRv23?");
}
static void cmd_otr_finish(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
QUERY_REC *query;
char *target;
g_return_if_fail(server != NULL);
if (!server->connected)
cmd_return_error(CMDERR_NOT_CONNECTED);
if (!IS_QUERY(item))
cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
query = QUERY(item);
target = query->name;
otr_finish(server, target);
}
static void cmd_otr_trust(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
QUERY_REC *query;
char *target;
char *fingerprint, *human_fingerprint;
void *free_arg;
g_return_if_fail(server != NULL);
query = QUERY(item);
target = query ? query->name : NULL;
if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST, &fingerprint))
return;
// We fallback to target if fingerprint isn't specified.
if (*fingerprint == '\0' && target == NULL)
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
human_fingerprint = g_ascii_strup(fingerprint, -1);
otr_trust(server, target, human_fingerprint, user_state_global);
g_free(human_fingerprint);
cmd_params_free(free_arg);
}
static void cmd_otr_distrust(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
QUERY_REC *query;
char *target;
char *fingerprint, *human_fingerprint;
void *free_arg;
g_return_if_fail(server != NULL);
query = QUERY(item);
target = query ? query->name : NULL;
if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST, &fingerprint))
return;
// We fallback to target if fingerprint isn't specified.
if (*fingerprint == '\0' && target == NULL)
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
human_fingerprint = g_ascii_strup(fingerprint, -1);
otr_distrust(server, target, human_fingerprint, user_state_global);
g_free(human_fingerprint);
cmd_params_free(free_arg);
}
static void cmd_otr_forget(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
QUERY_REC *query;
char *target;
char *fingerprint, *human_fingerprint;
void *free_arg;
g_return_if_fail(server != NULL);
if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST, &fingerprint))
return;
query = QUERY(item);
target = query ? query->name : NULL;
// We fallback to target if fingerprint isn't specified.
if (*fingerprint == '\0' && target == NULL)
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
human_fingerprint = g_ascii_strup(fingerprint, -1);
otr_forget(server, target, human_fingerprint, user_state_global);
g_free(human_fingerprint);
cmd_params_free(free_arg);
}
static void cmd_otr_authabort(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
QUERY_REC *query;
char *target;
query = QUERY(item);
target = query ? query->name : NULL;
if (server == NULL || target == NULL)
cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
otr_auth_abort(server, target);
}
static void cmd_otr_auth(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
QUERY_REC *query;
char *target;
char *secret;
void *free_arg;
if (!cmd_get_params(data, &free_arg, 1, &secret))
return;
query = QUERY(item);
target = query ? query->name : NULL;
if (server == NULL || target == NULL || *secret == '\0')
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
if (*secret == '\0')
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
otr_auth(server, target, NULL, secret);
cmd_params_free(free_arg);
}
static void cmd_otr_authq(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
QUERY_REC *query;
char *target;
char *question, *secret;
void *free_arg;
if (!cmd_get_params(data, &free_arg, 2, &question, &secret))
return;
query = QUERY(item);
target = query ? query->name : NULL;
if (server == NULL || target == NULL || *question == '\0' || *secret == '\0')
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
otr_auth(server, target, question, secret);
cmd_params_free(free_arg);
}
static void cmd_otr_genkey(const char *data)
{
char *account_name;
void *free_arg;
if (!cmd_get_params(data, &free_arg, 1, &account_name))
return;
if (*account_name == '\0')
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
key_gen_run(user_state_global, account_name);
cmd_params_free(free_arg);
}
static void cmd_otr_contexts(const char *data)
{
otr_contexts(user_state_global);
}
static void cmd_otr_info(const char *data)
{
gboolean empty = TRUE;
char ownfp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
OtrlPrivKey *key;
for (key = user_state_global->otr_state->privkey_root; key != NULL; key = key->next) {
otrl_privkey_fingerprint(user_state_global->otr_state, ownfp, key->accountname, OTR_PROTOCOL_ID);
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_OTR_FP_NICK, key->accountname, ownfp);
empty = FALSE;
}
if (empty)
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_OTR_KEYS_UNAVAILABLE);
}
static void statusbar_otr(struct SBAR_ITEM_REC *item, int get_size_only)
{
WI_ITEM_REC *wi_item = active_win->active;
QUERY_REC *query = QUERY(wi_item);
enum otr_status_format format = TXT_OTR_MODULE_NAME;
if (query && query->server && query->server->connrec) {
format = otr_get_status_format(query->server, query->name);
}
statusbar_item_default_handler(item, get_size_only,
format ? fe_otr_formats[format].def : "", " ", FALSE);
}
void otr_fe_init(void)
{
theme_register(fe_otr_formats);
command_bind("otr", NULL, (SIGNAL_FUNC) cmd_otr);
command_bind("otr debug", NULL, (SIGNAL_FUNC) cmd_otr_debug);
command_bind("otr init", NULL, (SIGNAL_FUNC) cmd_otr_init);
command_bind("otr finish", NULL, (SIGNAL_FUNC) cmd_otr_finish);
command_bind("otr trust", NULL, (SIGNAL_FUNC) cmd_otr_trust);
command_bind("otr distrust", NULL, (SIGNAL_FUNC) cmd_otr_distrust);
command_bind("otr forget", NULL, (SIGNAL_FUNC) cmd_otr_forget);
command_bind("otr authabort", NULL, (SIGNAL_FUNC) cmd_otr_authabort);
command_bind("otr auth", NULL, (SIGNAL_FUNC) cmd_otr_auth);
command_bind("otr authq", NULL, (SIGNAL_FUNC) cmd_otr_authq);
command_bind("otr genkey", NULL, (SIGNAL_FUNC) cmd_otr_genkey);
command_bind("otr contexts", NULL, (SIGNAL_FUNC) cmd_otr_contexts);
command_bind("otr info", NULL, (SIGNAL_FUNC) cmd_otr_info);
statusbar_item_register("otr", NULL, statusbar_otr);
statusbar_items_redraw("window");
}
void otr_fe_deinit(void)
{
theme_unregister();
command_unbind("otr", (SIGNAL_FUNC) cmd_otr);
command_unbind("otr debug", (SIGNAL_FUNC) cmd_otr_debug);
command_unbind("otr init", (SIGNAL_FUNC) cmd_otr_init);
command_unbind("otr finish", (SIGNAL_FUNC) cmd_otr_finish);
command_unbind("otr trust", (SIGNAL_FUNC) cmd_otr_trust);
command_unbind("otr distrust", (SIGNAL_FUNC) cmd_otr_distrust);
command_unbind("otr forget", (SIGNAL_FUNC) cmd_otr_forget);
command_unbind("otr authabort", (SIGNAL_FUNC) cmd_otr_authabort);
command_unbind("otr auth", (SIGNAL_FUNC) cmd_otr_auth);
command_unbind("otr authq", (SIGNAL_FUNC) cmd_otr_authq);
command_unbind("otr genkey", (SIGNAL_FUNC) cmd_otr_genkey);
command_unbind("otr contexts", (SIGNAL_FUNC) cmd_otr_contexts);
command_unbind("otr info", (SIGNAL_FUNC) cmd_otr_info);
statusbar_item_unregister("otr");
}

28
src/otr/otr-fe.h Normal file
View File

@ -0,0 +1,28 @@
/*
* Off-the-Record Messaging (OTR) module for the irssi IRC client
*
* Copyright (C) 2008 Uli Meis <a.sporto+bee@gmail.com>
* 2012 David Goulet <dgoulet@ev0ke.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#ifndef IRSSI_OTR_FE_H
#define IRSSI_OTR_FE_H
void otr_fe_init(void);
void otr_fe_deinit(void);
#endif

108
src/otr/otr-formats.c Normal file
View File

@ -0,0 +1,108 @@
/*
* Off-the-Record Messaging (OTR) modules for IRC
*
* Copyright (C) - 2012 David Goulet <dgoulet@ev0ke.net>
* 2014 Alexander Færøy <ahf@0x90.dk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#include "otr.h"
#include "otr-formats.h"
FORMAT_REC fe_otr_formats[] = {
{ MODULE_NAME, "Core", 0 },
/* Status bar format. */
{ NULL, "OTR Statusbar", 0 },
{ "otr_stb_plaintext", "{sb plaintext}", 0},
{ "otr_stb_finished", "{sb %yfinished%n}", 0},
{ "otr_stb_unknown", "{sb {hilight state unknown (BUG!)}}", 0},
{ "otr_stb_untrusted", "{sb %GOTR%n (%runverified%n)}", 0},
{ "otr_stb_trust", "{sb %GOTR%n}", 0},
/* OTR format. */
{ NULL, "OTR", 0 },
{ "otr_auth_aborted", "Authentication aborted", 0},
{ "otr_auth_initiated", "Initiated authentication", 0},
{ "otr_auth_ongoing_aborted", "Ongoing authentication aborted", 0},
{ "otr_auth_response", "Responding to authentication", 0},
{ "otr_ctx_list_header", "{hilight Contexts}", 0},
{ "otr_ctx_list_encrypted_line", "{hilight $0} - {hilight $1} (Encrypted)", 2, { 0, 0 }},
{ "otr_ctx_list_finished_line", "{hilight $0} - {hilight $1} (Finished)", 2, { 0, 0 }},
{ "otr_ctx_list_manual_line", " {hilight $0} (Manual)", 1, { 0, 0 }},
{ "otr_ctx_list_plaintext_line", "{hilight $0} - {hilight $1} (Plaintext)", 2, { 0, 0 }},
{ "otr_ctx_list_smp_line", " {hilight $0} (SMP)", 1, { 0, 0 }},
{ "otr_ctx_list_unknown_line", "{hilight $0} - {hilight $1} (Unknown)", 2, { 0, 0 }},
{ "otr_ctx_list_unused_line", "{hilight $0} - {hilight $1} (Unused)", 2, { 0, 0 }},
{ "otr_ctx_list_unverified_line", " {hilight $0} (Unverified)", 1, { 0, 0 }},
{ "otr_ctx_list_footer", "", 0},
{ "otr_ctx_missing", "{error No active OTR contexts found}", 0},
{ "otr_ctx_nick_missing", "{error Context for {hilight $0} not found}", 1, { 1 }},
{ "otr_fp_already_distrusted", "{error Already distrusting: {hilight $0}", 1, { 0 }},
{ "otr_fp_already_trusted", "{error Already trusting: {hilight $0}", 1, { 0 }},
{ "otr_fp_ctx_encrypted", "Fingerprint context is still encrypted. Finish the OTR session before forgetting a fingerprint", 0},
{ "otr_fp_distrusted", "Distrusting {hilight $0}", 1, { 0 }},
{ "otr_fp_forgotten", "Fingerprint {hilight $0} forgotten", 1, { 0 }},
{ "otr_fp_info", "OTR key fingerprint: {hilight $1} for {hilight $0}", 2, { 0, 0 }},
{ "otr_fp_missing", "{error Fingerprint {hilight $0} not found", 1, { 0 }},
{ "otr_fp_nick", "Fingerprint for {hilight $0}: {hilight $1}", 2, { 0, 0 }},
{ "otr_fp_trusted", "Trusting {hilight $0}", 1, { 0 }},
{ "otr_keygen_completed", "OTR key generation for {hilight $0} completed", 1, { 0 }},
{ "otr_keygen_failed", "OTR key generation for {hilight $0} failed: {error $1}", 2, { 0, 0 }},
{ "otr_keygen_running", "OTR key generation for {hilight $0} is still in progress", 1, { 0 }},
{ "otr_keygen_started", "OTR key generation for {hilight $0} started", 1, { 0 }},
{ "otr_keys_unavailable", "{error No OTR keys available}", 0},
{ "otr_msg_encryption_ended", "{hilight $0} has closed the connection to you", 1, { 0 }},
{ "otr_msg_encryption_error", "{error An error occured when encrypting your message}", 0},
{ "otr_msg_encryption_required", "Encryptioned is required", 0},
{ "otr_msg_error", "Error in private conversation: {error $0}", 1, { 0 }},
{ "otr_msg_general_error", "General Error: {error $0}", 1, { 0 }},
{ "otr_msg_malformed", "Malformed message from {hilight $0}", 1, { 0 }},
{ "otr_msg_not_in_private", "The encrypted message from {hilight $0} was is unreadable because you're not communicating privately", 1, { 0 }},
{ "otr_msg_reflected", "Received reflected message from {hilight $0}", 0, { 0 }},
{ "otr_msg_resent", "The last message to {hilight $0} was resent: $1", 2, { 0, 0 }},
{ "otr_msg_unencrypted", "The following message from {hilight $0} was {error not} encrypted", 1, { 0 }},
{ "otr_msg_unreadable", "Unreadable encrypted message from {hilight $0}", 1, { 0 }},
{ "otr_msg_unrecognized", "Unrecognized OTR message from {hilight $0}", 1, { 0 }},
{ "otr_session_already_finished", "Nothing to do", 0},
{ "otr_session_already_secured", "Secure session with {hilight $0} already established", 1, { 0 }},
{ "otr_session_finished", "{hilight $0} has finished the OTR session. Use /otr init to restart or /otr finish to finish.", 1, { 0 }},
{ "otr_session_finishing", "Finished conversation with {hilight $0}", 1, { 0 }},
{ "otr_session_initiating", "Initiating OTR session ...", 0},
{ "otr_session_insecure", "Session insecured", 0},
{ "otr_session_missing", "{error No OTR session available}", 0},
{ "otr_session_secure", "Session secured", 0},
{ "otr_session_unauthenticated_warning", "Your peer is not authenticated", 0},
{ "otr_smp_answer_footer", "Use /otr auth <answer> to complete", 0},
{ "otr_smp_answer_header", "{hilight $0} wants to authenticate and asked:", 1, { 0 }},
{ "otr_smp_answer_question", "Question: {hilight $0}", 1, { 0 }},
{ "otr_smp_failure", "Authentication with {hilight $0} failed", 1, { 0 }},
{ "otr_smp_in_progress", "{hilight $0} replied to your auth request", 1, { 0 }},
{ "otr_smp_secret_question", "{hilight $0} wants to authenticate. Use /otr auth <secret> to complete", 1, { 0 }},
{ "otr_smp_success", "Authentication with {hilight $0} succesful", 1, { 0 }},
/* Last element. */
{ NULL, NULL, 0 }
};

112
src/otr/otr-formats.h Normal file
View File

@ -0,0 +1,112 @@
/*
* Off-the-Record Messaging (OTR) modules for IRC
*
* Copyright (C) - 2012 David Goulet <dgoulet@ev0ke.net>
* 2014 Alexander Færøy <ahf@0x90.dk>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#ifndef IRSSI_OTR_FORMATS_H
#define IRSSI_OTR_FORMATS_H
#include "formats.h"
/*
* Must be in sync with the fe_otr_formats array.
*/
enum otr_status_format {
TXT_OTR_MODULE_NAME,
/* Status bar format. */
TXT_OTR_FILL_1,
TXT_OTR_STB_PLAINTEXT,
TXT_OTR_STB_FINISHED,
TXT_OTR_STB_UNKNOWN,
TXT_OTR_STB_UNTRUSTED,
TXT_OTR_STB_TRUST,
/* OTR format. */
TXT_OTR_FILL_2,
TXT_OTR_AUTH_ABORTED,
TXT_OTR_AUTH_INITIATED,
TXT_OTR_AUTH_ONGOING_ABORTED,
TXT_OTR_AUTH_RESPONSE,
TXT_OTR_CTX_LIST_HEADER,
TXT_OTR_CTX_LIST_ENCRYPTED_LINE,
TXT_OTR_CTX_LIST_FINISHED_LINE,
TXT_OTR_CTX_LIST_MANUAL_LINE,
TXT_OTR_CTX_LIST_PLAINTEXT_LINE,
TXT_OTR_CTX_LIST_SMP_LINE,
TXT_OTR_CTX_LIST_UNKNOWN_LINE,
TXT_OTR_CTX_LIST_UNUSED_LINE,
TXT_OTR_CTX_LIST_UNVERIFIED_LINE,
TXT_OTR_CTX_LIST_FOOTER,
TXT_OTR_CTX_MISSING,
TXT_OTR_CTX_NICK_MISSING,
TXT_OTR_FP_ALREADY_DISTRUSED,
TXT_OTR_FP_ALREADY_TRUSTED,
TXT_OTR_FP_CTX_ENCRYPTED,
TXT_OTR_FP_DISTRUSTED,
TXT_OTR_FP_FORGOTTEN,
TXT_OTR_FP_INFO,
TXT_OTR_FP_MISSING,
TXT_OTR_FP_NICK,
TXT_OTR_FP_TRUSTED,
TXT_OTR_KEYGEN_COMPLETED,
TXT_OTR_KEYGEN_FAILED,
TXT_OTR_KEYGEN_RUNNING,
TXT_OTR_KEYGEN_STARTED,
TXT_OTR_KEYS_UNAVAILABLE,
TXT_OTR_MSG_ENCRYPTION_ENDED,
TXT_OTR_MSG_ENCRYPTION_ERROR,
TXT_OTR_MSG_ENCRYPTION_REQUIRED,
TXT_OTR_MSG_ERROR,
TXT_OTR_MSG_GENERAL_ERROR,
TXT_OTR_MSG_MALFORMED,
TXT_OTR_MSG_NOT_IN_PRIVATE,
TXT_OTR_MSG_REFLECTED,
TXT_OTR_MSG_RESENT,
TXT_OTR_MSG_UNENCRYPTED,
TXT_OTR_MSG_UNREADABLE,
TXT_OTR_MSG_UNRECOGNIZED,
TXT_OTR_SESSION_ALREADY_FINISHED,
TXT_OTR_SESSION_ALREADY_SECURED,
TXT_OTR_SESSION_FINISHED,
TXT_OTR_SESSION_FINISHING,
TXT_OTR_SESSION_INITIATING,
TXT_OTR_SESSION_INSECURE,
TXT_OTR_SESSION_MISSING,
TXT_OTR_SESSION_SECURE,
TXT_OTR_SESSION_UNAUTHENTICATED_WARNING,
TXT_OTR_SMP_ANSWER_FOOTER,
TXT_OTR_SMP_ANSWER_HEADER,
TXT_OTR_SMP_ANSWER_QUESTION,
TXT_OTR_SMP_FAILURE,
TXT_OTR_SMP_IN_PROGRESS,
TXT_OTR_SMP_SECRET_QUESTION,
TXT_OTR_SMP_SUCCESS
};
extern FORMAT_REC fe_otr_formats[];
#endif /* IRSSI_OTR_FORMATS_H */

362
src/otr/otr-ops.c Normal file
View File

@ -0,0 +1,362 @@
/*
* Off-the-Record Messaging (OTR) modules for IRC
* Copyright (C) 2008 Uli Meis <a.sporto+bee@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#include "common.h"
#include "signals.h"
#include "levels.h"
#include "printtext.h"
#include "fe-windows.h"
#include "key.h"
#include "module.h"
#include "otr-formats.h"
#include "irssi-otr.h"
static OtrlPolicy OTR_DEFAULT_POLICY = OTRL_POLICY_MANUAL | OTRL_POLICY_WHITESPACE_START_AKE;
/*
* Return default policy for now.
*/
static OtrlPolicy ops_policy(void *opdata, ConnContext *context)
{
return OTR_DEFAULT_POLICY;
}
/*
* Request for key generation.
*
* The lib actually expects us to be finished before the call returns. Since
* this can take more than an hour on some systems there isn't even a point in
* trying...
*/
static void ops_create_privkey(void *opdata, const char *accountname,
const char *protocol)
{
key_gen_run(user_state_global, accountname);
}
/*
* Inject OTR message.
*/
static void ops_inject_msg(void *opdata, const char *accountname,
const char *protocol, const char *recipient, const char *message)
{
SERVER_REC *server = opdata;
IRSSI_OTR_DEBUG("Inject msg:\n[%s]", message);
otr_send_message(server, recipient, message);
}
/*
* Gone secure.
*/
static void ops_secure(void *opdata, ConnContext *context)
{
char ownfp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
char peerfp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
SERVER_REC *server = opdata;
struct otr_peer_context *opc;
g_return_if_fail(context != NULL);
/* This should *really* not happened */
g_return_if_fail(context->msgstate == OTRL_MSGSTATE_ENCRYPTED);
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_SESSION_SECURE);
otr_status_change(server, context->username, OTR_STATUS_GONE_SECURE);
opc = context->app_data;
opc->active_fingerprint = context->active_fingerprint;
if (otrl_context_is_fingerprint_trusted(context->active_fingerprint)) {
/* Secure and trusted */
return;
}
/* Not authenticated. Let's print out the fingerprints for comparison. */
otrl_privkey_hash_to_human(peerfp, context->active_fingerprint->fingerprint);
otrl_privkey_fingerprint(user_state_global->otr_state, ownfp, context->accountname, OTR_PROTOCOL_ID);
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_SESSION_UNAUTHENTICATED_WARNING);
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_FP_INFO, server->nick, ownfp);
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_FP_INFO, context->username, peerfp);
}
/*
* Gone insecure.
*/
static void ops_insecure(void *opdata, ConnContext *context)
{
SERVER_REC *server = opdata;
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_SESSION_INSECURE);
otr_status_change(server, context->username, OTR_STATUS_GONE_INSECURE);
}
/*
* Really critical with IRC. Unfortunately, we can't tell our peer which size
* to use.
*/
static int ops_max_msg(void *opdata, ConnContext *context)
{
return OTR_MAX_MSG_SIZE;
}
static void ops_handle_msg_event(void *opdata, OtrlMessageEvent msg_event, ConnContext *context, const char *message, gcry_error_t err)
{
SERVER_REC *server = opdata;
char *username = context->username;
switch (msg_event) {
case OTRL_MSGEVENT_NONE:
break;
case OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ENCRYPTION_REQUIRED);
break;
case OTRL_MSGEVENT_ENCRYPTION_ERROR:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ENCRYPTION_ERROR);
break;
case OTRL_MSGEVENT_CONNECTION_ENDED:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ENCRYPTION_ENDED, username);
break;
case OTRL_MSGEVENT_SETUP_ERROR:
if (!err) {
err = GPG_ERR_INV_VALUE;
}
switch (err) {
case GPG_ERR_INV_VALUE:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_MALFORMED, username);
break;
default:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ERROR, gcry_strerror(err));
break;
}
break;
case OTRL_MSGEVENT_MSG_REFLECTED:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_REFLECTED, username);
break;
case OTRL_MSGEVENT_MSG_RESENT:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_RESENT, username, message);
break;
case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_NOT_IN_PRIVATE, username);
break;
case OTRL_MSGEVENT_RCVDMSG_UNREADABLE:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_UNREADABLE, username);
break;
case OTRL_MSGEVENT_RCVDMSG_MALFORMED:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_MALFORMED, username);
break;
case OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD:
IRSSI_OTR_DEBUG("Heartbeat received from %s.", username);
break;
case OTRL_MSGEVENT_LOG_HEARTBEAT_SENT:
IRSSI_OTR_DEBUG("Heartbeat sent to %s.", username);
break;
case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ERROR, message);
break;
case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_UNENCRYPTED, username);
/*
* This is a hack I found to send the message in a private window of
* the username without creating an infinite loop since the 'message
* private' signal is hijacked in this module. If someone is able to
* clean this up with a more elegant solution, by all means PLEASE
* submit a patch or email me a better way.
*/
signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
signal_emit("message private", 4, server, message, username, server->connrec->address);
signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
break;
case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED:
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_UNRECOGNIZED, username);
break;
case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
IRSSI_OTR_DEBUG("%s has sent a message for a different instance.", username);
break;
}
}
/*
* A context changed.
*/
static void ops_up_ctx_list(void *opdata)
{
otr_status_change(opdata, NULL, OTR_STATUS_CTX_UPDATE);
}
/*
* Save fingerprint changes.
*/
static void ops_write_fingerprints(void *data)
{
key_write_fingerprints(user_state_global);
}
static int ops_is_logged_in(void *opdata, const char *accountname, const char *protocol, const char *recipient)
{
int ret;
SERVER_REC *server = opdata;
/* Logged in? */
ret = server != NULL;
IRSSI_OTR_DEBUG("User %s %s logged in", accountname, ret ? "" : "not");
return ret;
}
static void ops_create_instag(void *opdata, const char *accountname,
const char *protocol)
{
otrl_instag_generate(user_state_global->otr_state, "/dev/null", accountname, protocol);
key_write_instags(user_state_global);
}
static void ops_smp_event(void *opdata, OtrlSMPEvent smp_event,
ConnContext *context, unsigned short progress_percent, char *question)
{
SERVER_REC *server = opdata;
const char *from = context->username;
struct otr_peer_context *opc = context->app_data;
/*
* Without a peer context, we can't update the status bar. Code flow error
* if none is found. This context is created automatically by an otrl_*
* call or if non existent when returned from
* otrl_message_sending/receiving.
*/
g_return_if_fail(opc != NULL);
opc->smp_event = smp_event;
switch (smp_event) {
case OTRL_SMPEVENT_ASK_FOR_SECRET:
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_SECRET_QUESTION, from);
opc->ask_secret = 1;
otr_status_change(server, from, OTR_STATUS_SMP_INCOMING);
break;
case OTRL_SMPEVENT_ASK_FOR_ANSWER:
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_ANSWER_HEADER, from);
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_ANSWER_QUESTION, question);
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_ANSWER_FOOTER);
opc->ask_secret = 1;
otr_status_change(server, from, OTR_STATUS_SMP_INCOMING);
break;
case OTRL_SMPEVENT_IN_PROGRESS:
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_IN_PROGRESS, from);
otr_status_change(server, from, OTR_STATUS_SMP_FINALIZE);
break;
case OTRL_SMPEVENT_SUCCESS:
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_SUCCESS, from);
otr_status_change(server, from, OTR_STATUS_SMP_SUCCESS);
break;
case OTRL_SMPEVENT_ABORT:
otr_auth_abort(server, context->username);
otr_status_change(server, from, OTR_STATUS_SMP_ABORTED);
break;
case OTRL_SMPEVENT_FAILURE:
case OTRL_SMPEVENT_CHEATED:
case OTRL_SMPEVENT_ERROR:
printformat(server, from, MSGLEVEL_CLIENTERROR, TXT_OTR_SMP_FAILURE, from);
otr_status_change(server, from, OTR_STATUS_SMP_FAILED);
break;
default:
g_warning("Received unknown SMP event: %d", smp_event);
break;
}
}
/*
* timer_control callback.
*/
static void ops_timer_control(void *opdata, unsigned int interval)
{
otr_control_timer(interval, opdata);
}
/*
* Handle otr error message.
*/
static const char *ops_otr_error_message(void *opdata, ConnContext *context,
OtrlErrorCode code)
{
char *msg = NULL;
switch (code) {
case OTRL_ERRCODE_NONE:
break;
case OTRL_ERRCODE_ENCRYPTION_ERROR:
msg = strdup("Error occurred encrypting message.");
break;
case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE:
if (context) {
msg = strdup("You sent encrypted data which was unexpected");
}
break;
case OTRL_ERRCODE_MSG_UNREADABLE:
msg = strdup("You transmitted an unreadable encrypted message");
break;
case OTRL_ERRCODE_MSG_MALFORMED:
msg = strdup("You transmitted a malformed data message.");
break;
}
return msg;
}
/*
* Free otr error message callback.
*/
static void ops_otr_error_message_free(void *opdata, const char *err_msg)
{
g_free_not_null((char *)err_msg);
}
/*
* Assign OTR message operations.
*/
OtrlMessageAppOps otr_ops = {
ops_policy,
ops_create_privkey,
ops_is_logged_in,
ops_inject_msg,
ops_up_ctx_list,
NULL, /* new_fingerprint */
ops_write_fingerprints,
ops_secure,
ops_insecure,
NULL, /* still_secure */
ops_max_msg,
NULL, /* account_name */
NULL, /* account_name_free */
NULL, /* received_symkey */
ops_otr_error_message,
ops_otr_error_message_free,
NULL, /* resent_msg_prefix */
NULL, /* resent_msg_prefix_free */
ops_smp_event,
ops_handle_msg_event,
ops_create_instag,
NULL, /* convert_msg */
NULL, /* convert_free */
ops_timer_control,
};

941
src/otr/otr.c Normal file
View File

@ -0,0 +1,941 @@
/*
* Off-the-Record Messaging (OTR) modules for IRC
*
* Copyright (C) 2008 - Uli Meis <a.sporto+bee@gmail.com>
* 2012 - David Goulet <dgoulet@ev0ke.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#define _GNU_SOURCE
#include <glib.h>
#include <gcrypt.h>
#include <unistd.h>
#include "common.h"
#include "levels.h"
#include "signals.h"
#include "printtext.h"
#include "statusbar-item.h"
#include "irssi-otr.h"
#include "otr-formats.h"
#include "key.h"
static int otr_debug = 0;
static const char *statusbar_txt[] = {
"FINISHED",
"TRUST_MANUAL",
"TRUST_SMP",
"SMP_ABORT",
"SMP_STARTED",
"SMP_RESPONDED",
"SMP_INCOMING",
"SMP_FINALIZE",
"SMP_ABORTED",
"PEER_FINISHED",
"SMP_FAILED",
"SMP_SUCCESS",
"GONE_SECURE",
"GONE_INSECURE",
"CTX_UPDATE"
};
/* Glib timer for otr. */
static guint otr_timerid;
/*
* Load instance tags.
*/
static void instag_load(struct otr_user_state *ustate)
{
int ret;
char *filename;
gcry_error_t err;
g_return_if_fail(ustate != NULL);
/* Getting the otr instance filename path */
filename = g_strdup_printf("%s%s", get_irssi_dir(), OTR_INSTAG_FILE);
g_return_if_fail(filename != NULL);
ret = access(filename, F_OK);
if (ret < 0) {
IRSSI_OTR_DEBUG("no instance tags found at %9%s%9", filename);
g_free(filename);
return;
}
err = otrl_instag_read(ustate->otr_state, filename);
if (err == GPG_ERR_NO_ERROR)
IRSSI_OTR_DEBUG("Instance tags loaded from %9%s%9", filename);
else
IRSSI_OTR_DEBUG("Error loading instance tags: %d (%d)", gcry_strerror(err), gcry_strsource(err));
g_free(filename);
}
/*
* Free otr peer context. Callback passed to libotr.
*/
static void free_peer_context_cb(void *data)
{
g_free_not_null(data);
}
/*
* Allocate otr peer context. Callback passed to libotr.
*/
static void add_peer_context_cb(void *data, ConnContext *context)
{
struct otr_peer_context *opc;
opc = otr_create_peer_context();
if (opc == NULL) {
return;
}
opc->active_fingerprint = context->active_fingerprint;
context->app_data = opc;
context->app_data_free = free_peer_context_cb;
IRSSI_OTR_DEBUG("Peer context created for %s", context->username);
}
/*
* Find Irssi server record by network name.
*/
static SERVER_REC *find_server_by_network(const char *network)
{
GSList *tmp;
SERVER_REC *server;
g_return_val_if_fail(network != NULL, NULL);
for (tmp = servers; tmp; tmp = tmp->next) {
server = tmp->data;
if (g_ascii_strncasecmp(server->tag, network, strlen(server->tag)))
return server;
}
return NULL;
}
/*
* Check if fingerprint is in an encrypted context.
*
* Return 1 if it does, else 0.
*/
static int check_fp_encrypted_msgstate(Fingerprint *fp)
{
ConnContext *context;
g_return_val_if_fail(fp != NULL, 0);
/* Loop on all fingerprint's context(es). */
for (context = fp->context;
context != NULL && context->m_context == fp->context;
context = context->next) {
if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
context->active_fingerprint == fp) {
return 1;
}
}
/* No state is encrypted. */
return 0;
}
/*
* Timer called from the glib main loop and set up by the timer_control
* callback of libotr.
*/
static gboolean timer_fired_cb(gpointer data)
{
otrl_message_poll(user_state_global->otr_state, &otr_ops, NULL);
return TRUE;
}
void otr_control_timer(unsigned int interval, void *opdata)
{
if (otr_timerid) {
g_source_remove(otr_timerid);
otr_timerid = 0;
}
if (interval > 0) {
otr_timerid = g_timeout_add_seconds(interval, timer_fired_cb, opdata);
}
}
/*
* Is OTR debugging enabled or disabled?
*/
int otr_debug_get(void)
{
return otr_debug;
}
/*
* Toggle OTR debugging.
*/
void otr_debug_toggle(void)
{
otr_debug = !otr_debug;
}
/*
* Find context from nickname and irssi server record.
*/
ConnContext *otr_find_context(SERVER_REC *server, const char *nick, int create)
{
ConnContext *ctx = NULL;
g_return_val_if_fail(server != NULL, NULL);
g_return_val_if_fail(server->tag != NULL, NULL);
g_return_val_if_fail(nick != NULL, NULL);
ctx = otrl_context_find(user_state_global->otr_state, nick, server->tag,
OTR_PROTOCOL_ID, OTRL_INSTAG_BEST, create, NULL,
add_peer_context_cb, server);
return ctx;
}
/*
* Create otr peer context.
*/
struct otr_peer_context *otr_create_peer_context(void)
{
return g_new0(struct otr_peer_context, 1);
}
/*
* Return a newly allocated OTR user state.
*/
struct otr_user_state *otr_init_user_state(void)
{
struct otr_user_state *ous = NULL;
ous = g_new0(struct otr_user_state, 1);
if (ous == NULL) {
return ous;
}
ous->otr_state = otrl_userstate_create();
instag_load(ous);
/* Load keys and fingerprints. */
key_load(ous);
key_load_fingerprints(ous);
return ous;
}
/*
* Destroy otr user state.
*/
void otr_free_user_state(struct otr_user_state *ustate)
{
if (ustate->otr_state) {
otrl_userstate_free(ustate->otr_state);
ustate->otr_state = NULL;
}
g_free(ustate);
}
/*
* init otr lib.
*/
void otr_lib_init()
{
OTRL_INIT;
}
/*
* deinit otr lib.
*/
void otr_lib_uninit()
{
}
/*
* Hand the given message to OTR.
*
* Return 0 if the message was successfully handled or else a negative value.
*/
int otr_send(SERVER_REC *server, const char *msg, const char *to, char **otr_msg)
{
gcry_error_t err;
ConnContext *ctx = NULL;
g_return_val_if_fail(server != NULL, -1);
g_return_val_if_fail(server->tag != NULL, -1);
IRSSI_OTR_DEBUG("OTR: Sending message: %s", msg);
err = otrl_message_sending(user_state_global->otr_state, &otr_ops,
server, server->tag, OTR_PROTOCOL_ID, to, OTRL_INSTAG_BEST, msg, NULL, otr_msg,
OTRL_FRAGMENT_SEND_ALL_BUT_LAST, &ctx, add_peer_context_cb, server);
if (err) {
g_warning("OTR: Send failed: %s", gcry_strerror(err));
return -1;
}
/* Add peer context to OTR context if none exists. */
if (ctx && !ctx->app_data) {
add_peer_context_cb(server, ctx);
}
return 0;
}
/*
* List otr contexts to the main Irssi windows.
*/
void otr_contexts(struct otr_user_state *ustate)
{
char human_fp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN], *trust;
ConnContext *ctx, *c_iter;
Fingerprint *fp;
g_return_if_fail(ustate != NULL);
if (ustate->otr_state->context_root == NULL) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_OTR_CTX_MISSING);
return;
}
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_HEADER);
/* Iterate over all contextes of the user state. */
for (ctx = ustate->otr_state->context_root; ctx != NULL; ctx = ctx->next) {
OtrlMessageState best_mstate = OTRL_MSGSTATE_PLAINTEXT;
/* Skip master context. */
if (ctx != ctx->m_context)
continue;
for (fp = ctx->fingerprint_root.next; fp != NULL; fp = fp->next) {
int used = 0;
char *username, *accountname;
username = ctx->username;
accountname = ctx->accountname;
for (c_iter = ctx->m_context; c_iter && c_iter->m_context == ctx->m_context; c_iter = c_iter->next) {
/* Print account name, username and msgstate. */
if (c_iter->active_fingerprint == fp) {
used = 1;
if (c_iter->msgstate == OTRL_MSGSTATE_ENCRYPTED)
best_mstate = OTRL_MSGSTATE_ENCRYPTED;
else if (c_iter->msgstate == OTRL_MSGSTATE_FINISHED && best_mstate == OTRL_MSGSTATE_PLAINTEXT)
best_mstate = OTRL_MSGSTATE_FINISHED;
}
}
if (used) {
switch (best_mstate) {
case OTRL_MSGSTATE_ENCRYPTED:
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_ENCRYPTED_LINE, accountname, username);
break;
case OTRL_MSGSTATE_PLAINTEXT:
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_PLAINTEXT_LINE, accountname, username);
break;
case OTRL_MSGSTATE_FINISHED:
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_FINISHED_LINE, accountname, username);
break;
default:
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_UNKNOWN_LINE, accountname, username);
break;
};
} else
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_UNUSED_LINE, accountname, username);
/* Hash fingerprint to human. */
otrl_privkey_hash_to_human(human_fp, fp->fingerprint);
trust = fp->trust;
if (trust && trust[0] != '\0') {
if (strncmp(trust, "smp", 3) == 0)
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_SMP_LINE, human_fp);
else
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_MANUAL_LINE, human_fp);
} else
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_UNVERIFIED_LINE, human_fp);
}
}
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_OTR_CTX_LIST_FOOTER);
}
/*
* Finish the conversation.
*/
void otr_finish(SERVER_REC *server, const char *nick)
{
ConnContext *ctx;
g_return_if_fail(server != NULL);
g_return_if_fail(nick != NULL);
ctx = otr_find_context(server, nick, FALSE);
if (ctx == NULL) {
printformat(server, nick, MSGLEVEL_CRAP, TXT_OTR_SESSION_ALREADY_FINISHED);
return;
}
otrl_message_disconnect(user_state_global->otr_state, &otr_ops, server,
ctx->accountname, OTR_PROTOCOL_ID, nick, ctx->their_instance);
otr_status_change(server, nick, OTR_STATUS_FINISHED);
printformat(server, nick, MSGLEVEL_CRAP, TXT_OTR_SESSION_FINISHING, nick);
}
/*
* Finish all otr contexts.
*/
void otr_finishall(struct otr_user_state *ustate)
{
ConnContext *context;
SERVER_REC *server;
g_return_if_fail(ustate != NULL);
for (context = ustate->otr_state->context_root; context;
context = context->next) {
/* Only finish encrypted session. */
if (context->msgstate != OTRL_MSGSTATE_ENCRYPTED) {
continue;
}
server = find_server_by_network(context->accountname);
if (server == NULL) {
IRSSI_OTR_DEBUG("Unable to find server window for account %s", context->accountname);
continue;
}
otr_finish(server, context->username);
}
}
/*
* Trust our peer.
*/
void otr_trust(SERVER_REC *server, const char *nick, char *str_fp,
struct otr_user_state *ustate)
{
char peerfp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
struct otr_peer_context *opc;
ConnContext *ctx;
Fingerprint *fp_trust;
g_return_if_fail(ustate != NULL);
/* No human string fingerprint given. */
if (*str_fp == '\0') {
ctx = otr_find_context(server, nick, FALSE);
if (ctx == NULL) {
return;
}
opc = ctx->app_data;
/* Always NEED a peer context or else code error. */
g_return_if_fail(opc != NULL);
fp_trust = ctx->active_fingerprint;
} else {
fp_trust = otr_find_hash_fingerprint_from_human(str_fp, ustate);
}
if (fp_trust != NULL) {
otrl_privkey_hash_to_human(peerfp, fp_trust->fingerprint);
if (otrl_context_is_fingerprint_trusted(fp_trust)) {
printformat(server, nick, MSGLEVEL_CLIENTERROR, TXT_OTR_FP_ALREADY_TRUSTED, peerfp);
return;
}
/* Trust level is manual at this point. */
otrl_context_set_trust(fp_trust, "manual");
key_write_fingerprints(ustate);
otr_status_change(server, nick, OTR_STATUS_TRUST_MANUAL);
printformat(server, nick, MSGLEVEL_CLIENTCRAP, TXT_OTR_FP_TRUSTED, peerfp);
} else
printformat(server, nick, MSGLEVEL_CLIENTERROR, TXT_OTR_FP_MISSING, str_fp);
}
/*
* implements /otr authabort
*/
void otr_auth_abort(SERVER_REC *server, const char *nick)
{
ConnContext *ctx;
g_return_if_fail(server != NULL);
g_return_if_fail(nick != NULL);
ctx = otr_find_context(server, nick, FALSE);
if (ctx == NULL) {
printformat(server, nick, MSGLEVEL_CLIENTERROR, TXT_OTR_CTX_NICK_MISSING, nick);
return;
}
otrl_message_abort_smp(user_state_global->otr_state, &otr_ops, server, ctx);
otr_status_change(server, nick, OTR_STATUS_SMP_ABORT);
if (ctx->smstate->nextExpected != OTRL_SMP_EXPECT1)
printformat(server, nick, MSGLEVEL_CLIENTCRAP, TXT_OTR_AUTH_ONGOING_ABORTED);
else
printformat(server, nick, MSGLEVEL_CLIENTCRAP, TXT_OTR_AUTH_ABORTED);
}
/*
* Initiate or respond to SMP authentication.
*/
void otr_auth(SERVER_REC *server, const char *nick, const char *question,
const char *secret)
{
int ret;
size_t secret_len = 0;
ConnContext *ctx;
struct otr_peer_context *opc;
g_return_if_fail(server != NULL);
g_return_if_fail(nick != NULL);
ctx = otr_find_context(server, nick, 0);
if (ctx == NULL) {
printformat(server, nick, MSGLEVEL_CLIENTERROR, TXT_OTR_CTX_NICK_MISSING, nick);
return;
}
opc = ctx->app_data;
/* Again, code flow error. */
g_return_if_fail(opc != NULL);
if (ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED) {
printformat(server, nick, MSGLEVEL_CLIENTERROR, TXT_OTR_SESSION_MISSING);
return;
}
/* Aborting an ongoing auth */
if (ctx->smstate->nextExpected != OTRL_SMP_EXPECT1) {
otr_auth_abort(server, nick);
}
/* reset trust level */
if (ctx->active_fingerprint) {
ret = otrl_context_is_fingerprint_trusted(ctx->active_fingerprint);
if (!ret) {
otrl_context_set_trust(ctx->active_fingerprint, "");
key_write_fingerprints(user_state_global);
}
}
/* Libotr allows empty secret. */
if (secret) {
secret_len = strlen(secret);
}
if (opc->ask_secret) {
otrl_message_respond_smp(user_state_global->otr_state, &otr_ops,
server, ctx, (unsigned char *) secret, secret_len);
otr_status_change(server, nick, OTR_STATUS_SMP_RESPONDED);
printformat(server, nick, MSGLEVEL_CLIENTCRAP, TXT_OTR_AUTH_RESPONSE);
} else {
if (question != NULL)
otrl_message_initiate_smp_q(user_state_global->otr_state, &otr_ops, server, ctx, question, (unsigned char *) secret, secret_len);
else
otrl_message_initiate_smp(user_state_global->otr_state, &otr_ops, server, ctx, (unsigned char *) secret, secret_len);
otr_status_change(server, nick, OTR_STATUS_SMP_STARTED);
printformat(server, nick, MSGLEVEL_CLIENTCRAP, TXT_OTR_AUTH_INITIATED);
}
opc->ask_secret = 0;
}
/*
* For the given message we received through irssi, check if we need to queue
* it for the case where that message is part of a bigger OTR full message.
* This can happen with bitlbee for instance where OTR message are split in
* different PRIVMSG.
*
* This uses a "queue" in the peer context so it's it very important to have
* the peer context associated with the message (nickname + irssi object).
*
* Return an otr_msg_status code indicating the caller what to do with the msg.
* OTR_MSG_ERROR indicates an error probably memory related. OTR_MSG_WAIT_MORE
* tells the caller to NOT send out the message since we are waiting for more
* to complete the OTR original message. OTR_MSG_ORIGINAL tell the caller to
* simply use the original message. OTR_MSG_USE_QUEUE indicates that full_msg
* can be used containing the reconstructed message. The caller SHOULD free(3)
* this pointer after use.
*/
static enum otr_msg_status enqueue_otr_fragment(const char *msg, struct otr_peer_context *opc, char **full_msg)
{
enum otr_msg_status ret;
size_t msg_len;
g_return_val_if_fail(msg != NULL, OTR_MSG_ERROR);
g_return_val_if_fail(opc != NULL, OTR_MSG_ERROR);
/* We are going to use it quite a bit so ease our life a bit. */
msg_len = strlen(msg);
if (opc->full_msg) {
if (msg_len > (opc->msg_size - opc->msg_len)) {
char *tmp_ptr;
/* Realloc memory if there is not enough space. */
tmp_ptr = realloc(opc->full_msg, opc->msg_size + msg_len + 1);
if (tmp_ptr == NULL) {
free(opc->full_msg);
opc->full_msg = NULL;
ret = OTR_MSG_ERROR;
return ret;
}
opc->full_msg = tmp_ptr;
opc->msg_size += msg_len + 1;
}
/* Copy msg to full message since we already have a part pending. */
strncpy(opc->full_msg + opc->msg_len, msg, msg_len);
opc->msg_len += msg_len;
opc->full_msg[opc->msg_len] = '\0';
IRSSI_OTR_DEBUG("Partial OTR message added to queue: %s", msg);
/*
* Are we waiting for more? If the message ends with a ".", the
* transmission has ended else we have to wait for more.
*/
if (msg[msg_len - 1] != OTR_MSG_END_TAG) {
ret = OTR_MSG_WAIT_MORE;
return ret;
}
/*
* Dup the string with enough space for the NULL byte since we are
* about to free it before passing it to the caller.
*/
*full_msg = strndup(opc->full_msg, opc->msg_len + 1);
/* Reset everything. */
free(opc->full_msg);
opc->full_msg = NULL;
opc->msg_size = opc->msg_len = 0;
ret = OTR_MSG_USE_QUEUE;
return ret;
} else {
char *pos;
/*
* Try to find the OTR message tag at the _beginning_of the packet and
* check if this packet is not the end with the end tag of OTR "."
*/
pos = strstr(msg, OTR_MSG_BEGIN_TAG);
if (pos && (pos == msg) && msg[msg_len - 1] != OTR_MSG_END_TAG) {
/* Allocate full message buffer with an extra for NULL byte. */
opc->full_msg = g_new0(char, (msg_len * 2) + 1);
if (!opc->full_msg) {
ret = OTR_MSG_ERROR;
return ret;
}
/* Copy full message with NULL terminated byte. */
strncpy(opc->full_msg, msg, msg_len);
opc->msg_len += msg_len;
opc->msg_size += ((msg_len * 2) + 1);
opc->full_msg[opc->msg_len] = '\0';
ret = OTR_MSG_WAIT_MORE;
IRSSI_OTR_DEBUG("Partial OTR message begins the queue: %s", msg);
return ret;
}
/* Use original message. */
ret = OTR_MSG_ORIGINAL;
}
return ret;
}
/*
* Hand the given message to OTR.
*
* Returns 0 if its an OTR protocol message or else negative value.
*/
int otr_receive(SERVER_REC *server, const char *msg, const char *from, char **new_msg)
{
int ret = -1;
char *full_msg = NULL;
const char *recv_msg = NULL;
OtrlTLV *tlvs;
ConnContext *ctx;
struct otr_peer_context *opc;
OtrlTLV *tlv = NULL;
g_return_val_if_fail(server != NULL, -1);
g_return_val_if_fail(server->tag != NULL, -1);
IRSSI_OTR_DEBUG("Receiving message: %s", msg);
ctx = otr_find_context(server, from, 1);
if (ctx == NULL) {
return ret;
}
/* Add peer context to OTR context if none exists */
if (ctx->app_data == NULL)
add_peer_context_cb(server, ctx);
opc = ctx->app_data;
g_return_val_if_fail(opc != NULL, -1);
ret = enqueue_otr_fragment(msg, opc, &full_msg);
switch (ret) {
case OTR_MSG_ORIGINAL:
recv_msg = msg;
break;
case OTR_MSG_USE_QUEUE:
recv_msg = full_msg;
break;
case OTR_MSG_WAIT_MORE:
ret = 1;
g_free_not_null(full_msg);
return ret;
case OTR_MSG_ERROR:
ret = -1;
g_free_not_null(full_msg);
return ret;
}
ret = otrl_message_receiving(user_state_global->otr_state,
&otr_ops, server, server->tag, OTR_PROTOCOL_ID, from, recv_msg, new_msg,
&tlvs, &ctx, add_peer_context_cb, server);
if (ret) {
IRSSI_OTR_DEBUG("Ignoring message of length %d from %s to %s.\n%s", strlen(msg), from, server->tag, msg);
} else {
if (*new_msg) {
IRSSI_OTR_DEBUG("Converted received message.");
}
}
/* Check for disconnected message */
tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED);
if (tlv != NULL) {
otr_status_change(server, from, OTR_STATUS_PEER_FINISHED);
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SESSION_FINISHED, from);
}
otrl_tlv_free(tlvs);
IRSSI_OTR_DEBUG("Message received.");
g_free_not_null(full_msg);
return ret;
}
/*
* Get the OTR status of this conversation.
*/
enum otr_status_format otr_get_status_format(SERVER_REC *server, const char *nick)
{
int ret;
enum otr_status_format code;
ConnContext *ctx = NULL;
g_return_val_if_fail(server != NULL, TXT_OTR_STB_UNKNOWN);
ctx = otr_find_context(server, nick, FALSE);
if (ctx == NULL) {
code = TXT_OTR_STB_PLAINTEXT;
return code;
}
switch (ctx->msgstate) {
case OTRL_MSGSTATE_PLAINTEXT:
code = TXT_OTR_STB_PLAINTEXT;
break;
case OTRL_MSGSTATE_ENCRYPTED:
/* Begin by checking trust. */
ret = otrl_context_is_fingerprint_trusted(ctx->active_fingerprint);
if (ret) {
code = TXT_OTR_STB_TRUST;
} else {
code = TXT_OTR_STB_UNTRUSTED;
}
break;
case OTRL_MSGSTATE_FINISHED:
code = TXT_OTR_STB_FINISHED;
break;
default:
g_warning("BUG! Invalid msgstate: %d", ctx->msgstate);
code = TXT_OTR_STB_UNKNOWN;
break;
}
if (ctx) {
IRSSI_OTR_DEBUG("Code: %d, state: %d, sm_prog_state: %d, auth state: %d",
code, ctx->msgstate, ctx->smstate->sm_prog_state,
ctx->auth.authstate);
}
return code;
}
/*
* Change status bar text for a given nickname.
*/
void otr_status_change(SERVER_REC *server, const char *nick,
enum otr_status_event event)
{
statusbar_items_redraw("otr");
signal_emit("otr event", 3, server, nick, statusbar_txt[event]);
}
/*
* Search for a OTR Fingerprint object from the given human readable string and
* return a pointer to the object if found else NULL.
*/
Fingerprint *otr_find_hash_fingerprint_from_human(const char *human_fp, struct otr_user_state *ustate)
{
char str_fp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
Fingerprint *fp = NULL, *fp_iter = NULL;
ConnContext *context;
/* Loop on all context of the user state */
for (context = ustate->otr_state->context_root; context != NULL;
context = context->next) {
/* Loop on all fingerprint of the context */
for (fp_iter = context->fingerprint_root.next; fp_iter;
fp_iter = fp_iter->next) {
otrl_privkey_hash_to_human(str_fp, fp_iter->fingerprint);
/* Compare human fingerprint given in argument to the current. */
if (strncmp(str_fp, human_fp, sizeof(str_fp)) == 0) {
fp = otrl_context_find_fingerprint(context,
fp_iter->fingerprint, 0, NULL);
return fp;
}
}
}
return fp;
}
/*
* Forget a fingerprint.
*
* If str_fp is not NULL, it must be on the OTR human format like this:
* "487FFADA 5073FEDD C5AB5C14 5BB6C1FF 6D40D48A". If str_fp is NULL, get the
* context of the target nickname, check for the OTR peer context active
* fingerprint and forget this one if possible.
*/
void otr_forget(SERVER_REC *server, const char *nick, char *str_fp, struct otr_user_state *ustate)
{
char fp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
Fingerprint *fp_forget;
ConnContext *ctx = NULL;
struct otr_peer_context *opc;
/* No human string fingerprint given. */
if (*str_fp == '\0') {
ctx = otr_find_context(server, nick, FALSE);
if (ctx == NULL) {
return;
}
opc = ctx->app_data;
/* Always NEED a peer context or else code error. */
g_return_if_fail(opc != NULL);
fp_forget = opc->active_fingerprint;
} else {
fp_forget = otr_find_hash_fingerprint_from_human(str_fp, ustate);
}
if (fp_forget) {
/* Don't do anything if context is in encrypted state. */
if (check_fp_encrypted_msgstate(fp_forget)) {
printformat(server, nick, MSGLEVEL_CLIENTCRAP, TXT_OTR_FP_CTX_ENCRYPTED);
return;
}
otrl_privkey_hash_to_human(fp, fp_forget->fingerprint);
/* Forget fp and context if it's the only one remaining. */
otrl_context_forget_fingerprint(fp_forget, 1);
/* Update fingerprints file. */
key_write_fingerprints(ustate);
printformat(server, nick, MSGLEVEL_CLIENTCRAP, TXT_OTR_FP_FORGOTTEN, fp);
} else
printformat(server, nick, MSGLEVEL_CLIENTERROR, TXT_OTR_FP_MISSING, str_fp);
}
/*
* Distrust a fingerprint.
*
* If str_fp is not NULL, it must be on the OTR human format like this:
* "487FFADA 5073FEDD C5AB5C14 5BB6C1FF 6D40D48A". If str_fp is NULL, get the
* context of the target nickname, check for the OTR peer context active
* fingerprint and distrust it.
*/
void otr_distrust(SERVER_REC *server, const char *nick, char *str_fp,
struct otr_user_state *ustate)
{
char fp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
Fingerprint *fp_distrust;
ConnContext *ctx;
struct otr_peer_context *opc;
/* No human string fingerprint given. */
if (*str_fp == '\0') {
ctx = otr_find_context(server, nick, FALSE);
if (ctx == NULL) {
return;
}
opc = ctx->app_data;
/* Always NEED a peer context or else code error. */
g_return_if_fail(opc != NULL);
fp_distrust = opc->active_fingerprint;
} else
fp_distrust = otr_find_hash_fingerprint_from_human(str_fp, ustate);
if (fp_distrust != NULL) {
otrl_privkey_hash_to_human(fp, fp_distrust->fingerprint);
if (!otrl_context_is_fingerprint_trusted(fp_distrust)) {
/* Fingerprint already not trusted. Do nothing. */
printformat(server, nick, MSGLEVEL_CLIENTERROR, TXT_OTR_FP_ALREADY_DISTRUSED, fp);
return;
}
otrl_context_set_trust(fp_distrust, "");
/* Update fingerprints file. */
key_write_fingerprints(ustate);
printformat(server, nick, MSGLEVEL_CLIENTCRAP, TXT_OTR_FP_DISTRUSTED, fp);
} else
printformat(server, nick, MSGLEVEL_CLIENTERROR, TXT_OTR_FP_MISSING, str_fp);
}

170
src/otr/otr.h Normal file
View File

@ -0,0 +1,170 @@
/*
* Off-the-Record Messaging (OTR) modules for IRC
*
* Copyright (C) 2008 - Uli Meis <a.sporto+bee@gmail.com>
* 2012 - David Goulet <dgoulet@ev0ke.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
*/
#ifndef IRSSI_OTR_OTR_H
#define IRSSI_OTR_OTR_H
/* Libotr */
#include <libotr/proto.h>
#include <libotr/message.h>
#include <libotr/context.h>
#include <libotr/privkey.h>
#include "common.h"
#include "servers.h"
/* irssi module name */
#define MODULE_NAME "otr/core"
/*
* XXX: Maybe this should be configurable?
*/
#define OTR_MAX_MSG_SIZE 400
/* OTR protocol id */
#define OTR_PROTOCOL_ID "IRC"
#define OTR_DIR "otr"
#define OTR_KEYFILE OTR_DIR "/otr.key"
#define OTR_FINGERPRINTS_FILE OTR_DIR "/otr.fp"
#define OTR_INSTAG_FILE OTR_DIR "/otr.instag"
/*
* Specified in OTR protocol version 3. See:
* http://www.cypherpunks.ca/otr/Protocol-v3-4.0.0.html
*/
#define OTR_MSG_BEGIN_TAG "?OTR:"
#define OTR_MSG_END_TAG '.'
/* IRC /me command marker and len. */
#define OTR_IRC_MARKER_ME "/me "
#define OTR_IRC_MARKER_ME_LEN sizeof(OTR_IRC_MARKER_ME) - 1
/* Irssi otr user state */
struct otr_user_state {
OtrlUserState otr_state;
};
/*
* Peer OTR internal context.
*/
struct otr_peer_context {
/* The SMP event status. Used for the Irssi status bar. */
OtrlSMPEvent smp_event;
/* Did the SMP secret was asked so are we in a responder state? */
unsigned int ask_secret;
/*
* The fingerprint of the private message OTR session. This is useful for
* the forget command for which we can recover the fingerprint
* automatically.
*/
Fingerprint *active_fingerprint;
/*
* If needed, used to reconstruct the full message from fragmentation.
* Bitlbee for instance does that where we receive a *long* OTR message
* split in multiple PRIVMSG so we need to reconstruct it.
*/
char *full_msg;
/* Size of full_msg. Note this is the allocated memory size. */
size_t msg_size;
/* Len of the actual string in full_msg NOT counting the NULL byte. */
size_t msg_len;
};
/* given to otr_status_change */
enum otr_status_event {
OTR_STATUS_FINISHED,
OTR_STATUS_TRUST_MANUAL,
OTR_STATUS_TRUST_SMP,
OTR_STATUS_SMP_ABORT,
OTR_STATUS_SMP_STARTED,
OTR_STATUS_SMP_RESPONDED,
OTR_STATUS_SMP_INCOMING,
OTR_STATUS_SMP_FINALIZE,
OTR_STATUS_SMP_ABORTED,
OTR_STATUS_PEER_FINISHED,
OTR_STATUS_SMP_FAILED,
OTR_STATUS_SMP_SUCCESS,
OTR_STATUS_GONE_SECURE,
OTR_STATUS_GONE_INSECURE,
OTR_STATUS_CTX_UPDATE
};
enum otr_msg_status {
OTR_MSG_ORIGINAL = 1,
OTR_MSG_WAIT_MORE = 2,
OTR_MSG_USE_QUEUE = 3,
OTR_MSG_ERROR = 4,
};
/* there can be only one */
extern struct otr_user_state *user_state_global;
/* Libotr ops functions */
extern OtrlMessageAppOps otr_ops;
int otr_debug_get(void);
void otr_debug_toggle(void);
void otr_send_message(SERVER_REC *irssi, const char *recipient,
const char *message);
void otr_status_change(SERVER_REC *irssi, const char *nick,
enum otr_status_event event);
/* init stuff */
struct otr_user_state *otr_init_user_state(void);
void otr_free_user_state(struct otr_user_state *ustate);
void otr_lib_init();
void otr_lib_uninit();
void otr_control_timer(unsigned int interval, void *opdata);
/* Message transport. */
int otr_send(SERVER_REC *irssi, const char *msg, const char *to,
char **otr_msg);
int otr_receive(SERVER_REC *irssi, const char *msg,
const char *from, char **new_msg);
/* User interaction */
void otr_finish(SERVER_REC *irssi, const char *nick);
void otr_auth(SERVER_REC *irssi, const char *nick, const char *question,
const char *secret);
void otr_auth_abort(SERVER_REC *irssi, const char *nick);
void otr_contexts(struct otr_user_state *ustate);
void otr_finishall(struct otr_user_state *ustate);
void otr_forget(SERVER_REC *irssi, const char *nick, char *str_fp,
struct otr_user_state *ustate);
void otr_distrust(SERVER_REC *irssi, const char *nick, char *str_fp,
struct otr_user_state *ustate);
void otr_trust(SERVER_REC *irssi, const char *nick, char *str_fp,
struct otr_user_state *ustate);
enum otr_status_format otr_get_status_format(SERVER_REC *irssi,
const char *nick);
struct otr_peer_context *otr_create_peer_context(void);
ConnContext *otr_find_context(SERVER_REC *irssi, const char *nick, int create);
Fingerprint *otr_find_hash_fingerprint_from_human(const char *human_fp,
struct otr_user_state *ustate);
#endif /* IRSSI_OTR_OTR_H */