Index: Makefile
===================================================================
--- Makefile	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ Makefile	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -11,5 +11,5 @@
 # Program variables
 objects = account.o bitlbee.o chat.o crypting.o help.o ipc.o irc.o irc_commands.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) user.o
-headers = account.h bitlbee.h commands.h conf.h config.h crypting.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h lib/events.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/nogaim.h
+headers = account.h bitlbee.h commands.h conf.h config.h crypting.h help.h ipc.h irc.h log.h nick.h otr.h query.h set.h sock.h storage.h user.h lib/events.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/nogaim.h
 subdirs = lib protocols
 
@@ -20,5 +20,5 @@
 OUTFILE=bitlbee.exe
 else
-objects += unix.o conf.o log.o
+objects += unix.o conf.o log.o otr.o
 OUTFILE=bitlbee
 endif
@@ -94,8 +94,10 @@
 	mkdir -p $(DESTDIR)$(ETCDIR)
 	install -m 0644 motd.txt $(DESTDIR)$(ETCDIR)/motd.txt
+	install -m 0644 welcome.txt $(DESTDIR)$(ETCDIR)/welcome.txt
 	install -m 0644 bitlbee.conf $(DESTDIR)$(ETCDIR)/bitlbee.conf
 
 uninstall-etc:
 	rm -f $(DESTDIR)$(ETCDIR)/motd.txt
+	rm -f $(DESTDIR)$(ETCDIR)/welcome.txt
 	rm -f $(DESTDIR)$(ETCDIR)/bitlbee.conf
 	-rmdir $(DESTDIR)$(ETCDIR)
Index: bitlbee.conf
===================================================================
--- bitlbee.conf	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ bitlbee.conf	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -85,4 +85,11 @@
 # MotdFile = /etc/bitlbee/motd.txt
 
+## WelcomeFile
+##
+## Specify an alternative file for the welcome message displayed when joining the
+## control channel. Default value depends on the --etcdir argument to configure.
+##
+# WelcomeFile = /etc/bitlbee/welcome.txt
+
 ## ConfigDir
 ##
Index: bitlbee.h
===================================================================
--- bitlbee.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ bitlbee.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -30,4 +30,7 @@
 #define _GNU_SOURCE /* Stupid GNU :-P */
 #endif
+
+/* Depend on Windows 2000 for now since we need getaddrinfo() */
+#define _WIN32_WINNT 0x0501
 
 /* Depend on Windows 2000 for now since we need getaddrinfo() */
@@ -141,4 +144,5 @@
 #include "misc.h"
 #include "proxy.h"
+#include "otr.h"
 
 typedef struct global {
@@ -152,4 +156,5 @@
 	char *helpfile;
 	int restart;
+	OtrlMessageAppOps otr_ops;   /* collects interface functions required by OTR */
 } global_t;
 
Index: conf.c
===================================================================
--- conf.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ conf.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -60,4 +60,5 @@
 	conf->pidfile = g_strdup( PIDFILE );
 	conf->motdfile = g_strdup( ETCDIR "/motd.txt" );
+	conf->welcomefile = g_strdup( ETCDIR "/welcome.txt" );
 	conf->ping_interval = 180;
 	conf->ping_timeout = 300;
@@ -258,4 +259,9 @@
 				conf->motdfile = g_strdup( ini->value );
 			}
+			else if( g_strcasecmp( ini->key, "welcomefile" ) == 0 )
+			{
+				g_free( conf->welcomefile );
+				conf->welcomefile = g_strdup( ini->value );
+			}
 			else if( g_strcasecmp( ini->key, "account_storage" ) == 0 )
 			{
Index: conf.h
===================================================================
--- conf.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ conf.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -45,4 +45,5 @@
 	char *pidfile;
 	char *motdfile;
+	char *welcomefile;
 	char *primary_storage;
 	char **migrate_storage;
Index: configure
===================================================================
--- configure	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ configure	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -32,4 +32,5 @@
 gcov=0
 plugins=1
+otr=auto
 
 events=glib
@@ -73,4 +74,5 @@
 --gcov=0/1	Disable/enable test coverage reporting	$gcov
 --plugins=0/1	Disable/enable plugins support		$plugins
+--otr=0/1	Disable/enable OTR encryption support	$otr
 
 --events=...	Event handler (glib, libevent)		$events
@@ -452,4 +454,26 @@
 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
+	echo '#define WITH_OTR' >> config.h
+	echo "EFLAGS+=-L${otrprefix}/lib -lotr" >> Makefile.settings
+	echo "CFLAGS+=-I${otrprefix}/include" >> Makefile.settings
+else
+	echo '#undef WITH_OTR' >> config.h
+fi
+
 if [ ! -e doc/user-guide/help.txt ] && ! type xmlto > /dev/null 2> /dev/null; then
 	echo
@@ -606,4 +630,10 @@
 fi
 
+if [ "$otr" = "1" ]; then
+	echo '  Off-the-Record (OTR) Messaging enabled.'
+else
+	echo '  Off-the-Record (OTR) Messaging disabled.'
+fi
+
 echo '  Using event handler: '$events
 echo '  Using SSL library: '$ssl
Index: doc/README
===================================================================
--- doc/README	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ doc/README	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -67,4 +67,8 @@
 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 will be included by default if the
+configure script finds libotr in one of the usual places. You can pass
+--otr=1 or --otr=0 to force it on or off, respectively.
 
 These days, MSN Messenger clients have to connect to the MS Passport servers
@@ -143,6 +147,6 @@
 
 
-A NOTE ON ENCRYPTION
-====================
+A NOTE ON PASSWORD ENCRYPTION
+=============================
 
 There used to be a note here about the simple obfuscation method used to
Index: doc/user-guide/commands.xml
===================================================================
--- doc/user-guide/commands.xml	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ doc/user-guide/commands.xml	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -349,4 +349,153 @@
 		</description>
 	</bitlbee-command>
+	
+	<bitlbee-command name="otr">
+		<short-description>Off-the-Record encryption control</short-description>
+		<syntax>otr &lt;subcommand&gt; [&lt;arguments&gt;]</syntax>
+
+		<description>
+
+			<para>
+				Available subcommands: connect, disconnect, smp, trust, info, keygen, and forget. See <emphasis>help otr &lt;subcommand&gt;</emphasis> for more information.
+			</para>
+
+		</description>
+		
+		<bitlbee-command name="connect">
+			<syntax>otr connect &lt;nick&gt;</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 &lt;nick&gt;</syntax>
+			
+			<description>
+			
+				<para>
+					Resets the connection with the specified user to cleartext.
+				</para>
+				
+			</description>
+		
+		</bitlbee-command>
+		
+		<bitlbee-command name="smp">
+			<syntax>otr smp &lt;nick&gt; &lt;secret&gt;</syntax>
+			
+			<description>
+			
+				<para>
+					Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol.
+				</para>
+				
+				<para>
+					If an SMP challenge has already been received from the given user, responds with the specified secret. Otherwise, a challenge for the secret will be sent. If the protocol succeeds (i.e. both parties gave the same secret), the fingerprint will be trusted.
+				</para>
+				
+			</description>
+		
+		</bitlbee-command>
+		
+		<bitlbee-command name="trust">
+			<syntax>otr trust &lt;nick&gt; &lt;fp1&gt; &lt;fp2&gt; &lt;fp3&gt; &lt;fp4&gt; &lt;fp5&gt;</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 &lt;nick&gt;</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 &lt;account-no&gt;</syntax>
+			
+			<description>
+			
+				<para>
+					Generates a new OTR private key for the given account.
+				</para>
+				
+			</description>
+		
+		</bitlbee-command>
+		
+		<bitlbee-command name="forget">
+			<syntax>otr forget &lt;thing&gt; &lt;arguments&gt;</syntax>
+			
+			<description>
+			
+				<para>
+					Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See <emphasis>help otr forget &lt;thing&gt;</emphasis> for more information.
+				</para>
+			
+			</description>
+			
+			<bitlbee-command name="fingerprint">
+				<syntax>otr forget fingerprint &lt;nick&gt; &lt;fingerprint&gt;</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 &lt;nick&gt;</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 &lt;fingerprint&gt;</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">
@@ -533,4 +682,14 @@
 	</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>&amp;bitlbee</default>
@@ -619,4 +778,19 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="halfop_buddies" type="string" scope="global">
+		<default>encrypted</default>
+		<possible-values>encrypted, trusted, notaway, online, false</possible-values>
+
+		<description>
+			<para>
+				Specifies under which circumstances BitlBee should give the "halfop" mode flag (+h) to buddies.
+			</para>
+			
+			<para>
+				If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key. On "online", the flag is set whenever the user is online; only meaningful in conjunction with "show_offline".
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="ignore_auth_requests" type="boolean" scope="account">
 		<default>false</default>
@@ -627,5 +801,4 @@
 			</para>
 		</description>
-
 	</bitlbee-setting>
 
@@ -661,4 +834,54 @@
 		</description>
 
+	</bitlbee-setting>
+
+	<bitlbee-setting name="op_buddies" type="string" scope="global">
+		<default>trusted</default>
+		<possible-values>encrypted, trusted, notaway, online, false</possible-values>
+
+		<description>
+			<para>
+				Specifies under which circumstances BitlBee should give the "op" mode flag (+o) to buddies.
+			</para>
+			
+			<para>
+				If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key. On "online", the flag is set whenever the user is online; only meaningful in conjunction with "show_offline".
+			</para>
+		</description>
+
+	</bitlbee-setting>
+
+	<bitlbee-setting name="op_root" type="bool" scope="global">
+		<default>true</default>
+
+		<description>
+			<para>
+				Some people prefer themself and root to have operator status in &amp;bitlbee, other people don't. You can set the desired state for root using this setting.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="op_user" type="bool" scope="global">
+		<default>true</default>
+
+		<description>
+			<para>
+				Some people prefer themself and root to have operator status in &amp;bitlbee, other people don't. You can set the desired state for yourself using this setting.
+			</para>
+		</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>
 
@@ -695,5 +918,4 @@
 			</para>
 		</description>
-
 	</bitlbee-setting>
 
@@ -881,5 +1103,8 @@
 		<description>
 			<para>
-				If enabled causes BitlBee to also show offline users in Channel. Online-users will get op, away-users voice and offline users none of both. This option takes effect as soon as you reconnect.
+				If enabled causes BitlBee to also show offline users in Channel. You may want to adjust the settings "voice_buddies", "halfop_buddies" and "op_buddies" according to your liking. A reasonable candidate is "voice_buddies=online", "halfop_buddies=false", "op_buddies=notaway".
+			</para>
+			<para>
+				This option takes effect as soon as you reconnect.
 			</para>
 		</description>
@@ -1004,4 +1229,20 @@
 			</para>
 		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="voice_buddies" type="string" scope="global">
+		<default>notaway</default>
+		<possible-values>encrypted, trusted, notaway, online, false</possible-values>
+
+		<description>
+			<para>
+				Specifies under which circumstances BitlBee should give the "voice" mode flag (+v) to buddies.
+			</para>
+			
+			<para>
+				If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key. On "online", the flag is set whenever the user is online; only meaningful in conjunction with "show_offline".
+			</para>
+		</description>
+
 	</bitlbee-setting>
 
Index: ipc.c
===================================================================
--- ipc.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ ipc.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -33,4 +33,5 @@
 
 GSList *child_list = NULL;
+static char *statefile = NULL;
 
 static void ipc_master_cmd_client( irc_t *data, char **cmd )
@@ -62,23 +63,4 @@
 }
 
