diff options
author | Wilmer van der Gaast <wilmer@gaast.net> | 2010-10-09 11:41:19 -0700 |
---|---|---|
committer | Wilmer van der Gaast <wilmer@gaast.net> | 2010-10-09 11:41:19 -0700 |
commit | 619770237590e4a760346f2e12681d7e2220dda4 (patch) | |
tree | 8d0d391407280ab74e1fc876d6f272110b474897 | |
parent | 23b29c67968f3dd39e7d6970acc5669556f4c8b9 (diff) | |
parent | 27b407fde1844a0e03f1a9d92d2a1c4a40435f9b (diff) |
Merging OTR branch. It's more or less a plugin if you enable it, and
otherwise a no-op. DO NOT INSTALL THIS ON PUBLIC SERVERS.
-rw-r--r-- | Makefile | 18 | ||||
-rw-r--r-- | bitlbee.h | 1 | ||||
-rw-r--r-- | commands.h | 2 | ||||
-rwxr-xr-x | configure | 39 | ||||
-rw-r--r-- | debian/control | 13 | ||||
-rwxr-xr-x | debian/rules | 24 | ||||
-rw-r--r-- | doc/README | 9 | ||||
-rw-r--r-- | doc/user-guide/commands.xml | 202 | ||||
-rw-r--r-- | irc.c | 23 | ||||
-rw-r--r-- | irc.h | 41 | ||||
-rw-r--r-- | irc_commands.c | 4 | ||||
-rw-r--r-- | irc_im.c | 86 | ||||
-rw-r--r-- | lib/events.h | 6 | ||||
-rw-r--r-- | lib/events_glib.c | 5 | ||||
-rw-r--r-- | lib/misc.c | 12 | ||||
-rw-r--r-- | lib/ssl_bogus.c | 9 | ||||
-rw-r--r-- | lib/ssl_client.h | 3 | ||||
-rw-r--r-- | lib/ssl_gnutls.c | 11 | ||||
-rw-r--r-- | lib/ssl_nss.c | 12 | ||||
-rw-r--r-- | lib/ssl_openssl.c | 12 | ||||
-rw-r--r-- | log.c | 20 | ||||
-rw-r--r-- | log.h | 12 | ||||
-rw-r--r-- | nick.c | 4 | ||||
-rw-r--r-- | otr.c | 1783 | ||||
-rw-r--r-- | otr.h | 82 | ||||
-rw-r--r-- | protocols/bee_user.c | 4 | ||||
-rw-r--r-- | protocols/jabber/jabber.c | 1 | ||||
-rw-r--r-- | protocols/msn/msn.c | 1 | ||||
-rw-r--r-- | protocols/nogaim.h | 5 | ||||
-rw-r--r-- | protocols/oscar/oscar.c | 1 | ||||
-rw-r--r-- | protocols/twitter/twitter.c | 1 | ||||
-rw-r--r-- | protocols/yahoo/yahoo.c | 1 | ||||
-rw-r--r-- | query.c | 9 | ||||
-rw-r--r-- | root_commands.c | 53 | ||||
-rw-r--r-- | sock.h | 4 | ||||
-rw-r--r-- | storage.c | 29 | ||||
-rw-r--r-- | unix.c | 14 |
37 files changed, 2471 insertions, 85 deletions
@@ -9,7 +9,7 @@ -include Makefile.settings # Program variables -objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) +objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o $(OTR_BI) query.o root_commands.o set.o storage.o $(STORAGE_OBJS) headers = bitlbee.h commands.h conf.h config.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h lib/events.h lib/ftutil.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/account.h protocols/bee.h protocols/ft.h protocols/nogaim.h subdirs = lib protocols @@ -26,18 +26,18 @@ endif # Expansion of variables subdirobjs = $(foreach dir,$(subdirs),$(dir)/$(dir).o) -all: $(OUTFILE) +all: $(OUTFILE) $(OTR_PI) $(MAKE) -C doc uninstall: uninstall-bin uninstall-doc @echo -e '\nmake uninstall does not remove files in '$(DESTDIR)$(ETCDIR)', you can use make uninstall-etc to do that.\n' -install: install-bin install-doc +install: install-bin install-doc install-plugins @if ! [ -d $(DESTDIR)$(CONFIG) ]; then echo -e '\nThe configuration directory $(DESTDIR)$(CONFIG) does not exist yet, don'\''t forget to create it!'; fi @if ! [ -e $(DESTDIR)$(ETCDIR)/bitlbee.conf ]; then echo -e '\nNo files are installed in '$(DESTDIR)$(ETCDIR)' by make install. Run make install-etc to do that.'; fi @echo -.PHONY: install install-bin install-etc install-doc \ +.PHONY: install install-bin install-etc install-doc install-plugins \ uninstall uninstall-bin uninstall-etc uninstall-doc \ all clean distclean tar $(subdirs) @@ -103,6 +103,12 @@ uninstall-etc: rm -f $(DESTDIR)$(ETCDIR)/bitlbee.conf -rmdir $(DESTDIR)$(ETCDIR) +install-plugins: +ifdef OTR_PI + mkdir -p $(DESTDIR)$(PLUGINDIR) + install -m 0755 otr.so $(DESTDIR)$(PLUGINDIR) +endif + tar: fakeroot debian/rules clean || make distclean x=$$(basename $$(pwd)); \ @@ -112,6 +118,10 @@ tar: $(subdirs): @$(MAKE) -C $@ $(MAKECMDGOALS) +$(OTR_PI): %.so: $(SRCDIR)%.c + @echo '*' Building plugin $@ + @$(CC) $(CFLAGS) $(OTRFLAGS) -fPIC -shared $< -o $@ + $(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ @@ -161,6 +161,7 @@ gboolean bitlbee_io_current_client_write( gpointer data, gint source, b_input_co void root_command_string( irc_t *irc, char *command ); void root_command( irc_t *irc, char *command[] ); +gboolean root_command_add( const char *command, int params, void (*func)(irc_t *, char **args), int flags ); gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond ); gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond ); @@ -36,7 +36,7 @@ typedef struct command int flags; } command_t; -extern const command_t commands[]; +extern command_t root_commands[]; #define IRC_CMD_PRE_LOGIN 1 #define IRC_CMD_LOGGED_IN 2 @@ -33,6 +33,7 @@ debug=0 strip=1 gcov=0 plugins=1 +otr=0 events=glib ldap=0 @@ -71,11 +72,14 @@ Option Description Default --twitter=0/1 Disable/enable Twitter part $twitter --purple=0/1 Disable/enable libpurple support $purple + (automatically disables other protocol modules) --debug=0/1 Disable/enable debugging $debug --strip=0/1 Disable/enable binary stripping $strip --gcov=0/1 Disable/enable test coverage reporting $gcov --plugins=0/1 Disable/enable plugins support $plugins +--otr=0/1/auto/plugin + Disable/enable OTR encryption support $otr --events=... Event handler (glib, libevent) $events --ssl=... SSL library to use (gnutls, nss, openssl, bogus, auto) @@ -488,6 +492,33 @@ else echo '#define WITH_PLUGINS' >> config.h fi +otrprefix="" +for i in / /usr /usr/local; do + if [ -f ${i}/lib/libotr.a ]; then + otrprefix=${i} + break + fi +done +if [ "$otr" = "auto" ]; then + if [ -n "$otrprefix" ]; then + otr=1 + else + otr=0 + fi +fi +if [ "$otr" = 1 ]; then + # BI == built-in + echo '#define OTR_BI' >> config.h + echo "EFLAGS+=-L${otrprefix}/lib -lotr" >> Makefile.settings + echo "CFLAGS+=-I${otrprefix}/include" >> Makefile.settings + echo 'OTR_BI=otr.o' >> Makefile.settings +elif [ "$otr" = "plugin" ]; then + echo '#define OTR_PI' >> config.h + echo "OTRFLAGS=-L${otrprefix}/lib -lotr" >> Makefile.settings + echo "CFLAGS+=-I${otrprefix}/include" >> Makefile.settings + echo 'OTR_PI=otr.so' >> Makefile.settings +fi + if [ ! -e doc/user-guide/help.txt ] && ! type xmlto > /dev/null 2> /dev/null; then echo echo 'WARNING: Building from an unreleased source tree without prebuilt helpfile.' @@ -682,6 +713,14 @@ else echo ' Binary stripping disabled.' fi +if [ "$otr" = "1" ]; then + echo ' Off-the-Record (OTR) Messaging enabled.' +elif [ "$otr" = "plugin" ]; then + echo ' Off-the-Record (OTR) Messaging enabled (as a plugin).' +else + echo ' Off-the-Record (OTR) Messaging disabled.' +fi + echo ' Using event handler: '$events echo ' Using SSL library: '$ssl echo ' Building with these storage backends: '$STORAGES diff --git a/debian/control b/debian/control index 56859a58..19c3b587 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Priority: optional Maintainer: Wilmer van der Gaast <wilmer@gaast.net> Uploaders: Jelmer Vernooij <jelmer@samba.org> Standards-Version: 3.8.4 -Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), po-debconf, libpurple-dev, debhelper (>= 6) +Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), po-debconf, libpurple-dev, libotr2-dev, debhelper (>= 6) Homepage: http://www.bitlbee.org/ Vcs-Bzr: http://code.bitlbee.org/bitlbee/ DM-Upload-Allowed: yes @@ -58,3 +58,14 @@ Description: An IRC to other chat networks gateway (dev files) and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net. . This package holds development stuff for compiling plug-ins. + +Package: bitlbee-plugin-otr +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends}, bitlbee (= ${bee:Version}) | bitlbee-libpurple (= ${bee:Version}) +Description: An IRC to other chat networks gateway (default version) + This program can be used as an IRC server which forwards everything you + say to people on other chat networks: Jabber (which includes Google Talk + and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net. + . + This package contains a plugin that adds support for Off-The-Record + encryption of instant messages. diff --git a/debian/rules b/debian/rules index c5397085..8cc46b87 100755 --- a/debian/rules +++ b/debian/rules @@ -7,8 +7,9 @@ # exercise is over now. # -# Include the bitlbee-libpurple variant by default +# Include the bitlbee-libpurple variant and OTR plugin by default BITLBEE_LIBPURPLE ?= 1 +BITLBEE_OTR ?= plugin BITLBEE_CONFIGURE_FLAGS ?= DEBUG ?= 0 @@ -21,12 +22,16 @@ ifneq ($(BITLBEE_LIBPURPLE),1) DH_OPTIONS += -Nbitlbee-libpurple endif +ifneq ($(BITLBEE_OTR),plugin) +DH_OPTIONS += -Nbitlbee-plugin-otr +endif + build: build-stamp build-stamp: dh_testdir mkdir -p debian/build-native - ROOT=$$PWD; cd debian/build-native; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent $(BITLBEE_CONFIGURE_FLAGS) + ROOT=$$PWD; cd debian/build-native; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent --otr=$(BITLBEE_OTR) $(BITLBEE_CONFIGURE_FLAGS) $(MAKE) -C debian/build-native ifeq ($(BITLBEE_LIBPURPLE),1) @@ -55,19 +60,18 @@ install: build dh_clean -k dh_installdirs - $(MAKE) -C debian/build-native install DESTDIR=`pwd`/debian/bitlbee - $(MAKE) -C debian/build-native install-etc DESTDIR=`pwd`/debian/bitlbee-common + $(MAKE) -C debian/build-native install-bin DESTDIR=`pwd`/debian/bitlbee + $(MAKE) -C debian/build-native install-etc install-doc DESTDIR=`pwd`/debian/bitlbee-common $(MAKE) -C debian/build-native install-dev DESTDIR=`pwd`/debian/bitlbee-dev - patch debian/bitlbee-common/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff + $(MAKE) -C debian/build-native install-plugins DESTDIR=`pwd`/debian/bitlbee-plugin-otr ifeq ($(BITLBEE_LIBPURPLE),1) - $(MAKE) -C debian/build-libpurple install DESTDIR=`pwd`/debian/bitlbee-libpurple + $(MAKE) -C debian/build-libpurple install-bin DESTDIR=`pwd`/debian/bitlbee-libpurple ln -sf debian/bitlbee.prerm debian/bitlbee-libpurple.prerm endif - mkdir -p debian/bitlbee-common/usr - mv debian/bitlbee/usr/share debian/bitlbee-common/usr - rm -rf debian/bitlbee-libpurple/usr/share + patch debian/bitlbee-common/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff + chmod 640 debian/bitlbee-common/etc/bitlbee/bitlbee.conf binary-common: dh_testdir @@ -78,7 +82,7 @@ binary-common: dh_installdocs #--link-doc=bitlbee-common # TODO: Restore --link-doc up here and remove the hack below once # Hardy and Lenny are deprecated. - for p in bitlbee bitlbee-libpurple bitlbee-dev; do rm -r debian/$$p/usr/share/doc/$$p && ln -s bitlbee-common debian/$$p/usr/share/doc/$$p; done + for p in bitlbee bitlbee-libpurple bitlbee-dev bitlbee-plugin-otr; do rm -r debian/$$p/usr/share/doc/$$p && ln -s bitlbee-common debian/$$p/usr/share/doc/$$p || true; done dh_installdebconf dh_installinit --init-script=bitlbee dh_installman @@ -66,6 +66,11 @@ DEPENDENCIES BitlBee's only real dependency is GLib. This is available on virtually every platform. Any recent version of GLib (2.4 or higher) will work. +Off-the-Record encryption support can be included if libotr is available on +your machine. Pass --otr=1 to configure to build it into BitlBee, or +--otr=plugin to build it as a separate loadable plugin (mostly meant for +distro packages). + These days, many IM protocols use SSL/TLS connections (for authentication or for the whole session). BitlBee can use several SSL libraries for this: GnuTLS, NSS (which comes with Mozilla) and OpenSSL. OpenSSL is not GPL- @@ -144,8 +149,8 @@ More documentation on the Wiki: http://wiki.bitlbee.org/ -A NOTE ON ENCRYPTION -==================== +A NOTE ON PASSWORD ENCRYPTION +============================= There used to be a note here about the simple obfuscation method used to make the passwords in the configuration files unreadable. However, BitlBee diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index 6adc4001..6ccaab8c 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -366,6 +366,184 @@ </para> </description> </bitlbee-command> + + <bitlbee-command name="otr"> + <short-description>Off-the-Record encryption control</short-description> + <syntax>otr <subcommand> [<arguments>]</syntax> + + <description> + + <para> + Available subcommands: connect, disconnect, smp, smpq, trust, info, keygen, and forget. See <emphasis>help otr <subcommand></emphasis> for more information. + </para> + + </description> + + <bitlbee-command name="connect"> + <syntax>otr connect <nick></syntax> + + <description> + + <para> + Attempts to establish an encrypted connection with the specified user by sending a magic string. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="disconnect"> + <syntax>otr disconnect <nick></syntax> + + <description> + + <para> + Resets the connection with the specified user to cleartext. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="smp"> + <syntax>otr smp <nick> <secret></syntax> + + <description> + + <para> + Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol. + </para> + + <para> + If an SMP challenge has been received from the given user, responds with the specified secret/answer. Otherwise, sends a challenge for the given secret. + </para> + + <para> + Note that there are two flavors of SMP challenges: "shared-secret" and "question & answer". This command is used to respond to both of them, or to initiate a shared-secret style exchange. Use the <emphasis>otr smpq</emphasis> command to initiate a "Q&A" session. + </para> + + <para> + When responding to a "Q&A" challenge, the local trust value is not altered. Only the <emphasis>asking party</emphasis> sets trust in the case of success. Use <emphasis>otr smpq</emphasis> to pose your challenge. In a shared-secret exchange, both parties set their trust according to the outcome. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="smpq"> + <syntax>otr smpq <nick> <question> <answer></syntax> + + <description> + + <para> + Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol, Q&A style. + </para> + + <para> + Initiates an SMP session in "question & answer" style. The question is transmitted with the initial SMP packet and used to prompt the other party. You must be confident that only they know the answer. If the protocol succeeds (i.e. they answer correctly), the fingerprint will be trusted. Note that the answer must be entered exactly, case and punctuation count! + </para> + + <para> + Note that this style of SMP only affects the trust setting on your side. Expect your opponent to send you their own challenge. Alternatively, if you and the other party have a shared secret, use the <emphasis>otr smp</emphasis> command. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="trust"> + <syntax>otr trust <nick> <fp1> <fp2> <fp3> <fp4> <fp5></syntax> + + <description> + + <para> + Manually affirms trust in the specified fingerprint, given as five blocks of precisely eight (hexadecimal) digits each. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="info"> + <syntax>otr info</syntax> + <syntax>otr info <nick></syntax> + + <description> + + <para> + Shows information about the OTR state. The first form lists our private keys and current OTR contexts. The second form displays information about the connection with a given user, including the list of their known fingerprints. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="keygen"> + <syntax>otr keygen <account-no></syntax> + + <description> + + <para> + Generates a new OTR private key for the given account. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="forget"> + <syntax>otr forget <thing> <arguments></syntax> + + <description> + + <para> + Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See <emphasis>help otr forget <thing></emphasis> for more information. + </para> + + </description> + + <bitlbee-command name="fingerprint"> + <syntax>otr forget fingerprint <nick> <fingerprint></syntax> + + <description> + + <para> + Drops the specified fingerprint from the given user's OTR connection context. It is allowed to specify only a (unique) prefix of the desired fingerprint. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="context"> + <syntax>otr forget context <nick></syntax> + + <description> + + <para> + Forgets the entire OTR context associated with the given user. This includes current message and protocol states, as well as any fingerprints for that user. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="key"> + <syntax>otr forget key <fingerprint></syntax> + + <description> + + <para> + Forgets an OTR private key matching the specified fingerprint. It is allowed to specify only a (unique) prefix of the fingerprint. + </para> + + </description> + + </bitlbee-command> + + </bitlbee-command> + + </bitlbee-command> <bitlbee-command name="set"> <short-description>Miscellaneous settings</short-description> @@ -590,6 +768,16 @@ </bitlbee-setting> + <bitlbee-setting name="color_encrypted" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="control_channel" type="string" scope="global"> <default>&bitlbee</default> @@ -930,6 +1118,20 @@ </description> </bitlbee-setting> + <bitlbee-setting name="otr_policy" type="string" scope="global"> + <default>opportunistic</default> + <possible-values>never, opportunistic, manual, always</possible-values> + + <description> + <para> + This setting controls the policy for establishing Off-the-Record connections. + </para> + <para> + A value of "never" effectively disables the OTR subsystem. In "opportunistic" mode, a magic whitespace pattern will be appended to the first message sent to any user. If the peer is also running opportunistic OTR, an encrypted connection will be set up automatically. On "manual", on the other hand, OTR connections must be established explicitly using <emphasis>otr connect</emphasis>. Finally, the setting "always" enforces encrypted communication by causing BitlBee to refuse to send any cleartext messages at all. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="password" type="string" scope="both"> <description> <para> @@ -28,6 +28,7 @@ #include "dcc.h" GSList *irc_connection_list; +GSList *irc_plugins; static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond ); static char *set_eval_charset( set_t *set, char *value ); @@ -41,6 +42,7 @@ irc_t *irc_new( int fd ) socklen_t socklen = sizeof( sock ); char *host = NULL, *myhost = NULL; irc_user_t *iu; + GSList *l; set_t *s; bee_t *b; @@ -164,6 +166,13 @@ irc_t *irc_new( int fd ) nogaim_init(); + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->irc_new ) + p->irc_new( irc ); + } + return irc; } @@ -207,6 +216,8 @@ static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data ); void irc_free( irc_t * irc ) { + GSList *l; + irc->status |= USTATUS_SHUTDOWN; log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd ); @@ -215,6 +226,13 @@ void irc_free( irc_t * irc ) if( storage_save( irc, NULL, TRUE ) != STORAGE_OK ) log_message( LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick ); + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->irc_free ) + p->irc_free( irc ); + } + irc_connection_list = g_slist_remove( irc_connection_list, irc ); while( irc->queries != NULL ) @@ -932,3 +950,8 @@ static char *set_eval_bw_compat( set_t *set, char *value ) return SET_INVALID; } + +void register_irc_plugin( const struct irc_plugin *p ) +{ + irc_plugins = g_slist_prepend( irc_plugins, (gpointer) p ); +} @@ -27,7 +27,7 @@ #define _IRC_H #define IRC_MAX_LINE 512 -#define IRC_MAX_ARGS 8 +#define IRC_MAX_ARGS 16 #define IRC_LOGIN_TIMEOUT 60 #define IRC_PING_STRING "PinglBee" @@ -85,6 +85,9 @@ typedef struct irc gint ping_source_id; gint login_source_id; /* To slightly delay some events at login time. */ + struct otr *otr; /* OTR state and book keeping, used by the OTR plugin. + TODO: Some mechanism for plugindata. */ + struct bee *b; } irc_t; @@ -92,6 +95,9 @@ typedef enum { /* Replaced with iu->last_channel IRC_USER_PRIVATE = 1, */ IRC_USER_AWAY = 2, + + IRC_USER_OTR_ENCRYPTED = 0x10000, + IRC_USER_OTR_TRUSTED = 0x20000, } irc_user_flags_t; typedef struct irc_user @@ -213,6 +219,37 @@ typedef enum IRC_CDU_KICK, } irc_channel_del_user_type_t; +/* These are a glued a little bit to the core/bee layer and a little bit to + IRC. The first user is OTR, and I guess at some point we'll get to shape + this a little bit more as other uses come up. */ +typedef struct irc_plugin +{ + /* Called at the end of irc_new(). Can be used to add settings, etc. */ + gboolean (*irc_new)( irc_t *irc ); + /* At the end of irc_free(). */ + void (*irc_free)( irc_t *irc ); + + /* Problem with the following two functions is ordering if multiple + plugins are handling them. Let's keep fixing that problem for + whenever it becomes important. */ + + /* Called by bee_irc_user_privmsg_cb(). Return NULL if you want to + abort sending the msg. */ + char* (*filter_msg_out)( irc_user_t *iu, char *msg, int flags ); + /* Called by bee_irc_user_msg(). Return NULL if you swallowed the + message and don't want anything to go to the user. */ + char* (*filter_msg_in)( irc_user_t *iu, char *msg, int flags ); + + /* From storage.c functions. Ideally these should not be used + and instead data should be stored in settings which will get + saved automatically. Consider these deprecated! */ + void (*storage_load)( irc_t *irc ); + void (*storage_save)( irc_t *irc ); + void (*storage_remove)( const char *nick ); +} irc_plugin_t; + +extern GSList *irc_plugins; /* struct irc_plugin */ + /* irc.c */ extern GSList *irc_connection_list; @@ -238,6 +275,8 @@ int irc_check_login( irc_t *irc ); void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv ); +void register_irc_plugin( const struct irc_plugin *p ); + /* irc_channel.c */ irc_channel_t *irc_channel_new( irc_t *irc, const char *name ); irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name ); diff --git a/irc_commands.c b/irc_commands.c index ce276051..d9ff929f 100644 --- a/irc_commands.c +++ b/irc_commands.c @@ -634,8 +634,8 @@ static void irc_cmd_completions( irc_t *irc, char **cmd ) irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK" ); - for( i = 0; commands[i].command; i ++ ) - irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", commands[i].command ); + for( i = 0; root_commands[i].command; i ++ ) + irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", root_commands[i].command ); for( h = global.help; h; h = h->next ) irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title ); @@ -191,13 +191,15 @@ void bee_irc_channel_update( irc_t *irc, irc_channel_t *ic, irc_user_t *iu ) } } -static gboolean bee_irc_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at ) +static gboolean bee_irc_user_msg( bee_t *bee, bee_user_t *bu, const char *msg_, time_t sent_at ) { irc_t *irc = bee->ui_data; irc_user_t *iu = (irc_user_t *) bu->ui_data; char *dst, *prefix = NULL; char *wrapped, *ts = NULL; irc_channel_t *ic = NULL; + char *msg = g_strdup( msg_ ); + GSList *l; if( sent_at > 0 && set_getbool( &irc->b->set, "display_timestamps" ) ) ts = irc_format_timestamp( irc, sent_at ); @@ -223,11 +225,41 @@ static gboolean bee_irc_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, t ts = NULL; } + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->filter_msg_in ) + { + char *s = p->filter_msg_in( iu, msg, 0 ); + if( s ) + { + if( s != msg ) + g_free( msg ); + msg = s; + } + else + { + /* Modules can swallow messages. */ + return TRUE; + } + } + } + + if( ( g_strcasecmp( set_getstr( &bee->set, "strip_html" ), "always" ) == 0 ) || + ( ( bu->ic->flags & OPT_DOES_HTML ) && set_getbool( &bee->set, "strip_html" ) ) ) + { + char *s = g_strdup( msg ); + strip_html( s ); + g_free( msg ); + msg = s; + } + wrapped = word_wrap( msg, 425 ); irc_send_msg( iu, "PRIVMSG", dst, wrapped, prefix ); g_free( wrapped ); g_free( prefix ); + g_free( msg ); g_free( ts ); return TRUE; @@ -348,18 +380,18 @@ static gboolean bee_irc_user_privmsg( irc_user_t *iu, const char *msg ) set_getint( &iu->irc->b->set, "away_reply_timeout" ); } + if( iu->pastebuf == NULL ) + iu->pastebuf = g_string_new( msg ); + else + { + b_event_remove( iu->pastebuf_timer ); + g_string_append_printf( iu->pastebuf, "\n%s", msg ); + } + if( set_getbool( &iu->irc->b->set, "paste_buffer" ) ) { int delay; - if( iu->pastebuf == NULL ) - iu->pastebuf = g_string_new( msg ); - else - { - b_event_remove( iu->pastebuf_timer ); - g_string_append_printf( iu->pastebuf, "\n%s", msg ); - } - if( ( delay = set_getint( &iu->irc->b->set, "paste_buffer_delay" ) ) <= 5 ) delay *= 1000; @@ -368,17 +400,45 @@ static gboolean bee_irc_user_privmsg( irc_user_t *iu, const char *msg ) return TRUE; } else - return bee_user_msg( iu->irc->b, iu->bu, msg, 0 ); + { + bee_irc_user_privmsg_cb( iu, 0, 0 ); + + return TRUE; + } } static gboolean bee_irc_user_privmsg_cb( gpointer data, gint fd, b_input_condition cond ) { irc_user_t *iu = data; + char *msg = g_string_free( iu->pastebuf, FALSE ); + GSList *l; + + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->filter_msg_out ) + { + char *s = p->filter_msg_out( iu, msg, 0 ); + if( s ) + { + if( s != msg ) + g_free( msg ); + msg = s; + } + else + { + /* Modules can swallow messages. */ + iu->pastebuf = NULL; + g_free( msg ); + return FALSE; + } + } + } - bee_user_msg( iu->irc->b, iu->bu, iu->pastebuf->str, 0 ); + bee_user_msg( iu->irc->b, iu->bu, msg, 0 ); - g_string_free( iu->pastebuf, TRUE ); - iu->pastebuf = 0; + g_free( msg ); + iu->pastebuf = NULL; iu->pastebuf_timer = 0; return FALSE; diff --git a/lib/events.h b/lib/events.h index fa30cf27..66c4c6b4 100644 --- a/lib/events.h +++ b/lib/events.h @@ -80,10 +80,8 @@ G_MODULE_EXPORT gint b_input_add(int fd, b_input_condition cond, b_event_handler G_MODULE_EXPORT gint b_timeout_add(gint timeout, b_event_handler func, gpointer data); G_MODULE_EXPORT void b_event_remove(gint id); -/* For now, closesocket() is only a function when using libevent. With GLib - it's a preprocessor macro. */ -#ifdef EVENTS_LIBEVENT +/* With libevent, this one also cleans up event handlers if that wasn't already + done (the caller is expected to do so but may miss it sometimes). */ G_MODULE_EXPORT void closesocket(int fd); -#endif #endif /* _EVENTS_H_ */ diff --git a/lib/events_glib.c b/lib/events_glib.c index d6ac82cc..3fafc872 100644 --- a/lib/events_glib.c +++ b/lib/events_glib.c @@ -146,3 +146,8 @@ void b_event_remove(gint tag) if (tag > 0) g_source_remove(tag); } + +void closesocket( int fd ) +{ + close( fd ); +} @@ -156,6 +156,7 @@ void strip_html( char *in ) char out[strlen(in)+1]; char *s = out, *cs; int i, matched; + int taglen; memset( out, 0, sizeof( out ) ); @@ -172,9 +173,18 @@ void strip_html( char *in ) while( *in && *in != '>' ) in ++; + taglen = in - cs - 1; /* not <0 because the above loop runs at least once */ if( *in ) { - if( g_strncasecmp( cs+1, "br", 2) == 0 ) + if( g_strncasecmp( cs+1, "b", taglen) == 0 ) + *(s++) = '\x02'; + else if( g_strncasecmp( cs+1, "/b", taglen) == 0 ) + *(s++) = '\x02'; + else if( g_strncasecmp( cs+1, "i", taglen) == 0 ) + *(s++) = '\x1f'; + else if( g_strncasecmp( cs+1, "/i", taglen) == 0 ) + *(s++) = '\x1f'; + else if( g_strncasecmp( cs+1, "br", taglen) == 0 ) *(s++) = '\n'; in ++; } diff --git a/lib/ssl_bogus.c b/lib/ssl_bogus.c index 9c368c66..e7966f57 100644 --- a/lib/ssl_bogus.c +++ b/lib/ssl_bogus.c @@ -27,6 +27,10 @@ int ssl_errno; +void ssl_init( void ) +{ +} + void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) { return( NULL ); @@ -65,3 +69,8 @@ int ssl_pending( void *conn ) { return 0; } + +int ssl_pending( void *conn ) +{ + return 0; +} diff --git a/lib/ssl_client.h b/lib/ssl_client.h index 787d528a..d0340840 100644 --- a/lib/ssl_client.h +++ b/lib/ssl_client.h @@ -46,6 +46,9 @@ extern int ssl_errno; typedef gboolean (*ssl_input_function)(gpointer, void*, b_input_condition); +/* Perform any global initialization the SSL library might need. */ +G_MODULE_EXPORT void ssl_init( void ); + /* Connect to host:port, call the given function when the connection is ready to be used for SSL traffic. This is all done asynchronously, no blocking I/O! (Except for the DNS lookups, for now...) */ diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c index 6e68791d..ee166bd1 100644 --- a/lib/ssl_gnutls.c +++ b/lib/ssl_gnutls.c @@ -60,6 +60,13 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ); +void ssl_init( void ) +{ + gnutls_global_init(); + initialized = TRUE; + atexit( gnutls_global_deinit ); +} + void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) { struct scd *conn = g_new0( struct scd, 1 ); @@ -121,9 +128,7 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con if( !initialized ) { - gnutls_global_init(); - initialized = TRUE; - atexit( gnutls_global_deinit ); + ssl_init(); } gnutls_certificate_allocate_credentials( &conn->xcred ); diff --git a/lib/ssl_nss.c b/lib/ssl_nss.c index de6e7ec6..b0e2f9f9 100644 --- a/lib/ssl_nss.c +++ b/lib/ssl_nss.c @@ -90,6 +90,14 @@ static SECStatus nss_bad_cert (void *arg, PRFileDesc *socket) } +void ssl_init( void ) +{ + PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); + NSS_NoDB_Init(NULL); + NSS_SetDomesticPolicy(); + initialized = TRUE; +} + void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) { struct scd *conn = g_new0( struct scd, 1 ); @@ -106,9 +114,7 @@ void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data if( !initialized ) { - PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); - NSS_NoDB_Init(NULL); - NSS_SetDomesticPolicy(); + ssl_init(); } diff --git a/lib/ssl_openssl.c b/lib/ssl_openssl.c index e0143791..64bc9257 100644 --- a/lib/ssl_openssl.c +++ b/lib/ssl_openssl.c @@ -56,6 +56,13 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ); +void ssl_init( void ) +{ + initialized = TRUE; + SSL_library_init(); + // SSLeay_add_ssl_algorithms(); +} + void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) { struct scd *conn = g_new0( struct scd, 1 ); @@ -114,10 +121,7 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con if( !initialized ) { - initialized = TRUE; - SSL_library_init(); - //SSLeay_add_ssl_algorithms(); - //OpenSSL_add_all_algorithms(); + ssl_init(); } meth = TLSv1_client_method(); @@ -29,10 +29,10 @@ static log_t logoutput; -static void log_null(int level, char *logmessage); -static void log_irc(int level, char *logmessage); -static void log_syslog(int level, char *logmessage); -static void log_console(int level, char *logmessage); +static void log_null(int level, const char *logmessage); +static void log_irc(int level, const char *logmessage); +static void log_syslog(int level, const char *logmessage); +static void log_console(int level, const char *logmessage); void log_init(void) { openlog("bitlbee", LOG_PID, LOG_DAEMON); @@ -96,7 +96,7 @@ void log_link(int level, int output) { } -void log_message(int level, char *message, ... ) { +void log_message(int level, const char *message, ... ) { va_list ap; char *msgstring; @@ -121,17 +121,17 @@ void log_message(int level, char *message, ... ) { return; } -void log_error(char *functionname) { +void log_error(const char *functionname) { log_message(LOGLVL_ERROR, "%s: %s", functionname, strerror(errno)); return; } -static void log_null(int level, char *message) { +static void log_null(int level, const char *message) { return; } -static void log_irc(int level, char *message) { +static void log_irc(int level, const char *message) { if(level == LOGLVL_ERROR) irc_write_all(1, "ERROR :Error: %s", message); if(level == LOGLVL_WARNING) @@ -146,7 +146,7 @@ static void log_irc(int level, char *message) { return; } -static void log_syslog(int level, char *message) { +static void log_syslog(int level, const char *message) { if(level == LOGLVL_ERROR) syslog(LOG_ERR, "%s", message); if(level == LOGLVL_WARNING) @@ -160,7 +160,7 @@ static void log_syslog(int level, char *message) { return; } -static void log_console(int level, char *message) { +static void log_console(int level, const char *message) { if(level == LOGLVL_ERROR) fprintf(stderr, "Error: %s\n", message); if(level == LOGLVL_WARNING) @@ -43,17 +43,17 @@ typedef enum { } logoutput_t; typedef struct log_t { - void (*error)(int level, char *logmessage); - void (*warning)(int level, char *logmessage); - void (*informational)(int level, char *logmessage); + void (*error)(int level, const char *logmessage); + void (*warning)(int level, const char *logmessage); + void (*informational)(int level, const char *logmessage); #ifdef DEBUG - void (*debug)(int level, char *logmessage); + void (*debug)(int level, const char *logmessage); #endif } log_t; void log_init(void); void log_link(int level, int output); -void log_message(int level, char *message, ...) G_GNUC_PRINTF( 2, 3 ); -void log_error(char *functionname); +void log_message(int level, const char *message, ...) G_GNUC_PRINTF( 2, 3 ); +void log_error(const char *functionname); #endif @@ -195,8 +195,8 @@ char *nick_gen( bee_user_t *bu ) accents don't just get stripped. Note that it depends on LC_CTYPE being set to something other than C/POSIX. */ if( part ) - part = asc = g_convert( part, -1, "ASCII//TRANSLIT//IGNORE", - "UTF-8", NULL, NULL, NULL ); + part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT", + "UTF-8", "", NULL, NULL, NULL ); if( ret->len == 0 && part && isdigit( *part ) ) g_string_append_c( ret, '_' ); @@ -0,0 +1,1783 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + OTR support (cf. http://www.cypherpunks.ca/otr/) + + (c) 2008-2010 Sven Moritz Hallberg <pesco@khjk.org> + (c) 2008 funded by stonedcoder.org + + files used to store OTR data: + <configdir>/<nick>.otr_keys + <configdir>/<nick>.otr_fprints + + top-level todos: (search for TODO for more ;-)) + integrate otr_load/otr_save with existing storage backends + per-account policy settings + per-user policy settings +*/ + +/* + 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 with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" +#include "irc.h" +#include "otr.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <assert.h> +#include <signal.h> + + +/** OTR interface routines for the OtrlMessageAppOps struct: **/ + +OtrlPolicy op_policy(void *opdata, ConnContext *context); + +void op_create_privkey(void *opdata, const char *accountname, const char *protocol); + +int op_is_logged_in(void *opdata, const char *accountname, const char *protocol, + const char *recipient); + +void op_inject_message(void *opdata, const char *accountname, const char *protocol, + const char *recipient, const char *message); + +int op_display_otr_message(void *opdata, const char *accountname, const char *protocol, + const char *username, const char *msg); + +void op_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, + const char *protocol, const char *username, unsigned char fingerprint[20]); + +void op_write_fingerprints(void *opdata); + +void op_gone_secure(void *opdata, ConnContext *context); + +void op_gone_insecure(void *opdata, ConnContext *context); + +void op_still_secure(void *opdata, ConnContext *context, int is_reply); + +void op_log_message(void *opdata, const char *message); + +int op_max_message_size(void *opdata, ConnContext *context); + +const char *op_account_name(void *opdata, const char *account, const char *protocol); + + +/** otr sub-command handlers: **/ + +static void cmd_otr(irc_t *irc, char **args); +void cmd_otr_connect(irc_t *irc, char **args); +void cmd_otr_disconnect(irc_t *irc, char **args); +void cmd_otr_smp(irc_t *irc, char **args); +void cmd_otr_smpq(irc_t *irc, char **args); +void cmd_otr_trust(irc_t *irc, char **args); +void cmd_otr_info(irc_t *irc, char **args); +void cmd_otr_keygen(irc_t *irc, char **args); +void cmd_otr_forget(irc_t *irc, char **args); + +const command_t otr_commands[] = { + { "connect", 1, &cmd_otr_connect, 0 }, + { "disconnect", 1, &cmd_otr_disconnect, 0 }, + { "smp", 2, &cmd_otr_smp, 0 }, + { "smpq", 3, &cmd_otr_smpq, 0 }, + { "trust", 6, &cmd_otr_trust, 0 }, + { "info", 0, &cmd_otr_info, 0 }, + { "keygen", 1, &cmd_otr_keygen, 0 }, + { "forget", 2, &cmd_otr_forget, 0 }, + { NULL } +}; + +typedef struct { + void *fst; + void *snd; +} pair_t; + +static OtrlMessageAppOps otr_ops; /* collects interface functions required by OTR */ + + +/** misc. helpers/subroutines: **/ + +/* check whether we are already generating a key for a given account */ +int keygen_in_progress(irc_t *irc, const char *handle, const char *protocol); + +/* start background process to generate a (new) key for a given account */ +void otr_keygen(irc_t *irc, const char *handle, const char *protocol); + +/* main function for the forked keygen slave */ +void keygen_child_main(OtrlUserState us, int infd, int outfd); + +/* mainloop handler for when a keygen finishes */ +gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond); + +/* copy the contents of file a to file b, overwriting it if it exists */ +void copyfile(const char *a, const char *b); + +/* read one line of input from a stream, excluding trailing newline */ +void myfgets(char *s, int size, FILE *stream); + +/* some yes/no handlers */ +void yes_keygen(void *data); +void yes_forget_fingerprint(void *data); +void yes_forget_context(void *data); +void yes_forget_key(void *data); + +/* helper to make sure accountname and protocol match the incoming "opdata" */ +struct im_connection *check_imc(void *opdata, const char *accountname, + const char *protocol); + +/* determine the nick for a given handle/protocol pair + returns "handle/protocol" if not found */ +const char *peernick(irc_t *irc, const char *handle, const char *protocol); + +/* turn a hexadecimal digit into its numerical value */ +int hexval(char a); + +/* determine the irc_user_t for a given handle/protocol pair + returns NULL if not found */ +irc_user_t *peeruser(irc_t *irc, const char *handle, const char *protocol); + +/* handle SMP TLVs from a received message */ +void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs); + +/* combined handler for the 'otr smp' and 'otr smpq' commands */ +void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question, + const char *secret); + +/* update op/voice flag of given user according to encryption state and settings + returns 0 if neither op_buddies nor voice_buddies is set to "encrypted", + i.e. msgstate should be announced seperately */ +int otr_update_modeflags(irc_t *irc, irc_user_t *u); + +/* show general info about the OTR subsystem; called by 'otr info' */ +void show_general_otr_info(irc_t *irc); + +/* show info about a given OTR context */ +void show_otr_context_info(irc_t *irc, ConnContext *ctx); + +/* show the list of fingerprints associated with a given context */ +void show_fingerprints(irc_t *irc, ConnContext *ctx); + +/* find a fingerprint by prefix (given as any number of hex strings) */ +Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args); + +/* find a private key by fingerprint prefix (given as any number of hex strings) */ +OtrlPrivKey *match_privkey(irc_t *irc, const char **args); + +/* functions to be called for certain events */ +static const struct irc_plugin otr_plugin; + + +/*** routines declared in otr.h: ***/ + +#ifdef OTR_BI +#define init_plugin otr_init +#endif + +void init_plugin(void) +{ + OTRL_INIT; + + /* fill global OtrlMessageAppOps */ + otr_ops.policy = &op_policy; + otr_ops.create_privkey = &op_create_privkey; + otr_ops.is_logged_in = &op_is_logged_in; + otr_ops.inject_message = &op_inject_message; + otr_ops.notify = NULL; + otr_ops.display_otr_message = &op_display_otr_message; + otr_ops.update_context_list = NULL; + otr_ops.protocol_name = NULL; + otr_ops.protocol_name_free = NULL; + otr_ops.new_fingerprint = &op_new_fingerprint; + otr_ops.write_fingerprints = &op_write_fingerprints; + otr_ops.gone_secure = &op_gone_secure; + otr_ops.gone_insecure = &op_gone_insecure; + otr_ops.still_secure = &op_still_secure; + otr_ops.log_message = &op_log_message; + otr_ops.max_message_size = &op_max_message_size; + otr_ops.account_name = &op_account_name; + otr_ops.account_name_free = NULL; + + root_command_add( "otr", 1, cmd_otr, 0 ); + register_irc_plugin( &otr_plugin ); +} + +gboolean otr_irc_new(irc_t *irc) +{ + set_t *s; + GSList *l; + + irc->otr = g_new0(otr_t, 1); + irc->otr->us = otrl_userstate_create(); + + s = set_add( &irc->b->set, "otr_color_encrypted", "true", set_eval_bool, irc ); + + s = set_add( &irc->b->set, "otr_policy", "oppurtunistic", set_eval_list, irc ); + l = g_slist_prepend( NULL, "never" ); + l = g_slist_prepend( l, "opportunistic" ); + l = g_slist_prepend( l, "manual" ); + l = g_slist_prepend( l, "always" ); + s->eval_data = l; + + return TRUE; +} + +void otr_irc_free(irc_t *irc) +{ + otr_t *otr = irc->otr; + otrl_userstate_free(otr->us); + if(otr->keygen) { + kill(otr->keygen, SIGTERM); + waitpid(otr->keygen, NULL, 0); + /* TODO: remove stale keygen tempfiles */ + } + if(otr->to) + fclose(otr->to); + if(otr->from) + fclose(otr->from); + while(otr->todo) { + kg_t *p = otr->todo; + otr->todo = p->next; + g_free(p); + } + g_free(otr); +} + +void otr_load(irc_t *irc) +{ + char s[512]; + account_t *a; + gcry_error_t e; + gcry_error_t enoent = gcry_error_from_errno(ENOENT); + int kg=0; + + g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, irc->user->nick); + e = otrl_privkey_read(irc->otr->us, s); + if(e && e!=enoent) { + irc_usermsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); + } + g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick); + e = otrl_privkey_read_fingerprints(irc->otr->us, s, NULL, NULL); + if(e && e!=enoent) { + irc_usermsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); + } + + /* check for otr keys on all accounts */ + for(a=irc->b->accounts; a; a=a->next) { + kg = otr_check_for_key(a) || kg; + } + if(kg) { + irc_usermsg(irc, "Notice: " + "The accounts above do not have OTR encryption keys associated with them, yet. " + "These keys are now being generated in the background. " + "You will be notified as they are completed. " + "It is not necessary to wait; " + "BitlBee can be used normally during key generation. " + "You may safely ignore this message if you don't know what OTR is. ;)"); + } +} + +void otr_save(irc_t *irc) +{ + char s[512]; + gcry_error_t e; + + g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick); + e = otrl_privkey_write_fingerprints(irc->otr->us, s); + if(e) { + irc_usermsg(irc, "otr save: %s: %s", s, gcry_strerror(e)); + } + chmod(s, 0600); +} + +void otr_remove(const char *nick) +{ + char s[512]; + + g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, nick); + unlink(s); + g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, nick); + unlink(s); +} + +void otr_rename(const char *onick, const char *nnick) +{ + char s[512], t[512]; + + g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, onick); + g_snprintf(t, 511, "%s%s.otr_keys", global.conf->configdir, nnick); + rename(s,t); + g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, onick); + g_snprintf(t, 511, "%s%s.otr_fprints", global.conf->configdir, nnick); + rename(s,t); +} + +int otr_check_for_key(account_t *a) +{ + irc_t *irc = a->bee->ui_data; + OtrlPrivKey *k; + + /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ + if(a->prpl->options & OPT_NOOTR) { + return 0; + } + + k = otrl_privkey_find(irc->otr->us, a->user, a->prpl->name); + if(k) { + irc_usermsg(irc, "otr: %s/%s ready", a->user, a->prpl->name); + return 0; + } if(keygen_in_progress(irc, a->user, a->prpl->name)) { + irc_usermsg(irc, "otr: keygen for %s/%s already in progress", a->user, a->prpl->name); + return 0; + } else { + irc_usermsg(irc, "otr: starting background keygen for %s/%s", a->user, a->prpl->name); + otr_keygen(irc, a->user, a->prpl->name); + return 1; + } +} + +char *otr_filter_msg_in(irc_user_t *iu, char *msg, int flags) +{ + int ignore_msg; + char *newmsg = NULL; + OtrlTLV *tlvs = NULL; + char *colormsg; + irc_t *irc = iu->irc; + struct im_connection *ic = iu->bu->ic; + + /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ + if(ic->acc->prpl->options & OPT_NOOTR) { + return msg; + } + + ignore_msg = otrl_message_receiving(irc->otr->us, &otr_ops, ic, + ic->acc->user, ic->acc->prpl->name, iu->bu->handle, msg, &newmsg, + &tlvs, NULL, NULL); + + otr_handle_smp(ic, iu->bu->handle, tlvs); + + if(ignore_msg) { + /* this was an internal OTR protocol message */ + return NULL; + } else if(!newmsg) { + /* this was a non-OTR message */ + return g_strdup(msg); + } else { + /* OTR has processed this message */ + ConnContext *context = otrl_context_find(irc->otr->us, iu->bu->handle, + ic->acc->user, ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED && + set_getbool(&ic->bee->set, "otr_color_encrypted")) { + /* color according to f'print trust */ + int color; + const char *trust = context->active_fingerprint->trust; + if(trust && trust[0] != '\0') + color=3; /* green */ + else + color=5; /* red */ + + if(newmsg[0] == ',') { + /* could be a problem with the color code */ + /* insert a space between color spec and message */ + colormsg = g_strdup_printf("\x03%.2d %s\x0F", color, newmsg); + } else { + colormsg = g_strdup_printf("\x03%.2d%s\x0F", color, newmsg); + } + } else { + colormsg = g_strdup(newmsg); + } + otrl_message_free(newmsg); + return colormsg; + } +} + +char *otr_filter_msg_out(irc_user_t *iu, char *msg, int flags) +{ + int st; + char *otrmsg = NULL; + ConnContext *ctx = NULL; + irc_t *irc = iu->irc; + struct im_connection *ic = iu->bu->ic; + + /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ + if(ic->acc->prpl->options & OPT_NOOTR) { + return msg; + } + + st = otrl_message_sending(irc->otr->us, &otr_ops, ic, + ic->acc->user, ic->acc->prpl->name, iu->bu->handle, + msg, NULL, &otrmsg, NULL, NULL); + if(st) { + return NULL; + } + + ctx = otrl_context_find(irc->otr->us, + iu->bu->handle, ic->acc->user, ic->acc->prpl->name, + 1, NULL, NULL, NULL); + + if(otrmsg) { + if(!ctx) { + otrl_message_free(otrmsg); + return NULL; + } + st = otrl_message_fragment_and_send(&otr_ops, ic, ctx, + otrmsg, OTRL_FRAGMENT_SEND_ALL, NULL); + otrl_message_free(otrmsg); + } else { + /* note: otrl_message_sending handles policy, so that if REQUIRE_ENCRYPTION is set, + this case does not occur */ + return msg; + } + + /* TODO: Error reporting should be done here now (if st!=0), probably. */ + + return NULL; +} + +static const struct irc_plugin otr_plugin = +{ + otr_irc_new, + otr_irc_free, + otr_filter_msg_out, + otr_filter_msg_in, + otr_load, + otr_save, + otr_remove, +}; + +static void cmd_otr(irc_t *irc, char **args) +{ + const command_t *cmd; + + if(!args[0]) + return; + + if(!args[1]) + return; + + for(cmd=otr_commands; cmd->command; cmd++) { + if(strcmp(cmd->command, args[1]) == 0) + break; + } + + if(!cmd->command) { + irc_usermsg(irc, "%s: unknown subcommand \"%s\", see \x02help otr\x02", + args[0], args[1]); + return; + } + + if(!args[cmd->required_parameters+1]) { + irc_usermsg(irc, "%s %s: not enough arguments (%d req.)", + args[0], args[1], cmd->required_parameters); + return; + } + + cmd->execute(irc, args+1); +} + + +/*** OTR "MessageAppOps" callbacks for global.otr_ui: ***/ + +OtrlPolicy op_policy(void *opdata, ConnContext *context) +{ + struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); + irc_t *irc = ic->bee->ui_data; + const char *p; + + /* policy override during keygen: if we're missing the key for context but are currently + generating it, then that's as much as we can do. => temporarily return NEVER. */ + if(keygen_in_progress(irc, context->accountname, context->protocol) && + !otrl_privkey_find(irc->otr->us, context->accountname, context->protocol)) + return OTRL_POLICY_NEVER; + + p = set_getstr(&ic->bee->set, "otr_policy"); + if(!strcmp(p, "never")) + return OTRL_POLICY_NEVER; + if(!strcmp(p, "opportunistic")) + return OTRL_POLICY_OPPORTUNISTIC; + if(!strcmp(p, "manual")) + return OTRL_POLICY_MANUAL; + if(!strcmp(p, "always")) + return OTRL_POLICY_ALWAYS; + + return OTRL_POLICY_OPPORTUNISTIC; +} + +void op_create_privkey(void *opdata, const char *accountname, + const char *protocol) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + irc_t *irc = ic->bee->ui_data; + + /* will fail silently if keygen already in progress */ + otr_keygen(irc, accountname, protocol); +} + +int op_is_logged_in(void *opdata, const char *accountname, + const char *protocol, const char *recipient) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + bee_user_t *bu; + + /* lookup the irc_user_t for the given recipient */ + bu = bee_user_by_handle(ic->bee, ic, recipient); + if(bu) { + if(bu->flags & BEE_USER_ONLINE) + return 1; + else + return 0; + } else { + return -1; + } +} + +void op_inject_message(void *opdata, const char *accountname, + const char *protocol, const char *recipient, const char *message) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + irc_t *irc = ic->bee->ui_data; + + if (strcmp(accountname, recipient) == 0) { + /* huh? injecting messages to myself? */ + irc_usermsg(irc, "note to self: %s", message); + } else { + /* need to drop some consts here :-( */ + /* TODO: get flags into op_inject_message?! */ + ic->acc->prpl->buddy_msg(ic, (char *)recipient, (char *)message, 0); + /* ignoring return value :-/ */ + } +} + +int op_display_otr_message(void *opdata, const char *accountname, + const char *protocol, const char *username, const char *message) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + char *msg = g_strdup(message); + irc_t *irc = ic->bee->ui_data; + + strip_html(msg); + irc_usermsg(irc, "otr: %s", msg); + + g_free(msg); + return 0; +} + +void op_new_fingerprint(void *opdata, OtrlUserState us, + const char *accountname, const char *protocol, + const char *username, unsigned char fingerprint[20]) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + irc_t *irc = ic->bee->ui_data; + char hunam[45]; /* anybody looking? ;-) */ + + otrl_privkey_hash_to_human(hunam, fingerprint); + irc_usermsg(irc, "new fingerprint for %s: %s", + peernick(irc, username, protocol), hunam); +} + +void op_write_fingerprints(void *opdata) +{ + struct im_connection *ic = (struct im_connection *)opdata; + irc_t *irc = ic->bee->ui_data; + + otr_save(irc); +} + +void op_gone_secure(void *opdata, ConnContext *context) +{ + struct im_connection *ic = + check_imc(opdata, context->accountname, context->protocol); + irc_user_t *u; + irc_t *irc = ic->bee->ui_data; + const char *trust; + + u = peeruser(irc, context->username, context->protocol); + if(!u) { + log_message(LOGLVL_ERROR, + "BUG: otr.c: op_gone_secure: irc_user_t for %s/%s/%s not found!", + context->username, context->protocol, context->accountname); + return; + } + + trust = context->active_fingerprint->trust; + if(trust && trust[0]) + u->flags |= IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED; + else + u->flags = ( u->flags & ~IRC_USER_OTR_TRUSTED ) | IRC_USER_OTR_ENCRYPTED; + if(!otr_update_modeflags(irc, u)) + irc_usermsg(irc, "conversation with %s is now off the record", u->nick); +} + +void op_gone_insecure(void *opdata, ConnContext *context) +{ + struct im_connection *ic = + check_imc(opdata, context->accountname, context->protocol); + irc_t *irc = ic->bee->ui_data; + irc_user_t *u; + + u = peeruser(irc, context->username, context->protocol); + if(!u) { + log_message(LOGLVL_ERROR, + "BUG: otr.c: op_gone_insecure: irc_user_t for %s/%s/%s not found!", + context->username, context->protocol, context->accountname); + return; + } + u->flags &= ~( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED ); + if(!otr_update_modeflags(irc, u)) + irc_usermsg(irc, "conversation with %s is now in the clear", u->nick); +} + +void op_still_secure(void *opdata, ConnContext *context, int is_reply) +{ + struct im_connection *ic = + check_imc(opdata, context->accountname, context->protocol); + irc_t *irc = ic->bee->ui_data; + irc_user_t *u; + + u = peeruser(irc, context->username, context->protocol); + if(!u) { + log_message(LOGLVL_ERROR, + "BUG: otr.c: op_still_secure: irc_user_t for %s/%s/%s not found!", + context->username, context->protocol, context->accountname); + return; + } + if(context->active_fingerprint->trust[0]) + u->flags |= IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED; + else + u->flags = ( u->flags & ~IRC_USER_OTR_TRUSTED ) | IRC_USER_OTR_ENCRYPTED; + if(!otr_update_modeflags(irc, u)) + irc_usermsg(irc, "otr connection with %s has been refreshed", u->nick); +} + +void op_log_message(void *opdata, const char *message) +{ + char *msg = g_strdup(message); + + strip_html(msg); + log_message(LOGLVL_INFO, "otr: %s", msg); + g_free(msg); +} + +int op_max_message_size(void *opdata, ConnContext *context) +{ + struct im_connection *ic = + check_imc(opdata, context->accountname, context->protocol); + + return ic->acc->prpl->mms; +} + +const char *op_account_name(void *opdata, const char *account, const char *protocol) +{ + struct im_connection *ic = (struct im_connection *)opdata; + irc_t *irc = ic->bee->ui_data; + + return peernick(irc, account, protocol); +} + + +/*** OTR sub-command handlers ***/ + +void cmd_otr_disconnect(irc_t *irc, char **args) +{ + irc_user_t *u; + + u = irc_user_by_name(irc, args[1]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[1]); + return; + } + + otrl_message_disconnect(irc->otr->us, &otr_ops, + u->bu->ic, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, u->bu->handle); + + /* for some reason, libotr (3.1.0) doesn't do this itself: */ + if(u->flags & IRC_USER_OTR_ENCRYPTED) { + ConnContext *ctx; + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(ctx) + op_gone_insecure(u->bu->ic, ctx); + else /* huh? */ + u->flags &= ( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED ); + } +} + +void cmd_otr_connect(irc_t *irc, char **args) +{ + irc_user_t *u; + + u = irc_user_by_name(irc, args[1]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[1]); + return; + } + if(!(u->bu->flags & BEE_USER_ONLINE)) { + irc_usermsg(irc, "%s is offline", args[1]); + return; + } + + bee_user_msg(irc->b, u->bu, "?OTR?", 0); +} + +void cmd_otr_smp(irc_t *irc, char **args) +{ + otr_smp_or_smpq(irc, args[1], NULL, args[2]); /* no question */ +} + +void cmd_otr_smpq(irc_t *irc, char **args) +{ + otr_smp_or_smpq(irc, args[1], args[2], args[3]); +} + +void cmd_otr_trust(irc_t *irc, char **args) +{ + irc_user_t *u; + ConnContext *ctx; + unsigned char raw[20]; + Fingerprint *fp; + int i,j; + + u = irc_user_by_name(irc, args[1]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[1]); + return; + } + + ctx = otrl_context_find(irc->otr->us, u->bu->handle, + u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "%s: no otr context with user", args[1]); + return; + } + + /* convert given fingerprint to raw representation */ + for(i=0; i<5; i++) { + for(j=0; j<4; j++) { + char *p = args[2+i]+(2*j); + char *q = p+1; + int x, y; + + if(!*p || !*q) { + irc_usermsg(irc, "failed: truncated fingerprint block %d", i+1); + return; + } + + x = hexval(*p); + y = hexval(*q); + if(x<0) { + irc_usermsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+1, i+1); + return; + } + if(y<0) { + irc_usermsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+2, i+1); + return; + } + + raw[i*4+j] = x*16 + y; + } + } + fp = otrl_context_find_fingerprint(ctx, raw, 0, NULL); + if(!fp) { + irc_usermsg(irc, "failed: no such fingerprint for %s", args[1]); + } else { + char *trust = args[7] ? args[7] : "affirmed"; + otrl_context_set_trust(fp, trust); + irc_usermsg(irc, "fingerprint match, trust set to \"%s\"", trust); + if(u->flags & IRC_USER_OTR_ENCRYPTED) + u->flags |= IRC_USER_OTR_TRUSTED; + otr_update_modeflags(irc, u); + } +} + +void cmd_otr_info(irc_t *irc, char **args) +{ + if(!args[1]) { + show_general_otr_info(irc); + } else { + char *arg = g_strdup(args[1]); + char *myhandle, *handle=NULL, *protocol; + ConnContext *ctx; + + /* interpret arg as 'user/protocol/account' if possible */ + protocol = strchr(arg, '/'); + myhandle = NULL; + if(protocol) { + *(protocol++) = '\0'; + myhandle = strchr(protocol, '/'); + } + if(protocol && myhandle) { + *(myhandle++) = '\0'; + handle = arg; + ctx = otrl_context_find(irc->otr->us, handle, myhandle, protocol, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "no such context"); + g_free(arg); + return; + } + } else { + irc_user_t *u = irc_user_by_name(irc, args[1]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[1]); + g_free(arg); + return; + } + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "no otr context with %s", args[1]); + g_free(arg); + return; + } + } + + /* show how we resolved the (nick) argument, if we did */ + if(handle!=arg) { + irc_usermsg(irc, "%s is %s/%s; we are %s/%s to them", args[1], + ctx->username, ctx->protocol, ctx->accountname, ctx->protocol); + } + show_otr_context_info(irc, ctx); + g_free(arg); + } +} + +void cmd_otr_keygen(irc_t *irc, char **args) +{ + int i, n; + account_t *a; + + n = atoi(args[1]); + if(n<0 || (!n && strcmp(args[1], "0"))) { + irc_usermsg(irc, "%s: invalid account number", args[1]); + return; + } + + a = irc->b->accounts; + for(i=0; i<n && a; i++, a=a->next); + if(!a) { + irc_usermsg(irc, "%s: no such account", args[1]); + return; + } + + if(keygen_in_progress(irc, a->user, a->prpl->name)) { + irc_usermsg(irc, "keygen for account %d already in progress", n); + return; + } + + if(otrl_privkey_find(irc->otr->us, a->user, a->prpl->name)) { + char *s = g_strdup_printf("account %d already has a key, replace it?", n); + query_add(irc, NULL, s, yes_keygen, NULL, NULL, a); + g_free(s); + } else { + otr_keygen(irc, a->user, a->prpl->name); + } +} + +void yes_forget_fingerprint(void *data) +{ + pair_t *p = (pair_t *)data; + irc_t *irc = (irc_t *)p->fst; + Fingerprint *fp = (Fingerprint *)p->snd; + + g_free(p); + + if(fp == fp->context->active_fingerprint) { + irc_usermsg(irc, "that fingerprint is active, terminate otr connection first"); + return; + } + + otrl_context_forget_fingerprint(fp, 0); +} + +void yes_forget_context(void *data) +{ + pair_t *p = (pair_t *)data; + irc_t *irc = (irc_t *)p->fst; + ConnContext *ctx = (ConnContext *)p->snd; + + g_free(p); + + if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + irc_usermsg(irc, "active otr connection with %s, terminate it first", + peernick(irc, ctx->username, ctx->protocol)); + return; + } + + if(ctx->msgstate == OTRL_MSGSTATE_FINISHED) + otrl_context_force_plaintext(ctx); + otrl_context_forget(ctx); +} + +void yes_forget_key(void *data) +{ + OtrlPrivKey *key = (OtrlPrivKey *)data; + + otrl_privkey_forget(key); + /* Hm, libotr doesn't seem to offer a function for explicitly /writing/ + keyfiles. So the key will be back on the next load... */ + /* TODO: Actually erase forgotten keys from storage? */ +} + +void cmd_otr_forget(irc_t *irc, char **args) +{ + if(!strcmp(args[1], "fingerprint")) + { + irc_user_t *u; + ConnContext *ctx; + Fingerprint *fp; + char human[54]; + char *s; + pair_t *p; + + if(!args[3]) { + irc_usermsg(irc, "otr %s %s: not enough arguments (2 req.)", args[0], args[1]); + return; + } + + /* TODO: allow context specs ("user/proto/account") in 'otr forget fingerprint'? */ + u = irc_user_by_name(irc, args[2]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[2]); + return; + } + + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "no otr context with %s", args[2]); + return; + } + + fp = match_fingerprint(irc, ctx, ((const char **)args)+3); + if(!fp) { + /* match_fingerprint does error messages */ + return; + } + + if(fp == ctx->active_fingerprint) { + irc_usermsg(irc, "that fingerprint is active, terminate otr connection first"); + return; + } + + otrl_privkey_hash_to_human(human, fp->fingerprint); + s = g_strdup_printf("about to forget fingerprint %s, are you sure?", human); + p = g_malloc(sizeof(pair_t)); + if(!p) + return; + p->fst = irc; + p->snd = fp; + query_add(irc, NULL, s, yes_forget_fingerprint, NULL, NULL, p); + g_free(s); + } + + else if(!strcmp(args[1], "context")) + { + irc_user_t *u; + ConnContext *ctx; + char *s; + pair_t *p; + + /* TODO: allow context specs ("user/proto/account") in 'otr forget contex'? */ + u = irc_user_by_name(irc, args[2]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[2]); + return; + } + + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "no otr context with %s", args[2]); + return; + } + + if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + irc_usermsg(irc, "active otr connection with %s, terminate it first", args[2]); + return; + } + + s = g_strdup_printf("about to forget otr data about %s, are you sure?", args[2]); + p = g_malloc(sizeof(pair_t)); + if(!p) + return; + p->fst = irc; + p->snd = ctx; + query_add(irc, NULL, s, yes_forget_context, NULL, NULL, p); + g_free(s); + } + + else if(!strcmp(args[1], "key")) + { + OtrlPrivKey *key; + char *s; + + key = match_privkey(irc, ((const char **)args)+2); + if(!key) { + /* match_privkey does error messages */ + return; + } + + s = g_strdup_printf("about to forget the private key for %s/%s, are you sure?", + key->accountname, key->protocol); + query_add(irc, NULL, s, yes_forget_key, NULL, NULL, key); + g_free(s); + } + + else + { + irc_usermsg(irc, "otr %s: unknown subcommand \"%s\", see \x02help otr forget\x02", + args[0], args[1]); + } +} + + +/*** local helpers / subroutines: ***/ + +/* Socialist Millionaires' Protocol */ +void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs) +{ + irc_t *irc = ic->bee->ui_data; + OtrlUserState us = irc->otr->us; + OtrlMessageAppOps *ops = &otr_ops; + OtrlTLV *tlv = NULL; + ConnContext *context; + NextExpectedSMP nextMsg; + irc_user_t *u; + bee_user_t *bu; + + bu = bee_user_by_handle(ic->bee, ic, handle); + if(!bu || !(u = bu->ui_data)) return; + context = otrl_context_find(us, handle, + ic->acc->user, ic->acc->prpl->name, 1, NULL, NULL, NULL); + if(!context) { + /* huh? out of memory or what? */ + irc_usermsg(irc, "smp: failed to get otr context for %s", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + return; + } + nextMsg = context->smstate->nextExpected; + + if (context->smstate->sm_prog_state == OTRL_SMP_PROG_CHEATED) { + irc_usermsg(irc, "smp %s: opponent violated protocol, aborting", + u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + return; + } + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT1) { + irc_usermsg(irc, "smp %s: spurious SMP1Q received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + char *question = g_strndup((char *)tlv->data, tlv->len); + irc_usermsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick, + question); + irc_usermsg(irc, "smp: respond with \x02otr smp %s <answer>\x02", + u->nick); + g_free(question); + /* smp stays in EXPECT1 until user responds */ + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT1) { + irc_usermsg(irc, "smp %s: spurious SMP1 received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + irc_usermsg(irc, "smp: initiated by %s" + " - respond with \x02otr smp %s <secret>\x02", + u->nick, u->nick); + /* smp stays in EXPECT1 until user responds */ + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT2) { + irc_usermsg(irc, "smp %s: spurious SMP2 received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + /* SMP2 received, otrl_message_receiving will have sent SMP3 */ + context->smstate->nextExpected = OTRL_SMP_EXPECT4; + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT3) { + irc_usermsg(irc, "smp %s: spurious SMP3 received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + /* SMP3 received, otrl_message_receiving will have sent SMP4 */ + if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) { + if(context->smstate->received_question) { + irc_usermsg(irc, "smp %s: correct answer, you are trusted", + u->nick); + } else { + irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint trusted", + u->nick); + } + } else { + if(context->smstate->received_question) { + irc_usermsg(irc, "smp %s: wrong answer, you are not trusted", + u->nick); + } else { + irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted", + u->nick); + } + } + otrl_sm_state_free(context->smstate); + /* smp is in back in EXPECT1 */ + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT4) { + irc_usermsg(irc, "smp %s: spurious SMP4 received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + /* SMP4 received, otrl_message_receiving will have set fp trust */ + if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) { + irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint trusted", + u->nick); + } else { + irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted", + u->nick); + } + otrl_sm_state_free(context->smstate); + /* smp is in back in EXPECT1 */ + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT); + if (tlv) { + irc_usermsg(irc, "smp: received abort from %s", u->nick); + otrl_sm_state_free(context->smstate); + /* smp is in back in EXPECT1 */ + } +} + +/* combined handler for the 'otr smp' and 'otr smpq' commands */ +void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question, + const char *secret) +{ + irc_user_t *u; + ConnContext *ctx; + + u = irc_user_by_name(irc, nick); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", nick); + return; + } + if(!(u->bu->flags & BEE_USER_ONLINE)) { + irc_usermsg(irc, "%s is offline", nick); + return; + } + + ctx = otrl_context_find(irc->otr->us, u->bu->handle, + u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED) { + irc_usermsg(irc, "smp: otr inactive with %s, try \x02otr connect" + " %s\x02", nick, nick); + return; + } + + if(ctx->smstate->nextExpected != OTRL_SMP_EXPECT1) { + log_message(LOGLVL_INFO, + "SMP already in phase %d, sending abort before reinitiating", + ctx->smstate->nextExpected+1); + otrl_message_abort_smp(irc->otr->us, &otr_ops, u->bu->ic, ctx); + otrl_sm_state_free(ctx->smstate); + } + + if(question) { + /* this was 'otr smpq', just initiate */ + irc_usermsg(irc, "smp: initiating with %s...", u->nick); + otrl_message_initiate_smp_q(irc->otr->us, &otr_ops, u->bu->ic, ctx, + question, (unsigned char *)secret, strlen(secret)); + /* smp is now in EXPECT2 */ + } else { + /* this was 'otr smp', initiate or reply */ + /* warning: the following assumes that smstates are cleared whenever an SMP + is completed or aborted! */ + if(ctx->smstate->secret == NULL) { + irc_usermsg(irc, "smp: initiating with %s...", u->nick); + otrl_message_initiate_smp(irc->otr->us, &otr_ops, + u->bu->ic, ctx, (unsigned char *)secret, strlen(secret)); + /* smp is now in EXPECT2 */ + } else { + /* if we're still in EXPECT1 but smstate is initialized, we must have + received the SMP1, so let's issue a response */ + irc_usermsg(irc, "smp: responding to %s...", u->nick); + otrl_message_respond_smp(irc->otr->us, &otr_ops, + u->bu->ic, ctx, (unsigned char *)secret, strlen(secret)); + /* smp is now in EXPECT3 */ + } + } +} + +/* helper to assert that account and protocol names given to ops below always + match the im_connection passed through as opdata */ +struct im_connection *check_imc(void *opdata, const char *accountname, + const char *protocol) +{ + struct im_connection *ic = (struct im_connection *)opdata; + + if (strcmp(accountname, ic->acc->user) != 0) { + log_message(LOGLVL_WARNING, + "otr: internal account name mismatch: '%s' vs '%s'", + accountname, ic->acc->user); + } + if (strcmp(protocol, ic->acc->prpl->name) != 0) { + log_message(LOGLVL_WARNING, + "otr: internal protocol name mismatch: '%s' vs '%s'", + protocol, ic->acc->prpl->name); + } + + return ic; +} + +irc_user_t *peeruser(irc_t *irc, const char *handle, const char *protocol) +{ + GSList *l; + + for(l=irc->b->users; l; l = l->next) { + bee_user_t *bu = l->data; + struct prpl *prpl; + if(!bu->ui_data || !bu->ic || !bu->handle) + continue; + prpl = bu->ic->acc->prpl; + if(strcmp(prpl->name, protocol) == 0 + && prpl->handle_cmp(bu->handle, handle) == 0) { + return bu->ui_data; + } + } + + return NULL; +} + +int hexval(char a) +{ + int x=tolower(a); + + if(x>='a' && x<='f') + x = x - 'a' + 10; + else if(x>='0' && x<='9') + x = x - '0'; + else + return -1; + + return x; +} + +const char *peernick(irc_t *irc, const char *handle, const char *protocol) +{ + static char fallback[512]; + + irc_user_t *u = peeruser(irc, handle, protocol); + if(u) { + return u->nick; + } else { + g_snprintf(fallback, 511, "%s/%s", handle, protocol); + return fallback; + } +} + +int otr_update_modeflags(irc_t *irc, irc_user_t *u) +{ + return 1; +} + +void show_fingerprints(irc_t *irc, ConnContext *ctx) +{ + char human[45]; + Fingerprint *fp; + const char *trust; + int count=0; + + for(fp=&ctx->fingerprint_root; fp; fp=fp->next) { + if(!fp->fingerprint) + continue; + count++; + otrl_privkey_hash_to_human(human, fp->fingerprint); + if(!fp->trust || fp->trust[0] == '\0') { + trust="untrusted"; + } else { + trust=fp->trust; + } + if(fp == ctx->active_fingerprint) { + irc_usermsg(irc, " \x02%s (%s)\x02", human, trust); + } else { + irc_usermsg(irc, " %s (%s)", human, trust); + } + } + if(count==0) + irc_usermsg(irc, " (none)"); +} + +Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args) +{ + Fingerprint *fp, *fp2; + char human[45]; + char prefix[45], *p; + int n; + int i,j; + + /* assemble the args into a prefix in standard "human" form */ + n=0; + p=prefix; + for(i=0; args[i]; i++) { + for(j=0; args[i][j]; j++) { + char c = toupper(args[i][j]); + + if(n>=40) { + irc_usermsg(irc, "too many fingerprint digits given, expected at most 40"); + return NULL; + } + + if( (c>='A' && c<='F') || (c>='0' && c<='9') ) { + *(p++) = c; + } else { + irc_usermsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1); + return NULL; + } + + n++; + if(n%8 == 0) + *(p++) = ' '; + } + } + *p = '\0'; + + /* find first fingerprint with the given prefix */ + n = strlen(prefix); + for(fp=&ctx->fingerprint_root; fp; fp=fp->next) { + if(!fp->fingerprint) + continue; + otrl_privkey_hash_to_human(human, fp->fingerprint); + if(!strncmp(prefix, human, n)) + break; + } + if(!fp) { + irc_usermsg(irc, "%s: no match", prefix); + return NULL; + } + + /* make sure the match, if any, is unique */ + for(fp2=fp->next; fp2; fp2=fp2->next) { + if(!fp2->fingerprint) + continue; + otrl_privkey_hash_to_human(human, fp2->fingerprint); + if(!strncmp(prefix, human, n)) + break; + } + if(fp2) { + irc_usermsg(irc, "%s: multiple matches", prefix); + return NULL; + } + + return fp; +} + +OtrlPrivKey *match_privkey(irc_t *irc, const char **args) +{ + OtrlPrivKey *k, *k2; + char human[45]; + char prefix[45], *p; + int n; + int i,j; + + /* assemble the args into a prefix in standard "human" form */ + n=0; + p=prefix; + for(i=0; args[i]; i++) { + for(j=0; args[i][j]; j++) { + char c = toupper(args[i][j]); + + if(n>=40) { + irc_usermsg(irc, "too many fingerprint digits given, expected at most 40"); + return NULL; + } + + if( (c>='A' && c<='F') || (c>='0' && c<='9') ) { + *(p++) = c; + } else { + irc_usermsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1); + return NULL; + } + + n++; + if(n%8 == 0) + *(p++) = ' '; + } + } + *p = '\0'; + + /* find first key which matches the given prefix */ + n = strlen(prefix); + for(k=irc->otr->us->privkey_root; k; k=k->next) { + p = otrl_privkey_fingerprint(irc->otr->us, human, k->accountname, k->protocol); + if(!p) /* gah! :-P */ + continue; + if(!strncmp(prefix, human, n)) + break; + } + if(!k) { + irc_usermsg(irc, "%s: no match", prefix); + return NULL; + } + + /* make sure the match, if any, is unique */ + for(k2=k->next; k2; k2=k2->next) { + p = otrl_privkey_fingerprint(irc->otr->us, human, k2->accountname, k2->protocol); + if(!p) /* gah! :-P */ + continue; + if(!strncmp(prefix, human, n)) + break; + } + if(k2) { + irc_usermsg(irc, "%s: multiple matches", prefix); + return NULL; + } + + return k; +} + +void show_general_otr_info(irc_t *irc) +{ + ConnContext *ctx; + OtrlPrivKey *key; + char human[45]; + kg_t *kg; + + /* list all privkeys (including ones being generated) */ + irc_usermsg(irc, "\x1fprivate keys:\x1f"); + for(key=irc->otr->us->privkey_root; key; key=key->next) { + const char *hash; + + switch(key->pubkey_type) { + case OTRL_PUBKEY_TYPE_DSA: + irc_usermsg(irc, " %s/%s - DSA", key->accountname, key->protocol); + break; + default: + irc_usermsg(irc, " %s/%s - type %d", key->accountname, key->protocol, + key->pubkey_type); + } + + /* No, it doesn't make much sense to search for the privkey again by + account/protocol, but libotr currently doesn't provide a direct routine + for hashing a given 'OtrlPrivKey'... */ + hash = otrl_privkey_fingerprint(irc->otr->us, human, key->accountname, key->protocol); + if(hash) /* should always succeed */ + irc_usermsg(irc, " %s", human); + } + if(irc->otr->sent_accountname) { + irc_usermsg(irc, " %s/%s - DSA", irc->otr->sent_accountname, + irc->otr->sent_protocol); + irc_usermsg(irc, " (being generated)"); + } + for(kg=irc->otr->todo; kg; kg=kg->next) { + irc_usermsg(irc, " %s/%s - DSA", kg->accountname, kg->protocol); + irc_usermsg(irc, " (queued)"); + } + if(key == irc->otr->us->privkey_root && + !irc->otr->sent_accountname && + kg == irc->otr->todo) + irc_usermsg(irc, " (none)"); + + /* list all contexts */ + irc_usermsg(irc, "%s", ""); + irc_usermsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)"); + for(ctx=irc->otr->us->context_root; ctx; ctx=ctx->next) {\ + irc_user_t *u; + char *userstring; + + u = peeruser(irc, ctx->username, ctx->protocol); + if(u) + userstring = g_strdup_printf("%s/%s/%s (%s)", + ctx->username, ctx->protocol, ctx->accountname, u->nick); + else + userstring = g_strdup_printf("%s/%s/%s", + ctx->username, ctx->protocol, ctx->accountname); + + if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + irc_usermsg(irc, " \x02%s\x02", userstring); + } else { + irc_usermsg(irc, " %s", userstring); + } + + g_free(userstring); + } + if(ctx == irc->otr->us->context_root) + irc_usermsg(irc, " (none)"); +} + +void show_otr_context_info(irc_t *irc, ConnContext *ctx) +{ + switch(ctx->otr_offer) { + case OFFER_NOT: + irc_usermsg(irc, " otr offer status: none sent"); + break; + case OFFER_SENT: + irc_usermsg(irc, " otr offer status: awaiting reply"); + break; + case OFFER_ACCEPTED: + irc_usermsg(irc, " otr offer status: accepted our offer"); + break; + case OFFER_REJECTED: + irc_usermsg(irc, " otr offer status: ignored our offer"); + break; + default: + irc_usermsg(irc, " otr offer status: %d", ctx->otr_offer); + } + + switch(ctx->msgstate) { + case OTRL_MSGSTATE_PLAINTEXT: + irc_usermsg(irc, " connection state: cleartext"); + break; + case OTRL_MSGSTATE_ENCRYPTED: + irc_usermsg(irc, " connection state: encrypted (v%d)", ctx->protocol_version); + break; + case OTRL_MSGSTATE_FINISHED: + irc_usermsg(irc, " connection state: shut down"); + break; + default: + irc_usermsg(irc, " connection state: %d", ctx->msgstate); + } + + irc_usermsg(irc, " fingerprints: (bold=active)"); + show_fingerprints(irc, ctx); +} + +int keygen_in_progress(irc_t *irc, const char *handle, const char *protocol) +{ + kg_t *kg; + + if(!irc->otr->sent_accountname || !irc->otr->sent_protocol) + return 0; + + /* are we currently working on this key? */ + if(!strcmp(handle, irc->otr->sent_accountname) && + !strcmp(protocol, irc->otr->sent_protocol)) + return 1; + + /* do we have it queued for later? */ + for(kg=irc->otr->todo; kg; kg=kg->next) { + if(!strcmp(handle, kg->accountname) && + !strcmp(protocol, kg->protocol)) + return 1; + } + + return 0; +} + +void otr_keygen(irc_t *irc, const char *handle, const char *protocol) +{ + /* do nothing if a key for the requested account is already being generated */ + if(keygen_in_progress(irc, handle, protocol)) + return; + + /* see if we already have a keygen child running. if not, start one and put a + handler on its output. */ + if(!irc->otr->keygen || waitpid(irc->otr->keygen, NULL, WNOHANG)) { + pid_t p; + int to[2], from[2]; + FILE *tof, *fromf; + + if(pipe(to) < 0 || pipe(from) < 0) { + irc_usermsg(irc, "otr keygen: couldn't create pipe: %s", strerror(errno)); + return; + } + + tof = fdopen(to[1], "w"); + fromf = fdopen(from[0], "r"); + if(!tof || !fromf) { + irc_usermsg(irc, "otr keygen: couldn't streamify pipe: %s", strerror(errno)); + return; + } + + p = fork(); + if(p<0) { + irc_usermsg(irc, "otr keygen: couldn't fork: %s", strerror(errno)); + return; + } + + if(!p) { + /* child process */ + signal(SIGTERM, exit); + keygen_child_main(irc->otr->us, to[0], from[1]); + exit(0); + } + + irc->otr->keygen = p; + irc->otr->to = tof; + irc->otr->from = fromf; + irc->otr->sent_accountname = NULL; + irc->otr->sent_protocol = NULL; + irc->otr->todo = NULL; + b_input_add(from[0], B_EV_IO_READ, keygen_finish_handler, irc); + } + + /* is the keygen slave currently working? */ + if(irc->otr->sent_accountname) { + /* enqueue our job for later transmission */ + kg_t **kg = &irc->otr->todo; + while(*kg) + kg=&((*kg)->next); + *kg = g_new0(kg_t, 1); + (*kg)->accountname = g_strdup(handle); + (*kg)->protocol = g_strdup(protocol); + } else { + /* send our job over and remember it */ + fprintf(irc->otr->to, "%s\n%s\n", handle, protocol); + fflush(irc->otr->to); + irc->otr->sent_accountname = g_strdup(handle); + irc->otr->sent_protocol = g_strdup(protocol); + } +} + +void keygen_child_main(OtrlUserState us, int infd, int outfd) +{ + FILE *input, *output; + char filename[128], accountname[512], protocol[512]; + gcry_error_t e; + int tempfd; + + input = fdopen(infd, "r"); + output = fdopen(outfd, "w"); + + while(!feof(input) && !ferror(input) && !feof(output) && !ferror(output)) { + myfgets(accountname, 512, input); + myfgets(protocol, 512, input); + + strncpy(filename, "/tmp/bitlbee-XXXXXX", 128); + tempfd = mkstemp(filename); + close(tempfd); + + e = otrl_privkey_generate(us, filename, accountname, protocol); + if(e) { + fprintf(output, "\n"); /* this means failure */ + fprintf(output, "otr keygen: %s\n", gcry_strerror(e)); + unlink(filename); + } else { + fprintf(output, "%s\n", filename); + fprintf(output, "otr keygen for %s/%s complete\n", accountname, protocol); + } + fflush(output); + } + + fclose(input); + fclose(output); +} + +gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond) +{ + irc_t *irc = (irc_t *)data; + char filename[512], msg[512]; + + myfgets(filename, 512, irc->otr->from); + myfgets(msg, 512, irc->otr->from); + + irc_usermsg(irc, "%s", msg); + if(filename[0]) { + char *kf = g_strdup_printf("%s%s.otr_keys", global.conf->configdir, irc->user->nick); + char *tmp = g_strdup_printf("%s.new", kf); + copyfile(filename, tmp); + unlink(filename); + rename(tmp,kf); + otrl_privkey_read(irc->otr->us, kf); + g_free(kf); + g_free(tmp); + } + + /* forget this job */ + g_free(irc->otr->sent_accountname); + g_free(irc->otr->sent_protocol); + irc->otr->sent_accountname = NULL; + irc->otr->sent_protocol = NULL; + + /* see if there are any more in the queue */ + if(irc->otr->todo) { + kg_t *p = irc->otr->todo; + /* send the next one over */ + fprintf(irc->otr->to, "%s\n%s\n", p->accountname, p->protocol); + fflush(irc->otr->to); + irc->otr->sent_accountname = p->accountname; + irc->otr->sent_protocol = p->protocol; + irc->otr->todo = p->next; + g_free(p); + return TRUE; /* keep watching */ + } else { + /* okay, the slave is idle now, so kill him */ + fclose(irc->otr->from); + fclose(irc->otr->to); + irc->otr->from = irc->otr->to = NULL; + kill(irc->otr->keygen, SIGTERM); + waitpid(irc->otr->keygen, NULL, 0); + irc->otr->keygen = 0; + return FALSE; /* unregister ourselves */ + } +} + +void copyfile(const char *a, const char *b) +{ + int fda, fdb; + int n; + char buf[1024]; + + fda = open(a, O_RDONLY); + fdb = open(b, O_WRONLY | O_CREAT | O_TRUNC, 0600); + + while((n=read(fda, buf, 1024)) > 0) + write(fdb, buf, n); + + close(fda); + close(fdb); +} + +void myfgets(char *s, int size, FILE *stream) +{ + if(!fgets(s, size, stream)) { + s[0] = '\0'; + } else { + int n = strlen(s); + if(n>0 && s[n-1] == '\n') + s[n-1] = '\0'; + } +} + +void yes_keygen(void *data) +{ + account_t *acc = (account_t *)data; + irc_t *irc = acc->bee->ui_data; + + if(keygen_in_progress(irc, acc->user, acc->prpl->name)) { + irc_usermsg(irc, "keygen for %s/%s already in progress", + acc->user, acc->prpl->name); + } else { + irc_usermsg(irc, "starting background keygen for %s/%s", + acc->user, acc->prpl->name); + irc_usermsg(irc, "you will be notified when it completes"); + otr_keygen(irc, acc->user, acc->prpl->name); + } +} @@ -0,0 +1,82 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2008 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + OTR support (cf. http://www.cypherpunks.ca/otr/) + + 2008, Sven Moritz Hallberg <pesco@khjk.org> + (c) and funded by stonedcoder.org +*/ + +/* + 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 with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef BITLBEE_PROTOCOLS_OTR_H +#define BITLBEE_PROTOCOLS_OTR_H + +#include "bitlbee.h" + + +// forward decls to avoid mutual dependencies +struct irc; +struct im_connection; +struct account; + + +#include <libotr/proto.h> +#include <libotr/message.h> +#include <libotr/privkey.h> + +/* representing a keygen job */ +typedef struct kg { + char *accountname; + char *protocol; + + struct kg *next; +} kg_t; + +/* struct to encapsulate our book keeping stuff */ +typedef struct otr { + OtrlUserState us; + pid_t keygen; /* pid of keygen slave (0 if none) */ + FILE *to; /* pipe to keygen slave */ + FILE *from; /* pipe from keygen slave */ + + /* active keygen job (NULL if none) */ + char *sent_accountname; + char *sent_protocol; + + /* keygen jobs waiting to be sent to slave */ + kg_t *todo; +} otr_t; + +/* called from main() */ +void otr_init(void); + +/* called by storage_* functions */ +void otr_load(struct irc *irc); +void otr_save(struct irc *irc); +void otr_remove(const char *nick); +void otr_rename(const char *onick, const char *nnick); + +/* called from account_add() */ +int otr_check_for_key(struct account *a); + +#endif diff --git a/protocols/bee_user.c b/protocols/bee_user.c index 1f9b1b47..4ea538a9 100644 --- a/protocols/bee_user.c +++ b/protocols/bee_user.c @@ -264,10 +264,6 @@ void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, ui } } - if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) - strip_html( msg ); - if( bee->ui->user_msg && bu ) bee->ui->user_msg( bee, bu, msg, sent_at ); else diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index f7e1e664..e6f38e26 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -556,6 +556,7 @@ void jabber_initmodule() struct prpl *ret = g_new0( struct prpl, 1 ); ret->name = "jabber"; + ret->mms = 0; /* no limit */ ret->login = jabber_login; ret->init = jabber_init; ret->logout = jabber_logout; diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 003b4728..161f7590 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -358,6 +358,7 @@ void msn_initmodule() struct prpl *ret = g_new0(struct prpl, 1); ret->name = "msn"; + ret->mms = 1409; /* this guess taken from libotr UPGRADING file */ ret->login = msn_login; ret->init = msn_init; ret->logout = msn_logout; diff --git a/protocols/nogaim.h b/protocols/nogaim.h index 2069cf6b..a09a3f67 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -67,6 +67,7 @@ #define OPT_LOCALBUDDY 0x00000020 /* For nicks local to one groupchat */ #define OPT_TYPING 0x00000100 /* Some pieces of code make assumptions */ #define OPT_THINKING 0x00000200 /* about these values... Stupid me! */ +#define OPT_NOOTR 0x00001000 /* protocol not suitable for OTR */ /* ok. now the fun begins. first we create a connection structure */ struct im_connection @@ -141,6 +142,10 @@ struct prpl { * - The user sees this name ie. when imcb_log() is used. */ const char *name; void *data; + /* Maximum Message Size of this protocol. + * - Introduced for OTR, in order to fragment large protocol messages. + * - 0 means "unlimited". */ + unsigned int mms; /* Added this one to be able to add per-account settings, don't think * it should be used for anything else. You are supposed to use the diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c index 9283628a..3eea5825 100644 --- a/protocols/oscar/oscar.c +++ b/protocols/oscar/oscar.c @@ -2611,6 +2611,7 @@ void oscar_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); ret->name = "oscar"; + ret->mms = 2343; /* this guess taken from libotr UPGRADING file */ ret->away_states = oscar_away_states; ret->init = oscar_init; ret->login = oscar_login; diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 7a49c084..4ece97db 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -533,6 +533,7 @@ void twitter_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); + ret->options = OPT_NOOTR; ret->name = "twitter"; ret->login = twitter_login; ret->init = twitter_init; diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c index 7708ed63..8b3b0c05 100644 --- a/protocols/yahoo/yahoo.c +++ b/protocols/yahoo/yahoo.c @@ -378,6 +378,7 @@ void byahoo_initmodule( ) { struct prpl *ret = g_new0(struct prpl, 1); ret->name = "yahoo"; + ret->mms = 832; /* this guess taken from libotr UPGRADING file */ ret->init = byahoo_init; ret->login = byahoo_login; @@ -105,6 +105,9 @@ void query_del_by_conn( irc_t *irc, struct im_connection *ic ) query_t *q, *n, *def; int count = 0; + if( !ic ) + return; + q = irc->queries; def = query_default( irc ); @@ -147,7 +150,8 @@ void query_answer( irc_t *irc, query_t *q, int ans ) imcb_log( q->ic, "Accepted: %s", q->question ); else irc_usermsg( irc, "Accepted: %s", q->question ); - q->yes( q->data ); + if( q->yes ) + q->yes( q->data ); } else { @@ -155,7 +159,8 @@ void query_answer( irc_t *irc, query_t *q, int ans ) imcb_log( q->ic, "Rejected: %s", q->question ); else irc_usermsg( irc, "Rejected: %s", q->question ); - q->no( q->data ); + if( q->no ) + q->no( q->data ); } q->data = NULL; diff --git a/root_commands.c b/root_commands.c index f3186562..98105832 100644 --- a/root_commands.c +++ b/root_commands.c @@ -54,17 +54,17 @@ void root_command( irc_t *irc, char *cmd[] ) return; len = strlen( cmd[0] ); - for( i = 0; commands[i].command; i++ ) - if( g_strncasecmp( commands[i].command, cmd[0], len ) == 0 ) + for( i = 0; root_commands[i].command; i++ ) + if( g_strncasecmp( root_commands[i].command, cmd[0], len ) == 0 ) { - if( commands[i+1].command && - g_strncasecmp( commands[i+1].command, cmd[0], len ) == 0 ) + if( root_commands[i+1].command && + g_strncasecmp( root_commands[i+1].command, cmd[0], len ) == 0 ) /* Only match on the first letters if the match is unique. */ break; - MIN_ARGS( commands[i].required_parameters ); + MIN_ARGS( root_commands[i].required_parameters ); - commands[i].execute( irc, cmd ); + root_commands[i].execute( irc, cmd ); return; } @@ -1312,7 +1312,7 @@ static void bitlbee_whatsnew( irc_t *irc ) } /* IMPORTANT: Keep this list sorted! The short command logic needs that. */ -const command_t commands[] = { +command_t root_commands[] = { { "account", 1, cmd_account, 0 }, { "add", 2, cmd_add, 0 }, { "allow", 1, cmd_allow, 0 }, @@ -1336,5 +1336,42 @@ const command_t commands[] = { { "set", 0, cmd_set, 0 }, { "transfer", 0, cmd_transfer, 0 }, { "yes", 0, cmd_yesno, 0 }, - { NULL } + /* Not expecting too many plugins adding root commands so just make a + dumb array with some empty entried at the end. */ + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, }; +static const int num_root_commands = sizeof( root_commands ) / sizeof( command_t ); + +gboolean root_command_add( const char *command, int params, void (*func)(irc_t *, char **args), int flags ) +{ + int i; + + if( root_commands[num_root_commands-2].command ) + /* Planning fail! List is full. */ + return FALSE; + + for( i = 0; root_commands[i].command; i++ ) + { + if( g_strcasecmp( root_commands[i].command, command ) == 0 ) + return FALSE; + else if( g_strcasecmp( root_commands[i].command, command ) > 0 ) + break; + } + memmove( root_commands + i + 1, root_commands + i, + sizeof( command_t ) * ( num_root_commands - i - 1 ) ); + + root_commands[i].command = g_strdup( command ); + root_commands[i].required_parameters = params; + root_commands[i].execute = func; + root_commands[i].flags = flags; + + return TRUE; +} @@ -10,11 +10,7 @@ #define sock_make_nonblocking(fd) fcntl(fd, F_SETFL, O_NONBLOCK) #define sock_make_blocking(fd) fcntl(fd, F_SETFL, 0) #define sockerr_again() (errno == EINPROGRESS || errno == EINTR) -#ifndef EVENTS_LIBEVENT -#define closesocket(a) close(a) -#else void closesocket( int fd ); -#endif #else # include <winsock2.h> # include <ws2tcpip.h> @@ -114,7 +114,16 @@ storage_status_t storage_load (irc_t * irc, const char *password) status = st->load(irc, password); if (status == STORAGE_OK) + { + GSList *l; + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->storage_load ) + p->storage_load( irc ); + } return status; + } if (status != STORAGE_NO_SUCH_USER) return status; @@ -126,6 +135,7 @@ storage_status_t storage_load (irc_t * irc, const char *password) storage_status_t storage_save (irc_t *irc, char *password, int overwrite) { storage_status_t st; + GSList *l; if (password != NULL) { /* Should only use this in the "register" command. */ @@ -139,6 +149,13 @@ storage_status_t storage_save (irc_t *irc, char *password, int overwrite) st = ((storage_t *)global.storage->data)->save(irc, overwrite); + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->storage_save ) + p->storage_save( irc ); + } + if (password != NULL) { irc_setpass(irc, NULL); } @@ -150,6 +167,8 @@ storage_status_t storage_remove (const char *nick, const char *password) { GList *gl; storage_status_t ret = STORAGE_OK; + gboolean ok = FALSE; + GSList *l; /* Remove this account from all storage backends. If this isn't * done, the account will still be usable, it'd just be @@ -159,10 +178,20 @@ storage_status_t storage_remove (const char *nick, const char *password) storage_status_t status; status = st->remove(nick, password); + ok |= status == STORAGE_OK; if (status != STORAGE_NO_SUCH_USER && status != STORAGE_OK) ret = status; } + /* If at least one succeeded, remove plugin data. */ + if( ok ) + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->storage_remove ) + p->storage_remove( nick ); + } + return ret; } @@ -28,9 +28,11 @@ #include "arc.h" #include "base64.h" #include "commands.h" +#include "otr.h" #include "protocols/nogaim.h" #include "help.h" #include "ipc.h" +#include "lib/ssl_client.h" #include "md5.h" #include "misc.h" #include <signal.h> @@ -68,6 +70,18 @@ int main( int argc, char *argv[] ) b_main_init(); + /* Ugly Note: libotr and gnutls both use libgcrypt. libgcrypt + has a process-global config state whose initialization happpens + twice if libotr and gnutls are used together. libotr installs custom + memory management functions for libgcrypt while our gnutls module + uses the defaults. Therefore we initialize OTR after SSL. *sigh* */ + ssl_init(); +#ifdef OTR_BI + otr_init(); +#endif + /* And in case OTR is loaded as a plugin, it'll also get loaded after + this point. */ + srand( time( NULL ) ^ getpid() ); global.helpfile = g_strdup( HELP_FILE ); |