diff options
-rw-r--r-- | .travis.yml | 7 | ||||
-rw-r--r-- | AUTHORS | 2 | ||||
-rw-r--r-- | configure.ac | 69 | ||||
-rw-r--r-- | docs/help/in/otr.in | 111 | ||||
-rw-r--r-- | docs/help/in/statusbar.in | 51 | ||||
-rw-r--r-- | docs/signals.txt | 6 | ||||
-rw-r--r-- | m4/libgcrypt.m4 | 143 | ||||
-rw-r--r-- | m4/libotr.m4 | 134 | ||||
-rw-r--r-- | src/Makefile.am | 6 | ||||
-rw-r--r-- | src/common.h | 2 | ||||
-rw-r--r-- | src/core/net-nonblock.c | 35 | ||||
-rw-r--r-- | src/core/network.c | 33 | ||||
-rw-r--r-- | src/core/network.h | 2 | ||||
-rw-r--r-- | src/fe-text/Makefile.am | 5 | ||||
-rw-r--r-- | src/fe-text/irssi.c | 17 | ||||
-rw-r--r-- | src/fe-text/module-formats.c | 1 | ||||
-rw-r--r-- | src/fe-text/module-formats.h | 17 | ||||
-rw-r--r-- | src/fe-text/statusbar-config.c | 557 | ||||
-rw-r--r-- | src/otr/Makefile.am | 42 | ||||
-rw-r--r-- | src/otr/irssi-otr.h | 39 | ||||
-rw-r--r-- | src/otr/key.c | 404 | ||||
-rw-r--r-- | src/otr/key.h | 35 | ||||
-rw-r--r-- | src/otr/module.h | 29 | ||||
-rw-r--r-- | src/otr/otr-fe.c | 344 | ||||
-rw-r--r-- | src/otr/otr-fe.h | 28 | ||||
-rw-r--r-- | src/otr/otr-formats.c | 108 | ||||
-rw-r--r-- | src/otr/otr-formats.h | 112 | ||||
-rw-r--r-- | src/otr/otr-module.c | 267 | ||||
-rw-r--r-- | src/otr/otr-ops.c | 362 | ||||
-rw-r--r-- | src/otr/otr.c | 941 | ||||
-rw-r--r-- | src/otr/otr.h | 170 |
31 files changed, 3831 insertions, 248 deletions
diff --git a/.travis.yml b/.travis.yml index e2b3f57c..30f9cc35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,17 +27,20 @@ addons: packages: - libperl-dev - elinks + - libgcrypt11-dev before_install: - perl -V - - ./autogen.sh --with-proxy --with-bot --with-perl=module + - wget https://github.com/irssi-import/libotr/releases/download/4.1.1/travis-trusty-libotr-4.1.1.tar.gz + - tar zxf travis-trusty-libotr*.tar.gz -C $HOME + - ./autogen.sh --with-proxy --with-bot --with-perl=module --with-otr=yes --with-libotr-prefix=$HOME/otr-build/lib --with-libotr-inc-prefix=$HOME/otr-build/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-build/lib --with-libotr-inc-prefix=$HOME/otr-build/include --prefix=$HOME/irssi-build $( $UNITTESTS && echo --enable-always-build-tests ) - make CFLAGS="-Wall -Werror -Werror=declaration-after-statement" - make install @@ -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: diff --git a/configure.ac b/configure.ac index 3becf37a..d29c132b 100644 --- a/configure.ac +++ b/configure.ac @@ -148,6 +148,21 @@ AC_ARG_WITH(perl, fi, want_perl=static) +AC_ARG_WITH(otr, +[ --with-otr[=yes|no|static] Build with OTR support - also specifies + if it should be built into the main irssi + binary (static) 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 @@ -756,5 +816,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." diff --git a/docs/help/in/otr.in b/docs/help/in/otr.in new file mode 100644 index 00000000..f76a2d53 --- /dev/null +++ b/docs/help/in/otr.in @@ -0,0 +1,111 @@ + +OTR %|[OPTION] + +Command to control the OTR module. Without an option, /OTR INFO 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/ + diff --git a/docs/help/in/statusbar.in b/docs/help/in/statusbar.in index 7d64b816..9d68d1c9 100644 --- a/docs/help/in/statusbar.in +++ b/docs/help/in/statusbar.in @@ -5,27 +5,35 @@ %9Parameters:%9 - ENABLE: Adds a statusbar to the list of statusbars. - DISABLE: Removes a statusbar from the list. Note that for - built-in statusbars they can be enabled again should the - user wish to add back the default statusbars. + ADD: Adds a statusbar to the list of statusbars. + MODIFY: Modifies the configuration of a statusbar. RESET: Restores the default statusbar configuration. - TYPE: Sets the type of statusbar, for each split window or only + ADDITEM: Adds an item to the specified statusbar. It can be set to + appear before/after another item and left/right aligned + to a specified position on the screen. + MODIFYITEM: Changes an item position inside a bar. + REMOVEITEM: Removes an item from the specified statusbar. + INFO: List the current details and items of the specified + statusbar. + + -disable: Removes a statusbar from the list. + -type: Sets the type of statusbar, for each split window or only for the current root screen. - PLACEMENT: Sets the placement of the statusbar, either at the top or + -placement: Sets the placement of the statusbar, either at the top or the bottom of the screen. - POSITION: Sets the position of the statusbar. Represented as a + -position: Sets the position of the statusbar. Represented as a number, with 0 implying the first position. - VISIBLE: Sets the visibility of the statusbar or item. If set to + -visible: Sets the visibility of the statusbar or item. If set to always it is visible on all screens, otherwise if set to inactive or active then it is only visible on inactive or active screens, respectively. - ADD: Adds an item to the specified statusbar. It can be set to - appear before/after another item and left/right aligned - to a specified position on the screen. - REMOVE: Removes an item from the specified statusbar. + -before: This item is added before the other item. + -after: This item is added after the other item. + -priority: When the statusbar items overflow, the item with the + lowest priority is removed first + -alignment: Display the item on the right side. - Where name refers to the name of the statusbar; if no argument is + Where statusbar refers to the name of the statusbar; if no argument is given, the entire list of statusbars will be displayed. %9Description:%9 @@ -36,12 +44,17 @@ %9Examples:%9 /STATUSBAR - /STATUSBAR window - /STATUSBAR window REMOVE time - /STATUSBAR window ADD time - /STATUSBAR window RESET - /STATUSBAR topic DISABLE - /STATUSBAR topic ENABLE + /STATUSBAR INFO window + /STATUSBAR REMOVEITEM time window + /STATUSBAR ADDITEM time window + /STATUSBAR RESET window + /STATUSBAR MODIFY -disable topic + /STATUSBAR MODIFY -nodisable topic + +%9Remarks:%9 + + Statusbar syntax was changed in Irssi 1.2. The old syntax is still + accepted for backward compatibility, but no longer documented. %9See also:%9 WINDOW diff --git a/docs/signals.txt b/docs/signals.txt index 7776dad7..84d4518f 100644 --- a/docs/signals.txt +++ b/docs/signals.txt @@ -350,3 +350,9 @@ Perl ---- "script error", PERL_SCRIPT_REC, char *errormsg + +OTR Core +-------- + +otr.c: + "otr event", SERVER_REC, char *nick, char *status diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4 new file mode 100644 index 00000000..c67cfece --- /dev/null +++ b/m4/libgcrypt.m4 @@ -0,0 +1,143 @@ +# libgcrypt.m4 - Autoconf macros to detect libgcrypt +# Copyright (C) 2002, 2003, 2004, 2011, 2014 g10 Code GmbH +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Last-changed: 2014-10-02 + + +dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION, +dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) +dnl Test for libgcrypt and define LIBGCRYPT_CFLAGS and LIBGCRYPT_LIBS. +dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed +dnl with the API version to also check the API compatibility. Example: +dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed +dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using +dnl this features allows to prevent build against newer versions of libgcrypt +dnl with a changed API. +dnl +dnl If a prefix option is not used, the config script is first +dnl searched in $SYSROOT/bin and then along $PATH. If the used +dnl config script does not match the host specification the script +dnl is added to the gpg_config_script_warn variable. +dnl +AC_DEFUN([AM_PATH_LIBGCRYPT], +[ AC_REQUIRE([AC_CANONICAL_HOST]) + AC_ARG_WITH(libgcrypt-prefix, + AC_HELP_STRING([--with-libgcrypt-prefix=PFX], + [prefix where LIBGCRYPT is installed (optional)]), + libgcrypt_config_prefix="$withval", libgcrypt_config_prefix="") + if test x"${LIBGCRYPT_CONFIG}" = x ; then + if test x"${libgcrypt_config_prefix}" != x ; then + LIBGCRYPT_CONFIG="${libgcrypt_config_prefix}/bin/libgcrypt-config" + else + case "${SYSROOT}" in + /*) + if test -x "${SYSROOT}/bin/libgcrypt-config" ; then + LIBGCRYPT_CONFIG="${SYSROOT}/bin/libgcrypt-config" + fi + ;; + '') + ;; + *) + AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) + ;; + esac + fi + fi + + AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no) + tmp=ifelse([$1], ,1:1.2.0,$1) + if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then + req_libgcrypt_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` + min_libgcrypt_version=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\2/'` + else + req_libgcrypt_api=0 + min_libgcrypt_version="$tmp" + fi + + AC_MSG_CHECKING(for LIBGCRYPT - version >= $min_libgcrypt_version) + ok=no + if test "$LIBGCRYPT_CONFIG" != "no" ; then + req_major=`echo $min_libgcrypt_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` + req_minor=`echo $min_libgcrypt_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` + req_micro=`echo $min_libgcrypt_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` + libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version` + major=`echo $libgcrypt_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` + minor=`echo $libgcrypt_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'` + micro=`echo $libgcrypt_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'` + if test "$major" -gt "$req_major"; then + ok=yes + else + if test "$major" -eq "$req_major"; then + if test "$minor" -gt "$req_minor"; then + ok=yes + else + if test "$minor" -eq "$req_minor"; then + if test "$micro" -ge "$req_micro"; then + ok=yes + fi + fi + fi + fi + fi + fi + if test $ok = yes; then + AC_MSG_RESULT([yes ($libgcrypt_config_version)]) + else + AC_MSG_RESULT(no) + fi + if test $ok = yes; then + # If we have a recent libgcrypt, we should also check that the + # API is compatible + if test "$req_libgcrypt_api" -gt 0 ; then + tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0` + if test "$tmp" -gt 0 ; then + AC_MSG_CHECKING([LIBGCRYPT API version]) + if test "$req_libgcrypt_api" -eq "$tmp" ; then + AC_MSG_RESULT([okay]) + else + ok=no + AC_MSG_RESULT([does not match. want=$req_libgcrypt_api got=$tmp]) + fi + fi + fi + fi + if test $ok = yes; then + LIBGCRYPT_CFLAGS=`$LIBGCRYPT_CONFIG --cflags` + LIBGCRYPT_LIBS=`$LIBGCRYPT_CONFIG --libs` + ifelse([$2], , :, [$2]) + libgcrypt_config_host=`$LIBGCRYPT_CONFIG --host 2>/dev/null || echo none` + if test x"$libgcrypt_config_host" != xnone ; then + if test x"$libgcrypt_config_host" != x"$host" ; then + AC_MSG_WARN([[ +*** +*** The config script $LIBGCRYPT_CONFIG was +*** built for $libgcrypt_config_host and thus may not match the +*** used host $host. +*** You may want to use the configure option --with-libgcrypt-prefix +*** to specify a matching config script or use \$SYSROOT. +***]]) + gpg_config_script_warn="$gpg_config_script_warn libgcrypt" + fi + fi + else + LIBGCRYPT_CFLAGS="" + LIBGCRYPT_LIBS="" + ifelse([$3], , :, [$3]) + fi + AC_SUBST(LIBGCRYPT_CFLAGS) + AC_SUBST(LIBGCRYPT_LIBS) +]) diff --git a/m4/libotr.m4 b/m4/libotr.m4 new file mode 100644 index 00000000..80b25f86 --- /dev/null +++ b/m4/libotr.m4 @@ -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) +]) + diff --git a/src/Makefile.am b/src/Makefile.am index a7fb2ee2..48cdc3eb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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) diff --git a/src/common.h b/src/common.h index 746aad4e..de29abd5 100644 --- a/src/common.h +++ b/src/common.h @@ -6,7 +6,7 @@ #define IRSSI_GLOBAL_CONFIG "irssi.conf" /* config file name in /etc/ */ #define IRSSI_HOME_CONFIG "config" /* config file name in ~/.irssi/ */ -#define IRSSI_ABI_VERSION 15 +#define IRSSI_ABI_VERSION 16 #define DEFAULT_SERVER_ADD_PORT 6667 #define DEFAULT_SERVER_ADD_TLS_PORT 6697 diff --git a/src/core/net-nonblock.c b/src/core/net-nonblock.c index d6e767b6..94c53e74 100644 --- a/src/core/net-nonblock.c +++ b/src/core/net-nonblock.c @@ -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, diff --git a/src/core/network.c b/src/core/network.c index d280b463..0e326e22 100644 --- a/src/core/network.c +++ b/src/core/network.c @@ -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) diff --git a/src/core/network.h b/src/core/network.h index e60f607f..30fd6aab 100644 --- a/src/core/network.h +++ b/src/core/network.h @@ -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); diff --git a/src/fe-text/Makefile.am b/src/fe-text/Makefile.am index bdd3df22..6ce8427c 100644 --- a/src/fe-text/Makefile.am +++ b/src/fe-text/Makefile.am @@ -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@ diff --git a/src/fe-text/irssi.c b/src/fe-text/irssi.c index f30ce4b8..728f456e 100644 --- a/src/fe-text/irssi.c +++ b/src/fe-text/irssi.c @@ -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 */ diff --git a/src/fe-text/module-formats.c b/src/fe-text/module-formats.c index b234f46c..277872c6 100644 --- a/src/fe-text/module-formats.c +++ b/src/fe-text/module-formats.c @@ -67,6 +67,7 @@ FORMAT_REC gui_text_formats[] = { "statusbar_info_item_footer", "", 0 }, { "statusbar_info_item_name", "%# : $[35]0 $[9]1 $2", 3, { 0, 1, 0 } }, { "statusbar_not_found", "Statusbar doesn't exist: $0", 1, { 0 } }, + { "statusbar_not_found", "Statusbar is disabled: $0", 1, { 0 } }, { "statusbar_item_not_found", "Statusbar item doesn't exist: $0", 1, { 0 } }, { "statusbar_unknown_command", "Unknown statusbar command: $0", 1, { 0 } }, { "statusbar_unknown_type", "Statusbar type must be 'window' or 'root'", 1, { 0 } }, diff --git a/src/fe-text/module-formats.h b/src/fe-text/module-formats.h index b753238b..ac60fdb7 100644 --- a/src/fe-text/module-formats.h +++ b/src/fe-text/module-formats.h @@ -30,23 +30,24 @@ enum { TXT_FILL_3, - TXT_STATUSBAR_LIST_HEADER, + TXT_STATUSBAR_LIST_HEADER, TXT_STATUSBAR_LIST_FOOTER, TXT_STATUSBAR_LIST, TXT_STATUSBAR_INFO_NAME, TXT_STATUSBAR_INFO_TYPE, - TXT_STATUSBAR_INFO_PLACEMENT, + TXT_STATUSBAR_INFO_PLACEMENT, TXT_STATUSBAR_INFO_POSITION, TXT_STATUSBAR_INFO_VISIBLE, - TXT_STATUSBAR_INFO_ITEM_HEADER, + TXT_STATUSBAR_INFO_ITEM_HEADER, TXT_STATUSBAR_INFO_ITEM_FOOTER, - TXT_STATUSBAR_INFO_ITEM_NAME, - TXT_STATUSBAR_NOT_FOUND, - TXT_STATUSBAR_ITEM_NOT_FOUND, + TXT_STATUSBAR_INFO_ITEM_NAME, + TXT_STATUSBAR_NOT_FOUND, + TXT_STATUSBAR_NOT_ENABLED, + TXT_STATUSBAR_ITEM_NOT_FOUND, TXT_STATUSBAR_UNKNOWN_COMMAND, - TXT_STATUSBAR_UNKNOWN_TYPE, + TXT_STATUSBAR_UNKNOWN_TYPE, TXT_STATUSBAR_UNKNOWN_PLACEMENT, - TXT_STATUSBAR_UNKNOWN_VISIBILITY, + TXT_STATUSBAR_UNKNOWN_VISIBILITY, TXT_FILL_4, diff --git a/src/fe-text/statusbar-config.c b/src/fe-text/statusbar-config.c index 48f4aa61..314befaa 100644 --- a/src/fe-text/statusbar-config.c +++ b/src/fe-text/statusbar-config.c @@ -95,11 +95,13 @@ statusbar_config_find(STATUSBAR_GROUP_REC *group, const char *name) for (tmp = group->config_bars; tmp != NULL; tmp = tmp->next) { STATUSBAR_CONFIG_REC *config = tmp->data; - if (g_strcmp0(config->name, name) == 0) - return config; + if ((config->name == NULL || name == NULL) ? + config->name == name : + g_ascii_strcasecmp(config->name, name) == 0) + return config; } - return NULL; + return NULL; } static void statusbar_reset_defaults(void) @@ -273,6 +275,51 @@ static const char *sbar_get_visibility(STATUSBAR_CONFIG_REC *rec) rec->visible == STATUSBAR_VISIBLE_INACTIVE ? "inactive" : "??"; } +#define iconfig_sbar_node(a, b) config_sbar_node(mainconfig, a, b) +static CONFIG_NODE *config_sbar_node(CONFIG_REC *config, const char *name, gboolean create) +{ + CONFIG_NODE *node; + + node = config_node_traverse(config, "statusbar", create); + if (node != NULL) { + node = config_node_section(config, node, active_statusbar_group->name, + create ? NODE_TYPE_BLOCK : -1); + } + + if (node != NULL) { + node = config_node_section(config, node, name, create ? NODE_TYPE_BLOCK : -1); + } + + return node; +} + +static CONFIG_NODE *sbar_node(const char *name, gboolean create) +{ + STATUSBAR_CONFIG_REC *rec = statusbar_config_find(active_statusbar_group, name); + if (rec != NULL) { + name = rec->name; + } + + /* lookup/create the statusbar node */ + return iconfig_sbar_node(name, create); +} + +static gboolean sbar_node_isdefault(const char *name) +{ + CONFIG_REC *config; + CONFIG_NODE *node; + + /* read the default statusbar settings from internal config */ + config = config_open(NULL, -1); + config_parse_data(config, default_config, "internal"); + + node = config_sbar_node(config, name, FALSE); + + config_close(config); + + return node != NULL ? TRUE : FALSE; +} + static void statusbar_list_items(STATUSBAR_CONFIG_REC *bar) { GSList *tmp; @@ -334,166 +381,281 @@ static void cmd_statusbar_list(void) static void cmd_statusbar_print_info(const char *name) { - GSList *tmp; + STATUSBAR_CONFIG_REC *rec = statusbar_config_find(active_statusbar_group, name); - tmp = active_statusbar_group->config_bars; - for (; tmp != NULL; tmp = tmp->next) { - STATUSBAR_CONFIG_REC *rec = tmp->data; - - if (g_ascii_strcasecmp(rec->name, name) == 0) { - statusbar_print(rec); - return; - } + if (rec != NULL) { + statusbar_print(rec); + return; } - printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, - TXT_STATUSBAR_NOT_FOUND, name); + if (sbar_node(name, FALSE) != NULL || sbar_node_isdefault(name)) + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_STATUSBAR_NOT_ENABLED, name); + else + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_STATUSBAR_NOT_FOUND, name); } -/* SYNTAX: STATUSBAR <name> ENABLE */ -static void cmd_statusbar_enable(const char *data, void *server, - void *item, CONFIG_NODE *node) +/* SYNTAX: STATUSBAR ADD|MODIFY [-disable | -nodisable] [-type window|root] + [-placement top|bottom] [-position #] [-visible always|active|inactive] <statusbar> */ +static void cmd_statusbar_add_modify(const char *data, void *server, void *witem) { - iconfig_node_set_str(node, "disabled", NULL); -} + GHashTable *optlist; + CONFIG_NODE *node; + char *name, *type, *placement, *visible; + void *free_arg; + int error; + int add = GPOINTER_TO_INT(signal_get_user_data()); -/* SYNTAX: STATUSBAR <name> DISABLE */ -static void cmd_statusbar_disable(const char *data, void *server, - void *item, CONFIG_NODE *node) -{ - iconfig_node_set_bool(node, "disabled", TRUE); -} + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | PARAM_FLAG_STRIP_TRAILING_WS, + "statusbar add", &optlist, &name)) + return; -/* SYNTAX: STATUSBAR <name> RESET */ -static void cmd_statusbar_reset(const char *data, void *server, - void *item, CONFIG_NODE *node) -{ - CONFIG_NODE *parent; + if (*name == '\0') { + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + } - parent = iconfig_node_traverse("statusbar", TRUE); - parent = iconfig_node_section(parent, active_statusbar_group->name, - NODE_TYPE_BLOCK); + error = 0; + + type = NULL; + data = g_hash_table_lookup(optlist, "type"); + if (data != NULL) { + if (g_ascii_strcasecmp(data, "window") == 0) + type = "window"; + else if (g_ascii_strcasecmp(data, "root") == 0) + type = "root"; + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_UNKNOWN_TYPE, + data); + error++; + } + } - iconfig_node_set_str(parent, node->key, NULL); -} + placement = NULL; + data = g_hash_table_lookup(optlist, "placement"); + if (data != NULL) { + if (g_ascii_strcasecmp(data, "top") == 0) + placement = "top"; + else if (g_ascii_strcasecmp(data, "bottom") == 0) + placement = "bottom"; + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_STATUSBAR_UNKNOWN_PLACEMENT, data); + error++; + } + } -/* SYNTAX: STATUSBAR <name> TYPE window|root */ -static void cmd_statusbar_type(const char *data, void *server, - void *item, CONFIG_NODE *node) -{ - if (g_ascii_strcasecmp(data, "window") == 0) - iconfig_node_set_str(node, "type", "window"); - else if (g_ascii_strcasecmp(data, "root") == 0) - iconfig_node_set_str(node, "type", "root"); - else { - printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, - TXT_STATUSBAR_UNKNOWN_TYPE, data); + visible = NULL; + data = g_hash_table_lookup(optlist, "visible"); + if (data != NULL) { + if (g_ascii_strcasecmp(data, "always") == 0) + visible = "always"; + else if (g_ascii_strcasecmp(data, "active") == 0) + visible = "active"; + else if (g_ascii_strcasecmp(data, "inactive") == 0) + visible = "inactive"; + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_STATUSBAR_UNKNOWN_VISIBILITY, data); + error++; + } } + + if (!error) { + node = sbar_node(name, add); + if (node == NULL && !add && sbar_node_isdefault(name)) { + /* If this node is a default status bar, we need to create it in the config + * to configure it */ + node = sbar_node(name, TRUE); + } + + if (node == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_NOT_FOUND, name); + error++; + } + } + + if (error) { + cmd_params_free(free_arg); + return; + } + + if (g_hash_table_lookup(optlist, "nodisable")) + iconfig_node_set_str(node, "disabled", NULL); + if (g_hash_table_lookup(optlist, "disable")) + iconfig_node_set_bool(node, "disabled", TRUE); + if (type != NULL) + iconfig_node_set_str(node, "type", type); + if (placement != NULL) + iconfig_node_set_str(node, "placement", placement); + data = g_hash_table_lookup(optlist, "position"); + if (data != NULL) + iconfig_node_set_int(node, "position", atoi(data)); + if (visible != NULL) + iconfig_node_set_str(node, "visible", visible); + + read_statusbar_config(); + cmd_params_free(free_arg); } -/* SYNTAX: STATUSBAR <name> PLACEMENT top|bottom */ -static void cmd_statusbar_placement(const char *data, void *server, - void *item, CONFIG_NODE *node) +/* SYNTAX: STATUSBAR RESET <statusbar> */ +static void cmd_statusbar_reset(const char *data, void *server, void *witem) { - if (g_ascii_strcasecmp(data, "top") == 0) - iconfig_node_set_str(node, "placement", "top"); - else if (g_ascii_strcasecmp(data, "bottom") == 0) - iconfig_node_set_str(node, "placement", "bottom"); - else { - printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, - TXT_STATUSBAR_UNKNOWN_PLACEMENT, data); + CONFIG_NODE *node, *parent; + char *name; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_STRIP_TRAILING_WS, &name)) + return; + + if (*name == '\0') { + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); } + + node = sbar_node(name, FALSE); + if (node == NULL && !sbar_node_isdefault(name)) { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_NOT_FOUND, name); + cmd_params_free(free_arg); + return; + } + + parent = iconfig_node_traverse("statusbar", FALSE); + if (parent != NULL) { + parent = iconfig_node_section(parent, active_statusbar_group->name, -1); + } + + if (parent != NULL && node != NULL) { + iconfig_node_set_str(parent, node->key, NULL); + } + + read_statusbar_config(); + cmd_params_free(free_arg); } -/* SYNTAX: STATUSBAR <name> POSITION <num> */ -static void cmd_statusbar_position(const char *data, void *server, - void *item, CONFIG_NODE *node) +#define iconfig_sbar_items_section(a, b) config_sbar_items_section(mainconfig, a, b) +static CONFIG_NODE *config_sbar_items_section(CONFIG_REC *config, CONFIG_NODE *parent, + gboolean create) { - iconfig_node_set_int(node, "position", atoi(data)); + return config_node_section(config, parent, "items", create ? NODE_TYPE_BLOCK : -1); } -/* SYNTAX: STATUSBAR <name> VISIBLE always|active|inactive */ -static void cmd_statusbar_visible(const char *data, void *server, - void *item, CONFIG_NODE *node) +static CONFIG_NODE *statusbar_copy_config(CONFIG_REC *config, CONFIG_NODE *source, + CONFIG_NODE *parent) { - if (g_ascii_strcasecmp(data, "always") == 0) - iconfig_node_set_str(node, "visible", "always"); - else if (g_ascii_strcasecmp(data, "active") == 0) - iconfig_node_set_str(node, "visible", "active"); - else if (g_ascii_strcasecmp(data, "inactive") == 0) - iconfig_node_set_str(node, "visible", "inactive"); - else { - printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, - TXT_STATUSBAR_UNKNOWN_VISIBILITY, data); + GSList *tmp; + + g_return_val_if_fail(parent != NULL, NULL); + + parent = iconfig_sbar_items_section(parent, TRUE); + + /* since items list in config file overrides defaults, + we'll need to copy the whole list. */ + for (tmp = config_node_first(source->value); tmp != NULL; tmp = config_node_next(tmp)) { + int priority, right_alignment; + CONFIG_NODE *node, *snode; + + snode = tmp->data; + + priority = config_node_get_int(snode, "priority", 0); + right_alignment = + g_strcmp0(config_node_get_str(snode, "alignment", ""), "right") == 0; + + /* create new item */ + node = iconfig_node_section(parent, snode->key, NODE_TYPE_BLOCK); + + if (priority != 0) + iconfig_node_set_int(node, "priority", priority); + if (right_alignment) + iconfig_node_set_str(node, "alignment", "right"); } + + return parent; } -static CONFIG_NODE *statusbar_items_section(CONFIG_NODE *parent) +static CONFIG_NODE *sbar_find_item_with_defaults(const char *statusbar, const char *item, + gboolean create) { - STATUSBAR_CONFIG_REC *bar; - CONFIG_NODE *node; - GSList *tmp; + CONFIG_REC *config, *close_config; + CONFIG_NODE *node; - node = iconfig_node_section(parent, "items", -1); - if (node != NULL) - return node; + close_config = NULL; + config = mainconfig; + node = sbar_node(statusbar, FALSE); - /* find the statusbar configuration from memory */ - bar = statusbar_config_find(active_statusbar_group, parent->key); - if (bar == NULL) { - printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, - TXT_STATUSBAR_NOT_FOUND, parent->key); - return NULL; + if (node == NULL) { + /* we are looking up defaults from the internal config */ + close_config = config = config_open(NULL, -1); + config_parse_data(config, default_config, "internal"); + node = config_sbar_node(config, statusbar, FALSE); } - /* since items list in config file overrides defaults, - we'll need to copy the whole list. */ - parent = iconfig_node_section(parent, "items", NODE_TYPE_BLOCK); - for (tmp = bar->items; tmp != NULL; tmp = tmp->next) { - SBAR_ITEM_CONFIG_REC *rec = tmp->data; + if (node == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_NOT_FOUND, statusbar); + if (close_config != NULL) + config_close(close_config); + return NULL; + } - node = iconfig_node_section(parent, rec->name, - NODE_TYPE_BLOCK); - if (rec->priority != 0) - iconfig_node_set_int(node, "priority", rec->priority); - if (rec->right_alignment) - iconfig_node_set_str(node, "alignment", "right"); + node = config_sbar_items_section(config, node, create); + + if (node == NULL || (!create && config_node_section(config, node, item, -1) == NULL)) { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_ITEM_NOT_FOUND, item); + if (close_config != NULL) + config_close(close_config); + return NULL; } - return parent; + if (config != mainconfig) { + /* we need to copy default to user config */ + node = statusbar_copy_config(config, node, sbar_node(statusbar, TRUE)); + } + + if (close_config != NULL) + config_close(close_config); + + return node; } -/* SYNTAX: STATUSBAR <name> ADD [-before | -after <item>] - [-priority #] [-alignment left|right] <item> */ -static void cmd_statusbar_add(const char *data, void *server, - void *item, CONFIG_NODE *node) +/* SYNTAX: STATUSBAR ADDITEM|MODIFYITEM [-before | -after <item>] + [-priority #] [-alignment left|right] <item> <statusbar> */ +static void cmd_statusbar_additem_modifyitem(const char *data, void *server, void *witem) { - GHashTable *optlist; - char *name, *value; + CONFIG_NODE *node; + GHashTable *optlist; + char *item, *statusbar, *value; void *free_arg; - int index; + int index; + int additem = GPOINTER_TO_INT(signal_get_user_data()); - node = statusbar_items_section(node); - if (node == NULL) - return; + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_STRIP_TRAILING_WS, + "statusbar additem", &optlist, &item, &statusbar)) + return; + + if (*statusbar == '\0') { + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + } - if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS, - "statusbar add", &optlist, &name)) + node = sbar_find_item_with_defaults(statusbar, item, additem); + if (node == NULL) { + cmd_params_free(free_arg); return; + } - /* get the index */ + /* get the index */ index = -1; value = g_hash_table_lookup(optlist, "before"); - if (value != NULL) index = config_node_index(node, value); + if (value != NULL) + index = config_node_index(node, value); value = g_hash_table_lookup(optlist, "after"); - if (value != NULL) index = config_node_index(node, value)+1; + if (value != NULL) + index = config_node_index(node, value) + 1; - /* create/move item */ - node = iconfig_node_section_index(node, name, index, NODE_TYPE_BLOCK); + /* create/move item */ + node = iconfig_node_section_index(node, item, index, NODE_TYPE_BLOCK); - /* set the options */ - value = g_hash_table_lookup(optlist, "priority"); - if (value != NULL) iconfig_node_set_int(node, "priority", atoi(value)); + /* set the options */ + value = g_hash_table_lookup(optlist, "priority"); + if (value != NULL) iconfig_node_set_int(node, "priority", atoi(value)); value = g_hash_table_lookup(optlist, "alignment"); if (value != NULL) { @@ -502,67 +664,101 @@ static void cmd_statusbar_add(const char *data, void *server, "right" : NULL); } + read_statusbar_config(); cmd_params_free(free_arg); } -/* SYNTAX: STATUSBAR <name> REMOVE <item> */ -static void cmd_statusbar_remove(const char *data, void *server, - void *item, CONFIG_NODE *node) +/* SYNTAX: STATUSBAR REMOVEITEM <item> <statusbar> */ +static void cmd_statusbar_removeitem(const char *data, void *server, void *witem) { - node = statusbar_items_section(node); - if (node == NULL) - return; + CONFIG_NODE *node; + char *item, *statusbar; + void *free_arg; + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_STRIP_TRAILING_WS, &item, &statusbar)) + return; - if (iconfig_node_section(node, data, -1) != NULL) - iconfig_node_set_str(node, data, NULL); - else { - printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, - TXT_STATUSBAR_ITEM_NOT_FOUND, data); + if (*statusbar == '\0') { + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); } + + node = sbar_find_item_with_defaults(statusbar, item, FALSE); + + if (node != NULL) + iconfig_node_set_str(node, item, NULL); + + read_statusbar_config(); + cmd_params_free(free_arg); } -static void cmd_statusbar(const char *data) +/* SYNTAX: STATUSBAR INFO <statusbar> */ +static void cmd_statusbar_info(const char *data) { - CONFIG_NODE *node; - char *name, *cmd, *params, *signal; void *free_arg; - - if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, - &name, &cmd, ¶ms)) + char *name; + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_STRIP_TRAILING_WS, &name)) return; if (*name == '\0') { - /* list all statusbars */ - cmd_statusbar_list(); - cmd_params_free(free_arg); - return; + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); } - if (*cmd == '\0') { - /* print statusbar info */ - cmd_statusbar_print_info(name); - cmd_params_free(free_arg); - return; - } + /* print statusbar info */ + cmd_statusbar_print_info(name); + cmd_params_free(free_arg); + return; +} + +static void cmd_statusbar(const char *data) +{ + char *arg1, *arg2, *params, *oldcmd; + void *free_arg; - /* lookup/create the statusbar node */ - node = iconfig_node_traverse("statusbar", TRUE); - node = iconfig_node_section(node, active_statusbar_group->name, - NODE_TYPE_BLOCK); - node = iconfig_node_section(node, name, NODE_TYPE_BLOCK); + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + &arg1, &arg2, ¶ms)) + return; - /* call the subcommand */ - signal = g_strconcat("command statusbar ", cmd, NULL); - ascii_strdown(signal); - if (!signal_emit(signal, 4, params, NULL, NULL, node)) { - printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, - TXT_STATUSBAR_UNKNOWN_COMMAND, cmd); - } else { - read_statusbar_config(); + /* backward compatibility layer */ + oldcmd = NULL; + if (*arg1 == '\0') { + oldcmd = g_strdup("list"); + } else if (g_ascii_strcasecmp(arg2, "enable") == 0) { + oldcmd = g_strdup_printf("add -nodisable %s %s", arg1, params); + } else if (g_ascii_strcasecmp(arg2, "disable") == 0) { + oldcmd = g_strdup_printf("add -disable %s %s", arg1, params); + } else if (g_ascii_strcasecmp(arg2, "reset") == 0) { + oldcmd = g_strdup_printf("reset %s", arg1); + } else if (g_ascii_strcasecmp(arg2, "type") == 0) { + oldcmd = g_strdup_printf("add -type %s %s", params, arg1); + } else if (g_ascii_strcasecmp(arg2, "placement") == 0) { + oldcmd = g_strdup_printf("add -placement %s %s", params, arg1); + } else if (g_ascii_strcasecmp(arg2, "position") == 0) { + oldcmd = g_strdup_printf("add -position %s %s", params, arg1); + } else if (g_ascii_strcasecmp(arg2, "visible") == 0) { + oldcmd = g_strdup_printf("add -visible %s %s", params, arg1); + } else if (g_ascii_strcasecmp(arg2, "add") == 0) { + oldcmd = g_strdup_printf("additem %s %s", params, arg1); + } else if (g_ascii_strcasecmp(arg2, "remove") == 0) { + oldcmd = g_strdup_printf("removeitem %s %s", params, arg1); + } else if (*arg2 == '\0') { + oldcmd = g_strdup_printf("statusbar %s", arg1); + if (command_find(oldcmd) == NULL) { + g_free(oldcmd); + oldcmd = g_strdup_printf("info %s", arg1); + } else { + g_free(oldcmd); + oldcmd = NULL; + } } - g_free(signal); cmd_params_free(free_arg); + if (oldcmd) { + command_runsub("statusbar", oldcmd, NULL, NULL); + g_free(oldcmd); + } else { + command_runsub("statusbar", data, NULL, NULL); + } + + return; } void statusbar_config_init(void) @@ -571,18 +767,22 @@ void statusbar_config_init(void) signal_add_last("setup reread", (SIGNAL_FUNC) read_statusbar_config); signal_add("theme changed", (SIGNAL_FUNC) read_statusbar_config); - command_bind("statusbar", NULL, (SIGNAL_FUNC) cmd_statusbar); - command_bind("statusbar enable", NULL, (SIGNAL_FUNC) cmd_statusbar_enable); - command_bind("statusbar disable", NULL, (SIGNAL_FUNC) cmd_statusbar_disable); - command_bind("statusbar reset", NULL, (SIGNAL_FUNC) cmd_statusbar_reset); - command_bind("statusbar add", NULL, (SIGNAL_FUNC) cmd_statusbar_add); - command_bind("statusbar remove", NULL, (SIGNAL_FUNC) cmd_statusbar_remove); - command_bind("statusbar type", NULL, (SIGNAL_FUNC) cmd_statusbar_type); - command_bind("statusbar placement", NULL, (SIGNAL_FUNC) cmd_statusbar_placement); - command_bind("statusbar position", NULL, (SIGNAL_FUNC) cmd_statusbar_position); - command_bind("statusbar visible", NULL, (SIGNAL_FUNC) cmd_statusbar_visible); + command_bind("statusbar", NULL, (SIGNAL_FUNC) cmd_statusbar); + command_bind("statusbar list", NULL, (SIGNAL_FUNC) cmd_statusbar_list); + command_bind_data("statusbar add", NULL, (SIGNAL_FUNC) cmd_statusbar_add_modify, GINT_TO_POINTER(TRUE)); + command_bind_data("statusbar modify", NULL, (SIGNAL_FUNC) cmd_statusbar_add_modify, GINT_TO_POINTER(FALSE)); + command_bind("statusbar reset", NULL, (SIGNAL_FUNC) cmd_statusbar_reset); + command_bind("statusbar info", NULL, (SIGNAL_FUNC) cmd_statusbar_info); + command_bind_data("statusbar additem", NULL, (SIGNAL_FUNC) cmd_statusbar_additem_modifyitem, GINT_TO_POINTER(TRUE)); + command_bind_data("statusbar modifyitem", NULL, (SIGNAL_FUNC) cmd_statusbar_additem_modifyitem, GINT_TO_POINTER(FALSE)); + command_bind("statusbar removeitem", NULL, (SIGNAL_FUNC) cmd_statusbar_removeitem); - command_set_options("statusbar add", "+before +after +priority +alignment"); + command_set_options("statusbar additem", "+before +after +priority +alignment"); + command_set_options("statusbar modifyitem", "+before +after +priority +alignment"); + command_set_options("statusbar add", + "disable nodisable +type +placement +position +visible"); + command_set_options("statusbar modify", + "disable nodisable +type +placement +position +visible"); } void statusbar_config_deinit(void) @@ -590,14 +790,13 @@ void statusbar_config_deinit(void) signal_remove("setup reread", (SIGNAL_FUNC) read_statusbar_config); signal_remove("theme changed", (SIGNAL_FUNC) read_statusbar_config); - command_unbind("statusbar", (SIGNAL_FUNC) cmd_statusbar); - command_unbind("statusbar enable", (SIGNAL_FUNC) cmd_statusbar_enable); - command_unbind("statusbar disable", (SIGNAL_FUNC) cmd_statusbar_disable); - command_unbind("statusbar reset", (SIGNAL_FUNC) cmd_statusbar_reset); - command_unbind("statusbar add", (SIGNAL_FUNC) cmd_statusbar_add); - command_unbind("statusbar remove", (SIGNAL_FUNC) cmd_statusbar_remove); - command_unbind("statusbar type", (SIGNAL_FUNC) cmd_statusbar_type); - command_unbind("statusbar placement", (SIGNAL_FUNC) cmd_statusbar_placement); - command_unbind("statusbar position", (SIGNAL_FUNC) cmd_statusbar_position); - command_unbind("statusbar visible", (SIGNAL_FUNC) cmd_statusbar_visible); + command_unbind("statusbar", (SIGNAL_FUNC) cmd_statusbar); + command_unbind("statusbar list", (SIGNAL_FUNC) cmd_statusbar_list); + command_unbind_full("statusbar add", (SIGNAL_FUNC) cmd_statusbar_add_modify, GINT_TO_POINTER(TRUE)); + command_unbind_full("statusbar modify", (SIGNAL_FUNC) cmd_statusbar_add_modify, GINT_TO_POINTER(FALSE)); + command_unbind("statusbar reset", (SIGNAL_FUNC) cmd_statusbar_reset); + command_unbind("statusbar info", (SIGNAL_FUNC) cmd_statusbar_info); + command_unbind_full("statusbar additem", (SIGNAL_FUNC) cmd_statusbar_additem_modifyitem, GINT_TO_POINTER(TRUE)); + command_unbind_full("statusbar modifyitem", (SIGNAL_FUNC) cmd_statusbar_additem_modifyitem, GINT_TO_POINTER(FALSE)); + command_unbind("statusbar removeitem", (SIGNAL_FUNC) cmd_statusbar_removeitem); } diff --git a/src/otr/Makefile.am b/src/otr/Makefile.am new file mode 100644 index 00000000..7cc02d6d --- /dev/null +++ b/src/otr/Makefile.am @@ -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 \ + otr-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 diff --git a/src/otr/irssi-otr.h b/src/otr/irssi-otr.h new file mode 100644 index 00000000..718e1e66 --- /dev/null +++ b/src/otr/irssi-otr.h @@ -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 */ diff --git a/src/otr/key.c b/src/otr/key.c new file mode 100644 index 00000000..7fee4084 --- /dev/null +++ b/src/otr/key.c @@ -0,0 +1,404 @@ +/* + * 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_STARTED = 1, + KEY_GEN_RUNNING = 2, + KEY_GEN_FINISHED = 3, + KEY_GEN_ERROR = 4, +}; + +/* + * 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; + key_gen_state.status = KEY_GEN_STARTED; + + /* 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); +} diff --git a/src/otr/key.h b/src/otr/key.h new file mode 100644 index 00000000..259a8e67 --- /dev/null +++ b/src/otr/key.h @@ -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 */ diff --git a/src/otr/module.h b/src/otr/module.h new file mode 100644 index 00000000..513ee03b --- /dev/null +++ b/src/otr/module.h @@ -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, const char *target); + +#endif /* IRSSI_OTR_MODULE */ diff --git a/src/otr/otr-fe.c b/src/otr/otr-fe.c new file mode 100644 index 00000000..13e37576 --- /dev/null +++ b/src/otr/otr-fe.c @@ -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"); +} diff --git a/src/otr/otr-fe.h b/src/otr/otr-fe.h new file mode 100644 index 00000000..c9aaebb3 --- /dev/null +++ b/src/otr/otr-fe.h @@ -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 diff --git a/src/otr/otr-formats.c b/src/otr/otr-formats.c new file mode 100644 index 00000000..e430b6b5 --- /dev/null +++ b/src/otr/otr-formats.c @@ -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 } +}; diff --git a/src/otr/otr-formats.h b/src/otr/otr-formats.h new file mode 100644 index 00000000..454b2f6c --- /dev/null +++ b/src/otr/otr-formats.h @@ -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 */ diff --git a/src/otr/otr-module.c b/src/otr/otr-module.c new file mode 100644 index 00000000..f561b91c --- /dev/null +++ b/src/otr/otr-module.c @@ -0,0 +1,267 @@ +/* + * 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 "common.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, const char *target) +{ + 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(5, server, msg, nick, address, target); + } 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(5, server, new_msg, nick, address, target); + } + } + + 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(); +} + +void otr_core_abicheck(int *version) +{ + *version = IRSSI_ABI_VERSION; +} diff --git a/src/otr/otr-ops.c b/src/otr/otr-ops.c new file mode 100644 index 00000000..4e8c4e81 --- /dev/null +++ b/src/otr/otr-ops.c @@ -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", 5, server, message, username, server->connrec->address, server->nick); + 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, +}; diff --git a/src/otr/otr.c b/src/otr/otr.c new file mode 100644 index 00000000..765bc048 --- /dev/null +++ b/src/otr/otr.c @@ -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); +} diff --git a/src/otr/otr.h b/src/otr/otr.h new file mode 100644 index 00000000..60a08184 --- /dev/null +++ b/src/otr/otr.h @@ -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 */ |