-static void ipc_master_cmd_deaf( irc_t *data, char **cmd )
-{
-	if( global.conf->runmode == RUNMODE_DAEMON )
-	{
-		b_event_remove( global.listen_watch_source_id );
-		close( global.listen_socket );
-		
-		global.listen_socket = global.listen_watch_source_id = -1;
-	
-		ipc_to_children_str( "OPERMSG :Closed listening socket, waiting "
-		                     "for all users to disconnect." );
-	}
-	else
-	{
-		ipc_to_children_str( "OPERMSG :The DEAF command only works in "
-		                     "normal daemon mode. Try DIE instead." );
-	}
-}
-
 void ipc_master_cmd_rehash( irc_t *data, char **cmd )
 {
@@ -116,5 +98,4 @@
 	{ "hello",      0, ipc_master_cmd_client,     0 },
 	{ "die",        0, ipc_master_cmd_die,        0 },
-	{ "deaf",       0, ipc_master_cmd_deaf,       0 },
 	{ "wallops",    1, NULL,                      IPC_CMD_TO_CHILDREN },
 	{ "wall",       1, NULL,                      IPC_CMD_TO_CHILDREN },
@@ -460,5 +441,4 @@
 }
 
-#ifndef _WIN32
 char *ipc_master_save_state()
 {
@@ -501,4 +481,9 @@
 }
 
+void ipc_master_set_statefile( char *fn )
+{
+	statefile = g_strdup( fn );
+}
+
 
 static gboolean new_ipc_client( gpointer data, gint serversock, b_input_condition cond )
@@ -521,4 +506,5 @@
 }
 
+#ifndef _WIN32
 int ipc_master_listen_socket()
 {
@@ -557,12 +543,8 @@
 }
 #else
-int ipc_master_listen_socket()
-{
 	/* FIXME: Open named pipe \\.\BITLBEE */
-	return 0;
-}
 #endif
 
-int ipc_master_load_state( char *statefile )
+int ipc_master_load_state()
 {
 	struct bitlbee_child *child;
@@ -572,5 +554,4 @@
 	if( statefile == NULL )
 		return 0;
-	
 	fp = fopen( statefile, "r" );
 	unlink( statefile );	/* Why do it later? :-) */
Index: ipc.h
===================================================================
--- ipc.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ ipc.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -58,5 +58,6 @@
 
 char *ipc_master_save_state();
-int ipc_master_load_state( char *statefile );
+void ipc_master_set_statefile( char *fn );
+int ipc_master_load_state();
 int ipc_master_listen_socket();
 
Index: irc.c
===================================================================
--- irc.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ irc.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -29,6 +29,9 @@
 #include "crypting.h"
 #include "ipc.h"
+#include <sys/types.h>
+#include <sys/wait.h>
 
 static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond );
+static void irc_welcome( irc_t* irc );
 
 GSList *irc_connection_list = NULL;
@@ -177,8 +180,8 @@
 
 	irc_connection_list = g_slist_append( irc_connection_list, irc );
+
 	
 	s = set_add( &irc->set, "away", NULL,  set_eval_away_status, irc );
 	s->flags |= SET_NULL_OK;
-	s = set_add( &irc->set, "away_devoice", "true",  set_eval_away_devoice, irc );
 	s = set_add( &irc->set, "auto_connect", "true", set_eval_bool, irc );
 	s = set_add( &irc->set, "auto_reconnect", "true", set_eval_bool, irc );
@@ -187,4 +190,5 @@
 	s = set_add( &irc->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc );
 	s = set_add( &irc->set, "charset", "utf-8", set_eval_charset, irc );
+	s = set_add( &irc->set, "color_encrypted", "true", set_eval_bool, irc );
 	s = set_add( &irc->set, "control_channel", irc->channel, set_eval_control_channel, irc );
 	s = set_add( &irc->set, "debug", "false", set_eval_bool, irc );
@@ -193,6 +197,9 @@
 	s = set_add( &irc->set, "display_timestamps", "true", set_eval_bool, irc );
 	s = set_add( &irc->set, "handle_unknown", "root", NULL, irc );
+	s = set_add( &irc->set, "halfop_buddies", "encrypted", set_eval_halfop_buddies, irc );
 	s = set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc );
-	s = set_add( &irc->set, "ops", "both", set_eval_ops, irc );
+	s = set_add( &irc->set, "op_buddies", "trusted", set_eval_op_buddies, irc );
+	s = set_add( &irc->set, "op_root", "true", set_eval_op_root, irc );
+	s = set_add( &irc->set, "otr_policy", "oppurtunistic", set_eval_otr_policy, irc );
 	s = set_add( &irc->set, "password", NULL, set_eval_password, irc );
 	s->flags |= SET_NULL_OK;
@@ -203,13 +210,14 @@
 	s = set_add( &irc->set, "show_offline", "false", set_eval_bool, irc );
 	s = set_add( &irc->set, "simulate_netsplit", "true", set_eval_bool, irc );
-	s = set_add( &irc->set, "status", NULL,  set_eval_away_status, irc );
-	s->flags |= SET_NULL_OK;
 	s = set_add( &irc->set, "strip_html", "true", NULL, irc );
 	s = set_add( &irc->set, "timezone", "local", set_eval_timezone, irc );
 	s = set_add( &irc->set, "to_char", ": ", set_eval_to_char, irc );
 	s = set_add( &irc->set, "typing_notice", "false", set_eval_bool, irc );
