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,