+	s = set_add( &irc->set, "voice_buddies", "notaway", set_eval_voice_buddies, irc);
 	
 	conf_loaddefaults( irc );
-	
+
+	irc->otr = otr_new();
+
 	/* Evaluator sets the iconv/oconv structures. */
 	set_eval_charset( set_find( &irc->set, "charset" ), set_getstr( &irc->set, "charset" ) );
@@ -363,7 +371,9 @@
 	
 	g_free( irc->last_target );
+
+	otr_free(irc->otr);
 	
 	g_free( irc );
-	
+
 	if( global.conf->runmode == RUNMODE_INETD ||
 	    global.conf->runmode == RUNMODE_FORKDAEMON ||
@@ -734,4 +744,38 @@
 } 
 
+const char *user_mode_prefix( irc_t *irc, user_t *u )
+{
+	static char op[] = "@";
+	static char halfop[] = "%";
+	static char voice[] = "+";
+	static char none[] = "";
+
+	int or = set_getbool(&irc->set, "op_root");
+	int ou = set_getbool(&irc->set, "op_user");
+	char *ob = set_getstr(&irc->set, "op_buddies");
+	char *hb = set_getstr(&irc->set, "halfop_buddies");
+	char *vb = set_getstr(&irc->set, "voice_buddies");
+
+	if( (!strcmp(u->nick, irc->mynick) && or) ||
+	    (!strcmp(u->nick, irc->nick) && ou) ||
+        (!u->away && !strcmp(ob, "notaway")) ||
+        (u->encrypted && !strcmp(ob, "encrypted")) ||
+        (u->encrypted>1 && !strcmp(ob, "trusted"))
+      )
+		return op;
+	else if( (!u->away && !strcmp(hb, "notaway")) ||
+             (u->encrypted && !strcmp(hb, "encrypted")) ||
+             (u->encrypted>1 && !strcmp(hb, "trusted"))
+	       )
+		return halfop;
+	else if( (!u->away && !strcmp(vb, "notaway")) ||
+             (u->encrypted && !strcmp(vb, "encrypted")) ||
+             (u->encrypted>1 && !strcmp(vb, "trusted"))
+	       )
+		return voice;
+	else
+		return none;
+}
+			
 void irc_names( irc_t *irc, char *channel )
 {
@@ -739,5 +783,4 @@
 	char namelist[385] = "";
 	struct groupchat *c = NULL;
-	char *ops = set_getstr( &irc->set, "ops" );
 	
 	/* RFCs say there is no error reply allowed on NAMES, so when the
@@ -754,10 +797,5 @@
 			}
 			
-			if( u->ic && !u->away && set_getbool( &irc->set, "away_devoice" ) )
-				strcat( namelist, "+" );
-			else if( ( strcmp( u->nick, irc->mynick ) == 0 && ( strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) == 0 ) ) ||
-			         ( strcmp( u->nick, irc->nick ) == 0 && ( strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) == 0 ) ) )
-				strcat( namelist, "@" );
-			
+			strcat( namelist, user_mode_prefix(irc, u) );
 			strcat( namelist, u->nick );
 			strcat( namelist, " " );
@@ -770,6 +808,6 @@
 		/* root and the user aren't in the channel userlist but should
 		   show up in /NAMES, so list them first: */
-		sprintf( namelist, "%s%s %s%s ", strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->mynick,
-		                                 strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->nick );
+		sprintf( namelist, "%s%s %s%s ", set_getbool(&irc->set, "op_root") ? "@" : "", irc->mynick,
+		                                 set_getbool(&irc->set, "op_user") ? "@" : "", irc->nick );
 		
 		for( l = c->in_room; l; l = l->next ) if( ( u = user_findhandle( c->ic, l->data ) ) )
@@ -781,4 +819,5 @@
 			}
 			
+			strcat( namelist, user_mode_prefix(irc, u) );
 			strcat( namelist, u->nick );
 			strcat( namelist, " " );
@@ -822,5 +861,5 @@
 	irc_reply( irc,   3, ":%s", IRCD_INFO );
 	irc_reply( irc,   4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
-	irc_reply( irc,   5, "PREFIX=(ov)@+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee "
+	irc_reply( irc,   5, "PREFIX=(ohv)@%%+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee "
 	                     "CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server",
 	                     CTYPES, CMODES, MAX_NICK_LENGTH - 1 );
@@ -828,6 +867,5 @@
 	irc->umode[0] = '\0';
 	irc_umode_set( irc, "+" UMODE, 1 );
-
-	u = user_add( irc, irc->mynick );
+u = user_add( irc, irc->mynick );
 	u->host = g_strdup( irc->myhost );
 	u->realname = g_strdup( ROOT_FN );
@@ -851,10 +889,5 @@
 	irc_spawn( irc, u );
 	
-	irc_usermsg( irc, "Welcome to the BitlBee gateway!\n\n"
-	                  "If you've never used BitlBee before, please do read the help "
-	                  "information using the \x02help\x02 command. Lots of FAQs are "
-	                  "answered there.\n"
-	                  "If you already have an account on this server, just use the "
-	                  "\x02identify\x02 command to identify yourself." );
+	irc_welcome( irc );
 	
 	if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
@@ -871,4 +904,33 @@
 		root_command( irc, send_cmd );
 		g_free( send_cmd[1] );
+	}
+}
+
+static void irc_welcome( irc_t *irc )
+{
+	FILE *f;
+	
+	f = fopen( global.conf->welcomefile, "r" );
+	if( !f )
+	{
+		irc_usermsg( irc, "Welcome to the BitlBee gateway!\n\n"
+	        	          "If you've never used BitlBee before, please do read the help "
+		                  "information using the \x02help\x02 command. Lots of FAQs are "
+		                  "answered there.\n"
+				  "OTR users please note: Private key files are owned by the user "
+				  "BitlBee is running as.\n"
+		                  "If you already have an account on this server, just use the "
+		                  "\x02identify\x02 command to identify yourself." );
+	}
+	else
+	{
+		char linebuf[380];
+		
+		while( fgets( linebuf, 380, f ) )
+		{
+			irc_usermsg( irc, linebuf );
+		}
+		
+		fclose( f );
 	}
 }
Index: irc.h
===================================================================
--- irc.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ irc.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -27,6 +27,8 @@
 #define _IRC_H
 
+#include "otr.h"
+
 #define IRC_MAX_LINE 512
-#define IRC_MAX_ARGS 8
+#define IRC_MAX_ARGS 16
 
 #define IRC_LOGIN_TIMEOUT 60
@@ -94,4 +96,6 @@
 	gint w_watch_source_id;
 	gint ping_source_id;
+	
+	otr_t *otr;            /* OTR state and book keeping */
 } irc_t;
 
Index: irc_commands.c
===================================================================
--- irc_commands.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ irc_commands.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -614,5 +614,4 @@
 	{ "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
 	{ "die",         0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
-	{ "deaf",        0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
 	{ "wallops",     1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
 	{ "wall",        1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
Index: lib/misc.c
===================================================================
--- lib/misc.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ lib/misc.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -157,4 +157,5 @@
 	char *s = out, *cs;
 	int i, matched;
+	int taglen;
 	
 	memset( out, 0, sizeof( out ) );
@@ -173,7 +174,16 @@
 				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", 2) == 0 )
 					*(s++) = '\n';
 				in ++;
Index: lib/ssl_bogus.c
===================================================================
--- lib/ssl_bogus.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ lib/ssl_bogus.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -27,4 +27,8 @@
 
 int ssl_errno;
+
+void ssl_init( void )
+{
+}
 
 void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data )
@@ -66,2 +70,7 @@
 	return 0;
 }
+
+int ssl_pending( void *conn )
+{
+	return 0;
+}
Index: lib/ssl_client.h
===================================================================
--- lib/ssl_client.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ lib/ssl_client.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -47,4 +47,7 @@
 
 
+/* 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
Index: lib/ssl_gnutls.c
===================================================================
--- lib/ssl_gnutls.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ lib/ssl_gnutls.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -60,4 +60,11 @@
 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 )
@@ -122,7 +129,5 @@
 	if( !initialized )
 	{
-		gnutls_global_init();
-		initialized = TRUE;
-		atexit( gnutls_global_deinit );
+		ssl_init();
 	}
 	
Index: lib/ssl_nss.c
===================================================================
--- lib/ssl_nss.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ lib/ssl_nss.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -91,4 +91,12 @@
 
 
+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 )
 {
@@ -107,7 +115,5 @@
 	if( !initialized )
 	{
-		PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
-		NSS_NoDB_Init(NULL);
-		NSS_SetDomesticPolicy();
+		ssl_init();
 	}
 
Index: lib/ssl_openssl.c
===================================================================
--- lib/ssl_openssl.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ lib/ssl_openssl.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -56,4 +56,10 @@
 static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond );
 
+
+void ssl_init( void )
+{
+	initialized = TRUE;
+	SSLeay_add_ssl_algorithms();
+}
 
 void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data )
@@ -115,6 +121,5 @@
 	if( !initialized )
 	{
-		initialized = TRUE;
-		SSLeay_add_ssl_algorithms();
+		ssl_init();
 	}
 	
Index: log.c
===================================================================
--- log.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ log.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -30,8 +30,8 @@
 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) {
@@ -97,5 +97,5 @@
 }
 
-void log_message(int level, char *message, ... ) {
+void log_message(int level, const char *message, ... ) {
 
 	va_list ap;
@@ -122,5 +122,5 @@
 }
 
-void log_error(char *functionname) {
+void log_error(const char *functionname) {
 	log_message(LOGLVL_ERROR, "%s: %s", functionname, strerror(errno));
 	
@@ -128,9 +128,9 @@
 }
 
-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);
@@ -147,5 +147,5 @@
 }
 
-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);
@@ -161,5 +161,5 @@
 }
 
-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);
Index: log.h
===================================================================
--- log.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ log.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -44,9 +44,9 @@
 
 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;
@@ -54,6 +54,6 @@
 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
Index: otr.c
===================================================================
--- otr.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
+++ otr.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -0,0 +1,1731 @@
+  /********************************************************************\
+  * 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
+    
+  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"
+#ifdef WITH_OTR
+#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: **/
+
+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_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 },
+	{ "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;	
+
+
+/** 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 user_t for a given handle/protocol pair
+   returns NULL if not found */
+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);
+
+/* 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, 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);
+
+
+/*** routines declared in otr.h: ***/
+
+void otr_init(void)
+{
+	OTRL_INIT;
+	
+	/* fill global OtrlMessageAppOps */
+	global.otr_ops.policy = &op_policy;
+	global.otr_ops.create_privkey = &op_create_privkey;
+	global.otr_ops.is_logged_in = &op_is_logged_in;
+	global.otr_ops.inject_message = &op_inject_message;
+	global.otr_ops.notify = NULL;
+	global.otr_ops.display_otr_message = &op_display_otr_message;
+	global.otr_ops.update_context_list = NULL;
+	global.otr_ops.protocol_name = NULL;
+	global.otr_ops.protocol_name_free = NULL;
+	global.otr_ops.new_fingerprint = &op_new_fingerprint;
+	global.otr_ops.write_fingerprints = &op_write_fingerprints;
+	global.otr_ops.gone_secure = &op_gone_secure;
+	global.otr_ops.gone_insecure = &op_gone_insecure;
+	global.otr_ops.still_secure = &op_still_secure;
+	global.otr_ops.log_message = &op_log_message;
+	global.otr_ops.max_message_size = &op_max_message_size;
+	global.otr_ops.account_name = &op_account_name;
+	global.otr_ops.account_name_free = NULL;
+}
+
+otr_t *otr_new(void)
+{
+	otr_t *otr = g_new0(otr_t, 1);
+
+	otr->us = otrl_userstate_create();
+	
+	return otr;
+}
+
+void otr_free(otr_t *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->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->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->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->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->irc;
+	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_handle_message(struct im_connection *ic, const char *handle, const char *msg)
+{
+	int ignore_msg;
+	char *newmsg = NULL;
+	OtrlTLV *tlvs = NULL;
+	char *colormsg;
+	
+	/* don't do OTR on certain (not classic IM) protocols, e.g. twitter */
+	if(ic->acc->prpl->options & OPT_NOOTR) {
+		return (g_strdup(msg));
+	}
+	
+	ignore_msg = otrl_message_receiving(ic->irc->otr->us, &global.otr_ops, ic,
+		ic->acc->user, ic->acc->prpl->name, handle, msg, &newmsg,
+		&tlvs, NULL, NULL);
+
+	otr_handle_smp(ic, 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(ic->irc->otr->us, handle,
+			ic->acc->user, ic->acc->prpl->name, 0, NULL, NULL, NULL);
+		if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
+		   set_getbool(&ic->irc->set, "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;
+	}
+}
+
+int otr_send_message(struct im_connection *ic, const char *handle, const char *msg, int flags)
+{	
+	int st;
+	char *otrmsg = NULL;
+	ConnContext *ctx = NULL;
+
+	/* don't do OTR on certain (not classic IM) protocols, e.g. twitter */
+	if(ic->acc->prpl->options & OPT_NOOTR) {
+		return (ic->acc->prpl->buddy_msg(ic, handle, msg, flags));
+	}
+	
+	st = otrl_message_sending(ic->irc->otr->us, &global.otr_ops, ic,
+		ic->acc->user, ic->acc->prpl->name, handle,
+		msg, NULL, &otrmsg, NULL, NULL);
+	if(st) {
+		return st;
+	}
+
+	ctx = otrl_context_find(ic->irc->otr->us,
+			handle, ic->acc->user, ic->acc->prpl->name,
+			1, NULL, NULL, NULL);
+
+	if(otrmsg) {
+		if(!ctx) {
+			otrl_message_free(otrmsg);
+			return 1;
+		}
+		st = otrl_message_fragment_and_send(&global.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 */
+		st = ic->acc->prpl->buddy_msg( ic, (char *)handle, (char *)msg, flags );
+	}
+	
+	return st;
+}
+
+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);
+	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(ic->irc, context->accountname, context->protocol) &&
+	   !otrl_privkey_find(ic->irc->otr->us, context->accountname, context->protocol))
+		return OTRL_POLICY_NEVER;
+
+	p = set_getstr(&ic->irc->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);
+	
+	/* will fail silently if keygen already in progress */
+	otr_keygen(ic->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);
+	user_t *u;
+
+	/* lookup the user_t for the given recipient */
+	u = user_findhandle(ic, recipient);
+	if(u) {
+		if(u->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);
+
+	if (strcmp(accountname, recipient) == 0) {
+		/* huh? injecting messages to myself? */
+		irc_usermsg(ic->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);
+
+	strip_html(msg);
+	irc_usermsg(ic->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);
+	char hunam[45];		/* anybody looking? ;-) */
+	
+	otrl_privkey_hash_to_human(hunam, fingerprint);
+	irc_usermsg(ic->irc, "new fingerprint for %s: %s",
+		peernick(ic->irc, username, protocol), hunam);
+}
+
+void op_write_fingerprints(void *opdata)
+{
+	struct im_connection *ic = (struct im_connection *)opdata;
+
+	otr_save(ic->irc);
+}
+
+void op_gone_secure(void *opdata, ConnContext *context)
+{
+	struct im_connection *ic =
+		check_imc(opdata, context->accountname, context->protocol);
+	user_t *u;
+	const char *trust;
+
+	u = peeruser(ic->irc, context->username, context->protocol);
+	if(!u) {
+		log_message(LOGLVL_ERROR,
+			"BUG: otr.c: op_gone_secure: 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->encrypted = 2;
+	else
+		u->encrypted = 1;
+	if(!otr_update_modeflags(ic->irc, u))
+		irc_usermsg(ic->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);
+	user_t *u;
+
+	u = peeruser(ic->irc, context->username, context->protocol);
+	if(!u) {
+		log_message(LOGLVL_ERROR,
+			"BUG: otr.c: op_gone_insecure: user_t for %s/%s/%s not found!",
+			context->username, context->protocol, context->accountname);
+		return;
+	}
+	u->encrypted = 0;
+	if(!otr_update_modeflags(ic->irc, u))
+		irc_usermsg(ic->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);
+	user_t *u;
+
+	u = peeruser(ic->irc, context->username, context->protocol);
+	if(!u) {
+		log_message(LOGLVL_ERROR,
+			"BUG: otr.c: op_still_secure: user_t for %s/%s/%s not found!",
+			context->username, context->protocol, context->accountname);
+		return;
+	}
+	if(context->active_fingerprint->trust[0])
+		u->encrypted = 2;
+	else
+		u->encrypted = 1;
+	if(!otr_update_modeflags(ic->irc, u))
+		irc_usermsg(ic->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;
+
+	return peernick(ic->irc, account, protocol);
+}
+
+
+/*** OTR sub-command handlers ***/
+
+void cmd_otr_disconnect(irc_t *irc, char **args)
+{
+	user_t *u;
+
+	u = user_find(irc, args[1]);
+	if(!u || !u->ic) {
+		irc_usermsg(irc, "%s: unknown user", args[1]);
+		return;
+	}
+	
+	otrl_message_disconnect(irc->otr->us, &global.otr_ops,
+		u->ic, u->ic->acc->user, u->ic->acc->prpl->name, u->handle);
+	
+	/* for some reason, libotr (3.1.0) doesn't do this itself: */
+	if(u->encrypted) {
+		ConnContext *ctx;
+		ctx = otrl_context_find(irc->otr->us, u->handle, u->ic->acc->user,
+			u->ic->acc->prpl->name, 0, NULL, NULL, NULL);
+		if(ctx)
+			op_gone_insecure(u->ic, ctx);
+		else /* huh? */
+			u->encrypted = 0;
+	}
+}
+
+void cmd_otr_connect(irc_t *irc, char **args)
+{
+	user_t *u;
+
+	u = user_find(irc, args[1]);
+	if(!u || !u->ic) {
+		irc_usermsg(irc, "%s: unknown user", args[1]);
+		return;
+	}
+	if(!u->online) {
+		irc_usermsg(irc, "%s is offline", args[1]);
+		return;
+	}
+	
+	imc_buddy_msg(u->ic, u->handle, "?OTR?", 0);
+}
+
+void cmd_otr_smp(irc_t *irc, char **args)
+{
+	user_t *u;
+	ConnContext *ctx;
+	
+	u = user_find(irc, args[1]);
+	if(!u || !u->ic) {
+		irc_usermsg(irc, "%s: unknown user", args[1]);
+		return;
+	}
+	if(!u->online) {
+		irc_usermsg(irc, "%s is offline", args[1]);
+		return;
+	}
+	
+	ctx = otrl_context_find(irc->otr->us, u->handle,
+		u->ic->acc->user, u->ic->acc->prpl->name, 1, NULL, NULL, NULL);
+	if(!ctx) {
+		/* huh? out of memory or what? */
+		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, &global.otr_ops, u->ic, ctx);
+		otrl_sm_state_free(ctx->smstate);
+	}
+	
+	/* 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, &global.otr_ops,
+			u->ic, ctx, (unsigned char *)args[2], strlen(args[2]));
+		/* 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, &global.otr_ops,
+			u->ic, ctx, (unsigned char *)args[2], strlen(args[2]));
+		/* smp is now in EXPECT3 */
+	}
+}
+
+void cmd_otr_trust(irc_t *irc, char **args)
+{
+	user_t *u;
+	ConnContext *ctx;
+	unsigned char raw[20];
+	Fingerprint *fp;
+	int i,j;
+	
+	u = user_find(irc, args[1]);
+	if(!u || !u->ic) {
+		irc_usermsg(irc, "%s: unknown user", args[1]);
+		return;
+	}
+	
+	ctx = otrl_context_find(irc->otr->us, u->handle,
+		u->ic->acc->user, u->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->encrypted)
+			u->encrypted = 2;
+		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 {
+			user_t *u = user_find(irc, args[1]);
+			if(!u || !u->ic) {
+				irc_usermsg(irc, "%s: unknown user", args[1]);
+				g_free(arg);
+				return;
+			}
+			ctx = otrl_context_find(irc->otr->us, u->handle, u->ic->acc->user,
+				u->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->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, 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"))
+	{
+		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 = user_find(irc, args[2]);
+		if(!u || !u->ic) {
+			irc_usermsg(irc, "%s: unknown user", args[2]);
+			return;
+		}
+		
+		ctx = otrl_context_find(irc->otr->us, u->handle, u->ic->acc->user,
+			u->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, p);
+		g_free(s);
+	}
+	
+	else if(!strcmp(args[1], "context"))
+	{
+		user_t *u;
+		ConnContext *ctx;
+		char *s;
+		pair_t *p;
+		
+		/* TODO: allow context specs ("user/proto/account") in 'otr forget contex'? */
+		u = user_find(irc, args[2]);
+		if(!u || !u->ic) {
+			irc_usermsg(irc, "%s: unknown user", args[2]);
+			return;
+		}
+		
+		ctx = otrl_context_find(irc->otr->us, u->handle, u->ic->acc->user,
+			u->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, 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, 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->irc;
+	OtrlUserState us = irc->otr->us;
+	OtrlMessageAppOps *ops = &global.otr_ops;
+	OtrlTLV *tlv = NULL;
+	ConnContext *context;
+	NextExpectedSMP nextMsg;
+	user_t *u;
+
+	u = user_findhandle(ic, handle);
+	if(!u) 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? */
+		return;
+	}
+	nextMsg = context->smstate->nextExpected;
+
+	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->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->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->ic, context);
+			otrl_sm_state_free(context->smstate);
+		} else {
+			/* SMP3 received, otrl_message_receiving will have sent SMP4 and set fp trust */
+			const char *trust = context->active_fingerprint->trust;
+			if(!trust || trust[0]=='\0') {
+				irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
+					u->nick);
+			} else {
+				irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint 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->ic, context);
+			otrl_sm_state_free(context->smstate);
+		} else {
+			/* SMP4 received, otrl_message_receiving will have set fp trust */
+			const char *trust = context->active_fingerprint->trust;
+			if(!trust || trust[0]=='\0') {
+				irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
+					u->nick);
+			} else {
+				irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint 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 */
+	}
+}
+
+/* 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;
+}
+
+user_t *peeruser(irc_t *irc, const char *handle, const char *protocol)
+{
+	user_t *u;
+	
+	for(u=irc->users; u; u=u->next) {
+		struct prpl *prpl;
+		if(!u->ic || !u->handle)
+			continue;
+		prpl = u->ic->acc->prpl;
+		if(strcmp(prpl->name, protocol) == 0
+			&& prpl->handle_cmp(u->handle, handle) == 0) {
+			return u;
+		}
+	}
+	
+	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];
+	
+	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, user_t *u)
+{
+	char *vb = set_getstr(&irc->set, "voice_buddies");
+	char *hb = set_getstr(&irc->set, "halfop_buddies");
+	char *ob = set_getstr(&irc->set, "op_buddies");
+	int encrypted = u->encrypted;
+	int trusted = u->encrypted > 1;
+	char flags[7];
+	int nflags=0;
+	char *p = flags;
+	char *from;
+	int i;
+	
+	if(!strcmp(vb, "encrypted")) {
+		*(p++) = encrypted ? '+' : '-';
+		*(p++) = 'v';
+		nflags++;
+	} else if(!strcmp(vb, "trusted")) {
+		*(p++) = trusted ? '+' : '-';
+		*(p++) = 'v';
+		nflags++;
+	}
+	if(!strcmp(hb, "encrypted")) {
+		*(p++) = encrypted ? '+' : '-';
+		*(p++) = 'h';
+		nflags++;
+	} else if(!strcmp(hb, "trusted")) {
+		*(p++) = trusted ? '+' : '-';
+		*(p++) = 'h';
+		nflags++;
+	}
+	if(!strcmp(ob, "encrypted")) {
+		*(p++) = encrypted ? '+' : '-';
+		*(p++) = 'o';
+		nflags++;
+	} else if(!strcmp(ob, "trusted")) {
+		*(p++) = trusted ? '+' : '-';
+		*(p++) = 'o';
+		nflags++;
+	}
+	*p = '\0';
+	
+	p = g_malloc(nflags * (strlen(u->nick)+1) + 1);
+	*p = '\0';
+	if(!p)
+		return 0;
+	for(i=0; i<nflags; i++) {
+		strcat(p, " ");
+		strcat(p, u->nick);
+	}
+	if(set_getbool(&irc->set, "simulate_netsplit"))
+		from = g_strdup(irc->myhost);
+	else
+		from = g_strdup_printf("%s!%s@%s", irc->mynick, irc->mynick, irc->myhost);
+	irc_write(irc, ":%s MODE %s %s%s", from, irc->channel, flags, p);
+	g_free(from);
+	g_free(p);
+		
+	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) {\
+		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], GAIM_INPUT_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->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);
+		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;
+	
+	if(keygen_in_progress(acc->irc, acc->user, acc->prpl->name)) {
+		irc_usermsg(acc->irc, "keygen for %s/%s already in progress",
+			acc->user, acc->prpl->name);
+	} else {
+		irc_usermsg(acc->irc, "starting background keygen for %s/%s",
+			acc->user, acc->prpl->name);
+		irc_usermsg(acc->irc, "you will be notified when it completes");
+		otr_keygen(acc->irc, acc->user, acc->prpl->name);
+	}
+}
+
+
+#else /* WITH_OTR undefined */
+
+void cmd_otr(irc_t *irc, char **args)
+{
+	irc_usermsg(irc, "otr: n/a, compiled without OTR support");
+}
+
+#endif
Index: otr.h
===================================================================
--- otr.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
+++ otr.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -0,0 +1,117 @@
+  /********************************************************************\
+  * 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;
+
+// 'otr' root command, hooked up in root_commands.c
+void cmd_otr(struct irc *, char **args);
+
+
+#ifdef WITH_OTR
+#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 from irc_new()/irc_free() */
+otr_t *otr_new();
+void otr_free(otr_t *otr);
+
+/* 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);
+
+/* called from imcb_buddy_msg() */
+char *otr_handle_message(struct im_connection *ic, const char *handle,
+	const char *msg);
+	
+/* called from imc_buddy_msg() */
+int otr_send_message(struct im_connection *ic, const char *handle, const char *msg,
+	int flags);
+
+#else
+
+typedef void otr_t;
+typedef void *OtrlMessageAppOps;
+
+#define otr_init() {}
+#define otr_new() (NULL)
+#define otr_free(otr) {}
+#define otr_load(irc) {}
+#define otr_save(irc) {}
+#define otr_remove(nick) {}
+#define otr_rename(onick,nnick) {}
+#define otr_check_for_key(acc) (0)
+#define otr_handle_message(ic,handle,msg) (g_strdup(msg))
+#define otr_send_message(ic,h,m,f) (ic->acc->prpl->buddy_msg(ic,h,m,f))
+
+void cmd_otr_nosupport(void *, char **);
+
+#endif
+#endif
Index: protocols/jabber/jabber.c
===================================================================
--- protocols/jabber/jabber.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/jabber/jabber.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -526,4 +526,5 @@
 	
 	ret->name = "jabber";
+    ret->mms = 0;                        /* no limit */
 	ret->login = jabber_login;
 	ret->init = jabber_init;
Index: protocols/msn/msn.c
===================================================================
--- protocols/msn/msn.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/msn/msn.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -307,4 +307,5 @@
 	
 	ret->name = "msn";
+    ret->mms = 1409;         /* this guess taken from libotr UPGRADING file */
 	ret->login = msn_login;
 	ret->init = msn_init;
Index: protocols/msn/ns.c
===================================================================
--- protocols/msn/ns.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/msn/ns.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -271,23 +271,9 @@
 		if( num_parts == 5 )
 		{
-			int i, groupcount;
-			
-			groupcount = atoi( cmd[4] );
-			if( groupcount > 0 )
-			{
-				/* valgrind says this is leaking memory, I'm guessing
-				   that this happens during server redirects. */
-				if( md->grouplist )
-				{
-					for( i = 0; i < md->groupcount; i ++ )
-						g_free( md->grouplist[i] );
-					g_free( md->grouplist );
-				}
-				
-				md->groupcount = groupcount;
+			md->buddycount = atoi( cmd[3] );
+			md->groupcount = atoi( cmd[4] );
+			if( md->groupcount > 0 )
 				md->grouplist = g_new0( char *, md->groupcount );
-			}
-			
-			md->buddycount = atoi( cmd[3] );
+			
 			if( !*cmd[3] || md->buddycount == 0 )
 				msn_logged_in( ic );
Index: protocols/nogaim.c
===================================================================
--- protocols/nogaim.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/nogaim.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -725,6 +725,8 @@
 	}
 	
-	/* early if-clause for show_offline even if there is some redundant code here because this isn't LISP but C ;) */
-	if( set_getbool( &ic->irc->set, "show_offline" ) && set_getbool( &ic->irc->set, "away_devoice" ) )
+	/* LISPy... */
+	if( ( u->online ) &&						/* Don't touch offline people */
+	    ( ( u->online != oo ) ||					/* Do joining people */
+	      ( ( u->online == oo ) && ( oa == !u->away ) ) ) )		/* Do people changing state */
 	{
 		char *from;
@@ -740,40 +742,28 @@
 		}
 
-		/* if we use show_offline, we op online users, voice away users, and devoice/deop offline users */
-		if( flags & OPT_LOGGED_IN )
-		{
-			/* user is "online" (either really online or away) */
-			irc_write( ic->irc, ":%s MODE %s %cv%co %s %s", from, ic->irc->channel,
-			                                          u->away?'+':'-', u->away?'-':'+', u->nick, u->nick );
-		}
-		else
-		{
-			/* user is offline */
-			irc_write( ic->irc, ":%s MODE %s -vo %s %s", from, ic->irc->channel, u->nick, u->nick );
-		}
-	}
-	else
-	{ 
-		/* LISPy... */
-		if( ( set_getbool( &ic->irc->set, "away_devoice" ) ) &&		/* Don't do a thing when user doesn't want it */
-		    ( u->online ) &&						/* Don't touch offline people */
-		    ( ( ( u->online != oo ) && !u->away ) ||			/* Voice joining people */
-		      ( ( u->online == oo ) && ( oa == !u->away ) ) ) )		/* (De)voice people changing state */
-		{
-			char *from;
-
-			if( set_getbool( &ic->irc->set, "simulate_netsplit" ) )
-			{
-				from = g_strdup( ic->irc->myhost );
-			}
-			else
-			{
-				from = g_strdup_printf( "%s!%s@%s", ic->irc->mynick, ic->irc->mynick,
-				                                    ic->irc->myhost );
-			}
+		if(!strcmp(set_getstr(&ic->irc->set, "voice_buddies"), "online")) {
+			irc_write( ic->irc, ":%s MODE %s +v %s", from, ic->irc->channel, u->nick );
+		}
+		if(!strcmp(set_getstr(&ic->irc->set, "halfop_buddies"), "online")) {
+			irc_write( ic->irc, ":%s MODE %s +h %s", from, ic->irc->channel, u->nick );
+		}
+		if(!strcmp(set_getstr(&ic->irc->set, "op_buddies"), "online")) {
+			irc_write( ic->irc, ":%s MODE %s +o %s", from, ic->irc->channel, u->nick );
+		}
+
+		if(!strcmp(set_getstr(&ic->irc->set, "voice_buddies"), "notaway")) {
 			irc_write( ic->irc, ":%s MODE %s %cv %s", from, ic->irc->channel,
-			                                          u->away?'-':'+', u->nick );
-			g_free( from );
-		}
+		 	                                         u->away?'-':'+', u->nick );
+		}
+		if(!strcmp(set_getstr(&ic->irc->set, "halfop_buddies"), "notaway")) {
+			irc_write( ic->irc, ":%s MODE %s %ch %s", from, ic->irc->channel,
+		 	                                         u->away?'-':'+', u->nick );
+		}
+		if(!strcmp(set_getstr(&ic->irc->set, "op_buddies"), "notaway")) {
+			irc_write( ic->irc, ":%s MODE %s %co %s", from, ic->irc->channel,
+		 	                                         u->away?'-':'+', u->nick );
+		}
+
+		g_free( from );
 	}
 }
@@ -784,7 +774,13 @@
 	char *wrapped, *ts = NULL;
 	user_t *u;
-	
+
+	/* pass the message through OTR */
+	msg = otr_handle_message(ic, handle, msg);
+	if(!msg) {
+		/* this was an internal OTR protocol message */
+		return;
+	}
+
 	u = user_findhandle( ic, handle );
-	
 	if( !u )
 	{
@@ -796,4 +792,5 @@
 				imcb_log( ic, "Ignoring message from unknown handle %s", handle );
 			
+			g_free(msg);
 			return;
 		}
@@ -829,6 +826,6 @@
 	{
 		char *new = g_strconcat( ts, msg, NULL );
-		g_free( ts );
-		ts = msg = new;
+		g_free( msg );
+		msg = new;
 	}
 	
@@ -836,4 +833,5 @@
 	irc_msgfrom( irc, u->nick, wrapped );
 	g_free( wrapped );
+	g_free( msg );
 	g_free( ts );
 }
@@ -1112,59 +1110,4 @@
 /* Misc. BitlBee stuff which shouldn't really be here */
 
-char *set_eval_away_devoice( set_t *set, char *value )
-{
-	irc_t *irc = set->data;
-	int st;
-	
-	if( !is_bool( value ) )
-		return SET_INVALID;
-	
-	st = bool2int( value );
-	
-	/* Horror.... */
-	
-	if( st != set_getbool( &irc->set, "away_devoice" ) )
-	{
-		char list[80] = "";
-		user_t *u = irc->users;
-		int i = 0, count = 0;
-		char pm;
-		char v[80];
-		
-		if( st )
-			pm = '+';
-		else
-			pm = '-';
-		
-		while( u )
-		{
-			if( u->ic && u->online && !u->away )
-			{
-				if( ( strlen( list ) + strlen( u->nick ) ) >= 79 )
-				{
-					for( i = 0; i < count; v[i++] = 'v' ); v[i] = 0;
-					irc_write( irc, ":%s MODE %s %c%s%s",
-					           irc->myhost,
-		        			   irc->channel, pm, v, list );
-					
-					*list = 0;
-					count = 0;
-				}
-				
-				sprintf( list + strlen( list ), " %s", u->nick );
-				count ++;
-			}
-			u = u->next;
-		}
-		
-		/* $v = 'v' x $i */
-		for( i = 0; i < count; v[i++] = 'v' ); v[i] = 0;
-		irc_write( irc, ":%s MODE %s %c%s%s", irc->myhost,
-		                                            irc->channel, pm, v, list );
-	}
-	
-	return value;
-}
-
 char *set_eval_timezone( set_t *set, char *value )
 {
@@ -1269,8 +1212,9 @@
 		msg = buf;
 	}
-	
-	st = ic->acc->prpl->buddy_msg( ic, handle, msg, flags );
-	g_free( buf );
-	
+
+	/* if compiled without otr support, this just calls the prpl buddy_msg */
+	st = otr_send_message(ic, handle, msg, flags);
+	
+	g_free(buf);
 	return st;
 }
Index: protocols/nogaim.h
===================================================================
--- protocols/nogaim.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/nogaim.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -59,4 +59,5 @@
 #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 */
@@ -132,4 +133,8 @@
 	 * - The user sees this name ie. when imcb_log() is used. */
 	const char *name;
+	/* 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
@@ -326,5 +331,4 @@
 /* Misc. stuff */
 char *set_eval_timezone( set_t *set, char *value );
-char *set_eval_away_devoice( set_t *set, char *value );
 gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond );
 void cancel_auto_reconnect( struct account *a );
Index: protocols/oscar/oscar.c
===================================================================
--- protocols/oscar/oscar.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/oscar/oscar.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -2554,4 +2554,5 @@
 	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;
Index: protocols/twitter/twitter.c
===================================================================
--- protocols/twitter/twitter.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/twitter/twitter.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -356,4 +356,5 @@
 	struct prpl *ret = g_new0(struct prpl, 1);
 	
+	ret->options = OPT_NOOTR;
 	ret->name = "twitter";
 	ret->login = twitter_login;
Index: protocols/yahoo/libyahoo2.c
===================================================================
--- protocols/yahoo/libyahoo2.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/yahoo/libyahoo2.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -69,4 +69,6 @@
 #ifdef __MINGW32__
 # include <winsock2.h>
+# define write(a,b,c) send(a,b,c,0)
+# define read(a,b,c)  recv(a,b,c,0)
 #endif
 
Index: protocols/yahoo/yahoo.c
===================================================================
--- protocols/yahoo/yahoo.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/yahoo/yahoo.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -355,4 +355,5 @@
 	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;
 	
Index: protocols/yahoo/yahoo_httplib.c
===================================================================
--- protocols/yahoo/yahoo_httplib.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ protocols/yahoo/yahoo_httplib.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -51,4 +51,6 @@
 #ifdef __MINGW32__
 # include <winsock2.h>
+# define write(a,b,c) send(a,b,c,0)
+# define read(a,b,c)  recv(a,b,c,0)
 # define snprintf _snprintf
 #endif
Index: query.c
===================================================================
--- query.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ query.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -103,4 +103,7 @@
 	int count = 0;
 	
+	if(!ic)
+		return;
+	
 	q = irc->queries;
 	def = query_default( irc );
@@ -145,5 +148,6 @@
 		else
 			irc_usermsg( irc, "Accepted: %s", q->question );
-		q->yes( q->data );
+		if( q->yes )
+			q->yes( q->data );
 	}
 	else
@@ -153,5 +157,6 @@
 		else
 			irc_usermsg( irc, "Rejected: %s", q->question );
-		q->no( q->data );
+		if( q->no )
+			q->no( q->data );
 	}
 	q->data = NULL;
Index: root_commands.c
===================================================================
--- root_commands.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ root_commands.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -29,4 +29,5 @@
 #include "bitlbee.h"
 #include "help.h"
+#include "otr.h"
 #include "chat.h"
 
@@ -414,4 +415,8 @@
 		
 		irc_usermsg( irc, "Account successfully added" );
+		
+		if(otr_check_for_key(a)) {
+			irc_usermsg(irc, "otr: you will be notified when it completes");
+		}
 	}
 	else if( g_strcasecmp( cmd[1], "del" ) == 0 )
@@ -1177,4 +1182,5 @@
 	{ "qlist",          0, cmd_qlist,          0 },
 	{ "join_chat",      2, cmd_join_chat,      0 },
+	{ "otr",            1, cmd_otr,            0 },	
 	{ "chat",           1, cmd_chat,           0 },
 	{ NULL }
Index: set.c
===================================================================
--- set.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ set.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -225,23 +225,166 @@
 }
 
-char *set_eval_ops( set_t *set, char *value )
+char* set_eval_op_root( set_t *set, char* value )
 {
 	irc_t *irc = set->data;
-	
-	if( g_strcasecmp( value, "user" ) == 0 )
-		irc_write( irc, ":%s!%s@%s MODE %s %s %s %s", irc->mynick, irc->mynick, irc->myhost,
-		                                              irc->channel, "+o-o", irc->nick, irc->mynick );
-	else if( g_strcasecmp( value, "root" ) == 0 )
-		irc_write( irc, ":%s!%s@%s MODE %s %s %s %s", irc->mynick, irc->mynick, irc->myhost,
-		                                              irc->channel, "-o+o", irc->nick, irc->mynick );
-	else if( g_strcasecmp( value, "both" ) == 0 )
-		irc_write( irc, ":%s!%s@%s MODE %s %s %s %s", irc->mynick, irc->mynick, irc->myhost,
-		                                              irc->channel, "+oo", irc->nick, irc->mynick );
-	else if( g_strcasecmp( value, "none" ) == 0 )
-		irc_write( irc, ":%s!%s@%s MODE %s %s %s %s", irc->mynick, irc->mynick, irc->myhost,
-		                                              irc->channel, "-oo", irc->nick, irc->mynick );
+	char* ret = set_eval_bool(set, value);
+	int b = bool2int(ret);
+
+	irc_write( irc, ":%s!%s@%s MODE %s %s %s", irc->mynick, irc->mynick, irc->myhost,
+                                               irc->channel, b?"+o":"-o", irc->mynick);
+
+	return ret;
+}
+
+char* set_eval_op_user( set_t *set, char* value )
+{
+	irc_t *irc = set->data;
+	char* ret = set_eval_bool(set, value);
+	int b = bool2int(ret);
+
+	irc_write( irc, ":%s!%s@%s MODE %s %s %s", irc->mynick, irc->mynick, irc->myhost,
+                                               irc->channel, b?"+o":"-o", irc->nick);
+
+	return ret;
+}
+
+/* generalized version of set_eval_op/voice_buddies */
+char *set_eval_mode_buddies( set_t *set, char *value, char modeflag )
+{
+	irc_t *irc = set->data;
+	char op[64], deop[64];
+	int nop=0, ndeop=0;
+	user_t *u;
+	int mode;
+	
+	if(!strcmp(value, "false"))
+		mode=0;
+	else if(!strcmp(value, "encrypted"))
+		mode=1;
+	else if(!strcmp(value, "trusted"))
+		mode=2;
+	else if(!strcmp(value, "notaway"))
+		mode=3;
 	else
 		return SET_INVALID;
-	
+
+	/* sorry for calling them op/deop - too lazy for search+replace :P */
+	op[0]='\0';
+	deop[0]='\0';
+	for(u=irc->users; u; u=u->next) {
+		/* we're only concerned with online buddies */
+		if(!u->ic || !u->online)
+			continue;
+
+		/* just in case... */
+		if(strlen(u->nick) >= 64)
+			continue;
+		
+		/* dump out ops/deops when the corresponding name list fills up */
+		if(strlen(op)+strlen(u->nick)+2 > 64) {
+			char *flags = g_strnfill(nop, modeflag);
+			irc_write( irc, ":%s!%s@%s MODE %s +%s%s", irc->mynick, irc->mynick, irc->myhost,
+		                                               irc->channel, flags, op );
+		    op[0]='\0';
+            nop=0;
+		    g_free(flags);
+		}
+		if(strlen(deop)+strlen(u->nick)+2 > 64) {
+			char *flags = g_strnfill(ndeop, modeflag);
+			irc_write( irc, ":%s!%s@%s MODE %s -%s%s", irc->mynick, irc->mynick, irc->myhost,
+		                                               irc->channel, flags, deop );
+		    deop[0]='\0';
+            ndeop=0;
+		    g_free(flags);
+		}
+		
+		switch(mode) {
+		/* "false" */
+		case 0:
+			g_strlcat(deop, " ", 64);
+			g_strlcat(deop, u->nick, 64);
+			ndeop++;
+			break;
+		/* "encrypted" */
+		case 1:
+			if(u->encrypted) {
+				g_strlcat(op, " ", 64);
+				g_strlcat(op, u->nick, 64);
+				nop++;
+			} else {
+				g_strlcat(deop, " ", 64);
+				g_strlcat(deop, u->nick, 64);
+				ndeop++;
+			}
+			break;
+		/* "trusted" */
+		case 2:
+			if(u->encrypted > 1) {
+				g_strlcat(op, " ", 64);
+				g_strlcat(op, u->nick, 64);
+				nop++;
+			} else {
+				g_strlcat(deop, " ", 64);
+				g_strlcat(deop, u->nick, 64);
+				ndeop++;
+			}
+			break;
+		/* "notaway" */
+		case 3:
+			if(u->away) {
+				g_strlcat(deop, " ", 64);
+				g_strlcat(deop, u->nick, 64);
+				ndeop++;
+			} else {
+				g_strlcat(op, " ", 64);
+				g_strlcat(op, u->nick, 64);
+				nop++;
+			}
+		}
+	}
+	/* dump anything left in op/deop lists */
+	if(*op) {
+		char *flags = g_strnfill(nop, modeflag);
+		irc_write( irc, ":%s!%s@%s MODE %s +%s%s", irc->mynick, irc->mynick, irc->myhost,
+		                                               irc->channel, flags, op );
+		g_free(flags);
+	}
+	if(*deop) {
+		char *flags = g_strnfill(ndeop, modeflag);
+		irc_write( irc, ":%s!%s@%s MODE %s -%s%s", irc->mynick, irc->mynick, irc->myhost,
+                                                   irc->channel, flags, deop );
+        g_free(flags);
+    }
+
 	return value;
 }
+
+char *set_eval_op_buddies( set_t *set, char *value )
+{
+	return set_eval_mode_buddies(set, value, 'o');
+}
+
+char *set_eval_halfop_buddies( set_t *set, char *value )
+{
+	return set_eval_mode_buddies(set, value, 'h');
+}
+
+char *set_eval_voice_buddies( set_t *set, char *value )
+{
+	return set_eval_mode_buddies(set, value, 'v');
+}
+
+/* possible values: never, opportunistic, manual, always */
+char *set_eval_otr_policy( set_t *set, char *value )
+{
+	if ( !strcmp(value, "never") )
+		return value;
+	if ( !strcmp(value, "opportunistic") )
+		return value;
+	if ( !strcmp(value, "manual") )
+		return value;
+	if ( !strcmp(value, "always") )
+		return value;
+	return NULL;
+}
+
Index: set.h
===================================================================
--- set.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ set.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -100,5 +100,10 @@
 /* Some not very generic evaluators that really shouldn't be here... */
 char *set_eval_to_char( set_t *set, char *value );
-char *set_eval_ops( set_t *set, char *value );
+char *set_eval_op_root( set_t *set, char *value );
+char *set_eval_op_user( set_t *set, char *value );
+char *set_eval_op_buddies( set_t *set, char *value );
+char *set_eval_halfop_buddies( set_t *set, char *value );
+char *set_eval_voice_buddies( set_t *set, char *value );
+char *set_eval_otr_policy( set_t *set, char *value );
 
 #endif /* __SET_H__ */
Index: sock.h
===================================================================
--- sock.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ sock.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -16,9 +16,15 @@
 #else
 # include <winsock2.h>
-# include <ws2tcpip.h>
+# ifndef _MSC_VER
+#  include <ws2tcpip.h>
+# endif
 # if !defined(BITLBEE_CORE) && defined(_MSC_VER)
 #   pragma comment(lib,"bitlbee.lib")
 # endif
 # include <io.h>
+# define read(a,b,c) recv(a,b,c,0)
+# define write(a,b,c) send(a,b,c,0)
+# define umask _umask
+# define mode_t int
 # define sock_make_nonblocking(fd) { int non_block = 1; ioctlsocket(fd, FIONBIO, &non_block); }
 # define sock_make_blocking(fd) { int non_block = 0; ioctlsocket(fd, FIONBIO, &non_block); }
Index: storage.c
===================================================================
--- storage.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ storage.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -29,4 +29,5 @@
 #include "bitlbee.h"
 #include "crypting.h"
+#include "otr.h"
 
 extern storage_t storage_text;
@@ -116,7 +117,8 @@
 
 		status = st->load(irc, password);
-		if (status == STORAGE_OK)
+		if (status == STORAGE_OK) {
+			otr_load(irc);
 			return status;
-		
+		}
 		if (status != STORAGE_NO_SUCH_USER) 
 			return status;
@@ -139,5 +141,6 @@
 		return STORAGE_NO_SUCH_USER;
 	}
-	
+
+	otr_save(irc);
 	st = ((storage_t *)global.storage->data)->save(irc, overwrite);
 	
@@ -165,4 +168,7 @@
 			ret = status;
 	}
+	if (ret == STORAGE_OK) {
+		otr_remove(nick);
+	}
 	
 	return ret;
@@ -178,10 +184,12 @@
 	storage_t *primary_storage = gl->data;
 	irc_t *irc;
-
+	
 	/* First, try to rename in the current write backend, assuming onick 
 	 * is stored there */
 	status = primary_storage->rename(onick, nnick, password);
-	if (status != STORAGE_NO_SUCH_USER)
+	if (status != STORAGE_NO_SUCH_USER) {
+		otr_rename(onick, nnick);
 		return status;
+	}
 
 	/* Try to load from a migration backend and save to the current backend. 
@@ -207,4 +215,5 @@
 
 	storage_remove(onick, password);
+	otr_rename(onick, nnick);
 
 	return STORAGE_OK;
Index: storage_text.c
===================================================================
--- storage_text.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ storage_text.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -27,12 +27,4 @@
 #include "bitlbee.h"
 #include "crypting.h"
-#ifdef _WIN32
-# define umask _umask
-# define mode_t int
-#endif
-
-#ifndef F_OK
-#define F_OK 0
-#endif
 
 static void text_init (void)
Index: unix.c
===================================================================
--- unix.c	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ unix.c	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -29,7 +29,10 @@
 #include "base64.h"
 #include "commands.h"
+#include "crypting.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"
@@ -63,4 +66,11 @@
 	b_main_init();
 	nogaim_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();
+ 	otr_init();
 	
 	srand( time( NULL ) ^ getpid() );
Index: user.h
===================================================================
--- user.h	(revision 3dc6d86076dbea16c313bb87aa2f37166f289a8e)
+++ user.h	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -38,4 +38,5 @@
 	char is_private;
 	char online;
+	char encrypted;
 	
 	char *handle;
Index: welcome.txt
===================================================================
--- welcome.txt	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
+++ welcome.txt	(revision 37598495deeb42e1cd216a0a4ba8068e4a6da453)
@@ -0,0 +1,5 @@
+Welcome to the BitlBee gateway!
+
+If you've never used BitlBee before, please do read the help information using the help command. Lots of FAQs are answered there.
+OTR users please note: Private key files are owned by the user BitlBee is running as.
+If you already have an account on this server, just use the identify command to identify yourself.
