Index: Makefile
===================================================================
--- Makefile	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ Makefile	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -126,2 +126,7 @@
 ctags: 
 	ctags `find . -name "*.c"` `find . -name "*.h"`
+
+# Using this as a bogus Make target to test if a GNU-compatible version of
+# make is available.
+helloworld:
+	@echo Hello World
Index: bitlbee.c
===================================================================
--- bitlbee.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ bitlbee.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -36,4 +36,41 @@
 static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition condition );
 
+static gboolean try_listen( struct addrinfo *res )
+{
+	int i;
+	
+	global.listen_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol );
+	if( global.listen_socket < 0 )
+	{
+		log_error( "socket" );
+		return FALSE;
+	}
+
+#ifdef IPV6_V6ONLY		
+	if( res->ai_family == AF_INET6 )
+	{
+		i = 0;
+		setsockopt( global.listen_socket, IPPROTO_IPV6, IPV6_V6ONLY,
+		            (char *) &i, sizeof( i ) );
+	}
+#endif
+
+	/* TIME_WAIT (?) sucks.. */
+	i = 1;
+	setsockopt( global.listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) );
+
+	i = bind( global.listen_socket, res->ai_addr, res->ai_addrlen );
+	if( i == -1 )
+	{
+		closesocket( global.listen_socket );
+		global.listen_socket = -1;
+		
+		log_error( "bind" );
+		return FALSE;
+	}
+	
+	return TRUE;
+}
+
 int bitlbee_daemon_init()
 {
@@ -42,6 +79,6 @@
 	FILE *fp;
 	
-	log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG );
-	log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG );
+	log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE );
+	log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE );
 	
 	memset( &hints, 0, sizeof( hints ) );
@@ -63,25 +100,16 @@
 
 	global.listen_socket = -1;
-
+	
+	/* Try IPv6 first (which will become an IPv6+IPv4 socket). */
 	for( res = addrinfo_bind; res; res = res->ai_next )
-	{
-		global.listen_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol );
-		if( global.listen_socket < 0 )
-			continue;
-
-		/* TIME_WAIT (?) sucks.. */
-		i = 1;
-		setsockopt( global.listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) );
-
-		i = bind( global.listen_socket, res->ai_addr, res->ai_addrlen );
-		if( i == -1 )
-		{
-			log_error( "bind" );
-			return( -1 );
-		}
-
-		break;
-	}
-
+		if( res->ai_family == AF_INET6 && try_listen( res ) )
+			break;
+	
+	/* The rest (so IPv4, I guess). */
+	if( res == NULL )
+		for( res = addrinfo_bind; res; res = res->ai_next )
+			if( res->ai_family != AF_INET6 && try_listen( res ) )
+				break;
+	
 	freeaddrinfo( addrinfo_bind );
 
@@ -93,5 +121,5 @@
 	}
 	
-	global.listen_watch_source_id = b_input_add( global.listen_socket, GAIM_INPUT_READ, bitlbee_io_new_client, NULL );
+	global.listen_watch_source_id = b_input_add( global.listen_socket, B_EV_IO_READ, bitlbee_io_new_client, NULL );
 	
 #ifndef _WIN32
@@ -107,4 +135,5 @@
 			exit( 0 );
 		
+		setsid();
 		chdir( "/" );
 		
@@ -137,4 +166,10 @@
 #endif
 	
+	if( !global.conf->nofork )
+	{
+		log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG );
+		log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG );
+	}
+	
 	return( 0 );
 }
@@ -286,5 +321,5 @@
 			child->pid = client_pid;
 			child->ipc_fd = fds[0];
-			child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
+			child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
 			child_list = g_slist_append( child_list, child );
 			
@@ -314,5 +349,5 @@
 			/* We can store the IPC fd there now. */
 			global.listen_socket = fds[1];
-			global.listen_watch_source_id = b_input_add( fds[1], GAIM_INPUT_READ, ipc_child_read, irc );
+			global.listen_watch_source_id = b_input_add( fds[1], B_EV_IO_READ, ipc_child_read, irc );
 			
 			close( fds[0] );
Index: bitlbee.h
===================================================================
--- bitlbee.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ bitlbee.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -35,6 +35,8 @@
 
 #define PACKAGE "BitlBee"
-#define BITLBEE_VERSION "1.2.5"
+#define BITLBEE_VERSION "1.2.6a"
 #define VERSION BITLBEE_VERSION
+#define BITLBEE_VER(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+#define BITLBEE_VERSION_CODE BITLBEE_VER(1, 2, 6)
 
 #define MAX_STRING 511
Index: conf.c
===================================================================
--- conf.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ conf.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -82,5 +82,5 @@
 	}
 	
-	while( argc > 0 && ( opt = getopt( argc, argv, "i:p:P:nvIDFc:d:hR:u:" ) ) >= 0 )
+	while( argc > 0 && ( opt = getopt( argc, argv, "i:p:P:nvIDFc:d:hR:u:V" ) ) >= 0 )
 	/*     ^^^^ Just to make sure we skip this step from the REHASH handler. */
 	{
@@ -148,5 +148,12 @@
 			        "  -d  Specify alternative user configuration directory\n"
 			        "  -x  Command-line interface to password encryption/hashing\n"
-			        "  -h  Show this help page.\n" );
+			        "  -h  Show this help page.\n"
+			        "  -V  Show version info.\n" );
+			return NULL;
+		}
+		else if( opt == 'V' )
+		{
+			printf( "BitlBee %s\nAPI version %06x\n",
+			        BITLBEE_VERSION, BITLBEE_VERSION_CODE );
 			return NULL;
 		}
Index: configure
===================================================================
--- configure	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ configure	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -26,4 +26,6 @@
 oscar=1
 yahoo=1
+twitter=1
+purple=1
 
 debug=0
@@ -66,4 +68,7 @@
 --oscar=0/1	Disable/enable Oscar part (ICQ, AIM)	$oscar
 --yahoo=0/1	Disable/enable Yahoo part		$yahoo
+--twitter=0/1 Disable/enable Twitter part		$twitter
+
+--purple=0/1	Disable/enable libpurple support	$purple
 
 --debug=0/1	Disable/enable debugging		$debug
@@ -267,5 +272,5 @@
 detect_ldap()
 {
-	TMPFILE=$(mktemp)
+	TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
 	if $CC -o $TMPFILE -shared -lldap 2>/dev/null >/dev/null; then
 		cat<<EOF>>Makefile.settings
@@ -295,5 +300,5 @@
 detect_resolv_dynamic()
 {
-	TMPFILE=$(mktemp)
+	TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
 	ret=1
 	echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - -lresolv >/dev/null 2>/dev/null
@@ -309,5 +314,5 @@
 detect_resolv_static()
 {
-	TMPFILE=$(mktemp)
+	TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
 	ret=1
 	for i in $systemlibdirs; do
@@ -476,4 +481,18 @@
 fi
 
+if ! make helloworld > /dev/null 2>&1; then
+	echo "WARNING: Your version of make (BSD make?) does not support BitlBee's makefiles."
+	echo "BitlBee needs GNU make to build properly. On most systems GNU make is available"
+	echo "under the name 'gmake'."
+	echo
+	if gmake helloworld > /dev/null 2>&1; then
+		echo "gmake seems to be available on your machine, great."
+		echo
+	else
+		echo "gmake is not installed (or not working). Please try to install it."
+		echo
+	fi
+fi
+
 cat <<EOF>bitlbee.pc
 prefix=$prefix
@@ -492,4 +511,36 @@
 protoobjs=''
 
+if [ "$purple" = 0 ]; then
+	echo '#undef WITH_PURPLE' >> config.h
+else
+	if ! $PKG_CONFIG purple; then
+		echo
+		echo 'Cannot find libpurple development libraries, aborting. (Install libpurple-dev?)'
+		exit 1
+	fi
+	echo '#define WITH_PURPLE' >> config.h
+	cat<<EOF>>Makefile.settings
+EFLAGS += $($PKG_CONFIG purple --libs)
+PURPLE_CFLAGS += $($PKG_CONFIG purple --cflags)
+EOF
+	protocols=$protocols'purple '
+	protoobjs=$protoobjs'purple_mod.o '
+
+	# Having both libpurple and native IM modules in one binary may
+	# do strange things. Let's not do that.
+	msn=0
+	jabber=0
+	oscar=0
+	yahoo=0
+	twitter=0
+	
+	if [ "$events" = "libevent" ]; then
+		echo
+		echo 'Warning: Some libpurple modules (including msn-pecan) do their event handling'
+		echo 'outside libpurple, talking to GLib directly. At least for now the combination'
+		echo 'libpurple + libevent is *not* recommended!'
+	fi
+fi
+
 if [ "$msn" = 0 ]; then
 	echo '#undef WITH_MSN' >> config.h
@@ -524,4 +575,12 @@
 fi
 
+if [ "$twitter" = 0 ]; then
+	echo '#undef WITH_TWITTER' >> config.h
+else
+	echo '#define WITH_TWITTER' >> config.h
+	protocols=$protocols'twitter '
+	protoobjs=$protoobjs'twitter_mod.o '
+fi
+
 if [ "$protocols" = "PROTOCOLS = " ]; then
 	echo "Warning: You haven't selected any communication protocol to compile!"
Index: dcc.c
===================================================================
--- dcc.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ dcc.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -154,5 +154,5 @@
 
 	/* watch */
-	df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df );
+	df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_send_proto, df );
 
 	df->ic->irc->file_transfers = g_slist_prepend( df->ic->irc->file_transfers, file );
@@ -267,5 +267,5 @@
 	file_transfer_t *file = df->ft;
 	
-	if( ( cond & GAIM_INPUT_READ ) &&
+	if( ( cond & B_EV_IO_READ ) &&
 	    ( file->status & FT_STATUS_LISTENING ) )
 	{ 	
@@ -287,10 +287,10 @@
 
 		/* reschedule for reading on new fd */
-		df->watch_in = b_input_add( fd, GAIM_INPUT_READ, dccs_send_proto, df );
+		df->watch_in = b_input_add( fd, B_EV_IO_READ, dccs_send_proto, df );
 
 		return FALSE;
 	}
 
-	if( cond & GAIM_INPUT_READ ) 
+	if( cond & B_EV_IO_READ ) 
 	{
 		int ret;
@@ -364,5 +364,5 @@
 
 	/* watch */
-	df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df );
+	df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_recv_proto, df );
 	ft->write_request = dccs_recv_write_request;
 
@@ -377,10 +377,10 @@
 	file_transfer_t *ft = df->ft;
 
-	if( ( cond & GAIM_INPUT_WRITE ) &&
+	if( ( cond & B_EV_IO_WRITE ) &&
 	    ( ft->status & FT_STATUS_CONNECTING ) )
 	{
 		ft->status = FT_STATUS_TRANSFERRING;
 
-		//df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
+		//df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
 
 		df->watch_out = 0;
@@ -388,5 +388,5 @@
 	}
 
-	if( cond & GAIM_INPUT_READ )
+	if( cond & B_EV_IO_READ )
 	{
 		int ret, done;
@@ -445,5 +445,5 @@
 		return dcc_abort( df, "BUG: write_request() called while watching" );
 
-	df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
+	df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
 
 	return TRUE;
@@ -488,5 +488,5 @@
 
 	if( df->bytes_sent < df->ft->file_size )
-		df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_can_write, df );
+		df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_send_can_write, df );
 
 	return TRUE;
Index: debian/bitlbee.init
===================================================================
--- debian/bitlbee.init	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ debian/bitlbee.init	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -38,6 +38,6 @@
 d_start() {
 	# Make sure BitlBee can actually write its PID...
-	touch /var/run/bitlbee.pid
-	chown bitlbee: /var/run/bitlbee.pid
+	touch $PIDFILE
+	chown bitlbee: $PIDFILE
 	
 	start-stop-daemon --start --quiet --pidfile $PIDFILE \
Index: debian/changelog
===================================================================
--- debian/changelog	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ debian/changelog	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -1,2 +1,11 @@
+bitlbee (1.2.6a-1) unstable; urgency=low
+
+  * New upstream version.
+  * Native support for Twitter.
+  * Fixed /WHOIS response format. (Closes: #576120)
+  * Problems with bitlbee-skype are solved by now. (Closes: #575572)
+
+ -- Wilmer van der Gaast <wilmer@peer.gaast.net>  Tue, 20 Apr 2010 00:34:51 +0200
+
 bitlbee (1.2.5-1) unstable; urgency=low
 
Index: debian/control
===================================================================
--- debian/control	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ debian/control	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -5,5 +5,5 @@
 Uploaders: Jelmer Vernooij <jelmer@samba.org>
 Standards-Version: 3.8.0
-Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), debconf-2.0, po-debconf
+Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), debconf-2.0, po-debconf, libpurple-dev
 Homepage: http://www.bitlbee.org/
 Vcs-Bzr: http://code.bitlbee.org/bitlbee/
Index: debian/rules
===================================================================
--- debian/rules	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ debian/rules	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -1,4 +1,5 @@
 #!/usr/bin/make -f
 
+BITLBEE_CONFIGURE_FLAGS ?=
 DEBUG ?= 0
 
@@ -14,5 +15,5 @@
 build-arch-stamp:
 	[ -d debian ]
-	./configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent
+	./configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent $(BITLBEE_CONFIGURE_FLAGS)
 	$(MAKE)
 #	$(MAKE) -C doc/ all
@@ -99,5 +100,5 @@
 		find usr -type f -exec md5sum {} \; > DEBIAN/md5sums
 
-	dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev
+	dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev -v1:$(BITLBEE_VERSION)-0
 
 	dpkg --build debian/bitlbee-dev ..
Index: doc/CHANGES
===================================================================
--- doc/CHANGES	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ doc/CHANGES	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -3,4 +3,31 @@
 
 http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on
+
+Version 1.2.6a:
+- Fixed a typo that renders the Twitter groupchat mode unusable. A last-
+  minute change that came a few minutes late.
+
+Finished 19 Apr 2010
+
+Version 1.2.6:
+- Native (very basic) support for Twitter, implemented by Geert Mulders.
+  Currently supported are posting tweets, reading the ones of people you
+  follow, and sending (not yet receiving!) direct messages.
+- Fixed format of status messages in /WHOIS to improve IRC client
+  compatibility.
+- Show timestamps of offline messages/channel backlogs.
+- Allow saving MSN display names locally since sometimes this stuff breaks
+  server-side. (Use the local_display_name per-account setting.)
+- Suppress empty "Headline:" messages for certain new XMPP broadcast 
+  messages.
+- Better handling of XMPP contacts with multiple resources on-line. Default
+  behaviour now is to write to wherever the last message came from, or to
+  the bare JID (usually becomes a broadcast) if there wasn't any recent msg.
+- Added a switchboard_keepalives setting which should solve some issues with
+  talking to offline MSN contacts. (Although full support for offline
+  messages is not ready yet!)
+- The usual misc. bug fixes.
+
+Finished 19 Apr 2010
 
 Version 1.2.5:
@@ -23,5 +50,5 @@
   the main client).
 
-Fixed 17 Mar 2010
+Finished 17 Mar 2010
 
 Version 1.2.4:
Index: doc/user-guide/commands.xml
===================================================================
--- doc/user-guide/commands.xml	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ doc/user-guide/commands.xml	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -21,5 +21,5 @@
 			<description>
 				<para>
-					Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ) and Yahoo. For more information about adding an account, see <emphasis>help account add &lt;protocol&gt;</emphasis>.
+					Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ), Yahoo and Twitter. For more information about adding an account, see <emphasis>help account add &lt;protocol&gt;</emphasis>.
 				</para>
 			</description>
@@ -63,4 +63,26 @@
 				</ircexample>
 			</bitlbee-command>
+			
+			<bitlbee-command name="twitter">
+				<syntax>account add twitter &lt;handle&gt; &lt;password&gt;</syntax>
+
+				<description>
+					<para>
+						This module gives you simple access to Twitter. Although it uses the Twitter API, only Twitter itself is supported at the moment.
+					</para>
+					
+					<para>
+						By default all your Twitter contacts will come from a contact called twitter_(yourusername). You can change this behaviour using the <emphasis>mode</emphasis> setting (see <emphasis>help set mode</emphasis>).
+					</para>
+					
+					<para>
+						To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option.
+					</para>
+
+					<para>
+						Since Twitter now requires OAuth authentication, you should not enter your Twitter password into BitlBee. Just type a bogus password. The first time you log in, BitlBee will start OAuth authentication. (See <emphasis>help set oauth</emphasis>.)
+					</para>
+				</description>
+			</bitlbee-command>
 
 			<bitlbee-command name="yahoo">
@@ -401,5 +423,5 @@
 
 	<bitlbee-setting name="auto_reconnect" type="boolean" scope="both">
-		<default>false</default>
+		<default>true</default>
 
 		<description>
@@ -556,4 +578,14 @@
 			<para>
 				With this option enabled, root will inform you when someone in your buddy list changes his/her "friendly name".
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="display_timestamps" type="boolean" scope="global">
+		<default>true</default>
+
+		<description>
+			<para>
+				When incoming messages are old (i.e. offline messages and channel backlogs), BitlBee will prepend them with a timestamp. If you find them ugly or useless, you can use this setting to hide them.
 			</para>
 		</description>
@@ -609,4 +641,15 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="local_display_name" type="boolean" scope="account">
+		<default>false</default>
+
+		<description>
+			<para>
+				Mostly meant to work around a bug in MSN servers (forgetting the display name set by the user), this setting tells BitlBee to store your display name locally and set this name on the MSN servers when connecting.
+			</para>
+		</description>
+
+	</bitlbee-setting>
+
 	<bitlbee-setting name="mail_notifications" type="boolean" scope="account">
 		<default>false</default>
@@ -615,4 +658,39 @@
 			<para>
 				Some protocols (MSN, Yahoo!) can notify via IM about new e-mail. Since most people use their Hotmail/Yahoo! addresses as a spam-box, this is disabled default. If you want these notifications, you can enable this setting.
+			</para>
+		</description>
+
+	</bitlbee-setting>
+
+	<bitlbee-setting name="message_length" type="integer" scope="account">
+		<default>140</default>
+
+		<description>
+			<para>
+				Since Twitter rejects messages longer than 140 characters, BitlBee can count message length and emit a warning instead of waiting for Twitter to reject it.
+			</para>
+
+			<para>
+				You can change this limit here but this won't disable length checks on Twitter's side. You can also set it to 0 to disable the check in case you believe BitlBee doesn't count the characters correctly.
+			</para>
+		</description>
+
+	</bitlbee-setting>
+
+	<bitlbee-setting name="mode" type="string" scope="account">
+		<possible-values>one, many, chat</possible-values>
+		<default>one</default>
+
+		<description>
+			<para>
+				By default, everything from the Twitter module will come from one nick, twitter_(yourusername). If you prefer to have individual nicks for everyone, you can set this setting to "many" instead.
+			</para>
+			
+			<para>
+				If you prefer to have all your Twitter things in a separate channel, you can set this setting to "chat".
+			</para>
+			
+			<para>
+				In the last two modes, you can send direct messages by /msg'ing your contacts directly. Note, however, that incoming DMs are not fetched yet.
 			</para>
 		</description>
@@ -644,4 +722,23 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="oauth" type="boolean" scope="account">
+		<default>true</default>
+
+		<description>
+			<para>
+				This enables OAuth authentication for Twitter accounts. From June 2010 this will be mandatory.
+			</para>
+
+			<para>
+				With OAuth enabled, you shouldn't tell BitlBee your Twitter password. Just add your account with a bogus password and type <emphasis>account on</emphasis>. BitlBee will then give you a URL to authenticate with Twitter. If this succeeds, Twitter will return a PIN code which you can give back to BitlBee to finish the process.
+			</para>
+
+			<para>
+				The resulting access token will be saved permanently, so you have to do this only once.
+			</para>
+		</description>
+
+	</bitlbee-setting>
+
 	<bitlbee-setting name="ops" type="string" scope="global">
 		<default>both</default>
@@ -779,4 +876,14 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="show_offline" type="boolean" scope="global">
+		<default>false</default>
+
+		<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.
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="simulate_netsplit" type="boolean" scope="global">
 		<default>true</default>
@@ -824,4 +931,37 @@
 			<para>
 				If BitlBee fails to detect this sometimes (most likely in AIM messages over an ICQ connection), you can set this setting to <emphasis>always</emphasis>, but this might sometimes accidentally strip non-HTML things too.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="switchboard_keepalives" type="boolean" scope="account">
+		<default>false</default>
+
+		<description>
+			<para>
+				Turn on this flag if you have difficulties talking to offline/invisible contacts.
+			</para>
+			
+			<para>
+				With this setting enabled, BitlBee will send keepalives to MSN switchboards with offline/invisible contacts every twenty seconds. This should keep the server and client on the other side from shutting it down.
+			</para>
+			
+			<para>
+				This is useful because BitlBee doesn't support MSN offline messages yet and the MSN servers won't let the user reopen switchboards to offline users. Once offline messaging is supported, this flag might be removed.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="timezone" type="string" scope="global">
+		<default>local</default>
+		<possible-values>local, utc, gmt, timezone-spec</possible-values>
+
+		<description>
+			<para>
+				If message timestamps are available for offline messages or chatroom backlogs, BitlBee will display them as part of the message. By default it will use the local timezone. If you're not in the same timezone as the BitlBee server, you can adjust the timestamps using this setting.
+			</para>
+
+			<para>
+				Values local/utc/gmt should be self-explanatory. timezone-spec is a time offset in hours:minutes, for example: -8 for Pacific Standard Time, +2 for Central European Summer Time, +5:30 for Indian Standard Time.
 			</para>
 		</description>
Index: help.c
===================================================================
--- help.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ help.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -2,5 +2,5 @@
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2005 Wilmer van der Gaast and others                *
+  * Copyright 2002-2009 Wilmer van der Gaast and others                *
   \********************************************************************/
 
@@ -169,2 +169,26 @@
 	return NULL;
 }
+
+int help_add_mem( help_t **help, const char *title, const char *content )
+{
+	help_t *h, *l = NULL;
+	
+	for( h = *help; h; h = h->next )
+	{
+		if( g_strcasecmp( h->title, title ) == 0 )
+			return 0;
+		
+		l = h;
+	}
+	
+	if( l )
+		h = l->next = g_new0( struct help, 1 );
+	else
+		*help = h = g_new0( struct help, 1 );
+	h->fd = -1;
+	h->title = g_strdup( title );
+	h->length = strlen( content );
+	h->offset.mem_offset = g_strdup( content );
+	
+	return 1;
+}
Index: help.h
===================================================================
--- help.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ help.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -46,4 +46,5 @@
 void help_free( help_t **help );
 char *help_get( help_t **help, char *title );
+int help_add_mem( help_t **help, const char *title, const char *content_ );
 
 #endif
Index: ipc.c
===================================================================
--- ipc.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ ipc.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -514,5 +514,5 @@
 	}
 		
-	child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
+	child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
 	
 	child_list = g_slist_append( child_list, child );
@@ -552,5 +552,5 @@
 	}
 	
-	b_input_add( serversock, GAIM_INPUT_READ, new_ipc_client, NULL );
+	b_input_add( serversock, B_EV_IO_READ, new_ipc_client, NULL );
 	
 	return 1;
@@ -597,5 +597,5 @@
 			return 0;
 		}
-		child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
+		child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
 		
 		child_list = g_slist_append( child_list, child );
Index: irc.c
===================================================================
--- irc.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ irc.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -52,4 +52,6 @@
 {
 	irc_t *irc = set->data;
+	char *test;
+	gsize test_bytes = 0;
 	GIConv ic, oc;
 
@@ -57,11 +59,25 @@
 		value = g_strdup( "utf-8" );
 
+	if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
+	{
+		return NULL;
+	}
+	
+	/* Do a test iconv to see if the user picked an IRC-compatible
+	   charset (for example utf-16 goes *horribly* wrong). */
+	if( ( test = g_convert_with_iconv( " ", 1, oc, NULL, &test_bytes, NULL ) ) == NULL ||
+	    test_bytes > 1 )
+	{
+		g_free( test );
+		g_iconv_close( oc );
+		irc_usermsg( irc, "Unsupported character set: The IRC protocol "
+		                  "only supports 8-bit character sets." );
+		return NULL;
+	}
+	g_free( test );
+	
 	if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
 	{
-		return NULL;
-	}
-	if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
-	{
-		g_iconv_close( ic );
+		g_iconv_close( oc );
 		return NULL;
 	}
@@ -109,5 +125,5 @@
 	sock_make_nonblocking( irc->fd );
 	
-	irc->r_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_READ, bitlbee_io_current_client_read, irc );
+	irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc );
 	
 	irc->status = USTATUS_OFFLINE;
@@ -175,4 +191,5 @@
 	s = set_add( &irc->set, "default_target", "root", NULL, irc );
 	s = set_add( &irc->set, "display_namechanges", "false", set_eval_bool, irc );
+	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, "lcnicks", "true", set_eval_bool, irc );
@@ -184,8 +201,10 @@
 	s = set_add( &irc->set, "root_nick", irc->mynick, set_eval_root_nick, irc );
 	s = set_add( &irc->set, "save_on_quit", "true", set_eval_bool, irc );
+	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 );
@@ -195,4 +214,6 @@
 	/* Evaluator sets the iconv/oconv structures. */
 	set_eval_charset( set_find( &irc->set, "charset" ), set_getstr( &irc->set, "charset" ) );
+	
+	nogaim_init();
 	
 	return( irc );
@@ -677,8 +698,8 @@
 		   in the event queue. */
 		/* Really can't be done as long as the code doesn't do error checking very well:
-		if( bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE ) ) */
+		if( bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ) ) */
 		
 		/* So just always do it via the event handler. */
-		irc->w_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc );
+		irc->w_watch_source_id = b_input_add( irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc );
 	}
 	
@@ -706,5 +727,5 @@
 		if( now )
 		{
-			bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
+			bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE );
 		}
 		temp = temp->next;
Index: irc_commands.c
===================================================================
--- irc_commands.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ irc_commands.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -498,5 +498,5 @@
 			irc_reply( irc, 301, "%s :%s", u->nick, u->away );
 		if( u->status_msg )
-			irc_reply( irc, 333, "%s :Status: %s", u->nick, u->status_msg );
+			irc_reply( irc, 320, "%s :%s", u->nick, u->status_msg );
 		
 		irc_reply( irc, 318, "%s :End of /WHOIS list", nick );
Index: lib/Makefile
===================================================================
--- lib/Makefile	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/Makefile	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -10,5 +10,5 @@
 
 # [SH] Program variables
-objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o ftutil.o
+objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o
 
 CFLAGS += -Wall
Index: lib/events.h
===================================================================
--- lib/events.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/events.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -48,6 +48,8 @@
    the given callback function. */
 typedef enum {
-	GAIM_INPUT_READ = 1 << 1,
-	GAIM_INPUT_WRITE = 1 << 2
+	B_EV_IO_READ = 1 << 0,
+	B_EV_IO_WRITE = 1 << 1,
+	B_EV_FLAG_FORCE_ONCE = 1 << 16,
+	B_EV_FLAG_FORCE_REPEAT = 1 << 17,
 } b_input_condition;
 typedef gboolean (*b_event_handler)(gpointer data, gint fd, b_input_condition cond);
Index: lib/events_glib.c
===================================================================
--- lib/events_glib.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/events_glib.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -49,4 +49,5 @@
 	b_event_handler function;
 	gpointer data;
+	guint flags;
 } GaimIOClosure;
 
@@ -76,7 +77,7 @@
 
 	if (condition & GAIM_READ_COND)
-		gaim_cond |= GAIM_INPUT_READ;
+		gaim_cond |= B_EV_IO_READ;
 	if (condition & GAIM_WRITE_COND)
-		gaim_cond |= GAIM_INPUT_WRITE;
+		gaim_cond |= B_EV_IO_WRITE;
 	
 	event_debug( "gaim_io_invoke( %d, %d, 0x%x )\n", g_io_channel_unix_get_fd(source), condition, data );
@@ -87,5 +88,10 @@
 		event_debug( "Returned FALSE, cancelling.\n" );
 	
-	return st;
+	if (closure->flags & B_EV_FLAG_FORCE_ONCE)
+		return FALSE;
+	else if (closure->flags & B_EV_FLAG_FORCE_REPEAT)
+		return TRUE;
+	else
+		return st;
 }
 
@@ -105,8 +111,9 @@
 	closure->function = function;
 	closure->data = data;
+	closure->flags = condition;
 	
-	if (condition & GAIM_INPUT_READ)
+	if (condition & B_EV_IO_READ)
 		cond |= GAIM_READ_COND;
-	if (condition & GAIM_INPUT_WRITE)
+	if (condition & B_EV_IO_WRITE)
 		cond |= GAIM_WRITE_COND;
 	
Index: lib/events_libevent.c
===================================================================
--- lib/events_libevent.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/events_libevent.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -60,4 +60,5 @@
 	b_event_handler function;
 	void *data;
+	guint flags;
 };
 
@@ -126,7 +127,7 @@
 	{
 		if( event & EV_READ )
-			cond |= GAIM_INPUT_READ;
+			cond |= B_EV_IO_READ;
 		if( event & EV_WRITE )
-			cond |= GAIM_INPUT_WRITE;
+			cond |= B_EV_IO_WRITE;
 	}
 	
@@ -150,5 +151,5 @@
 		return;
 	}
-	else if( !st )
+	else if( !st && !( b_ev->flags & B_EV_FLAG_FORCE_REPEAT ) )
 	{
 		event_debug( "Handler returned FALSE: " );
@@ -174,6 +175,6 @@
 	event_debug( "b_input_add( %d, %d, 0x%x, 0x%x ) ", fd, condition, function, data );
 	
-	if( ( condition & GAIM_INPUT_READ  && ( b_ev = g_hash_table_lookup( read_hash,  &fd ) ) ) ||
-	    ( condition & GAIM_INPUT_WRITE && ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) )
+	if( ( condition & B_EV_IO_READ  && ( b_ev = g_hash_table_lookup( read_hash,  &fd ) ) ) ||
+	    ( condition & B_EV_IO_WRITE && ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) )
 	{
 		/* We'll stick with this libevent entry, but give it a new BitlBee id. */
@@ -198,7 +199,7 @@
 		
 		out_cond = EV_PERSIST;
-		if( condition & GAIM_INPUT_READ )
+		if( condition & B_EV_IO_READ )
 			out_cond |= EV_READ;
-		if( condition & GAIM_INPUT_WRITE )
+		if( condition & B_EV_IO_WRITE )
 			out_cond |= EV_WRITE;
 		
@@ -212,4 +213,5 @@
 	}
 	
+	b_ev->flags = condition;
 	g_hash_table_insert( id_hash, &b_ev->id, b_ev );
 	return b_ev->id;
Index: lib/http_client.c
===================================================================
--- lib/http_client.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/http_client.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -149,8 +149,8 @@
 	if( req->bytes_written < req->request_length )
 		req->inpa = b_input_add( source,
-		                         req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_WRITE,
+		                         req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_WRITE,
 	        	                 http_connected, req );
 	else
-		req->inpa = b_input_add( source, GAIM_INPUT_READ, http_incoming_data, req );
+		req->inpa = b_input_add( source, B_EV_IO_READ, http_incoming_data, req );
 	
 	return FALSE;
@@ -234,5 +234,5 @@
 	/* There will be more! */
 	req->inpa = b_input_add( req->fd,
-	                         req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_READ,
+	                         req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ,
 	                         http_incoming_data, req );
 	
Index: lib/misc.c
===================================================================
--- lib/misc.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/misc.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -77,4 +77,39 @@
 	
 	return mktime(&tm);
+}
+
+time_t mktime_utc( struct tm *tp )
+{
+	struct tm utc;
+	time_t res, tres;
+	
+	tp->tm_isdst = -1;
+	res = mktime( tp );
+	/* Problem is, mktime() just gave us the GMT timestamp for the
+	   given local time... While the given time WAS NOT local. So
+	   we should fix this now.
+	   
+	   Now I could choose between messing with environment variables
+	   (kludgy) or using timegm() (not portable)... Or doing the
+	   following, which I actually prefer...
+	   
+	   tzset() may also work but in other places I actually want to
+	   use local time.
+	   
+	   FFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUU!! */
+	gmtime_r( &res, &utc );
+	utc.tm_isdst = -1;
+	if( utc.tm_hour == tp->tm_hour && utc.tm_min == tp->tm_min )
+		/* Sweet! We're in UTC right now... */
+		return res;
+	
+	tres = mktime( &utc );
+	res += res - tres;
+	
+	/* Yes, this is a hack. And it will go wrong around DST changes.
+	   BUT this is more likely to be threadsafe than messing with
+	   environment variables, and possibly more portable... */
+	
+	return res;
 }
 
@@ -271,6 +306,5 @@
 	for( i = j = 0; t[i]; i ++, j ++ )
 	{
-		/* if( t[i] <= ' ' || ((unsigned char *)t)[i] >= 128 || t[i] == '%' ) */
-		if( !isalnum( t[i] ) )
+		if( !isalnum( t[i] ) && !strchr( "._-~", t[i] ) )
 		{
 			sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] );
Index: lib/misc.h
===================================================================
--- lib/misc.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/misc.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -43,4 +43,5 @@
 
 G_MODULE_EXPORT time_t get_time( int year, int month, int day, int hour, int min, int sec );
+G_MODULE_EXPORT time_t mktime_utc( struct tm *tp );
 double gettime( void );
 
Index: lib/oauth.c
===================================================================
--- lib/oauth.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ lib/oauth.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,453 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Simple OAuth client (consumer) implementation.                           *
+*                                                                           *
+*  Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net>                   *
+*                                                                           *
+*  This library is free software; you can redistribute it and/or            *
+*  modify it under the terms of the GNU Lesser General Public               *
+*  License as published by the Free Software Foundation, version            *
+*  2.1.                                                                     *
+*                                                                           *
+*  This library 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        *
+*  Lesser General Public License for more details.                          *
+*                                                                           *
+*  You should have received a copy of the GNU Lesser General Public License *
+*  along with this library; if not, write to the Free Software Foundation,  *
+*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
+*                                                                           *
+\***************************************************************************/
+
+#include <glib.h>
+#include <gmodule.h>
+#include <stdlib.h>
+#include <string.h>
+#include "http_client.h"
+#include "base64.h"
+#include "misc.h"
+#include "sha1.h"
+#include "url.h"
+#include "oauth.h"
+
+#define HMAC_BLOCK_SIZE 64
+
+static char *oauth_sign( const char *method, const char *url,
+                         const char *params, struct oauth_info *oi )
+{
+	sha1_state_t sha1;
+	uint8_t hash[sha1_hash_size];
+	uint8_t key[HMAC_BLOCK_SIZE+1];
+	char *s;
+	int i;
+	
+	/* Create K. If our current key is >64 chars we have to hash it,
+	   otherwise just pad. */
+	memset( key, 0, HMAC_BLOCK_SIZE );
+	i = strlen( oi->sp->consumer_secret ) + 1 + ( oi->token_secret ? strlen( oi->token_secret ) : 0 );
+	if( i > HMAC_BLOCK_SIZE )
+	{
+		sha1_init( &sha1 );
+		sha1_append( &sha1, (uint8_t*) oi->sp->consumer_secret, strlen( oi->sp->consumer_secret ) );
+		sha1_append( &sha1, (uint8_t*) "&", 1 );
+		if( oi->token_secret )
+			sha1_append( &sha1, (uint8_t*) oi->token_secret, strlen( oi->token_secret ) );
+		sha1_finish( &sha1, key );
+	}
+	else
+	{
+		g_snprintf( (gchar*) key, HMAC_BLOCK_SIZE + 1, "%s&%s",
+		            oi->sp->consumer_secret, oi->token_secret ? : "" );
+	}
+	
+	/* Inner part: H(K XOR 0x36, text) */
+	sha1_init( &sha1 );
+	
+	for( i = 0; i < HMAC_BLOCK_SIZE; i ++ )
+		key[i] ^= 0x36;
+	sha1_append( &sha1, key, HMAC_BLOCK_SIZE );
+	
+	/* OAuth: text = method&url&params, all http_encoded. */
+	sha1_append( &sha1, (const uint8_t*) method, strlen( method ) );
+	sha1_append( &sha1, (const uint8_t*) "&", 1 );
+	
+	s = g_new0( char, strlen( url ) * 3 + 1 );
+	strcpy( s, url );
+	http_encode( s );
+	sha1_append( &sha1, (const uint8_t*) s, strlen( s ) );
+	sha1_append( &sha1, (const uint8_t*) "&", 1 );
+	g_free( s );
+	
+	s = g_new0( char, strlen( params ) * 3 + 1 );
+	strcpy( s, params );
+	http_encode( s );
+	sha1_append( &sha1, (const uint8_t*) s, strlen( s ) );
+	g_free( s );
+	
+	sha1_finish( &sha1, hash );
+	
+	/* Final result: H(K XOR 0x5C, inner stuff) */
+	sha1_init( &sha1 );
+	for( i = 0; i < HMAC_BLOCK_SIZE; i ++ )
+		key[i] ^= 0x36 ^ 0x5c;
+	sha1_append( &sha1, key, HMAC_BLOCK_SIZE );
+	sha1_append( &sha1, hash, sha1_hash_size );
+	sha1_finish( &sha1, hash );
+	
+	/* base64_encode + HTTP escape it (both consumers 
+	   need it that away) and we're done. */
+	s = base64_encode( hash, sha1_hash_size );
+	s = g_realloc( s, strlen( s ) * 3 + 1 );
+	http_encode( s );
+	
+	return s;
+}
+
+static char *oauth_nonce()
+{
+	unsigned char bytes[9];
+	
+	random_bytes( bytes, sizeof( bytes ) );
+	return base64_encode( bytes, sizeof( bytes ) );
+}
+
+void oauth_params_add( GSList **params, const char *key, const char *value )
+{
+	char *item;
+	
+	item = g_strdup_printf( "%s=%s", key, value );
+	*params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp );
+}
+
+void oauth_params_del( GSList **params, const char *key )
+{
+	int key_len = strlen( key );
+	GSList *l, *n;
+	
+	for( l = *params; l; l = n )
+	{
+		n = l->next;
+		
+		if( strncmp( (char*) l->data, key, key_len ) == 0 &&
+		    ((char*)l->data)[key_len] == '=' )
+		{
+			g_free( l->data );
+			*params = g_slist_remove( *params, l->data );
+		}
+	}
+}
+
+void oauth_params_set( GSList **params, const char *key, const char *value )
+{
+	oauth_params_del( params, key );
+	oauth_params_add( params, key, value );
+}
+
+const char *oauth_params_get( GSList **params, const char *key )
+{
+	int key_len = strlen( key );
+	GSList *l;
+	
+	for( l = *params; l; l = l->next )
+	{
+		if( strncmp( (char*) l->data, key, key_len ) == 0 &&
+		    ((char*)l->data)[key_len] == '=' )
+			return (const char*) l->data + key_len + 1;
+	}
+	
+	return NULL;
+}
+
+static void oauth_params_parse( GSList **params, char *in )
+{
+	char *amp, *eq, *s;
+	
+	while( in && *in )
+	{
+		eq = strchr( in, '=' );
+		if( !eq )
+			break;
+		
+		*eq = '\0';
+		if( ( amp = strchr( eq + 1, '&' ) ) )
+			*amp = '\0';
+		
+		s = g_strdup( eq + 1 );
+		http_decode( s );
+		oauth_params_add( params, in, s );
+		g_free( s );
+		
+		*eq = '=';
+		if( amp == NULL )
+			break;
+		
+		*amp = '&';
+		in = amp + 1;
+	}
+}
+
+void oauth_params_free( GSList **params )
+{
+	while( params && *params )
+	{
+		g_free( (*params)->data );
+		*params = g_slist_remove( *params, (*params)->data );
+	}
+}
+
+char *oauth_params_string( GSList *params )
+{
+	GSList *l;
+	GString *str = g_string_new( "" );
+	
+	for( l = params; l; l = l->next )
+	{
+		char *s, *eq;
+		
+		s = g_malloc( strlen( l->data ) * 3 + 1 );
+		strcpy( s, l->data );
+		if( ( eq = strchr( s, '=' ) ) )
+			http_encode( eq + 1 );
+		g_string_append( str, s );
+		g_free( s );
+		
+		if( l->next )
+			g_string_append_c( str, '&' );
+	}
+	
+	return g_string_free( str, FALSE );
+}
+
+void oauth_info_free( struct oauth_info *info )
+{
+	if( info )
+	{
+		g_free( info->auth_url );
+		g_free( info->request_token );
+		g_free( info->token );
+		g_free( info->token_secret );
+		g_free( info );
+	}
+}
+
+static void oauth_add_default_params( GSList **params, const struct oauth_service *sp )
+{
+	char *s;
+	
+	oauth_params_set( params, "oauth_consumer_key", sp->consumer_key );
+	oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" );
+	
+	s = g_strdup_printf( "%d", (int) time( NULL ) );
+	oauth_params_set( params, "oauth_timestamp", s );
+	g_free( s );
+	
+	s = oauth_nonce();
+	oauth_params_set( params, "oauth_nonce", s );
+	g_free( s );
+	
+	oauth_params_set( params, "oauth_version", "1.0" );
+}
+
+static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, struct oauth_info *oi )
+{
+	GSList *params = NULL;
+	char *s, *params_s, *post;
+	void *req;
+	url_t url_p;
+	
+	if( !url_set( &url_p, url ) )
+	{
+		oauth_params_free( params_ );
+		return NULL;
+	}
+	
+	if( params_ )
+		params = *params_;
+	
+	oauth_add_default_params( &params, oi->sp );
+	
+	params_s = oauth_params_string( params );
+	oauth_params_free( &params );
+	
+	s = oauth_sign( "POST", url, params_s, oi );
+	post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s );
+	g_free( params_s );
+	g_free( s );
+	
+	s = g_strdup_printf( "POST %s HTTP/1.0\r\n"
+	                     "Host: %s\r\n"
+	                     "Content-Type: application/x-www-form-urlencoded\r\n"
+	                     "Content-Length: %zd\r\n"
+	                     "\r\n"
+	                     "%s", url_p.file, url_p.host, strlen( post ), post );
+	g_free( post );
+	
+	req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS,
+	                      s, func, oi );
+	g_free( s );
+	
+	return req;
+}
+
+static void oauth_request_token_done( struct http_request *req );
+
+struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data )
+{
+	struct oauth_info *st = g_new0( struct oauth_info, 1 );
+	GSList *params = NULL;
+	
+	st->func = func;
+	st->data = data;
+	st->sp = sp;
+	
+	oauth_params_add( &params, "oauth_callback", "oob" );
+	
+	if( !oauth_post_request( sp->url_request_token, &params, oauth_request_token_done, st ) )
+	{
+		oauth_info_free( st );
+		return NULL;
+	}
+	
+	return st;
+}
+
+static void oauth_request_token_done( struct http_request *req )
+{
+	struct oauth_info *st = req->data;
+	
+	st->http = req;
+	
+	if( req->status_code == 200 )
+	{
+		GSList *params = NULL;
+		
+		st->auth_url = g_strdup_printf( "%s?%s", st->sp->url_authorize, req->reply_body );
+		oauth_params_parse( &params, req->reply_body );
+		st->request_token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+		oauth_params_free( &params );
+	}
+	
+	st->stage = OAUTH_REQUEST_TOKEN;
+	st->func( st );
+}
+
+static void oauth_access_token_done( struct http_request *req );
+
+gboolean oauth_access_token( const char *pin, struct oauth_info *st )
+{
+	GSList *params = NULL;
+	
+	oauth_params_add( &params, "oauth_token", st->request_token );
+	oauth_params_add( &params, "oauth_verifier", pin );
+	
+	return oauth_post_request( st->sp->url_access_token, &params, oauth_access_token_done, st ) != NULL;
+}
+
+static void oauth_access_token_done( struct http_request *req )
+{
+	struct oauth_info *st = req->data;
+	
+	st->http = req;
+	
+	if( req->status_code == 200 )
+	{
+		GSList *params = NULL;
+		
+		oauth_params_parse( &params, req->reply_body );
+		st->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+		st->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
+		oauth_params_free( &params );
+	}
+	
+	st->stage = OAUTH_ACCESS_TOKEN;
+	if( st->func( st ) )
+	{
+		/* Don't need these anymore, but keep the rest. */
+		g_free( st->auth_url );
+		st->auth_url = NULL;
+		g_free( st->request_token );
+		st->request_token = NULL;
+	}
+}
+
+char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args )
+{
+	GSList *params = NULL, *l;
+	char *sig = NULL, *params_s, *s;
+	GString *ret = NULL;
+	
+	oauth_params_add( &params, "oauth_token", oi->token );
+	oauth_add_default_params( &params, oi->sp );
+	
+	/* Start building the OAuth header. 'key="value", '... */
+	ret = g_string_new( "OAuth " );
+	for( l = params; l; l = l->next )
+	{
+		char *kv = l->data;
+		char *eq = strchr( kv, '=' );
+		char esc[strlen(kv)*3+1];
+		
+		if( eq == NULL )
+			break; /* WTF */
+		
+		strcpy( esc, eq + 1 );
+		http_encode( esc );
+		
+		g_string_append_len( ret, kv, eq - kv + 1 );
+		g_string_append_c( ret, '"' );
+		g_string_append( ret, esc );
+		g_string_append( ret, "\", " );
+	}
+	
+	/* Now, before generating the signature, add GET/POST arguments to params
+	   since they should be included in the base signature string (but not in
+	   the HTTP header). */
+	if( args )
+		oauth_params_parse( &params, args );
+	if( ( s = strchr( url, '?' ) ) )
+	{
+		s = g_strdup( s + 1 );
+		oauth_params_parse( &params, s );
+		g_free( s );
+	}
+	
+	/* Append the signature and we're done! */
+	params_s = oauth_params_string( params );
+	sig = oauth_sign( method, url, params_s, oi );
+	g_string_append_printf( ret, "oauth_signature=\"%s\"", sig );
+	g_free( params_s );
+	
+	oauth_params_free( &params );
+	g_free( sig );
+	
+	return ret ? g_string_free( ret, FALSE ) : NULL;
+}
+
+char *oauth_to_string( struct oauth_info *oi )
+{
+	GSList *params = NULL;
+	char *ret;
+	
+	oauth_params_add( &params, "oauth_token", oi->token );
+	oauth_params_add( &params, "oauth_token_secret", oi->token_secret );
+	ret = oauth_params_string( params );
+	oauth_params_free( &params );
+	
+	return ret;
+}
+
+struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp )
+{
+	struct oauth_info *oi = g_new0( struct oauth_info, 1 );
+	GSList *params = NULL;
+	
+	oauth_params_parse( &params, in );
+	oi->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+	oi->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
+	oauth_params_free( &params );
+	oi->sp = sp;
+	
+	return oi;
+}
Index: lib/oauth.h
===================================================================
--- lib/oauth.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ lib/oauth.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,90 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Simple OAuth client (consumer) implementation.                           *
+*                                                                           *
+*  Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net>                   *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+/* http://oauth.net/core/1.0a/ */
+
+struct oauth_info;
+
+/* Callback function called twice during the access token request process.
+   Return FALSE if something broke and the process must be aborted. */
+typedef gboolean (*oauth_cb)( struct oauth_info * );
+
+typedef enum
+{
+	OAUTH_INIT,
+	OAUTH_REQUEST_TOKEN,
+	OAUTH_ACCESS_TOKEN,
+} oauth_stage_t;
+
+struct oauth_info
+{
+	oauth_stage_t stage;
+	const struct oauth_service *sp;
+	
+	oauth_cb func;
+	void *data;
+	
+	struct http_request *http;
+	
+	char *auth_url;
+	char *request_token;
+	
+	char *token;
+	char *token_secret;
+};
+
+struct oauth_service
+{
+	char *url_request_token;
+	char *url_access_token;
+	char *url_authorize;
+	
+	char *consumer_key;
+	char *consumer_secret;
+};
+
+/* http://oauth.net/core/1.0a/#auth_step1 (section 6.1) 
+   Request an initial anonymous token which can be used to construct an
+   authorization URL for the user. This is passed to the callback function
+   in a struct oauth_info. */
+struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data );
+
+/* http://oauth.net/core/1.0a/#auth_step3 (section 6.3)
+   The user gets a PIN or so which we now exchange for the final access
+   token. This is passed to the callback function in the same
+   struct oauth_info. */
+gboolean oauth_access_token( const char *pin, struct oauth_info *st );
+
+/* http://oauth.net/core/1.0a/#anchor12 (section 7)
+   Generate an OAuth Authorization: HTTP header. access_token should be
+   saved/fetched using the functions above. args can be a string with
+   whatever's going to be in the POST body of the request. GET args will
+   automatically be grabbed from url. */
+char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args );
+
+/* Shouldn't normally be required unless the process is aborted by the user. */
+void oauth_info_free( struct oauth_info *info );
+
+/* Convert to and back from strings, for easier saving. */
+char *oauth_to_string( struct oauth_info *oi );
+struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp );
Index: lib/proxy.c
===================================================================
--- lib/proxy.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/proxy.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -91,7 +91,7 @@
 		b_event_remove(phb->inpa);
 		if( phb->proxy_func )
-			phb->proxy_func(phb->proxy_data, -1, GAIM_INPUT_READ);
+			phb->proxy_func(phb->proxy_data, -1, B_EV_IO_READ);
 		else {
-			phb->func(phb->data, -1, GAIM_INPUT_READ);
+			phb->func(phb->data, -1, B_EV_IO_READ);
 			g_free(phb);
 		}
@@ -102,7 +102,7 @@
 	b_event_remove(phb->inpa);
 	if( phb->proxy_func )
-		phb->proxy_func(phb->proxy_data, source, GAIM_INPUT_READ);
+		phb->proxy_func(phb->proxy_data, source, B_EV_IO_READ);
 	else {
-		phb->func(phb->data, source, GAIM_INPUT_READ);
+		phb->func(phb->data, source, B_EV_IO_READ);
 		g_free(phb);
 	}
@@ -147,5 +147,5 @@
 		return -1;
 	} else {
-		phb->inpa = b_input_add(fd, GAIM_INPUT_WRITE, gaim_io_connected, phb);
+		phb->inpa = b_input_add(fd, B_EV_IO_WRITE, gaim_io_connected, phb);
 		phb->fd = fd;
 		
@@ -179,5 +179,5 @@
 	if ((memcmp(HTTP_GOODSTRING, inputline, strlen(HTTP_GOODSTRING)) == 0) ||
 	    (memcmp(HTTP_GOODSTRING2, inputline, strlen(HTTP_GOODSTRING2)) == 0)) {
-		phb->func(phb->data, source, GAIM_INPUT_READ);
+		phb->func(phb->data, source, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -186,5 +186,5 @@
 
 	close(source);
-	phb->func(phb->data, -1, GAIM_INPUT_READ);
+	phb->func(phb->data, -1, B_EV_IO_READ);
 	g_free(phb->host);
 	g_free(phb);
@@ -204,5 +204,5 @@
 	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -215,5 +215,5 @@
 	if (send(source, cmd, strlen(cmd), 0) < 0) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -230,5 +230,5 @@
 		if (send(source, cmd, strlen(cmd), 0) < 0) {
 			close(source);
-			phb->func(phb->data, -1, GAIM_INPUT_READ);
+			phb->func(phb->data, -1, B_EV_IO_READ);
 			g_free(phb->host);
 			g_free(phb);
@@ -240,11 +240,11 @@
 	if (send(source, cmd, strlen(cmd), 0) < 0) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
-		g_free(phb->host);
-		g_free(phb);
-		return FALSE;
-	}
-
-	phb->inpa = b_input_add(source, GAIM_INPUT_READ, http_canread, phb);
+		phb->func(phb->data, -1, B_EV_IO_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return FALSE;
+	}
+
+	phb->inpa = b_input_add(source, B_EV_IO_READ, http_canread, phb);
 	
 	return FALSE;
@@ -273,5 +273,5 @@
 	memset(packet, 0, sizeof(packet));
 	if (read(source, packet, 9) >= 4 && packet[1] == 90) {
-		phb->func(phb->data, source, GAIM_INPUT_READ);
+		phb->func(phb->data, source, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -280,5 +280,5 @@
 
 	close(source);
-	phb->func(phb->data, -1, GAIM_INPUT_READ);
+	phb->func(phb->data, -1, B_EV_IO_READ);
 	g_free(phb->host);
 	g_free(phb);
@@ -299,5 +299,5 @@
 	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -309,5 +309,5 @@
 	if (!(hp = gethostbyname(phb->host))) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -326,11 +326,11 @@
 	if (write(source, packet, 9) != 9) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
-		g_free(phb->host);
-		g_free(phb);
-		return FALSE;
-	}
-
-	phb->inpa = b_input_add(source, GAIM_INPUT_READ, s4_canread, phb);
+		phb->func(phb->data, -1, B_EV_IO_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return FALSE;
+	}
+
+	phb->inpa = b_input_add(source, B_EV_IO_READ, s4_canread, phb);
 	
 	return FALSE;
@@ -359,5 +359,5 @@
 	if (read(source, buf, 10) < 10) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -366,11 +366,11 @@
 	if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
-		g_free(phb->host);
-		g_free(phb);
-		return FALSE;
-	}
-
-	phb->func(phb->data, source, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return FALSE;
+	}
+
+	phb->func(phb->data, source, B_EV_IO_READ);
 	g_free(phb->host);
 	g_free(phb);
@@ -396,5 +396,5 @@
 	if (write(source, buf, (5 + strlen(phb->host) + 2)) < (5 + strlen(phb->host) + 2)) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -402,5 +402,5 @@
 	}
 
-	phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_canread_again, phb);
+	phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread_again, phb);
 }
 
@@ -414,5 +414,5 @@
 	if (read(source, buf, 2) < 2) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -422,5 +422,5 @@
 	if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -442,5 +442,5 @@
 	if (read(source, buf, 2) < 2) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -450,5 +450,5 @@
 	if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -465,5 +465,5 @@
 		if (write(source, buf, 3 + i + j) < 3 + i + j) {
 			close(source);
-			phb->func(phb->data, -1, GAIM_INPUT_READ);
+			phb->func(phb->data, -1, B_EV_IO_READ);
 			g_free(phb->host);
 			g_free(phb);
@@ -471,5 +471,5 @@
 		}
 
-		phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_readauth, phb);
+		phb->inpa = b_input_add(source, B_EV_IO_READ, s5_readauth, phb);
 	} else {
 		s5_sendconnect(phb, source);
@@ -491,5 +491,5 @@
 	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
+		phb->func(phb->data, -1, B_EV_IO_READ);
 		g_free(phb->host);
 		g_free(phb);
@@ -513,11 +513,11 @@
 	if (write(source, buf, i) < i) {
 		close(source);
-		phb->func(phb->data, -1, GAIM_INPUT_READ);
-		g_free(phb->host);
-		g_free(phb);
-		return FALSE;
-	}
-
-	phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_canread, phb);
+		phb->func(phb->data, -1, B_EV_IO_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return FALSE;
+	}
+
+	phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread, phb);
 	
 	return FALSE;
Index: lib/ssl_bogus.c
===================================================================
--- lib/ssl_bogus.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/ssl_bogus.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -59,5 +59,5 @@
 b_input_condition ssl_getdirection( void *conn )
 {
-	return GAIM_INPUT_READ;
+	return B_EV_IO_READ;
 }
 
Index: lib/ssl_client.h
===================================================================
--- lib/ssl_client.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/ssl_client.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -71,5 +71,5 @@
 G_MODULE_EXPORT int ssl_getfd( void *conn );
 
-/* This function returns GAIM_INPUT_READ/WRITE. With SSL connections it's
+/* This function returns B_EV_IO_READ/WRITE. With SSL connections it's
    possible that something has to be read while actually were trying to
    write something (think about key exchange/refresh/etc). So when an
Index: lib/ssl_gnutls.c
===================================================================
--- lib/ssl_gnutls.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/ssl_gnutls.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -106,5 +106,5 @@
 	struct scd *conn = data;
 	
-	return ssl_connected( conn, conn->fd, GAIM_INPUT_WRITE );
+	return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );
 }
 
@@ -244,4 +244,4 @@
 {
 	return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
-	        GAIM_INPUT_WRITE : GAIM_INPUT_READ );
-}
+	        B_EV_IO_WRITE : B_EV_IO_READ );
+}
Index: lib/ssl_nss.c
===================================================================
--- lib/ssl_nss.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/ssl_nss.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -193,4 +193,4 @@
 {
 	/* Just in case someone calls us, let's return the most likely case: */
-	return GAIM_INPUT_READ;
+	return B_EV_IO_READ;
 }
Index: lib/ssl_openssl.c
===================================================================
--- lib/ssl_openssl.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/ssl_openssl.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -102,5 +102,5 @@
 	struct scd *conn = data;
 	
-	return ssl_connected( conn, conn->fd, GAIM_INPUT_WRITE );
+	return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );
 }
 
@@ -270,4 +270,4 @@
 b_input_condition ssl_getdirection( void *conn )
 {
-	return( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ? GAIM_INPUT_WRITE : GAIM_INPUT_READ );
-}
+	return( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ? B_EV_IO_WRITE : B_EV_IO_READ );
+}
Index: lib/ssl_sspi.c
===================================================================
--- lib/ssl_sspi.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/ssl_sspi.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -275,4 +275,4 @@
 GaimInputCondition ssl_getdirection( void *conn )
 {
-	return GAIM_INPUT_WRITE; /* FIXME: or GAIM_INPUT_READ */
-}
+	return B_EV_IO_WRITE; /* FIXME: or B_EV_IO_READ */
+}
Index: lib/url.c
===================================================================
--- lib/url.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/url.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -27,5 +27,5 @@
 
 /* Convert an URL to a url_t structure */
-int url_set( url_t *url, char *set_url )
+int url_set( url_t *url, const char *set_url )
 {
 	char s[MAX_STRING+1];
Index: lib/url.h
===================================================================
--- lib/url.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/url.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -42,3 +42,3 @@
 } url_t;
 
-int url_set( url_t *url, char *set_url );
+int url_set( url_t *url, const char *set_url );
Index: lib/xmltree.c
===================================================================
--- lib/xmltree.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ lib/xmltree.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -449,5 +449,9 @@
 	while( node )
 	{
-		if( g_strcasecmp( node->name, name ) == 0 )
+		char *colon;
+		
+		if( g_strcasecmp( node->name, name ) == 0 ||
+		    ( ( colon = strchr( node->name, ':' ) ) &&
+		      g_strcasecmp( colon + 1, name ) == 0 ) )
 			break;
 		
@@ -461,4 +465,5 @@
 {
 	int i;
+	char *colon;
 	
 	if( !node )
@@ -468,4 +473,19 @@
 		if( g_strcasecmp( node->attr[i].key, key ) == 0 )
 			break;
+	
+	/* This is an awful hack that only takes care of namespace prefixes
+	   inside a tag. Since IMHO excessive namespace usage in XMPP is
+	   massive overkill anyway (this code exists for almost four years
+	   now and never really missed it): Meh. */
+	if( !node->attr[i].key && strcmp( key, "xmlns" ) == 0 &&
+	    ( colon = strchr( node->name, ':' ) ) )
+	{
+		*colon = '\0';
+		for( i = 0; node->attr[i].key; i ++ )
+			if( strncmp( node->attr[i].key, "xmlns:", 6 ) == 0 &&
+			    strcmp( node->attr[i].key + 6, node->name ) == 0 )
+				break;
+		*colon = ':';
+	}
 	
 	return node->attr[i].value;
Index: log.c
===================================================================
--- log.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ log.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -172,4 +172,6 @@
 		fprintf(stdout, "Debug: %s\n", message);
 #endif
+	/* Always log stuff in syslogs too. */
+	log_syslog(level, message);
 	return;
 }
Index: protocols/jabber/conference.c
===================================================================
--- protocols/jabber/conference.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/jabber/conference.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -272,6 +272,8 @@
 		}
 		
-		if( bud != jc->me )
-		{
+		if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS )
+		{
+			/* If JIDs are anonymized, add them to the local
+			   list for the duration of this chat. */
 			imcb_add_buddy( ic, bud->ext_jid, NULL );
 			imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource );
Index: protocols/jabber/io.c
===================================================================
--- protocols/jabber/io.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/jabber/io.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -64,5 +64,5 @@
 		   most cases it probably won't be necessary.) */
 		if( ( ret = jabber_write_queue( ic ) ) && jd->tx_len > 0 )
-			jd->w_inpa = b_input_add( jd->fd, GAIM_INPUT_WRITE, jabber_write_callback, ic );
+			jd->w_inpa = b_input_add( jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic );
 	}
 	else
@@ -504,5 +504,5 @@
 	
 	if( jd->r_inpa <= 0 )
-		jd->r_inpa = b_input_add( jd->fd, GAIM_INPUT_READ, jabber_read_callback, ic );
+		jd->r_inpa = b_input_add( jd->fd, B_EV_IO_READ, jabber_read_callback, ic );
 	
 	greet = g_strdup_printf( "%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" "
Index: protocols/jabber/jabber_util.c
===================================================================
--- protocols/jabber/jabber_util.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/jabber/jabber_util.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -671,8 +671,7 @@
 time_t jabber_get_timestamp( struct xt_node *xt )
 {
-	struct tm tp, utc;
 	struct xt_node *c;
-	time_t res, tres;
 	char *s = NULL;
+	struct tm tp;
 	
 	for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
@@ -692,28 +691,6 @@
 	tp.tm_year -= 1900;
 	tp.tm_mon --;
-	tp.tm_isdst = -1; /* GRRRRRRRRRRR */
-	
-	res = mktime( &tp );
-	/* Problem is, mktime() just gave us the GMT timestamp for the
-	   given local time... While the given time WAS NOT local. So
-	   we should fix this now.
-	
-	   Now I could choose between messing with environment variables
-	   (kludgy) or using timegm() (not portable)... Or doing the
-	   following, which I actually prefer... */
-	gmtime_r( &res, &utc );
-	utc.tm_isdst = -1; /* Once more: GRRRRRRRRRRRRRRRRRR!!! */
-	if( utc.tm_hour == tp.tm_hour && utc.tm_min == tp.tm_min )
-		/* Sweet! We're in UTC right now... */
-		return res;
-	
-	tres = mktime( &utc );
-	res += res - tres;
-	
-	/* Yes, this is a hack. And it will go wrong around DST changes.
-	   BUT this is more likely to be threadsafe than messing with
-	   environment variables, and possibly more portable... */
-	
-	return res;
+	
+	return mktime_utc( &tp );
 }
 
Index: protocols/jabber/message.c
===================================================================
--- protocols/jabber/message.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/jabber/message.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -80,6 +80,6 @@
 		if( type && strcmp( type, "headline" ) == 0 )
 		{
-			c = xt_find_node( node->children, "subject" );
-			g_string_append_printf( fullmsg, "Headline: %s\n", c && c->text_len > 0 ? c->text : "" );
+			if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 )
+				g_string_append_printf( fullmsg, "Headline: %s\n", c->text );
 			
 			/* <x xmlns="jabber:x:oob"><url>http://....</url></x> can contain a URL, it seems. */
Index: protocols/jabber/s5bytestream.c
===================================================================
--- protocols/jabber/s5bytestream.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/jabber/s5bytestream.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -406,5 +406,5 @@
 			bt->phase = BS_PHASE_CONNECTED;
 			
-			bt->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_recv_handshake, bt );
+			bt->tf->watch_out = b_input_add( fd, B_EV_IO_WRITE, jabber_bs_recv_handshake, bt );
 
 			/* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */
@@ -433,5 +433,5 @@
 			bt->phase = BS_PHASE_REQUEST;
 
-			bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_recv_handshake, bt );
+			bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_recv_handshake, bt );
 
 			bt->tf->watch_out = 0;
@@ -589,5 +589,5 @@
 
 	tf->ft->data = tf;
-	tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt );
+	tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
 	tf->ft->write_request = jabber_bs_recv_write_request;
 
@@ -631,5 +631,5 @@
 		if( ( ret == -1 ) && ( errno == EAGAIN ) )
 		{
-			tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt );
+			tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
 			return FALSE;
 		}
@@ -707,5 +707,5 @@
 		imcb_file_finished( ft );
 	else
-		bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt );
+		bt->tf->watch_out = b_input_add( tf->fd, B_EV_IO_WRITE, jabber_bs_send_can_write, bt );
 		
 	return TRUE;
@@ -918,5 +918,5 @@
 				bt->streamhosts = g_slist_append( bt->streamhosts, sh );
 
-				bt->tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt );
+				bt->tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_send_handshake, bt );
 				bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt );
 			} else {
@@ -1055,5 +1055,5 @@
 			bt->phase = BS_PHASE_CONNECTED;
 
-			bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt );
+			bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_send_handshake, bt );
 			return FALSE;
 		}
Index: protocols/msn/invitation.c
===================================================================
--- protocols/msn/invitation.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/msn/invitation.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -209,5 +209,5 @@
 	sock_make_nonblocking( fd );
 
-	msn_file->r_event_id = b_input_add( fd, GAIM_INPUT_READ, msn_ftp_read, file );
+	msn_file->r_event_id = b_input_add( fd, B_EV_IO_READ, msn_ftp_read, file );
 
 	return FALSE;
@@ -230,5 +230,5 @@
 	}
 
-	msn_file->r_event_id = b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftps_connected, file );
+	msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file );
 
 	g_snprintf( buf, sizeof( buf ),
@@ -318,5 +318,5 @@
 	
 	sock_make_nonblocking( msn_file->fd );
-	msn_file->r_event_id = b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftp_read, file );
+	msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file );
 	
 	return FALSE;
@@ -415,5 +415,5 @@
 	    ( msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size ) ) {
 		if( !msn_file->w_event_id )
-			msn_file->w_event_id = b_input_add( msn_file->fd, GAIM_INPUT_WRITE, msn_ftp_send, file );
+			msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file );
 		return TRUE;
 	}
@@ -452,5 +452,5 @@
 		/* we might already be listening if this is data from an overflow */
 		if( !msn_file->w_event_id )
-			msn_file->w_event_id = b_input_add( msn_file->fd, GAIM_INPUT_WRITE, msn_ftp_send, file );
+			msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file );
 	}
 
@@ -617,5 +617,5 @@
 
 	msn_file->r_event_id = 
-		b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftp_read, file );
+		b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file );
 
 	return TRUE;
Index: protocols/msn/msn.c
===================================================================
--- protocols/msn/msn.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/msn/msn.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -31,14 +31,12 @@
 GSList *msn_switchboards;
 
-static char *msn_set_display_name( set_t *set, char *value );
+static char *set_eval_display_name( set_t *set, char *value );
 
 static void msn_init( account_t *acc )
 {
-	set_t *s;
-	
-	s = set_add( &acc->set, "display_name", NULL, msn_set_display_name, acc );
-	s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
-
-	s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
+	set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc );
+	set_add( &acc->set, "local_display_name", "false", set_eval_bool, acc );
+	set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
+	set_add( &acc->set, "switchboard_keepalives", "false", set_eval_bool, acc );
 }
 
@@ -171,5 +169,5 @@
 static void msn_set_my_name( struct im_connection *ic, char *info )
 {
-	msn_set_display_name( set_find( &ic->acc->set, "display_name" ), info );
+	msn_set_display_name( ic, info );
 }
 
@@ -287,16 +285,12 @@
 }
 
-static char *msn_set_display_name( set_t *set, char *value )
+static char *set_eval_display_name( set_t *set, char *value )
 {
 	account_t *acc = set->data;
 	struct im_connection *ic = acc->ic;
-	struct msn_data *md;
-	char buf[1024], *fn;
-	
-	/* Double-check. */
+	
+	/* Allow any name if we're offline. */
 	if( ic == NULL )
-		return NULL;
-	
-	md = ic->proto_data;
+		return value;
 	
 	if( strlen( value ) > 129 )
@@ -305,15 +299,9 @@
 		return NULL;
 	}
-	
-	fn = msn_http_encode( value );
-	
-	g_snprintf( buf, sizeof( buf ), "REA %d %s %s\r\n", ++md->trId, ic->acc->user, fn );
-	msn_write( ic, buf, strlen( buf ) );
-	g_free( fn );
 	
 	/* Returning NULL would be better, because the server still has to
 	   confirm the name change. However, it looks a bit confusing to the
 	   user. */
-	return value;
+	return msn_set_display_name( ic, value ) ? value : NULL;
 }
 
Index: protocols/msn/msn.h
===================================================================
--- protocols/msn/msn.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/msn/msn.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -31,4 +31,5 @@
 #define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\r\r\r"
 #define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r"
+#define SB_KEEPALIVE_MESSAGE "\r\r\rDONT HANG UP ON ME!\r\r\r"
 
 #ifdef DEBUG_MSN
@@ -53,4 +54,8 @@
                            "TypingUser: %s\r\n" \
                            "\r\n\r\n"
+
+#define SB_KEEPALIVE_HEADERS "MIME-Version: 1.0\r\n" \
+                             "Content-Type: text/x-ping\r\n" \
+                             "\r\n\r\n"
 
 #define PROFILE_URL "http://members.msn.com/"
@@ -84,4 +89,5 @@
 	gint inp;
 	struct msn_handler_data *handler;
+	gint keepalive;
 	
 	int trId;
@@ -162,4 +168,5 @@
 char *msn_http_encode( const char *input );
 void msn_msgq_purge( struct im_connection *ic, GSList **list );
+gboolean msn_set_display_name( struct im_connection *ic, const char *rawname );
 
 /* tables.c */
@@ -180,4 +187,6 @@
 gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond );
 int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m );
+void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial );
+void msn_sb_stop_keepalives( struct msn_switchboard *sb );
 
 /* invitation.c */
Index: protocols/msn/msn_util.c
===================================================================
--- protocols/msn/msn_util.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/msn/msn_util.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -38,8 +38,8 @@
 		imcb_error( ic, "Short write() to main server" );
 		imc_logout( ic, TRUE );
-		return( 0 );
-	}
-	
-	return( 1 );
+		return 0;
+	}
+	
+	return 1;
 }
 
@@ -377,2 +377,14 @@
 	g_string_free( ret, TRUE );
 }
+
+gboolean msn_set_display_name( struct im_connection *ic, const char *rawname )
+{
+	char *fn = msn_http_encode( rawname );
+	struct msn_data *md = ic->proto_data;
+	char buf[1024];
+	
+	g_snprintf( buf, sizeof( buf ), "REA %d %s %s\r\n", ++md->trId, ic->acc->user, fn );
+	g_free( fn );
+	
+	return msn_write( ic, buf, strlen( buf ) ) != 0;
+}
Index: protocols/msn/ns.c
===================================================================
--- protocols/msn/ns.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/msn/ns.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -35,4 +35,5 @@
 
 static void msn_auth_got_passport_token( struct msn_auth_data *mad );
+static gboolean msn_ns_got_display_name( struct im_connection *ic, char *name );
 
 gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond )
@@ -75,5 +76,5 @@
 	if( msn_write( ic, s, strlen( s ) ) )
 	{
-		ic->inpa = b_input_add( md->fd, GAIM_INPUT_READ, msn_ns_callback, ic );
+		ic->inpa = b_input_add( md->fd, B_EV_IO_READ, msn_ns_callback, ic );
 		imcb_log( ic, "Connected to server, waiting for reply" );
 	}
@@ -231,23 +232,8 @@
 		else if( num_parts >= 7 && strcmp( cmd[2], "OK" ) == 0 )
 		{
-			set_t *s;
-			
 			if( num_parts == 7 )
-			{
-				http_decode( cmd[4] );
-				
-				strncpy( ic->displayname, cmd[4], sizeof( ic->displayname ) );
-				ic->displayname[sizeof(ic->displayname)-1] = 0;
-				
-				if( ( s = set_find( &ic->acc->set, "display_name" ) ) )
-				{
-					g_free( s->value );
-					s->value = g_strdup( cmd[4] );
-				}
-			}
+				msn_ns_got_display_name( ic, cmd[4] );
 			else
-			{
 				imcb_log( ic, "Warning: Friendly name in server response was corrupted" );
-			}
 			
 			imcb_log( ic, "Authenticated, getting buddy list" );
@@ -436,6 +422,10 @@
 	else if( strcmp( cmd[0], "FLN" ) == 0 )
 	{
-		if( cmd[1] )
-			imcb_buddy_status( ic, cmd[1], 0, NULL, NULL );
+		if( cmd[1] == NULL )
+			return 1;
+		
+		imcb_buddy_status( ic, cmd[1], 0, NULL, NULL );
+		
+		msn_sb_start_keepalives( msn_sb_by_handle( ic, cmd[1] ), TRUE );
 	}
 	else if( strcmp( cmd[0], "NLN" ) == 0 )
@@ -463,4 +453,6 @@
 		                   ( st != msn_away_state_list ? OPT_AWAY : 0 ),
 		                   st->name, NULL );
+		
+		msn_sb_stop_keepalives( msn_sb_by_handle( ic, cmd[2] ) );
 	}
 	else if( strcmp( cmd[0], "RNG" ) == 0 )
@@ -567,4 +559,7 @@
 		return( 0 );
 	}
+#if 0
+	/* Discard this one completely for now since I don't care about the ack
+	   and since MSN servers can apparently screw up the formatting. */
 	else if( strcmp( cmd[0], "REA" ) == 0 )
 	{
@@ -597,4 +592,5 @@
 		}
 	}
+#endif
 	else if( strcmp( cmd[0], "IPG" ) == 0 )
 	{
@@ -746,2 +742,47 @@
 	}
 }
+
+static gboolean msn_ns_got_display_name( struct im_connection *ic, char *name )
+{
+	set_t *s;
+	
+	if( ( s = set_find( &ic->acc->set, "display_name" ) ) == NULL )
+		return FALSE; /* Shouldn't happen.. */
+	
+	http_decode( name );
+	
+	if( s->value && strcmp( s->value, name ) == 0 )
+	{
+		return TRUE;
+		/* The names match, nothing to worry about. */
+	}
+	else if( s->value != NULL &&
+	         ( strcmp( name, ic->acc->user ) == 0 ||
+	           set_getbool( &ic->acc->set, "local_display_name" ) ) )
+	{
+		/* The server thinks our display name is our e-mail address
+		   which is probably wrong, or the user *wants* us to do this:
+		   Always use the locally set display_name. */
+		return msn_set_display_name( ic, s->value );
+	}
+	else
+	{
+		if( s->value && *s->value )
+			imcb_log( ic, "BitlBee thinks your display name is `%s' but "
+			              "the MSN server says it's `%s'. Using the MSN "
+			              "server's name. Set local_display_name to true "
+			              "to use the local name.", s->value, name );
+		
+		if( g_utf8_validate( name, -1, NULL ) )
+		{
+			g_free( s->value );
+			s->value = g_strdup( name );
+		}
+		else
+		{
+			imcb_log( ic, "Warning: Friendly name in server response was corrupted" );
+		}
+		
+		return TRUE;
+	}
+}
Index: protocols/msn/sb.c
===================================================================
--- protocols/msn/sb.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/msn/sb.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -180,4 +180,9 @@
 			i = strlen( buf );
 		}
+		else if( strcmp( text, SB_KEEPALIVE_MESSAGE ) == 0 )
+		{
+			buf = g_strdup( SB_KEEPALIVE_HEADERS );
+			i = strlen( buf );
+		}
 		else
 		{
@@ -256,4 +261,5 @@
 	
 	msn_msgq_purge( ic, &sb->msgq );
+	msn_sb_stop_keepalives( sb );
 	
 	if( sb->key ) g_free( sb->key );
@@ -315,5 +321,5 @@
 	
 	if( msn_sb_write( sb, buf, strlen( buf ) ) )
-		sb->inp = b_input_add( sb->fd, GAIM_INPUT_READ, msn_sb_callback, sb );
+		sb->inp = b_input_add( sb->fd, B_EV_IO_READ, msn_sb_callback, sb );
 	else
 		debug( "Error %d while connecting to switchboard server", 2 );
@@ -328,7 +334,11 @@
 	struct msn_data *md = ic->proto_data;
 	
-	if( msn_handler( sb->handler ) == -1 )
+	if( msn_handler( sb->handler ) != -1 )
+		return TRUE;
+	
+	if( sb->msgq != NULL )
 	{
 		time_t now = time( NULL );
+		char buf[1024];
 		
 		if( now - md->first_sb_failure > 600 )
@@ -347,35 +357,26 @@
 			              "There might be problems delivering your messages." );
 		
-		if( sb->msgq != NULL )
-		{
-			char buf[1024];
-			
-			if( md->msgq == NULL )
-			{
-				md->msgq = sb->msgq;
-			}
-			else
-			{
-				GSList *l;
-				
-				for( l = md->msgq; l->next; l = l->next );
-				l->next = sb->msgq;
-			}
-			sb->msgq = NULL;
-			
-			debug( "Moved queued messages back to the main queue, creating a new switchboard to retry." );
-			g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
-			if( !msn_write( ic, buf, strlen( buf ) ) )
-				return FALSE;
-		}
-		
-		msn_sb_destroy( sb );
-		
-		return FALSE;
-	}
-	else
-	{
-		return TRUE;
-	}
+		if( md->msgq == NULL )
+		{
+			md->msgq = sb->msgq;
+		}
+		else
+		{
+			GSList *l;
+			
+			for( l = md->msgq; l->next; l = l->next );
+			l->next = sb->msgq;
+		}
+		sb->msgq = NULL;
+		
+		debug( "Moved queued messages back to the main queue, "
+		       "creating a new switchboard to retry." );
+		g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
+		if( !msn_write( ic, buf, strlen( buf ) ) )
+			return FALSE;
+	}
+	
+	msn_sb_destroy( sb );
+	return FALSE;
 }
 
@@ -477,4 +478,6 @@
 		
 		sb->ready = 1;
+		
+		msn_sb_start_keepalives( sb, FALSE );
 	}
 	else if( strcmp( cmd[0], "CAL" ) == 0 )
@@ -526,4 +529,6 @@
 			}
 			
+			msn_sb_start_keepalives( sb, FALSE );
+			
 			return( st );
 		}
@@ -587,4 +592,6 @@
 		if( sb->who )
 		{
+			msn_sb_stop_keepalives( sb );
+			
 			/* This is a single-person chat, and the other person is leaving. */
 			g_free( sb->who );
@@ -749,2 +756,32 @@
 	return( 1 );
 }
+
+static gboolean msn_sb_keepalive( gpointer data, gint source, b_input_condition cond )
+{
+	struct msn_switchboard *sb = data;
+	return sb->ready && msn_sb_sendmessage( sb, SB_KEEPALIVE_MESSAGE );
+}
+
+void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial )
+{
+	struct buddy *b;
+	
+	if( sb && sb->who && sb->keepalive == 0 &&
+	    ( b = imcb_find_buddy( sb->ic, sb->who ) ) && !b->present &&
+	    set_getbool( &sb->ic->acc->set, "switchboard_keepalives" ) )
+	{
+		if( initial )
+			msn_sb_keepalive( sb, 0, 0 );
+		
+		sb->keepalive = b_timeout_add( 20000, msn_sb_keepalive, sb );
+	}
+}
+
+void msn_sb_stop_keepalives( struct msn_switchboard *sb )
+{
+	if( sb && sb->keepalive > 0 )
+	{
+		b_event_remove( sb->keepalive );
+		sb->keepalive = 0;
+	}
+}
Index: protocols/nogaim.c
===================================================================
--- protocols/nogaim.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/nogaim.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -39,4 +39,5 @@
 
 static int remove_chat_buddy_silent( struct groupchat *b, const char *handle );
+static char *format_timestamp( irc_t *irc, time_t msg_ts );
 
 GSList *connections;
@@ -116,10 +117,13 @@
 {
 	GList *gl;
-	for (gl = protocols; gl; gl = gl->next) 
+	
+	for( gl = protocols; gl; gl = gl->next )
  	{
  		struct prpl *proto = gl->data;
- 		if(!g_strcasecmp(proto->name, name)) 
+ 		
+ 		if( g_strcasecmp( proto->name, name ) == 0 )
 			return proto;
  	}
+ 	
  	return NULL;
 }
@@ -132,4 +136,6 @@
 	extern void byahoo_initmodule();
 	extern void jabber_initmodule();
+	extern void twitter_initmodule();
+	extern void purple_initmodule();
 
 #ifdef WITH_MSN
@@ -147,4 +153,12 @@
 #ifdef WITH_JABBER
 	jabber_initmodule();
+#endif
+
+#ifdef WITH_TWITTER
+	twitter_initmodule();
+#endif
+	
+#ifdef WITH_PURPLE
+	purple_initmodule();
 #endif
 
@@ -651,8 +665,19 @@
 	u->away = u->status_msg = NULL;
 	
-	if( ( flags & OPT_LOGGED_IN ) && !u->online )
-	{
+	if( set_getbool( &ic->irc->set, "show_offline" ) && !u->online )
+	{
+		/* always set users as online */
 		irc_spawn( ic->irc, u );
 		u->online = 1;
+		if( !( flags & OPT_LOGGED_IN ) )
+		{
+			/* set away message if user isn't really online */
+			u->away = g_strdup( "User is offline" );
+		}
+	}
+	else if( ( flags & OPT_LOGGED_IN ) && !u->online )
+	{
+		irc_spawn( ic->irc, u );
+		u->online = 1;
 	}
 	else if( !( flags & OPT_LOGGED_IN ) && u->online )
@@ -660,12 +685,28 @@
 		struct groupchat *c;
 		
-		irc_kill( ic->irc, u );
-		u->online = 0;
-		
-		/* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */
-		for( c = ic->groupchats; c; c = c->next )
-			remove_chat_buddy_silent( c, handle );
-	}
-	
+		if( set_getbool( &ic->irc->set, "show_offline" ) )
+		{
+			/* keep offline users in channel and set away message to "offline" */
+			u->away = g_strdup( "User is offline" );
+
+			/* Keep showing him/her in the control channel but not in groupchats. */
+			for( c = ic->groupchats; c; c = c->next )
+			{
+				if( remove_chat_buddy_silent( c, handle ) && c->joined )
+					irc_part( c->ic->irc, u, c->channel );
+			}
+		}
+		else
+		{
+			/* kill offline users */
+			irc_kill( ic->irc, u );
+			u->online = 0;
+
+			/* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */
+			for( c = ic->groupchats; c; c = c->next )
+				remove_chat_buddy_silent( c, handle );
+		}
+	}
+
 	if( flags & OPT_AWAY )
 	{
@@ -692,9 +733,6 @@
 	}
 	
-	/* 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 */
+	/* 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" ) )
 	{
 		char *from;
@@ -709,7 +747,41 @@
 			                                    ic->irc->myhost );
 		}
-		irc_write( ic->irc, ":%s MODE %s %cv %s", from, ic->irc->channel,
-		                                          u->away?'-':'+', u->nick );
-		g_free( from );
+
+		/* 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 );
+			}
+			irc_write( ic->irc, ":%s MODE %s %cv %s", from, ic->irc->channel,
+			                                          u->away?'-':'+', u->nick );
+			g_free( from );
+		}
 	}
 }
@@ -718,5 +790,5 @@
 {
 	irc_t *irc = ic->irc;
-	char *wrapped;
+	char *wrapped, *ts = NULL;
 	user_t *u;
 	
@@ -760,8 +832,17 @@
 	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) )
 		strip_html( msg );
-
+	
+	if( set_getbool( &ic->irc->set, "display_timestamps" ) &&
+	    ( ts = format_timestamp( irc, sent_at ) ) )
+	{
+		char *new = g_strconcat( ts, msg, NULL );
+		g_free( ts );
+		ts = msg = new;
+	}
+	
 	wrapped = word_wrap( msg, 425 );
 	irc_msgfrom( irc, u->nick, wrapped );
 	g_free( wrapped );
+	g_free( ts );
 }
 
@@ -805,4 +886,33 @@
 	
 	return c;
+}
+
+void imcb_chat_name_hint( struct groupchat *c, const char *name )
+{
+	if( !c->joined )
+	{
+		struct im_connection *ic = c->ic;
+		char stripped[MAX_NICK_LENGTH+1], *full_name;
+		
+		strncpy( stripped, name, MAX_NICK_LENGTH );
+		stripped[MAX_NICK_LENGTH] = '\0';
+		nick_strip( stripped );
+		if( set_getbool( &ic->irc->set, "lcnicks" ) )
+			nick_lc( stripped );
+		
+		full_name = g_strdup_printf( "&%s", stripped );
+		
+		if( stripped[0] &&
+		    nick_cmp( stripped, ic->irc->channel + 1 ) != 0 &&
+		    irc_chat_by_channel( ic->irc, full_name ) == NULL )
+		{
+			g_free( c->channel );
+			c->channel = full_name;
+		}
+		else
+		{
+			g_free( full_name );
+		}
+	}
 }
 
@@ -867,5 +977,9 @@
 	if( c && u )
 	{
-		irc_privmsg( ic->irc, u, "PRIVMSG", c->channel, "", wrapped );
+		char *ts = NULL;
+		if( set_getbool( &ic->irc->set, "display_timestamps" ) )
+			ts = format_timestamp( ic->irc, sent_at );
+		irc_privmsg( ic->irc, u, "PRIVMSG", c->channel, ts ? : "", wrapped );
+		g_free( ts );
 	}
 	else
@@ -1061,6 +1175,92 @@
 }
 
-
-
+char *set_eval_timezone( set_t *set, char *value )
+{
+	char *s;
+	
+	if( strcmp( value, "local" ) == 0 ||
+	    strcmp( value, "gmt" ) == 0 || strcmp( value, "utc" ) == 0 )
+		return value;
+	
+	/* Otherwise: +/- at the beginning optional, then one or more numbers,
+	   possibly followed by a colon and more numbers. Don't bother bound-
+	   checking them since users are free to shoot themselves in the foot. */
+	s = value;
+	if( *s == '+' || *s == '-' )
+		s ++;
+	
+	/* \d+ */
+	if( !isdigit( *s ) )
+		return SET_INVALID;
+	while( *s && isdigit( *s ) ) s ++;
+	
+	/* EOS? */
+	if( *s == '\0' )
+		return value;
+	
+	/* Otherwise, colon */
+	if( *s != ':' )
+		return SET_INVALID;
+	s ++;
+	
+	/* \d+ */
+	if( !isdigit( *s ) )
+		return SET_INVALID;
+	while( *s && isdigit( *s ) ) s ++;
+	
+	/* EOS */
+	return *s == '\0' ? value : SET_INVALID;
+}
+
+static char *format_timestamp( irc_t *irc, time_t msg_ts )
+{
+	time_t now_ts = time( NULL );
+	struct tm now, msg;
+	char *set;
+	
+	/* If the timestamp is <= 0 or less than a minute ago, discard it as
+	   it doesn't seem to add to much useful info and/or might be noise. */
+	if( msg_ts <= 0 || msg_ts > now_ts - 60 )
+		return NULL;
+	
+	set = set_getstr( &irc->set, "timezone" );
+	if( strcmp( set, "local" ) == 0 )
+	{
+		localtime_r( &now_ts, &now );
+		localtime_r( &msg_ts, &msg );
+	}
+	else
+	{
+		int hr, min = 0, sign = 60;
+		
+		if( set[0] == '-' )
+		{
+			sign *= -1;
+			set ++;
+		}
+		else if( set[0] == '+' )
+		{
+			set ++;
+		}
+		
+		if( sscanf( set, "%d:%d", &hr, &min ) >= 1 )
+		{
+			msg_ts += sign * ( hr * 60 + min );
+			now_ts += sign * ( hr * 60 + min );
+		}
+		
+		gmtime_r( &now_ts, &now );
+		gmtime_r( &msg_ts, &msg );
+	}
+	
+	if( msg.tm_year == now.tm_year && msg.tm_yday == now.tm_yday )
+		return g_strdup_printf( "\x02[\x02\x02\x02%02d:%02d:%02d\x02]\x02 ",
+		                        msg.tm_hour, msg.tm_min, msg.tm_sec );
+	else
+		return g_strdup_printf( "\x02[\x02\x02\x02%04d-%02d-%02d "
+		                        "%02d:%02d:%02d\x02]\x02 ",
+		                        msg.tm_year + 1900, msg.tm_mon + 1, msg.tm_mday,
+		                        msg.tm_hour, msg.tm_min, msg.tm_sec );
+}
 
 /* The plan is to not allow straight calls to prpl functions anymore, but do
@@ -1105,4 +1305,8 @@
 {
 	char *away, *msg = NULL;
+	
+	if( ic->acc->prpl->away_states == NULL ||
+	    ic->acc->prpl->set_away == NULL )
+		return 0;
 	
 	away = set_getstr( &ic->acc->set, "away" ) ?
Index: protocols/nogaim.h
===================================================================
--- protocols/nogaim.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/nogaim.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -134,4 +134,5 @@
 	 * - The user sees this name ie. when imcb_log() is used. */
 	const char *name;
+	void *data;
 
 	/* Added this one to be able to add per-account settings, don't think
@@ -307,4 +308,5 @@
  *   user, too. */
 G_MODULE_EXPORT struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle );
+G_MODULE_EXPORT void imcb_chat_name_hint( struct groupchat *c, const char *name );
 G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *b, const char *handle );
 /* To remove a handle from a group chat. Reason can be NULL. */
@@ -329,4 +331,5 @@
 
 /* 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 );
Index: protocols/oscar/oscar.c
===================================================================
--- protocols/oscar/oscar.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/oscar/oscar.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -205,5 +205,4 @@
 static int gaim_icbm_param_info  (aim_session_t *, aim_frame_t *, ...);
 static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...);
-static int gaim_memrequest       (aim_session_t *, aim_frame_t *, ...);
 static int gaim_selfinfo         (aim_session_t *, aim_frame_t *, ...);
 static int gaim_offlinemsg       (aim_session_t *, aim_frame_t *, ...);
@@ -291,5 +290,5 @@
 	odata = (struct oscar_data *)ic->proto_data;
 
-	if (condition & GAIM_INPUT_READ) {
+	if (condition & B_EV_IO_READ) {
 		if (aim_get_command(odata->sess, conn) >= 0) {
 			aim_rxdispatch(odata->sess);
@@ -363,5 +362,5 @@
 
 	aim_conn_completeconnect(sess, conn);
-	ic->inpa = b_input_add(conn->fd, GAIM_INPUT_READ,
+	ic->inpa = b_input_add(conn->fd, B_EV_IO_READ,
 			oscar_callback, conn);
 	
@@ -493,5 +492,5 @@
 
 	aim_conn_completeconnect(sess, bosconn);
-	ic->inpa = b_input_add(bosconn->fd, GAIM_INPUT_READ,
+	ic->inpa = b_input_add(bosconn->fd, B_EV_IO_READ,
 			oscar_callback, bosconn);
 	imcb_log(ic, _("Connection established, cookie sent"));
@@ -570,5 +569,4 @@
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ERROR, gaim_parse_genericerr, 0);
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_ERROR, gaim_parse_genericerr, 0);
-	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, 0x1f, gaim_memrequest, 0);
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_SELFINFO, gaim_selfinfo, 0);
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0);
@@ -604,140 +602,6 @@
 }
 
-struct pieceofcrap {
-	struct im_connection *ic;
-	unsigned long offset;
-	unsigned long len;
-	char *modname;
-	int fd;
-	aim_conn_t *conn;
-	unsigned int inpa;
-};
-
-static gboolean damn_you(gpointer data, gint source, b_input_condition c)
-{
-	struct pieceofcrap *pos = data;
-	struct oscar_data *od = pos->ic->proto_data;
-	char in = '\0';
-	int x = 0;
-	unsigned char m[17];
-
-	while (read(pos->fd, &in, 1) == 1) {
-		if (in == '\n')
-			x++;
-		else if (in != '\r')
-			x = 0;
-		if (x == 2)
-			break;
-		in = '\0';
-	}
-	if (in != '\n') {
-		imcb_error(pos->ic, "Gaim was unable to get a valid hash for logging into AIM."
-				" You may be disconnected shortly.");
-		b_event_remove(pos->inpa);
-		closesocket(pos->fd);
-		g_free(pos);
-		return FALSE;
-	}
-	/* [WvG] Wheeeee! Who needs error checking anyway? ;-) */
-	read(pos->fd, m, 16);
-	m[16] = '\0';
-	b_event_remove(pos->inpa);
-	closesocket(pos->fd);
-	aim_sendmemblock(od->sess, pos->conn, 0, 16, m, AIM_SENDMEMBLOCK_FLAG_ISHASH);
-	g_free(pos);
-	
-	return FALSE;
-}
-
-static gboolean straight_to_hell(gpointer data, gint source, b_input_condition cond) {
-	struct pieceofcrap *pos = data;
-	char buf[BUF_LONG];
-
-	if (source < 0) {
-		imcb_error(pos->ic, "Gaim was unable to get a valid hash for logging into AIM."
-				" You may be disconnected shortly.");
-		if (pos->modname)
-			g_free(pos->modname);
-		g_free(pos);
-		return FALSE;
-	}
-
-	g_snprintf(buf, sizeof(buf), "GET " AIMHASHDATA
-			"?offset=%ld&len=%ld&modname=%s HTTP/1.0\n\n",
-			pos->offset, pos->len, pos->modname ? pos->modname : "");
-	write(pos->fd, buf, strlen(buf));
-	if (pos->modname)
-		g_free(pos->modname);
-	pos->inpa = b_input_add(pos->fd, GAIM_INPUT_READ, damn_you, pos);
-	return FALSE;
-}
-
 /* size of icbmui.ocm, the largest module in AIM 3.5 */
 #define AIM_MAX_FILE_SIZE 98304
-
-int gaim_memrequest(aim_session_t *sess, aim_frame_t *fr, ...) {
-	va_list ap;
-	struct pieceofcrap *pos;
-	guint32 offset, len;
-	char *modname;
-	int fd;
-
-	va_start(ap, fr);
-	offset = (guint32)va_arg(ap, unsigned long);
-	len = (guint32)va_arg(ap, unsigned long);
-	modname = va_arg(ap, char *);
-	va_end(ap);
-
-	if (len == 0) {
-		aim_sendmemblock(sess, fr->conn, offset, len, NULL,
-				AIM_SENDMEMBLOCK_FLAG_ISREQUEST);
-		return 1;
-	}
-	/* uncomment this when you're convinced it's right. remember, it's been wrong before.
-	if (offset > AIM_MAX_FILE_SIZE || len > AIM_MAX_FILE_SIZE) {
-		char *buf;
-		int i = 8;
-		if (modname)
-			i += strlen(modname);
-		buf = g_malloc(i);
-		i = 0;
-		if (modname) {
-			memcpy(buf, modname, strlen(modname));
-			i += strlen(modname);
-		}
-		buf[i++] = offset & 0xff;
-		buf[i++] = (offset >> 8) & 0xff;
-		buf[i++] = (offset >> 16) & 0xff;
-		buf[i++] = (offset >> 24) & 0xff;
-		buf[i++] = len & 0xff;
-		buf[i++] = (len >> 8) & 0xff;
-		buf[i++] = (len >> 16) & 0xff;
-		buf[i++] = (len >> 24) & 0xff;
-		aim_sendmemblock(sess, command->conn, offset, i, buf, AIM_SENDMEMBLOCK_FLAG_ISREQUEST);
-		g_free(buf);
-		return 1;
-	}
-	*/
-
-	pos = g_new0(struct pieceofcrap, 1);
-	pos->ic = sess->aux_data;
-	pos->conn = fr->conn;
-
-	pos->offset = offset;
-	pos->len = len;
-	pos->modname = modname ? g_strdup(modname) : NULL;
-
-	fd = proxy_connect("gaim.sourceforge.net", 80, straight_to_hell, pos);
-	if (fd < 0) {
-		if (pos->modname)
-			g_free(pos->modname);
-		g_free(pos);
-		imcb_error(sess->aux_data, "Gaim was unable to get a valid hash for logging into AIM."
-				" You may be disconnected shortly.");
-	}
-	pos->fd = fd;
-
-	return 1;
-}
 
 static int gaim_parse_login(aim_session_t *sess, aim_frame_t *fr, ...) {
@@ -838,5 +702,5 @@
 
 	aim_conn_completeconnect(sess, tstconn);
-	odata->cnpa = b_input_add(tstconn->fd, GAIM_INPUT_READ,
+	odata->cnpa = b_input_add(tstconn->fd, B_EV_IO_READ,
 					oscar_callback, tstconn);
 	
@@ -866,5 +730,5 @@
 
 	aim_conn_completeconnect(sess, tstconn);
-	odata->paspa = b_input_add(tstconn->fd, GAIM_INPUT_READ,
+	odata->paspa = b_input_add(tstconn->fd, B_EV_IO_READ,
 				oscar_callback, tstconn);
 	
@@ -902,5 +766,5 @@
 	aim_conn_completeconnect(sess, ccon->conn);
 	ccon->inpa = b_input_add(tstconn->fd,
-			GAIM_INPUT_READ,
+			B_EV_IO_READ,
 			oscar_callback, tstconn);
 	odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon);
@@ -2651,5 +2515,6 @@
 	char * chatname;
 	
-	chatname = g_strdup_printf("%s%d", ic->acc->user, chat_id++);
+	chatname = g_strdup_printf("%s%s_%d", isdigit(*ic->acc->user) ? "icq_" : "",
+	                           ic->acc->user, chat_id++);
   
 	ret = oscar_chat_join(ic, chatname, NULL, NULL);
Index: protocols/purple/Makefile
===================================================================
--- protocols/purple/Makefile	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/purple/Makefile	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,41 @@
+###########################
+## Makefile for BitlBee  ##
+##                       ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+
+# [SH] Program variables
+objects = ft.o purple.o
+
+CFLAGS += -Wall $(PURPLE_CFLAGS)
+LFLAGS += -r
+
+# [SH] Phony targets
+all: purple_mod.o
+check: all
+lcov: check
+gcov: 
+	gcov *.c
+
+.PHONY: all clean distclean
+
+clean:
+	rm -f *.o core
+
+distclean: clean
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: %.c
+	@echo '*' Compiling $<
+	@$(CC) -c $(CFLAGS) $< -o $@
+
+purple_mod.o: $(objects)
+	@echo '*' Linking purple_mod.o
+	$(LD) $(LFLAGS) $(objects) -o purple_mod.o
Index: protocols/purple/ft-direct.c
===================================================================
--- protocols/purple/ft-direct.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/purple/ft-direct.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,239 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  libpurple module - File transfer stuff                                   *
+*                                                                           *
+*  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+/* This code tries to do direct file transfers, i.e. without caching the file
+   locally on disk first. Since libpurple can only do this since version 2.6.0
+   and even then very unreliably (and not with all IM modules), I'm canning
+   this code for now. */
+
+#include "bitlbee.h"
+
+#include <stdarg.h>
+
+#include <glib.h>
+#include <purple.h>
+
+struct prpl_xfer_data
+{
+	PurpleXfer *xfer;
+	file_transfer_t *ft;
+	gint ready_timer;
+	char *buf;
+	int buf_len;
+};
+
+static file_transfer_t *next_ft;
+
+struct im_connection *purple_ic_by_pa( PurpleAccount *pa );
+
+/* Glorious hack: We seem to have to remind at least some libpurple plugins
+   that we're ready because this info may get lost if we give it too early.
+   So just do it ten times a second. :-/ */
+static gboolean prplcb_xfer_write_request_cb( gpointer data, gint fd, b_input_condition cond )
+{
+	struct prpl_xfer_data *px = data;
+	
+	purple_xfer_ui_ready( px->xfer );
+	
+	return purple_xfer_get_type( px->xfer ) == PURPLE_XFER_RECEIVE;
+}
+
+static gboolean prpl_xfer_write_request( struct file_transfer *ft )
+{
+	struct prpl_xfer_data *px = ft->data;
+	px->ready_timer = b_timeout_add( 100, prplcb_xfer_write_request_cb, px );
+	return TRUE;
+}
+
+static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len )
+{
+	struct prpl_xfer_data *px = ft->data;
+	
+	px->buf = g_memdup( buffer, len );
+	px->buf_len = len;
+
+	//purple_xfer_ui_ready( px->xfer );
+	px->ready_timer = b_timeout_add( 0, prplcb_xfer_write_request_cb, px );
+	
+	return TRUE;
+}
+
+static void prpl_xfer_accept( struct file_transfer *ft )
+{
+	struct prpl_xfer_data *px = ft->data;
+	purple_xfer_request_accepted( px->xfer, NULL );
+	prpl_xfer_write_request( ft );
+}
+
+static void prpl_xfer_canceled( struct file_transfer *ft, char *reason )
+{
+	struct prpl_xfer_data *px = ft->data;
+	purple_xfer_request_denied( px->xfer );
+}
+
+static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond )
+{
+	PurpleXfer *xfer = data;
+	struct im_connection *ic = purple_ic_by_pa( xfer->account );
+	struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
+	PurpleBuddy *buddy;
+	const char *who;
+	
+	buddy = purple_find_buddy( xfer->account, xfer->who );
+	who = buddy ? purple_buddy_get_name( buddy ) : xfer->who;
+	
+	/* TODO(wilmer): After spreading some more const goodness in BitlBee,
+	   remove the evil cast below. */
+	px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size );
+	px->ft->data = px;
+	px->xfer = data;
+	px->xfer->ui_data = px;
+	
+	px->ft->accept = prpl_xfer_accept;
+	px->ft->canceled = prpl_xfer_canceled;
+	px->ft->write_request = prpl_xfer_write_request;
+	
+	return FALSE;
+}
+
+static void prplcb_xfer_new( PurpleXfer *xfer )
+{
+	if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE )
+	{
+		/* This should suppress the stupid file dialog. */
+		purple_xfer_set_local_filename( xfer, "/tmp/wtf123" );
+		
+		/* Sadly the xfer struct is still empty ATM so come back after
+		   the caller is done. */
+		b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer );
+	}
+	else
+	{
+		struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
+		
+		px->ft = next_ft;
+		px->ft->data = px;
+		px->xfer = xfer;
+		px->xfer->ui_data = px;
+		
+		purple_xfer_set_filename( xfer, px->ft->file_name );
+		purple_xfer_set_size( xfer, px->ft->file_size );
+		
+		next_ft = NULL;
+	}
+}
+
+static void prplcb_xfer_progress( PurpleXfer *xfer, double percent )
+{
+	fprintf( stderr, "prplcb_xfer_dbg 0x%p %f\n", xfer, percent );
+}
+
+static void prplcb_xfer_dbg( PurpleXfer *xfer )
+{
+	fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer );
+}
+
+static gssize prplcb_xfer_write( PurpleXfer *xfer, const guchar *buffer, gssize size )
+{
+	struct prpl_xfer_data *px = xfer->ui_data;
+	gboolean st;
+	
+	fprintf( stderr, "xfer_write %d %d\n", size, px->buf_len );
+	
+	b_event_remove( px->ready_timer );
+	px->ready_timer = 0;
+	
+	st = px->ft->write( px->ft, (char*) buffer, size );
+	
+	if( st && xfer->bytes_remaining == size )
+		imcb_file_finished( px->ft );
+	
+	return st ? size : 0;
+}
+
+gssize prplcb_xfer_read( PurpleXfer *xfer, guchar **buffer, gssize size )
+{
+	struct prpl_xfer_data *px = xfer->ui_data;
+	
+	fprintf( stderr, "xfer_read %d %d\n", size, px->buf_len );
+
+	if( px->buf )
+	{
+		*buffer = px->buf;
+		px->buf = NULL;
+		
+		px->ft->write_request( px->ft );
+		
+		return px->buf_len;
+	}
+	
+	return 0;
+}
+
+PurpleXferUiOps bee_xfer_uiops =
+{
+	prplcb_xfer_new,
+	prplcb_xfer_dbg,
+	prplcb_xfer_dbg,
+	prplcb_xfer_progress,
+	prplcb_xfer_dbg,
+	prplcb_xfer_dbg,
+	prplcb_xfer_write,
+	prplcb_xfer_read,
+	prplcb_xfer_dbg,
+};
+
+static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond );
+
+void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle )
+{
+	PurpleAccount *pa = ic->proto_data;
+	struct prpl_xfer_data *px;
+	
+	/* xfer_new() will pick up this variable. It's a hack but we're not
+	   multi-threaded anyway. */
+	next_ft = ft;
+	serv_send_file( purple_account_get_connection( pa ), handle, ft->file_name );
+	
+	ft->write = prpl_xfer_write;
+	
+	px = ft->data;
+	imcb_file_recv_start( ft );
+	
+	px->ready_timer = b_timeout_add( 100, prplcb_xfer_send_cb, px );
+}
+
+static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond )
+{
+	struct prpl_xfer_data *px = data;
+	
+	if( px->ft->status & FT_STATUS_TRANSFERRING )
+	{
+		fprintf( stderr, "The ft, it is ready...\n" );
+		px->ft->write_request( px->ft );
+		
+		return FALSE;
+	}
+	
+	return TRUE;
+}
Index: protocols/purple/ft.c
===================================================================
--- protocols/purple/ft.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/purple/ft.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,353 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  libpurple module - File transfer stuff                                   *
+*                                                                           *
+*  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+/* Do file transfers via disk for now, since libpurple was really designed
+   for straight-to/from disk fts and is only just learning how to pass the
+   file contents the the UI instead (2.6.0 and higher it seems, and with
+   varying levels of success). */
+
+#include "bitlbee.h"
+
+#include <stdarg.h>
+
+#include <glib.h>
+#include <purple.h>
+
+struct prpl_xfer_data
+{
+	PurpleXfer *xfer;
+	file_transfer_t *ft;
+	struct im_connection *ic;
+	int fd;
+	char *fn, *handle;
+	gboolean ui_wants_data;
+};
+
+static file_transfer_t *next_ft;
+
+struct im_connection *purple_ic_by_pa( PurpleAccount *pa );
+static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond );
+static gboolean prpl_xfer_write_request( struct file_transfer *ft );
+
+
+/* Receiving files (IM->UI): */
+static void prpl_xfer_accept( struct file_transfer *ft )
+{
+	struct prpl_xfer_data *px = ft->data;
+	purple_xfer_request_accepted( px->xfer, NULL );
+	prpl_xfer_write_request( ft );
+}
+
+static void prpl_xfer_canceled( struct file_transfer *ft, char *reason )
+{
+	struct prpl_xfer_data *px = ft->data;
+	purple_xfer_request_denied( px->xfer );
+}
+
+static void prplcb_xfer_new( PurpleXfer *xfer )
+{
+	if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE )
+	{
+		struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
+		
+		xfer->ui_data = px;
+		px->xfer = xfer;
+		px->fn = mktemp( g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ) );
+		px->fd = -1;
+		
+		purple_xfer_set_local_filename( xfer, px->fn );
+		
+		/* Sadly the xfer struct is still empty ATM so come back after
+		   the caller is done. */
+		b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer );
+	}
+	else
+	{
+		struct file_transfer *ft = next_ft;
+		struct prpl_xfer_data *px = ft->data;
+		
+		xfer->ui_data = px;
+		px->xfer = xfer;
+		
+		next_ft = NULL;
+	}
+}
+
+static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond )
+{
+	PurpleXfer *xfer = data;
+	struct im_connection *ic = purple_ic_by_pa( xfer->account );
+	struct prpl_xfer_data *px = xfer->ui_data;
+	PurpleBuddy *buddy;
+	const char *who;
+	
+	buddy = purple_find_buddy( xfer->account, xfer->who );
+	who = buddy ? purple_buddy_get_name( buddy ) : xfer->who;
+	
+	/* TODO(wilmer): After spreading some more const goodness in BitlBee,
+	   remove the evil cast below. */
+	px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size );
+	px->ft->data = px;
+	
+	px->ft->accept = prpl_xfer_accept;
+	px->ft->canceled = prpl_xfer_canceled;
+	px->ft->write_request = prpl_xfer_write_request;
+	
+	return FALSE;
+}
+
+gboolean try_write_to_ui( gpointer data, gint fd, b_input_condition cond )
+{
+	struct file_transfer *ft = data;
+	struct prpl_xfer_data *px = ft->data;
+	struct stat fs;
+	off_t tx_bytes;
+	
+	/* If we don't have the file opened yet, there's no data so wait. */
+	if( px->fd < 0 || !px->ui_wants_data )
+		return FALSE;
+	
+	tx_bytes = lseek( px->fd, 0, SEEK_CUR );
+	fstat( px->fd, &fs );
+	
+	if( fs.st_size > tx_bytes )
+	{
+		char buf[1024];
+		size_t n = MIN( fs.st_size - tx_bytes, sizeof( buf ) );
+		
+		if( read( px->fd, buf, n ) == n && ft->write( ft, buf, n ) )
+		{
+			px->ui_wants_data = FALSE;
+		}
+		else
+		{
+			purple_xfer_cancel_local( px->xfer );
+			imcb_file_canceled( ft, "Read error" );
+		}
+	}
+	
+	if( lseek( px->fd, 0, SEEK_CUR ) == px->xfer->size )
+	{
+		/*purple_xfer_end( px->xfer );*/
+		imcb_file_finished( ft );
+	}
+	
+	return FALSE;
+}
+
+/* UI calls this when its buffer is empty and wants more data to send to the user. */
+static gboolean prpl_xfer_write_request( struct file_transfer *ft )
+{
+	struct prpl_xfer_data *px = ft->data;
+	
+	px->ui_wants_data = TRUE;
+	try_write_to_ui( ft, 0, 0 );
+	
+	return FALSE;
+}
+
+
+/* Generic (IM<>UI): */
+static void prplcb_xfer_destroy( PurpleXfer *xfer )
+{
+	struct prpl_xfer_data *px = xfer->ui_data;
+	
+	g_free( px->fn );
+	g_free( px->handle );
+	if( px->fd >= 0 )
+		close( px->fd );
+	g_free( px );
+}
+
+static void prplcb_xfer_progress( PurpleXfer *xfer, double percent )
+{
+	struct prpl_xfer_data *px = xfer->ui_data;
+	
+	if( px == NULL )
+		return;
+	
+	if( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND )
+	{
+		if( *px->fn )
+		{
+			char *slash;
+			
+			unlink( px->fn );
+			if( ( slash = strrchr( px->fn, '/' ) ) )
+			{
+				*slash = '\0';
+				rmdir( px->fn );
+			}
+			*px->fn = '\0';
+		}
+		
+		return;
+	}
+	
+	if( px->fd == -1 && percent > 0 )
+	{
+		/* Weeeeeeeee, we're getting data! That means the file exists
+		   by now so open it and start sending to the UI. */
+		px->fd = open( px->fn, O_RDONLY );
+		
+		/* Unlink it now, because we don't need it after this. */
+		unlink( px->fn );
+	}
+	
+	if( percent < 1 )
+		try_write_to_ui( px->ft, 0, 0 );
+	else
+		/* Another nice problem: If we have the whole file, it only
+		   gets closed when we return. Problem: There may still be
+		   stuff buffered and not written, we'll only see it after
+		   the caller close()s the file. So poll the file after that. */
+		b_timeout_add( 0, try_write_to_ui, px->ft );
+}
+
+static void prplcb_xfer_cancel_remote( PurpleXfer *xfer )
+{
+	struct prpl_xfer_data *px = xfer->ui_data;
+	
+	if( px->ft )
+		imcb_file_canceled( px->ft, "Canceled by remote end" );
+	else
+		/* px->ft == NULL for sends, because of the two stages. :-/ */
+		imcb_error( px->ic, "File transfer cancelled by remote end" );
+}
+
+static void prplcb_xfer_dbg( PurpleXfer *xfer )
+{
+	fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer );
+}
+
+
+/* Sending files (UI->IM): */
+static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len );
+static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond );
+
+void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle )
+{
+	struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
+	char *dir, *basename;
+	
+	ft->data = px;
+	px->ft = ft;
+	
+	dir = g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" );
+	if( !mkdtemp( dir ) )
+	{
+		imcb_error( ic, "Could not create temporary file for file transfer" );
+		g_free( px );
+		g_free( dir );
+		return;
+	}
+	
+	if( ( basename = strrchr( ft->file_name, '/' ) ) )
+		basename++;
+	else
+		basename = ft->file_name;
+	px->fn = g_strdup_printf( "%s/%s", dir, basename );
+	px->fd = open( px->fn, O_WRONLY | O_CREAT, 0600 );
+	g_free( dir );
+	
+	if( px->fd < 0 )
+	{
+		imcb_error( ic, "Could not create temporary file for file transfer" );
+		g_free( px );
+		g_free( px->fn );
+		return;
+	}
+	
+	px->ic = ic;
+	px->handle = g_strdup( handle );
+	
+	imcb_log( ic, "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait..." );
+	
+	b_timeout_add( 0, purple_transfer_request_cb, ft );
+}
+
+static void purple_transfer_forward( struct file_transfer *ft )
+{
+	struct prpl_xfer_data *px = ft->data;
+	PurpleAccount *pa = px->ic->proto_data;
+	
+	/* xfer_new() will pick up this variable. It's a hack but we're not
+	   multi-threaded anyway. */
+	next_ft = ft;
+	serv_send_file( purple_account_get_connection( pa ), px->handle, px->fn );
+}
+
+static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond )
+{
+	file_transfer_t *ft = data;
+	
+	if( ft->write == NULL )
+	{
+		ft->write = prpl_xfer_write;
+		imcb_file_recv_start( ft );
+	}
+	
+	ft->write_request( ft );
+	
+	return FALSE;
+}
+
+static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len )
+{
+	struct prpl_xfer_data *px = ft->data;
+	
+	if( write( px->fd, buffer, len ) != len )
+	{
+		imcb_file_canceled( ft, "Error while writing temporary file" );
+		return FALSE;
+	}
+	
+	if( lseek( px->fd, 0, SEEK_CUR ) >= ft->file_size )
+	{
+		close( px->fd );
+		px->fd = -1;
+		
+		purple_transfer_forward( ft );
+		imcb_file_finished( ft );
+		px->ft = NULL;
+	}
+	else
+		b_timeout_add( 0, purple_transfer_request_cb, ft );
+	
+	return TRUE;
+}
+
+
+
+PurpleXferUiOps bee_xfer_uiops =
+{
+	prplcb_xfer_new,
+	prplcb_xfer_destroy,
+	NULL, /* prplcb_xfer_add, */
+	prplcb_xfer_progress,
+	prplcb_xfer_dbg,
+	prplcb_xfer_cancel_remote,
+	NULL,
+	NULL,
+	prplcb_xfer_dbg,
+};
Index: protocols/purple/purple.c
===================================================================
--- protocols/purple/purple.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/purple/purple.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,1143 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  libpurple module - Main file                                             *
+*                                                                           *
+*  Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net>              *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#include "bitlbee.h"
+#include "help.h"
+
+#include <stdarg.h>
+
+#include <glib.h>
+#include <purple.h>
+
+GSList *purple_connections;
+
+/* This makes me VERY sad... :-( But some libpurple callbacks come in without
+   any context so this is the only way to get that. Don't want to support
+   libpurple in daemon mode anyway. */
+static irc_t *local_irc;
+
+static char *set_eval_display_name( set_t *set, char *value );
+
+struct im_connection *purple_ic_by_pa( PurpleAccount *pa )
+{
+	GSList *i;
+	
+	for( i = purple_connections; i; i = i->next )
+		if( ((struct im_connection *)i->data)->proto_data == pa )
+			return i->data;
+	
+	return NULL;
+}
+
+static struct im_connection *purple_ic_by_gc( PurpleConnection *gc )
+{
+	return purple_ic_by_pa( purple_connection_get_account( gc ) );
+}
+
+static gboolean purple_menu_cmp( const char *a, const char *b )
+{
+	while( *a && *b )
+	{
+		while( *a == '_' ) a ++;
+		while( *b == '_' ) b ++;
+		if( tolower( *a ) != tolower( *b ) )
+			return FALSE;
+		
+		a ++;
+		b ++;
+	}
+	
+	return ( *a == '\0' && *b == '\0' );
+}
+
+static void purple_init( account_t *acc )
+{
+	PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data );
+	PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+	PurpleAccount *pa;
+	GList *i, *st;
+	set_t *s;
+	char help_title[64];
+	GString *help;
+	
+	help = g_string_new( "" );
+	g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:",
+	                        (char*) acc->prpl->name, prpl->info->name );
+	
+	/* Convert all protocol_options into per-account setting variables. */
+	for( i = pi->protocol_options; i; i = i->next )
+	{
+		PurpleAccountOption *o = i->data;
+		const char *name;
+		char *def = NULL;
+		set_eval eval = NULL;
+		void *eval_data = NULL;
+		GList *io = NULL;
+		GSList *opts = NULL;
+		
+		name = purple_account_option_get_setting( o );
+		
+		switch( purple_account_option_get_type( o ) )
+		{
+		case PURPLE_PREF_STRING:
+			def = g_strdup( purple_account_option_get_default_string( o ) );
+			
+			g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
+			                        name, purple_account_option_get_text( o ),
+			                        "string", def );
+			
+			break;
+		
+		case PURPLE_PREF_INT:
+			def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) );
+			eval = set_eval_int;
+			
+			g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
+			                        name, purple_account_option_get_text( o ),
+			                        "integer", def );
+			
+			break;
+		
+		case PURPLE_PREF_BOOLEAN:
+			if( purple_account_option_get_default_bool( o ) )
+				def = g_strdup( "true" );
+			else
+				def = g_strdup( "false" );
+			eval = set_eval_bool;
+			
+			g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
+			                        name, purple_account_option_get_text( o ),
+			                        "boolean", def );
+			
+			break;
+		
+		case PURPLE_PREF_STRING_LIST:
+			def = g_strdup( purple_account_option_get_default_list_value( o ) );
+			
+			g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
+			                        name, purple_account_option_get_text( o ),
+			                        "list", def );
+			g_string_append( help, "\n  Possible values: " );
+			
+			for( io = purple_account_option_get_list( o ); io; io = io->next )
+			{
+				PurpleKeyValuePair *kv = io->data;
+				opts = g_slist_append( opts, kv->value );
+				/* TODO: kv->value is not a char*, WTF? */
+				if( strcmp( kv->value, kv->key ) != 0 )
+					g_string_append_printf( help, "%s (%s), ", (char*) kv->value, kv->key );
+				else
+					g_string_append_printf( help, "%s, ", (char*) kv->value );
+			}
+			g_string_truncate( help, help->len - 2 );
+			eval = set_eval_list;
+			eval_data = opts;
+			
+			break;
+			
+		default:
+			irc_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
+			             name, purple_account_option_get_type( o ) );
+			name = NULL;
+		}
+		
+		if( name != NULL )
+		{
+			s = set_add( &acc->set, name, def, eval, acc );
+			s->flags |= ACC_SET_OFFLINE_ONLY;
+			s->eval_data = eval_data;
+			g_free( def );
+		}
+	}
+	
+	g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name );
+	help_add_mem( &global.help, help_title, help->str );
+	g_string_free( help, TRUE );
+	
+	s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc );
+	s->flags |= ACC_SET_ONLINE_ONLY;
+	
+	if( pi->options & OPT_PROTO_MAIL_CHECK )
+	{
+		s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
+		s->flags |= ACC_SET_OFFLINE_ONLY;
+	}
+	
+	/* Go through all away states to figure out if away/status messages
+	   are possible. */
+	pa = purple_account_new( acc->user, (char*) acc->prpl->data );
+	for( st = purple_account_get_status_types( pa ); st; st = st->next )
+	{
+		PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
+		
+		if( prim == PURPLE_STATUS_AVAILABLE )
+		{
+			if( purple_status_type_get_attr( st->data, "message" ) )
+				acc->flags |= ACC_FLAG_STATUS_MESSAGE;
+		}
+		else if( prim != PURPLE_STATUS_OFFLINE )
+		{
+			if( purple_status_type_get_attr( st->data, "message" ) )
+				acc->flags |= ACC_FLAG_AWAY_MESSAGE;
+		}
+	}
+	purple_accounts_remove( pa );
+}
+
+static void purple_sync_settings( account_t *acc, PurpleAccount *pa )
+{
+	PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+	PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+	GList *i;
+	
+	for( i = pi->protocol_options; i; i = i->next )
+	{
+		PurpleAccountOption *o = i->data;
+		const char *name;
+		set_t *s;
+		
+		name = purple_account_option_get_setting( o );
+		s = set_find( &acc->set, name );
+		if( s->value == NULL )
+			continue;
+		
+		switch( purple_account_option_get_type( o ) )
+		{
+		case PURPLE_PREF_STRING:
+		case PURPLE_PREF_STRING_LIST:
+			purple_account_set_string( pa, name, set_getstr( &acc->set, name ) );
+			break;
+		
+		case PURPLE_PREF_INT:
+			purple_account_set_int( pa, name, set_getint( &acc->set, name ) );
+			break;
+		
+		case PURPLE_PREF_BOOLEAN:
+			purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) );
+			break;
+		
+		default:
+			break;
+		}
+	}
+	
+	if( pi->options & OPT_PROTO_MAIL_CHECK )
+		purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) );
+}
+
+static void purple_login( account_t *acc )
+{
+	struct im_connection *ic = imcb_new( acc );
+	PurpleAccount *pa;
+	
+	if( local_irc != NULL && local_irc != acc->irc )
+	{
+		irc_usermsg( acc->irc, "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
+		                       "Please use inetd or ForkDaemon mode instead." );
+		return;
+	}
+	local_irc = acc->irc;
+	
+	/* For now this is needed in the _connected() handlers if using
+	   GLib event handling, to make sure we're not handling events
+	   on dead connections. */
+	purple_connections = g_slist_prepend( purple_connections, ic );
+	
+	ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data );
+	purple_account_set_password( pa, acc->pass );
+	purple_sync_settings( acc, pa );
+	
+	purple_account_set_enabled( pa, "BitlBee", TRUE );
+}
+
+static void purple_logout( struct im_connection *ic )
+{
+	PurpleAccount *pa = ic->proto_data;
+	
+	purple_account_set_enabled( pa, "BitlBee", FALSE );
+	purple_connections = g_slist_remove( purple_connections, ic );
+	purple_accounts_remove( pa );
+}
+
+static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
+{
+	PurpleConversation *conv;
+	
+	if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
+	                                                    who, ic->proto_data ) ) == NULL )
+	{
+		conv = purple_conversation_new( PURPLE_CONV_TYPE_IM,
+		                                ic->proto_data, who );
+	}
+	
+	purple_conv_im_send( purple_conversation_get_im_data( conv ), message );
+	
+	return 1;
+}
+
+static GList *purple_away_states( struct im_connection *ic )
+{
+	PurpleAccount *pa = ic->proto_data;
+	GList *st, *ret = NULL;
+	
+	for( st = purple_account_get_status_types( pa ); st; st = st->next )
+	{
+		PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
+		if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE )
+			ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) );
+	}
+	
+	return ret;
+}
+
+static void purple_set_away( struct im_connection *ic, char *state_txt, char *message )
+{
+	PurpleAccount *pa = ic->proto_data;
+	GList *status_types = purple_account_get_status_types( pa ), *st;
+	PurpleStatusType *pst = NULL;
+	GList *args = NULL;
+	
+	for( st = status_types; st; st = st->next )
+	{
+		pst = st->data;
+		
+		if( state_txt == NULL &&
+		    purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE )
+			break;
+
+		if( state_txt != NULL &&
+		    g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 )
+			break;
+	}
+	
+	if( message && purple_status_type_get_attr( pst, "message" ) )
+	{
+		args = g_list_append( args, "message" );
+		args = g_list_append( args, message );
+	}
+	
+	purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away",
+		                        TRUE, args );
+
+	g_list_free( args );
+}
+
+static char *set_eval_display_name( set_t *set, char *value )
+{
+	account_t *acc = set->data;
+	struct im_connection *ic = acc->ic;
+	
+	return NULL;
+}
+
+static void purple_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+	PurpleBuddy *pb;
+	
+	pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
+	purple_blist_add_buddy( pb, NULL, NULL, NULL );
+	purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb );
+}
+
+static void purple_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+	PurpleBuddy *pb;
+	
+	pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
+	if( pb != NULL )
+	{
+		purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, NULL );
+		purple_blist_remove_buddy( pb );
+	}
+}
+
+static void purple_add_permit( struct im_connection *ic, char *who )
+{
+	PurpleAccount *pa = ic->proto_data;
+	
+	purple_privacy_permit_add( pa, who, FALSE );
+}
+
+static void purple_add_deny( struct im_connection *ic, char *who )
+{
+	PurpleAccount *pa = ic->proto_data;
+	
+	purple_privacy_deny_add( pa, who, FALSE );
+}
+
+static void purple_rem_permit( struct im_connection *ic, char *who )
+{
+	PurpleAccount *pa = ic->proto_data;
+	
+	purple_privacy_permit_remove( pa, who, FALSE );
+}
+
+static void purple_rem_deny( struct im_connection *ic, char *who )
+{
+	PurpleAccount *pa = ic->proto_data;
+	
+	purple_privacy_deny_remove( pa, who, FALSE );
+}
+
+static void purple_get_info( struct im_connection *ic, char *who )
+{
+	serv_get_info( purple_account_get_connection( ic->proto_data ), who );
+}
+
+static void purple_keepalive( struct im_connection *ic )
+{
+}
+
+static int purple_send_typing( struct im_connection *ic, char *who, int flags )
+{
+	PurpleTypingState state = PURPLE_NOT_TYPING;
+	PurpleConversation *conv;
+	
+	if( flags & OPT_TYPING )
+		state = PURPLE_TYPING;
+	else if( flags & OPT_THINKING )
+		state = PURPLE_TYPED;
+	
+	if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
+	                                                    who, ic->proto_data ) ) == NULL )
+	{
+		purple_conv_im_set_typing_state( purple_conversation_get_im_data( conv ), state );
+		return 1;
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+static void purple_chat_msg( struct groupchat *gc, char *message, int flags )
+{
+	PurpleConversation *pc = gc->data;
+	
+	purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message );
+}
+
+struct groupchat *purple_chat_with( struct im_connection *ic, char *who )
+{
+	/* No, "of course" this won't work this way. Or in fact, it almost
+	   does, but it only lets you send msgs to it, you won't receive
+	   any. Instead, we have to click the virtual menu item.
+	PurpleAccount *pa = ic->proto_data;
+	PurpleConversation *pc;
+	PurpleConvChat *pcc;
+	struct groupchat *gc;
+	
+	gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" );
+	gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" );
+	pc->ui_data = gc;
+	
+	pcc = PURPLE_CONV_CHAT( pc );
+	purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE );
+	purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE );
+	//purple_conv_chat_add_user( pcc, who, "", 0, TRUE );
+	*/
+	
+	/* There went my nice afternoon. :-( */
+	
+	PurpleAccount *pa = ic->proto_data;
+	PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+	PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+	PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
+	PurpleMenuAction *mi;
+	GList *menu;
+	void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */
+	
+	if( !pb || !pi || !pi->blist_node_menu )
+		return NULL;
+	
+	menu = pi->blist_node_menu( &pb->node );
+	while( menu )
+	{
+		mi = menu->data;
+		if( purple_menu_cmp( mi->label, "initiate chat" ) ||
+		    purple_menu_cmp( mi->label, "initiate conference" ) )
+			break;
+		menu = menu->next;
+	}
+	
+	if( menu == NULL )
+		return NULL;
+	
+	/* Call the fucker. */
+	callback = (void*) mi->callback;
+	callback( &pb->node, menu->data );
+	
+	return NULL;
+}
+
+void purple_chat_invite( struct groupchat *gc, char *who, char *message )
+{
+	PurpleConversation *pc = gc->data;
+	PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc );
+	
+	serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ),
+	                  purple_conv_chat_get_id( pcc ), 
+	                  message && *message ? message : "Please join my chat",
+	                  who );
+}
+
+void purple_chat_leave( struct groupchat *gc )
+{
+	PurpleConversation *pc = gc->data;
+	
+	purple_conversation_destroy( pc );
+}
+
+struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password )
+{
+	PurpleAccount *pa = ic->proto_data;
+	PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+	PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+	GHashTable *chat_hash;
+	PurpleConversation *conv;
+	GList *info, *l;
+	
+	if( !pi->chat_info || !pi->chat_info_defaults ||
+	    !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) )
+	{
+		imcb_error( ic, "Joining chatrooms not supported by this protocol" );
+		return NULL;
+	}
+	
+	if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) )
+		purple_conversation_destroy( conv );
+	
+	chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room );
+	
+	for( l = info; l; l = l->next )
+	{
+		struct proto_chat_entry *pce = l->data;
+		
+		if( strcmp( pce->identifier, "handle" ) == 0 )
+			g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) );
+		else if( strcmp( pce->identifier, "password" ) == 0 )
+			g_hash_table_replace( chat_hash, "password", g_strdup( password ) );
+		else if( strcmp( pce->identifier, "passwd" ) == 0 )
+			g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) );
+	}
+	
+	serv_join_chat( purple_account_get_connection( pa ), chat_hash );
+	
+	return NULL;
+}
+
+void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle );
+
+static void purple_ui_init();
+
+GHashTable *prplcb_ui_info()
+{
+	static GHashTable *ret;
+	
+	if( ret == NULL )
+	{
+		ret = g_hash_table_new(g_str_hash, g_str_equal);
+		g_hash_table_insert( ret, "name", "BitlBee" );
+		g_hash_table_insert( ret, "version", BITLBEE_VERSION );
+	}
+	
+	return ret;
+}
+
+static PurpleCoreUiOps bee_core_uiops = 
+{
+	NULL,
+	NULL,
+	purple_ui_init,
+	NULL,
+	prplcb_ui_info,
+};
+
+static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count )
+{
+	struct im_connection *ic = purple_ic_by_gc( gc );
+	
+	imcb_log( ic, "%s", text );
+}
+
+static void prplcb_conn_connected( PurpleConnection *gc )
+{
+	struct im_connection *ic = purple_ic_by_gc( gc );
+	const char *dn;
+	set_t *s;
+	
+	imcb_connected( ic );
+	
+	if( ( dn = purple_connection_get_display_name( gc ) ) &&
+	    ( s = set_find( &ic->acc->set, "display_name" ) ) )
+	{
+		g_free( s->value );
+		s->value = g_strdup( dn );
+	}
+	
+	if( gc->flags & PURPLE_CONNECTION_HTML )
+		ic->flags |= OPT_DOES_HTML;
+}
+
+static void prplcb_conn_disconnected( PurpleConnection *gc )
+{
+	struct im_connection *ic = purple_ic_by_gc( gc );
+	
+	if( ic != NULL )
+	{
+		imc_logout( ic, !gc->wants_to_die );
+	}
+}
+
+static void prplcb_conn_notice( PurpleConnection *gc, const char *text )
+{
+	struct im_connection *ic = purple_ic_by_gc( gc );
+	
+	if( ic != NULL )
+		imcb_log( ic, "%s", text );
+}
+
+static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text )
+{
+	struct im_connection *ic = purple_ic_by_gc( gc );
+	
+	/* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
+	   should probably handle that. */
+	if( ic != NULL )
+		imcb_error( ic, "%s", text );
+}
+
+static PurpleConnectionUiOps bee_conn_uiops =
+{
+	prplcb_conn_progress,
+	prplcb_conn_connected,
+	prplcb_conn_disconnected,
+	prplcb_conn_notice,
+	NULL,
+	NULL,
+	NULL,
+	prplcb_conn_report_disconnect_reason,
+};
+
+static void prplcb_blist_new( PurpleBlistNode *node )
+{
+	PurpleBuddy *bud = (PurpleBuddy*) node;
+	
+	if( node->type == PURPLE_BLIST_BUDDY_NODE )
+	{
+		struct im_connection *ic = purple_ic_by_pa( bud->account );
+		
+		if( ic == NULL )
+			return;
+		
+		imcb_add_buddy( ic, bud->name, NULL );
+		if( bud->server_alias )
+		{
+			imcb_rename_buddy( ic, bud->name, bud->server_alias );
+			imcb_buddy_nick_hint( ic, bud->name, bud->server_alias );
+		}
+	}
+}
+
+static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
+{
+	PurpleBuddy *bud = (PurpleBuddy*) node;
+	
+	if( node->type == PURPLE_BLIST_BUDDY_NODE )
+	{
+		struct im_connection *ic = purple_ic_by_pa( bud->account );
+		PurpleStatus *as;
+		int flags = 0;
+		
+		if( ic == NULL )
+			return;
+		
+		if( bud->server_alias )
+			imcb_rename_buddy( ic, bud->name, bud->server_alias );
+		
+		flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
+		flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
+		
+		as = purple_presence_get_active_status( bud->presence );
+		
+		imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
+		                   purple_status_get_attr_string( as, "message" ) );
+	}
+}
+
+static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
+{
+	/*
+	PurpleBuddy *bud = (PurpleBuddy*) node;
+	
+	if( node->type == PURPLE_BLIST_BUDDY_NODE )
+	{
+		struct im_connection *ic = purple_ic_by_pa( bud->account );
+		
+		if( ic == NULL )
+			return;
+		
+		imcb_remove_buddy( ic, bud->name, NULL );
+	}
+	*/
+}
+
+static PurpleBlistUiOps bee_blist_uiops =
+{
+	NULL,
+	prplcb_blist_new,
+	NULL,
+	prplcb_blist_update,
+	prplcb_blist_remove,
+};
+
+void prplcb_conv_new( PurpleConversation *conv )
+{
+	if( conv->type == PURPLE_CONV_TYPE_CHAT )
+	{
+		struct im_connection *ic = purple_ic_by_pa( conv->account );
+		struct groupchat *gc;
+		
+		gc = imcb_chat_new( ic, conv->name );
+		conv->ui_data = gc;
+		gc->data = conv;
+		
+		/* libpurple brokenness: Whatever. Show that we join right away,
+		   there's no clear "This is you!" signaling in _add_users so
+		   don't even try. */
+		imcb_chat_add_buddy( gc, gc->ic->acc->user );
+	}
+}
+
+void prplcb_conv_free( PurpleConversation *conv )
+{
+	struct groupchat *gc = conv->ui_data;
+	
+	imcb_chat_free( gc );
+}
+
+void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals )
+{
+	struct groupchat *gc = conv->ui_data;
+	GList *b;
+	
+	for( b = cbuddies; b; b = b->next )
+	{
+		PurpleConvChatBuddy *pcb = b->data;
+		
+		imcb_chat_add_buddy( gc, pcb->name );
+	}
+}
+
+void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies )
+{
+	struct groupchat *gc = conv->ui_data;
+	GList *b;
+	
+	for( b = cbuddies; b; b = b->next )
+		imcb_chat_remove_buddy( gc, b->data, "" );
+}
+
+void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
+{
+	struct groupchat *gc = conv->ui_data;
+	PurpleBuddy *buddy;
+	
+	/* ..._SEND means it's an outgoing message, no need to echo those. */
+	if( flags & PURPLE_MESSAGE_SEND )
+		return;
+	
+	buddy = purple_find_buddy( conv->account, who );
+	if( buddy != NULL )
+		who = purple_buddy_get_name( buddy );
+	
+	imcb_chat_msg( gc, who, (char*) message, 0, mtime );
+}
+
+static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
+{
+	struct im_connection *ic = purple_ic_by_pa( conv->account );
+	PurpleBuddy *buddy;
+	
+	/* ..._SEND means it's an outgoing message, no need to echo those. */
+	if( flags & PURPLE_MESSAGE_SEND )
+		return;
+	
+	buddy = purple_find_buddy( conv->account, who );
+	if( buddy != NULL )
+		who = purple_buddy_get_name( buddy );
+	
+	imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
+}
+
+static PurpleConversationUiOps bee_conv_uiops = 
+{
+	prplcb_conv_new,           /* create_conversation  */
+	prplcb_conv_free,          /* destroy_conversation */
+	prplcb_conv_chat_msg,      /* write_chat           */
+	prplcb_conv_im,            /* write_im             */
+	NULL,                      /* write_conv           */
+	prplcb_conv_add_users,     /* chat_add_users       */
+	NULL,                      /* chat_rename_user     */
+	prplcb_conv_del_users,     /* chat_remove_users    */
+	NULL,                      /* chat_update_user     */
+	NULL,                      /* present              */
+	NULL,                      /* has_focus            */
+	NULL,                      /* custom_smiley_add    */
+	NULL,                      /* custom_smiley_write  */
+	NULL,                      /* custom_smiley_close  */
+	NULL,                      /* send_confirm         */
+};
+
+struct prplcb_request_action_data
+{
+	void *user_data, *bee_data;
+	PurpleRequestActionCb yes, no;
+	int yes_i, no_i;
+};
+
+static void prplcb_request_action_yes( void *data )
+{
+	struct prplcb_request_action_data *pqad = data;
+	
+	pqad->yes( pqad->user_data, pqad->yes_i );
+	g_free( pqad );
+}
+
+static void prplcb_request_action_no( void *data )
+{
+	struct prplcb_request_action_data *pqad = data;
+	
+	pqad->no( pqad->user_data, pqad->no_i );
+	g_free( pqad );
+}
+
+static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
+                                    int default_action, PurpleAccount *account, const char *who,
+                                    PurpleConversation *conv, void *user_data, size_t action_count,
+                                    va_list actions )
+{
+	struct prplcb_request_action_data *pqad; 
+	int i;
+	char *q;
+	
+	pqad = g_new0( struct prplcb_request_action_data, 1 );
+	
+	for( i = 0; i < action_count; i ++ )
+	{
+		char *caption;
+		void *fn;
+		
+		caption = va_arg( actions, char* );
+		fn = va_arg( actions, void* );
+		
+		if( strstr( caption, "Accept" ) )
+		{
+			pqad->yes = fn;
+			pqad->yes_i = i;
+		}
+		else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) )
+		{
+			pqad->no = fn;
+			pqad->no_i = i;
+		}
+	}
+	
+	pqad->user_data = user_data;
+	
+	q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
+	pqad->bee_data = query_add( local_irc, purple_ic_by_pa( account ), q,
+		prplcb_request_action_yes, prplcb_request_action_no, pqad );
+	
+	g_free( q );
+	
+	return pqad;
+}
+
+static PurpleRequestUiOps bee_request_uiops =
+{
+	NULL,
+	NULL,
+	prplcb_request_action,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+};
+
+static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name )
+{
+	struct im_connection *ic = purple_ic_by_pa( account );
+	
+	if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
+		ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) );
+}
+
+static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name )
+{
+	struct im_connection *ic = purple_ic_by_pa( account );
+	void *n;
+	
+	n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
+	ic->permit = g_slist_remove( ic->permit, n );
+}
+
+static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name )
+{
+	struct im_connection *ic = purple_ic_by_pa( account );
+	
+	if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
+		ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) );
+}
+
+static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name )
+{
+	struct im_connection *ic = purple_ic_by_pa( account );
+	void *n;
+	
+	n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
+	ic->deny = g_slist_remove( ic->deny, n );
+}
+
+static PurplePrivacyUiOps bee_privacy_uiops =
+{
+	prplcb_privacy_permit_added,
+	prplcb_privacy_permit_removed,
+	prplcb_privacy_deny_added,
+	prplcb_privacy_deny_removed,
+};
+
+static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
+{
+	fprintf( stderr, "DEBUG %s: %s", category, arg_s );
+}
+
+static PurpleDebugUiOps bee_debug_uiops =
+{
+	prplcb_debug_print,
+};
+
+static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
+{
+	return b_timeout_add( interval, (b_event_handler) func, udata );
+}
+
+static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
+{
+	return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
+}
+
+static gboolean prplcb_ev_remove( guint id )
+{
+	b_event_remove( (gint) id );
+	return TRUE;
+}
+
+static PurpleEventLoopUiOps glib_eventloops = 
+{
+	prplcb_ev_timeout_add,
+	prplcb_ev_remove,
+	prplcb_ev_input_add,
+	prplcb_ev_remove,
+};
+
+static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from,
+                                  const char *to, const char *url )
+{
+	struct im_connection *ic = purple_ic_by_gc( gc );
+	
+	imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url );
+	
+	return NULL;
+}
+
+static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info )
+{
+	struct im_connection *ic = purple_ic_by_gc( gc );
+	GString *info = g_string_new( "" );
+	GList *l = purple_notify_user_info_get_entries( user_info );
+	char *key;
+	const char *value;
+	int n;
+	
+	while( l )
+	{
+		PurpleNotifyUserInfoEntry *e = l->data;
+		
+		switch( purple_notify_user_info_entry_get_type( e ) )
+		{
+		case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
+		case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
+			key = g_strdup( purple_notify_user_info_entry_get_label( e ) );
+			value = purple_notify_user_info_entry_get_value( e );
+			
+			if( key )
+			{
+				strip_html( key );
+				g_string_append_printf( info, "%s: ", key );
+				
+				if( value )
+				{
+					n = strlen( value ) - 1;
+					while( isspace( value[n] ) )
+						n --;
+					g_string_append_len( info, value, n + 1 );
+				}
+				g_string_append_c( info, '\n' );
+				g_free( key );
+			}
+			
+			break;
+		case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
+			g_string_append( info, "------------------------\n" );
+			break;
+		}
+		
+		l = l->next;
+	}
+	
+	imcb_log( ic, "User %s info:\n%s", who, info->str );
+	g_string_free( info, TRUE );
+	
+	return NULL;
+}
+
+static PurpleNotifyUiOps bee_notify_uiops =
+{
+        NULL,
+        prplcb_notify_email,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        prplcb_notify_userinfo,
+};
+
+extern PurpleXferUiOps bee_xfer_uiops;
+
+static void purple_ui_init()
+{
+	purple_blist_set_ui_ops( &bee_blist_uiops );
+	purple_connections_set_ui_ops( &bee_conn_uiops );
+	purple_conversations_set_ui_ops( &bee_conv_uiops );
+	purple_request_set_ui_ops( &bee_request_uiops );
+	purple_notify_set_ui_ops( &bee_notify_uiops );
+	purple_xfers_set_ui_ops( &bee_xfer_uiops );
+	purple_privacy_set_ui_ops( &bee_privacy_uiops );
+	
+	if( getenv( "BITLBEE_DEBUG" ) )
+		purple_debug_set_ui_ops( &bee_debug_uiops );
+}
+
+void purple_initmodule()
+{
+	struct prpl funcs;
+	GList *prots;
+	GString *help;
+	
+	if( B_EV_IO_READ != PURPLE_INPUT_READ ||
+	    B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
+	{
+		/* FIXME FIXME FIXME FIXME FIXME :-) */
+		exit( 1 );
+	}
+	
+	purple_util_set_user_dir("/tmp");
+	purple_debug_set_enabled(FALSE);
+	purple_core_set_ui_ops(&bee_core_uiops);
+	purple_eventloop_set_ui_ops(&glib_eventloops);
+	if( !purple_core_init( "BitlBee") )
+	{
+		/* Initializing the core failed. Terminate. */
+		fprintf( stderr, "libpurple initialization failed.\n" );
+		abort();
+	}
+	
+	/* This seems like stateful shit we don't want... */
+	purple_set_blist(purple_blist_new());
+	purple_blist_load();
+	
+	/* Meh? */
+	purple_prefs_load();
+	
+	memset( &funcs, 0, sizeof( funcs ) );
+	funcs.login = purple_login;
+	funcs.init = purple_init;
+	funcs.logout = purple_logout;
+	funcs.buddy_msg = purple_buddy_msg;
+	funcs.away_states = purple_away_states;
+	funcs.set_away = purple_set_away;
+	funcs.add_buddy = purple_add_buddy;
+	funcs.remove_buddy = purple_remove_buddy;
+	funcs.add_permit = purple_add_permit;
+	funcs.add_deny = purple_add_deny;
+	funcs.rem_permit = purple_rem_permit;
+	funcs.rem_deny = purple_rem_deny;
+	funcs.get_info = purple_get_info;
+	funcs.keepalive = purple_keepalive;
+	funcs.send_typing = purple_send_typing;
+	funcs.handle_cmp = g_strcasecmp;
+	/* TODO(wilmer): Set these only for protocols that support them? */
+	funcs.chat_msg = purple_chat_msg;
+	funcs.chat_with = purple_chat_with;
+	funcs.chat_invite = purple_chat_invite;
+	funcs.chat_leave = purple_chat_leave;
+	funcs.chat_join = purple_chat_join;
+	funcs.transfer_request = purple_transfer_request;
+	
+	help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
+	
+	/* Add a protocol entry to BitlBee's structures for every protocol
+	   supported by this libpurple instance. */	
+	for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
+	{
+		PurplePlugin *prot = prots->data;
+		struct prpl *ret;
+		
+		ret = g_memdup( &funcs, sizeof( funcs ) );
+		ret->name = ret->data = prot->info->id;
+		if( strncmp( ret->name, "prpl-", 5 ) == 0 )
+			ret->name += 5;
+		register_protocol( ret );
+		
+		g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
+		
+		/* libpurple doesn't define a protocol called OSCAR, but we
+		   need it to be compatible with normal BitlBee. */
+		if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
+		{
+			ret = g_memdup( &funcs, sizeof( funcs ) );
+			ret->name = "oscar";
+			ret->data = prot->info->id;
+			register_protocol( ret );
+		}
+	}
+	
+	g_string_append( help, "\n\nFor used protocols, more information about available "
+	                 "settings can be found using \x02help purple <protocol name>\x02" );
+	
+	/* Add a simple dynamically-generated help item listing all
+	   the supported protocols. */
+	help_add_mem( &global.help, "purple", help->str );
+	g_string_free( help, TRUE );
+}
Index: protocols/twitter/Makefile
===================================================================
--- protocols/twitter/Makefile	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/twitter/Makefile	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,43 @@
+###########################
+## Makefile for BitlBee  ##
+##                       ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+
+# [SH] Program variables
+objects = twitter.o twitter_http.o twitter_lib.o
+
+CFLAGS += -Wall
+LFLAGS += -r
+
+# [SH] Phony targets
+all: twitter_mod.o
+check: all
+lcov: check
+gcov: 
+	gcov *.c
+	
+.PHONY: all clean distclean
+
+clean:
+	rm -f *.o core
+
+distclean: clean
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: %.c
+	@echo '*' Compiling $<
+	@$(CC) -c $(CFLAGS) $< -o $@
+
+twitter_mod.o: $(objects)
+	@echo '*' Linking twitter_mod.o
+	@$(LD) $(LFLAGS) $(objects) -o twitter_mod.o
+	
+
Index: protocols/twitter/twitter.c
===================================================================
--- protocols/twitter/twitter.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/twitter/twitter.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,364 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Simple module to facilitate twitter functionality.                       *
+*                                                                           *
+*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
+*                                                                           *
+*  This library is free software; you can redistribute it and/or            *
+*  modify it under the terms of the GNU Lesser General Public               *
+*  License as published by the Free Software Foundation, version            *
+*  2.1.                                                                     *
+*                                                                           *
+*  This library 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        *
+*  Lesser General Public License for more details.                          *
+*                                                                           *
+*  You should have received a copy of the GNU Lesser General Public License *
+*  along with this library; if not, write to the Free Software Foundation,  *
+*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
+*                                                                           *
+****************************************************************************/
+
+#include "nogaim.h"
+#include "oauth.h"
+#include "twitter.h"
+#include "twitter_http.h"
+#include "twitter_lib.h"
+
+/**
+ * Main loop function
+ */
+gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
+{
+	struct im_connection *ic = data;
+	
+	// Check if we are still logged in...
+	if (!g_slist_find( twitter_connections, ic ))
+		return 0;
+
+	// If the user uses multiple private message windows we need to get the 
+	// users buddies.
+	if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "many") == 0)
+		twitter_get_statuses_friends(ic, -1);
+
+	// Do stuff..
+	twitter_get_home_timeline(ic, -1);
+
+	// If we are still logged in run this function again after timeout.
+	return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN;
+}
+
+static void twitter_main_loop_start( struct im_connection *ic )
+{
+	struct twitter_data *td = ic->proto_data;
+	
+	imcb_log( ic, "Connecting to Twitter" );
+
+	// Run this once. After this queue the main loop function.
+	twitter_main_loop(ic, -1, 0);
+
+	// Queue the main_loop
+	// Save the return value, so we can remove the timeout on logout.
+	td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic);
+}
+
+
+static const struct oauth_service twitter_oauth =
+{
+	"http://api.twitter.com/oauth/request_token",
+	"http://api.twitter.com/oauth/access_token",
+	"http://api.twitter.com/oauth/authorize",
+	.consumer_key = "xsDNKJuNZYkZyMcu914uEA",
+	.consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
+};
+
+static gboolean twitter_oauth_callback( struct oauth_info *info );
+
+static void twitter_oauth_start( struct im_connection *ic )
+{
+	struct twitter_data *td = ic->proto_data;
+	
+	imcb_log( ic, "Requesting OAuth request token" );
+
+	td->oauth_info = oauth_request_token( &twitter_oauth, twitter_oauth_callback, ic );
+}
+
+static gboolean twitter_oauth_callback( struct oauth_info *info )
+{
+	struct im_connection *ic = info->data;
+	struct twitter_data *td;
+	
+	if( !g_slist_find( twitter_connections, ic ) )
+		return FALSE;
+	
+	td = ic->proto_data;
+	if( info->stage == OAUTH_REQUEST_TOKEN )
+	{
+		char name[strlen(ic->acc->user)+9], *msg;
+		
+		if( info->request_token == NULL )
+		{
+			imcb_error( ic, "OAuth error: %s", info->http->status_string );
+			imc_logout( ic, TRUE );
+			return FALSE;
+		}
+		
+		sprintf( name, "twitter_%s", ic->acc->user );
+		msg = g_strdup_printf( "To finish OAuth authentication, please visit "
+		                       "%s and respond with the resulting PIN code.",
+		                       info->auth_url );
+		imcb_buddy_msg( ic, name, msg, 0, 0 );
+		g_free( msg );
+	}
+	else if( info->stage == OAUTH_ACCESS_TOKEN )
+	{
+		if( info->token == NULL || info->token_secret == NULL )
+		{
+			imcb_error( ic, "OAuth error: %s", info->http->status_string );
+			imc_logout( ic, TRUE );
+			return FALSE;
+		}
+		
+		/* IM mods didn't do this so far and it's ugly but I should
+		   be able to get away with it... */
+		g_free( ic->acc->pass );
+		ic->acc->pass = oauth_to_string( info );
+		
+		twitter_main_loop_start( ic );
+	}
+	
+	return TRUE;
+}
+
+
+static char *set_eval_mode( set_t *set, char *value )
+{
+	if( g_strcasecmp( value, "one" ) == 0 ||
+	    g_strcasecmp( value, "many" ) == 0 ||
+	    g_strcasecmp( value, "chat" ) == 0 )
+		return value;
+	else
+		return NULL;
+}
+
+static gboolean twitter_length_check( struct im_connection *ic, gchar *msg )
+{
+	int max = set_getint( &ic->acc->set, "message_length" ), len;
+	
+	if( max == 0 || ( len = g_utf8_strlen( msg, -1 ) ) <= max )
+		return TRUE;
+	
+	imcb_error( ic, "Maximum message length exceeded: %d > %d", len, max );
+	
+	return FALSE;
+}
+
+static void twitter_init( account_t *acc )
+{
+	set_t *s;
+	
+	s = set_add( &acc->set, "message_length", "140", set_eval_int, acc );
+	
+	s = set_add( &acc->set, "mode", "one", set_eval_mode, acc );
+	s->flags |= ACC_SET_OFFLINE_ONLY;
+	
+	s = set_add( &acc->set, "oauth", "true", set_eval_bool, acc );
+}
+
+/**
+ * Login method. Since the twitter API works with seperate HTTP request we 
+ * only save the user and pass to the twitter_data object.
+ */
+static void twitter_login( account_t *acc )
+{
+	struct im_connection *ic = imcb_new( acc );
+	struct twitter_data *td = g_new0( struct twitter_data, 1 );
+	char name[strlen(acc->user)+9];
+
+	twitter_connections = g_slist_append( twitter_connections, ic );
+	ic->proto_data = td;
+	ic->flags |= OPT_DOES_HTML;
+	
+	td->user = acc->user;
+	if( !set_getbool( &acc->set, "oauth" ) )
+		td->pass = g_strdup( acc->pass );
+	else if( strstr( acc->pass, "oauth_token=" ) )
+		td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth );
+	td->home_timeline_id = 0;
+	
+	sprintf( name, "twitter_%s", acc->user );
+	imcb_add_buddy( ic, name, NULL );
+	imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
+	
+	if( td->pass || td->oauth_info )
+		twitter_main_loop_start( ic );
+	else
+		twitter_oauth_start( ic );
+}
+
+/**
+ * Logout method. Just free the twitter_data.
+ */
+static void twitter_logout( struct im_connection *ic )
+{
+	struct twitter_data *td = ic->proto_data;
+	
+	// Set the status to logged out.
+	ic->flags = 0;
+
+	// Remove the main_loop function from the function queue.
+	b_event_remove(td->main_loop_id);
+
+	if(td->home_timeline_gc)
+		imcb_chat_free(td->home_timeline_gc);
+
+	if( td )
+	{
+		oauth_info_free( td->oauth_info );
+		g_free( td->pass );
+		g_free( td );
+	}
+
+	twitter_connections = g_slist_remove( twitter_connections, ic );
+}
+
+/**
+ *
+ */
+static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
+{
+	struct twitter_data *td = ic->proto_data;
+	
+	if (g_strncasecmp(who, "twitter_", 8) == 0 &&
+	    g_strcasecmp(who + 8, ic->acc->user) == 0)
+	{
+		if( set_getbool( &ic->acc->set, "oauth" ) &&
+		    td->oauth_info && td->oauth_info->token == NULL )
+		{
+			if( !oauth_access_token( message, td->oauth_info ) )
+			{
+				imcb_error( ic, "OAuth error: %s", "Failed to send access token request" );
+				imc_logout( ic, TRUE );
+				return FALSE;
+			}
+		}
+		else if( twitter_length_check(ic, message) )
+			twitter_post_status(ic, message);
+	}
+	else
+	{
+		twitter_direct_messages_new(ic, who, message);
+	}
+	return( 0 );
+}
+
+/**
+ *
+ */
+static void twitter_set_my_name( struct im_connection *ic, char *info )
+{
+}
+
+static void twitter_get_info(struct im_connection *ic, char *who) 
+{
+}
+
+static void twitter_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+}
+
+static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+}
+
+static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
+{
+	if( c && message && twitter_length_check(c->ic, message))
+		twitter_post_status(c->ic, message);
+}
+
+static void twitter_chat_invite( struct groupchat *c, char *who, char *message )
+{
+}
+
+static void twitter_chat_leave( struct groupchat *c )
+{
+	struct twitter_data *td = c->ic->proto_data;
+	
+	if( c != td->home_timeline_gc )
+		return; /* WTF? */
+	
+	/* If the user leaves the channel: Fine. Rejoin him/her once new
+	   tweets come in. */
+	imcb_chat_free(td->home_timeline_gc);
+	td->home_timeline_gc = NULL;
+}
+
+static struct groupchat *twitter_chat_with( struct im_connection *ic, char *who )
+{
+	return NULL;
+}
+
+static void twitter_keepalive( struct im_connection *ic )
+{
+}
+
+static void twitter_add_permit( struct im_connection *ic, char *who )
+{
+}
+
+static void twitter_rem_permit( struct im_connection *ic, char *who )
+{
+}
+
+static void twitter_add_deny( struct im_connection *ic, char *who )
+{
+}
+
+static void twitter_rem_deny( struct im_connection *ic, char *who )
+{
+}
+
+static int twitter_send_typing( struct im_connection *ic, char *who, int typing )
+{
+	return( 1 );
+}
+
+//static char *twitter_set_display_name( set_t *set, char *value )
+//{
+//	return value;
+//}
+
+void twitter_initmodule()
+{
+	struct prpl *ret = g_new0(struct prpl, 1);
+	
+	ret->name = "twitter";
+	ret->login = twitter_login;
+	ret->init = twitter_init;
+	ret->logout = twitter_logout;
+	ret->buddy_msg = twitter_buddy_msg;
+	ret->get_info = twitter_get_info;
+	ret->set_my_name = twitter_set_my_name;
+	ret->add_buddy = twitter_add_buddy;
+	ret->remove_buddy = twitter_remove_buddy;
+	ret->chat_msg = twitter_chat_msg;
+	ret->chat_invite = twitter_chat_invite;
+	ret->chat_leave = twitter_chat_leave;
+	ret->chat_with = twitter_chat_with;
+	ret->keepalive = twitter_keepalive;
+	ret->add_permit = twitter_add_permit;
+	ret->rem_permit = twitter_rem_permit;
+	ret->add_deny = twitter_add_deny;
+	ret->rem_deny = twitter_rem_deny;
+	ret->send_typing = twitter_send_typing;
+	ret->handle_cmp = g_strcasecmp;
+
+	register_protocol(ret);
+
+	// Initialise the twitter_connections GSList.
+	twitter_connections = NULL;
+}
+
Index: protocols/twitter/twitter.h
===================================================================
--- protocols/twitter/twitter.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/twitter/twitter.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,53 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Simple module to facilitate twitter functionality.                       *
+*                                                                           *
+*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
+*                                                                           *
+*  This library is free software; you can redistribute it and/or            *
+*  modify it under the terms of the GNU Lesser General Public               *
+*  License as published by the Free Software Foundation, version            *
+*  2.1.                                                                     *
+*                                                                           *
+*  This library 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        *
+*  Lesser General Public License for more details.                          *
+*                                                                           *
+*  You should have received a copy of the GNU Lesser General Public License *
+*  along with this library; if not, write to the Free Software Foundation,  *
+*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
+*                                                                           *
+****************************************************************************/
+
+#include "nogaim.h"
+
+#ifndef _TWITTER_H
+#define _TWITTER_H
+
+#ifdef DEBUG_TWITTER
+#define debug( text... ) imcb_log( ic, text );
+#else
+#define debug( text... )
+#endif
+
+struct twitter_data
+{
+	char* user;
+	char* pass;
+	struct oauth_info *oauth_info;
+	guint64 home_timeline_id;
+	gint main_loop_id;
+	struct groupchat *home_timeline_gc;
+	gint http_fails;
+};
+
+/**
+ * This has the same function as the msn_connections GSList. We use this to 
+ * make sure the connection is still alive in callbacks before we do anything
+ * else.
+ */
+GSList *twitter_connections;
+
+#endif //_TWITTER_H
Index: protocols/twitter/twitter_http.c
===================================================================
--- protocols/twitter/twitter_http.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/twitter/twitter_http.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,175 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Simple module to facilitate twitter functionality.                       *
+*                                                                           *
+*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
+*                                                                           *
+*  This library is free software; you can redistribute it and/or            *
+*  modify it under the terms of the GNU Lesser General Public               *
+*  License as published by the Free Software Foundation, version            *
+*  2.1.                                                                     *
+*                                                                           *
+*  This library 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        *
+*  Lesser General Public License for more details.                          *
+*                                                                           *
+*  You should have received a copy of the GNU Lesser General Public License *
+*  along with this library; if not, write to the Free Software Foundation,  *
+*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
+*                                                                           *
+****************************************************************************/
+
+/***************************************************************************\
+*                                                                           *
+*  Some funtions within this file have been copied from other files within  *
+*  BitlBee.                                                                 *
+*                                                                           *
+****************************************************************************/ 
+
+#include "twitter.h"
+#include "bitlbee.h"
+#include "url.h"
+#include "misc.h"
+#include "base64.h"
+#include "oauth.h"
+#include <ctype.h>
+#include <errno.h>
+
+#include "twitter_http.h"
+
+
+char *twitter_url_append(char *url, char *key, char* value);
+
+/**
+ * Do a request.
+ * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c
+ */
+void *twitter_http(char *url_string, http_input_function func, gpointer data, int is_post, char* user, char* pass, struct oauth_info* oi, char** arguments, int arguments_len)
+{
+	url_t *url = g_new0( url_t, 1 );
+	char *tmp;
+	char *request;
+	void *ret;
+	char *userpass = NULL;
+	char *userpass_base64;
+	char *url_arguments;
+
+	// Fill the url structure.
+	if( !url_set( url, url_string ) )
+	{
+		g_free( url );
+		return NULL;
+	}
+
+	if( url->proto != PROTO_HTTP && url->proto != PROTO_HTTPS )
+	{
+		g_free( url );
+		return NULL;
+	}
+
+	// Concatenate user and pass
+	if (user && pass) {
+		userpass = g_strdup_printf("%s:%s", user, pass);
+		userpass_base64 = base64_encode((unsigned char*)userpass, strlen(userpass));
+	} else {
+		userpass_base64 = NULL;
+	}
+
+	url_arguments = g_malloc(1);
+	url_arguments[0] = '\0';
+
+	// Construct the url arguments.
+	if (arguments_len != 0)
+	{
+		int i;
+		for (i=0; i<arguments_len; i+=2) 
+		{
+			tmp = twitter_url_append(url_arguments, arguments[i], arguments[i+1]);
+			g_free(url_arguments);
+			url_arguments = tmp;
+		}
+	}
+
+	// Do GET stuff...
+	if (!is_post)
+	{
+		// Find the char-pointer of the end of the string.
+		tmp = url->file + strlen(url->file);
+		tmp[0] = '?';
+		// append the url_arguments to the end of the url->file.
+		// TODO GM: Check the length?
+		g_stpcpy (tmp+1, url_arguments);
+	}
+
+
+	// Make the request.
+	request = g_strdup_printf(  "%s %s HTTP/1.0\r\n"
+	                            "Host: %s\r\n"
+	                            "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
+	                            is_post ? "POST" : "GET", url->file, url->host );
+
+	// If a pass and user are given we append them to the request.
+	if (oi)
+	{
+		char *full_header;
+		
+		full_header = oauth_http_header(oi, is_post ? "POST" : "GET",
+		                                url_string, url_arguments);
+		
+		tmp = g_strdup_printf("%sAuthorization: %s\r\n", request, full_header);
+		g_free(request);
+		g_free(full_header);
+		request = tmp;
+	}
+	else if (userpass_base64)
+	{
+		tmp = g_strdup_printf("%sAuthorization: Basic %s\r\n", request, userpass_base64);
+		g_free(request);
+		request = tmp;
+	}
+
+	// Do POST stuff..
+	if (is_post)
+	{
+		// Append the Content-Type and url-encoded arguments.
+		tmp = g_strdup_printf("%sContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %zd\r\n\r\n%s", 
+								request, strlen(url_arguments), url_arguments);
+		g_free(request);
+		request = tmp;
+	} else {
+		// Append an extra \r\n to end the request...
+		tmp = g_strdup_printf("%s\r\n", request);
+		g_free(request);
+		request = tmp;
+	}
+
+	ret = http_dorequest( url->host, url->port,	url->proto == PROTO_HTTPS, request, func, data );
+
+	g_free( url );
+	g_free( userpass );
+	g_free( userpass_base64 );
+	g_free( url_arguments );
+	g_free( request );
+	return ret;
+}
+
+char *twitter_url_append(char *url, char *key, char* value)
+{
+	char *key_encoded = g_strndup(key, 3 * strlen(key));
+	http_encode(key_encoded);
+	char *value_encoded = g_strndup(value, 3 * strlen(value));
+	http_encode(value_encoded);
+
+	char *retval;
+	if (strlen(url) != 0)
+		retval = g_strdup_printf("%s&%s=%s", url, key_encoded, value_encoded);
+	else
+		retval = g_strdup_printf("%s=%s", key_encoded, value_encoded);
+
+	g_free(key_encoded);
+	g_free(value_encoded);
+
+	return retval;
+}
Index: protocols/twitter/twitter_http.h
===================================================================
--- protocols/twitter/twitter_http.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/twitter/twitter_http.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,36 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Simple module to facilitate twitter functionality.                       *
+*                                                                           *
+*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
+*                                                                           *
+*  This library is free software; you can redistribute it and/or            *
+*  modify it under the terms of the GNU Lesser General Public               *
+*  License as published by the Free Software Foundation, version            *
+*  2.1.                                                                     *
+*                                                                           *
+*  This library 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        *
+*  Lesser General Public License for more details.                          *
+*                                                                           *
+*  You should have received a copy of the GNU Lesser General Public License *
+*  along with this library; if not, write to the Free Software Foundation,  *
+*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
+*                                                                           *
+****************************************************************************/
+
+#ifndef _TWITTER_HTTP_H
+#define _TWITTER_HTTP_H
+
+#include "nogaim.h"
+#include "http_client.h"
+
+struct oauth_info;
+
+void *twitter_http(char *url_string, http_input_function func, gpointer data, int is_post, 
+                   char* user, char* pass, struct oauth_info *oi, char** arguments, int arguments_len);
+
+#endif //_TWITTER_HTTP_H
+
Index: protocols/twitter/twitter_lib.c
===================================================================
--- protocols/twitter/twitter_lib.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/twitter/twitter_lib.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,677 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Simple module to facilitate twitter functionality.                       *
+*                                                                           *
+*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
+*                                                                           *
+*  This library is free software; you can redistribute it and/or            *
+*  modify it under the terms of the GNU Lesser General Public               *
+*  License as published by the Free Software Foundation, version            *
+*  2.1.                                                                     *
+*                                                                           *
+*  This library 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        *
+*  Lesser General Public License for more details.                          *
+*                                                                           *
+*  You should have received a copy of the GNU Lesser General Public License *
+*  along with this library; if not, write to the Free Software Foundation,  *
+*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
+*                                                                           *
+****************************************************************************/
+
+/* For strptime(): */
+#define _XOPEN_SOURCE
+
+#include "twitter_http.h"
+#include "twitter.h"
+#include "bitlbee.h"
+#include "url.h"
+#include "misc.h"
+#include "base64.h"
+#include "xmltree.h"
+#include "twitter_lib.h"
+#include <ctype.h>
+#include <errno.h>
+
+#define TXL_STATUS 1
+#define TXL_USER 2
+#define TXL_ID 3
+
+struct twitter_xml_list {
+	int type;
+	int next_cursor;
+	GSList *list;
+	gpointer data;
+};
+
+struct twitter_xml_user {
+	char *name;
+	char *screen_name;
+};
+
+struct twitter_xml_status {
+	time_t created_at;
+	char *text;
+	struct twitter_xml_user *user;
+	guint64 id;
+};
+
+/**
+ * Frees a twitter_xml_user struct.
+ */
+static void txu_free(struct twitter_xml_user *txu)
+{
+	g_free(txu->name);
+	g_free(txu->screen_name);
+	g_free(txu);
+}
+
+
+/**
+ * Frees a twitter_xml_status struct.
+ */
+static void txs_free(struct twitter_xml_status *txs)
+{
+	g_free(txs->text);
+	txu_free(txs->user);
+	g_free(txs);
+}
+
+/**
+ * Free a twitter_xml_list struct.
+ * type is the type of list the struct holds.
+ */
+static void txl_free(struct twitter_xml_list *txl)
+{
+	GSList *l;
+	for ( l = txl->list; l ; l = g_slist_next(l) )
+		if (txl->type == TXL_STATUS)
+			txs_free((struct twitter_xml_status *)l->data);
+		else if (txl->type == TXL_ID)
+			g_free(l->data);
+	g_slist_free(txl->list);
+}
+
+/**
+ * Add a buddy if it is not allready added, set the status to logged in.
+ */
+static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
+{
+	struct twitter_data *td = ic->proto_data;
+
+	// Check if the buddy is allready in the buddy list.
+	if (!imcb_find_buddy( ic, name ))
+	{
+		char *mode = set_getstr(&ic->acc->set, "mode");
+		
+		// The buddy is not in the list, add the buddy and set the status to logged in.
+		imcb_add_buddy( ic, name, NULL );
+		imcb_rename_buddy( ic, name, fullname );
+		if (g_strcasecmp(mode, "chat") == 0)
+			imcb_chat_add_buddy( td->home_timeline_gc, name );
+		else if (g_strcasecmp(mode, "many") == 0)
+			imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
+	}
+}
+
+static void twitter_http_get_friends_ids(struct http_request *req);
+
+/**
+ * Get the friends ids.
+ */
+void twitter_get_friends_ids(struct im_connection *ic, int next_cursor)
+{
+	struct twitter_data *td = ic->proto_data;
+
+	// Primitive, but hey! It works...	
+	char* args[2];
+	args[0] = "cursor";
+	args[1] = g_strdup_printf ("%d", next_cursor);
+	twitter_http(TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, td->user, td->pass, td->oauth_info, args, 2);
+
+	g_free(args[1]);
+}
+
+/**
+ * Function to help fill a list.
+ */
+static xt_status twitter_xt_next_cursor( struct xt_node *node, struct twitter_xml_list *txl )
+{
+	// Do something with the cursor.
+	txl->next_cursor = node->text != NULL ? atoi(node->text) : -1;
+
+	return XT_HANDLED;
+}
+
+/**
+ * Fill a list of ids.
+ */
+static xt_status twitter_xt_get_friends_id_list( struct xt_node *node, struct twitter_xml_list *txl )
+{
+	struct xt_node *child;
+	
+	// Set the list type.
+	txl->type = TXL_ID;
+
+	// The root <statuses> node should hold the list of statuses <status>
+	// Walk over the nodes children.
+	for( child = node->children ; child ; child = child->next )
+	{
+		if ( g_strcasecmp( "id", child->name ) == 0)
+		{
+			// Add the item to the list.
+			txl->list = g_slist_append (txl->list, g_memdup( child->text, child->text_len + 1 ));
+		}
+		else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
+		{
+			twitter_xt_next_cursor(child, txl);
+		}
+	}
+
+	return XT_HANDLED;
+}
+
+/**
+ * Callback for getting the friends ids.
+ */
+static void twitter_http_get_friends_ids(struct http_request *req)
+{
+	struct im_connection *ic;
+	struct xt_parser *parser;
+	struct twitter_xml_list *txl;
+	struct twitter_data *td;
+
+	ic = req->data;
+
+	// Check if the connection is still active.
+	if( !g_slist_find( twitter_connections, ic ) )
+		return;
+	
+	td = ic->proto_data;
+
+	// Check if the HTTP request went well.
+	if (req->status_code != 200) {
+		// It didn't go well, output the error and return.
+		if (++td->http_fails >= 5)
+			imcb_error(ic, "Could not retrieve friends. HTTP STATUS: %d", req->status_code);
+		
+		return;
+	} else {
+		td->http_fails = 0;
+	}
+
+	txl = g_new0(struct twitter_xml_list, 1);
+
+	// Parse the data.
+	parser = xt_new( NULL, txl );
+	xt_feed( parser, req->reply_body, req->body_size );
+	twitter_xt_get_friends_id_list(parser->root, txl);
+	xt_free( parser );
+
+	if (txl->next_cursor)
+		twitter_get_friends_ids(ic, txl->next_cursor);
+
+	txl_free(txl);
+	g_free(txl);
+}
+
+/**
+ * Function to fill a twitter_xml_user struct.
+ * It sets:
+ *  - the name and
+ *  - the screen_name.
+ */
+static xt_status twitter_xt_get_user( struct xt_node *node, struct twitter_xml_user *txu )
+{
+	struct xt_node *child;
+
+	// Walk over the nodes children.
+	for( child = node->children ; child ; child = child->next )
+	{
+		if ( g_strcasecmp( "name", child->name ) == 0)
+		{
+			txu->name = g_memdup( child->text, child->text_len + 1 );
+		}
+		else if (g_strcasecmp( "screen_name", child->name ) == 0)
+		{
+			txu->screen_name = g_memdup( child->text, child->text_len + 1 );
+		}
+	}
+	return XT_HANDLED;
+}
+
+/**
+ * Function to fill a twitter_xml_list struct.
+ * It sets:
+ *  - all <user>s from the <users> element.
+ */
+static xt_status twitter_xt_get_users( struct xt_node *node, struct twitter_xml_list *txl )
+{
+	struct twitter_xml_user *txu;
+	struct xt_node *child;
+
+	// Set the type of the list.
+	txl->type = TXL_USER;
+
+	// The root <users> node should hold the list of users <user>
+	// Walk over the nodes children.
+	for( child = node->children ; child ; child = child->next )
+	{
+		if ( g_strcasecmp( "user", child->name ) == 0)
+		{
+			txu = g_new0(struct twitter_xml_user, 1);
+			twitter_xt_get_user(child, txu);
+			// Put the item in the front of the list.
+			txl->list = g_slist_prepend (txl->list, txu);
+		}
+	}
+
+	return XT_HANDLED;
+}
+
+/**
+ * Function to fill a twitter_xml_list struct.
+ * It calls twitter_xt_get_users to get the <user>s from a <users> element.
+ * It sets:
+ *  - the next_cursor.
+ */
+static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_xml_list *txl )
+{
+	struct xt_node *child;
+
+	// Set the type of the list.
+	txl->type = TXL_USER;
+
+	// The root <user_list> node should hold a users <users> element
+	// Walk over the nodes children.
+	for( child = node->children ; child ; child = child->next )
+	{
+		if ( g_strcasecmp( "users", child->name ) == 0)
+		{
+			twitter_xt_get_users(child, txl);
+		}
+		else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
+		{
+			twitter_xt_next_cursor(child, txl);
+		}
+	}
+
+	return XT_HANDLED;
+}
+
+
+/**
+ * Function to fill a twitter_xml_status struct.
+ * It sets:
+ *  - the status text and
+ *  - the created_at timestamp and
+ *  - the status id and
+ *  - the user in a twitter_xml_user struct.
+ */
+static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml_status *txs )
+{
+	struct xt_node *child;
+
+	// Walk over the nodes children.
+	for( child = node->children ; child ; child = child->next )
+	{
+		if ( g_strcasecmp( "text", child->name ) == 0)
+		{
+			txs->text = g_memdup( child->text, child->text_len + 1 );
+		}
+		else if (g_strcasecmp( "created_at", child->name ) == 0)
+		{
+			struct tm parsed;
+			
+			/* Very sensitive to changes to the formatting of
+			   this field. :-( Also assumes the timezone used
+			   is UTC since C time handling functions suck. */
+			if( strptime( child->text, "%a %b %d %H:%M:%S %z %Y", &parsed ) != NULL )
+				txs->created_at = mktime_utc( &parsed );
+		}
+		else if (g_strcasecmp( "user", child->name ) == 0)
+		{
+			txs->user = g_new0(struct twitter_xml_user, 1);
+			twitter_xt_get_user( child, txs->user );
+		}
+		else if (g_strcasecmp( "id", child->name ) == 0)
+		{
+			txs->id = g_ascii_strtoull (child->text, NULL, 10);
+		}
+	}
+	return XT_HANDLED;
+}
+
+/**
+ * Function to fill a twitter_xml_list struct.
+ * It sets:
+ *  - all <status>es within the <status> element and
+ *  - the next_cursor.
+ */
+static xt_status twitter_xt_get_status_list( struct xt_node *node, struct twitter_xml_list *txl )
+{
+	struct twitter_xml_status *txs;
+	struct xt_node *child;
+
+	// Set the type of the list.
+	txl->type = TXL_STATUS;
+
+	// The root <statuses> node should hold the list of statuses <status>
+	// Walk over the nodes children.
+	for( child = node->children ; child ; child = child->next )
+	{
+		if ( g_strcasecmp( "status", child->name ) == 0)
+		{
+			txs = g_new0(struct twitter_xml_status, 1);
+			twitter_xt_get_status(child, txs);
+			// Put the item in the front of the list.
+			txl->list = g_slist_prepend (txl->list, txs);
+		}
+		else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
+		{
+			twitter_xt_next_cursor(child, txl);
+		}
+	}
+
+	return XT_HANDLED;
+}
+
+static void twitter_http_get_home_timeline(struct http_request *req);
+
+/**
+ * Get the timeline.
+ */
+void twitter_get_home_timeline(struct im_connection *ic, int next_cursor)
+{
+	struct twitter_data *td = ic->proto_data;
+
+	char* args[4];
+	args[0] = "cursor";
+	args[1] = g_strdup_printf ("%d", next_cursor);
+	if (td->home_timeline_id) {
+		args[2] = "since_id";
+		args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id);
+	}
+
+	twitter_http(TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, td->user, td->pass, td->oauth_info, args, td->home_timeline_id ? 4 : 2);
+
+	g_free(args[1]);
+	if (td->home_timeline_id) {
+		g_free(args[3]);
+	}
+}
+
+/**
+ * Function that is called to see the statuses in a groupchat window.
+ */
+static void twitter_groupchat(struct im_connection *ic, GSList *list)
+{
+	struct twitter_data *td = ic->proto_data;
+	GSList *l = NULL;
+	struct twitter_xml_status *status;
+	struct groupchat *gc;
+
+	// Create a new groupchat if it does not exsist.
+	if (!td->home_timeline_gc)
+	{   
+		char *name_hint = g_strdup_printf( "Twitter_%s", ic->acc->user );
+		td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
+		imcb_chat_name_hint( gc, name_hint );
+		g_free( name_hint );
+		// Add the current user to the chat...
+		imcb_chat_add_buddy( gc, ic->acc->user );
+	}
+	else
+	{   
+		gc = td->home_timeline_gc;
+	}
+
+	for ( l = list; l ; l = g_slist_next(l) )
+	{
+		status = l->data;
+		twitter_add_buddy(ic, status->user->screen_name, status->user->name);
+		
+		// Say it!
+		if (g_strcasecmp(td->user, status->user->screen_name) == 0)
+			imcb_chat_log (gc, "Your Tweet: %s", status->text);
+		else
+			imcb_chat_msg (gc, status->user->screen_name, status->text, 0, status->created_at );
+		
+		// Update the home_timeline_id to hold the highest id, so that by the next request
+		// we won't pick up the updates allready in the list.
+		td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
+	}
+}
+
+/**
+ * Function that is called to see statuses as private messages.
+ */
+static void twitter_private_message_chat(struct im_connection *ic, GSList *list)
+{
+	struct twitter_data *td = ic->proto_data;
+	GSList *l = NULL;
+	struct twitter_xml_status *status;
+	char from[MAX_STRING];
+	gboolean mode_one;
+	
+	mode_one = g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) == 0;
+
+	if( mode_one )
+	{
+		g_snprintf( from, sizeof( from ) - 1, "twitter_%s", ic->acc->user );
+		from[MAX_STRING-1] = '\0';
+	}
+	
+	for ( l = list; l ; l = g_slist_next(l) )
+	{
+		char *text = NULL;
+		
+		status = l->data;
+		
+		if( mode_one )
+			text = g_strdup_printf( "\002<\002%s\002>\002 %s",
+			                        status->user->screen_name, status->text );
+		else
+			twitter_add_buddy(ic, status->user->screen_name, status->user->name);
+		
+		imcb_buddy_msg( ic,
+		                mode_one ? from : status->user->screen_name,
+		                mode_one ? text : status->text,
+		                0, status->created_at );
+		
+		// Update the home_timeline_id to hold the highest id, so that by the next request
+		// we won't pick up the updates allready in the list.
+		td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
+		
+		g_free( text );
+	}
+}
+
+/**
+ * Callback for getting the home timeline.
+ */
+static void twitter_http_get_home_timeline(struct http_request *req)
+{
+	struct im_connection *ic = req->data;
+	struct twitter_data *td;
+	struct xt_parser *parser;
+	struct twitter_xml_list *txl;
+
+	// Check if the connection is still active.
+	if( !g_slist_find( twitter_connections, ic ) )
+		return;
+	
+	td = ic->proto_data;
+
+	// Check if the HTTP request went well.
+	if (req->status_code == 200)
+	{
+		td->http_fails = 0;
+		if (!(ic->flags & OPT_LOGGED_IN))
+			imcb_connected(ic);
+	}
+	else if (req->status_code == 401)
+	{
+		imcb_error( ic, "Authentication failure" );
+		imc_logout( ic, FALSE );
+		return;
+	}
+	else
+	{
+		// It didn't go well, output the error and return.
+		if (++td->http_fails >= 5)
+			imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ". HTTP STATUS: %d", req->status_code);
+		
+		return;
+	}
+
+	txl = g_new0(struct twitter_xml_list, 1);
+	txl->list = NULL;
+
+	// Parse the data.
+	parser = xt_new( NULL, txl );
+	xt_feed( parser, req->reply_body, req->body_size );
+	// The root <statuses> node should hold the list of statuses <status>
+	twitter_xt_get_status_list(parser->root, txl);
+	xt_free( parser );
+
+	// See if the user wants to see the messages in a groupchat window or as private messages.
+	if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
+		twitter_groupchat(ic, txl->list);
+	else
+		twitter_private_message_chat(ic, txl->list);
+
+	// Free the structure.	
+	txl_free(txl);
+	g_free(txl);
+}
+
+/**
+ * Callback for getting (twitter)friends...
+ *
+ * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has 
+ * hundreds of friends?" you wonder? You probably not, since you are reading the source of 
+ * BitlBee... Get a life and meet new people!
+ */
+static void twitter_http_get_statuses_friends(struct http_request *req)
+{
+	struct im_connection *ic = req->data;
+	struct twitter_data *td;
+	struct xt_parser *parser;
+	struct twitter_xml_list *txl;
+	GSList *l = NULL;
+	struct twitter_xml_user *user;
+
+	// Check if the connection is still active.
+	if( !g_slist_find( twitter_connections, ic ) )
+		return;
+	
+	td = ic->proto_data;
+	
+	// Check if the HTTP request went well.
+	if (req->status_code != 200) {
+		// It didn't go well, output the error and return.
+		if (++td->http_fails >= 5)
+			imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL " HTTP STATUS: %d", req->status_code);
+		
+		return;
+	} else {
+		td->http_fails = 0;
+	}
+
+	txl = g_new0(struct twitter_xml_list, 1);
+	txl->list = NULL;
+
+	// Parse the data.
+	parser = xt_new( NULL, txl );
+	xt_feed( parser, req->reply_body, req->body_size );
+
+	// Get the user list from the parsed xml feed.
+	twitter_xt_get_user_list(parser->root, txl);
+	xt_free( parser );
+
+	// Add the users as buddies.
+	for ( l = txl->list; l ; l = g_slist_next(l) )
+	{
+		user = l->data;
+		twitter_add_buddy(ic, user->screen_name, user->name);
+	}
+
+	// if the next_cursor is set to something bigger then 0 there are more friends to gather.
+	if (txl->next_cursor > 0)
+		twitter_get_statuses_friends(ic, txl->next_cursor);
+
+	// Free the structure.
+	txl_free(txl);
+	g_free(txl);
+}
+
+/**
+ * Get the friends.
+ */
+void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor)
+{
+	struct twitter_data *td = ic->proto_data;
+
+	char* args[2];
+	args[0] = "cursor";
+	args[1] = g_strdup_printf ("%d", next_cursor);
+
+	twitter_http(TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, td->user, td->pass, td->oauth_info, args, 2);
+
+	g_free(args[1]);
+}
+
+/**
+ * Callback after sending a new update to twitter.
+ */
+static void twitter_http_post_status(struct http_request *req)
+{
+	struct im_connection *ic = req->data;
+
+	// Check if the connection is still active.
+	if( !g_slist_find( twitter_connections, ic ) )
+		return;
+
+	// Check if the HTTP request went well.
+	if (req->status_code != 200) {
+		// It didn't go well, output the error and return.
+		imcb_error(ic, "Could not post message... HTTP STATUS: %d", req->status_code);
+		return;
+	}
+}
+
+/**
+ * Function to POST a new status to twitter.
+ */ 
+void twitter_post_status(struct im_connection *ic, char* msg)
+{
+	struct twitter_data *td = ic->proto_data;
+
+	char* args[2];
+	args[0] = "status";
+	args[1] = msg;
+	twitter_http(TWITTER_STATUS_UPDATE_URL, twitter_http_post_status, ic, 1, td->user, td->pass, td->oauth_info, args, 2);
+//	g_free(args[1]);
+}
+
+
+/**
+ * Function to POST a new message to twitter.
+ */
+void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
+{
+	struct twitter_data *td = ic->proto_data;
+
+	char* args[4];
+	args[0] = "screen_name";
+	args[1] = who;
+	args[2] = "text";
+	args[3] = msg;
+	// Use the same callback as for twitter_post_status, since it does basically the same.
+	twitter_http(TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post_status, ic, 1, td->user, td->pass, td->oauth_info, args, 4);
+//	g_free(args[1]);
+//	g_free(args[3]);
+}
Index: protocols/twitter/twitter_lib.h
===================================================================
--- protocols/twitter/twitter_lib.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
+++ protocols/twitter/twitter_lib.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -0,0 +1,86 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Simple module to facilitate twitter functionality.                       *
+*                                                                           *
+*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
+*                                                                           *
+*  This library is free software; you can redistribute it and/or            *
+*  modify it under the terms of the GNU Lesser General Public               *
+*  License as published by the Free Software Foundation, version            *
+*  2.1.                                                                     *
+*                                                                           *
+*  This library 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        *
+*  Lesser General Public License for more details.                          *
+*                                                                           *
+*  You should have received a copy of the GNU Lesser General Public License *
+*  along with this library; if not, write to the Free Software Foundation,  *
+*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
+*                                                                           *
+****************************************************************************/
+
+
+#ifndef _TWITTER_LIB_H
+#define _TWITTER_LIB_H
+
+#include "nogaim.h"
+#include "twitter_http.h"
+
+#define TWITTER_API_URL "http://twitter.com"
+
+/* Status URLs */
+#define TWITTER_STATUS_UPDATE_URL TWITTER_API_URL "/statuses/update.xml"
+#define TWITTER_STATUS_SHOW_URL TWITTER_API_URL "/statuses/show/"
+#define TWITTER_STATUS_DESTROY_URL TWITTER_API_URL "/statuses/destroy/"
+
+/* Timeline URLs */
+#define TWITTER_PUBLIC_TIMELINE_URL TWITTER_API_URL "/statuses/public_timeline.xml"
+#define TWITTER_FEATURED_USERS_URL TWITTER_API_URL "/statuses/featured.xml"
+#define TWITTER_FRIENDS_TIMELINE_URL TWITTER_API_URL "/statuses/friends_timeline.xml"
+#define TWITTER_HOME_TIMELINE_URL TWITTER_API_URL "/statuses/home_timeline.xml"
+#define TWITTER_MENTIONS_URL TWITTER_API_URL "/statuses/mentions.xml"
+#define TWITTER_USER_TIMELINE_URL TWITTER_API_URL "/statuses/user_timeline.xml"
+
+/* Users URLs */
+#define TWITTER_SHOW_USERS_URL TWITTER_API_URL "/users/show.xml"
+#define TWITTER_SHOW_FRIENDS_URL TWITTER_API_URL "/statuses/friends.xml"
+#define TWITTER_SHOW_FOLLOWERS_URL TWITTER_API_URL "/statuses/followers.xml"
+
+/* Direct messages URLs */
+#define TWITTER_DIRECT_MESSAGES_URL TWITTER_API_URL "/direct_messages.xml"
+#define TWITTER_DIRECT_MESSAGES_NEW_URL TWITTER_API_URL "/direct_messages/new.xml"
+#define TWITTER_DIRECT_MESSAGES_SENT_URL TWITTER_API_URL "/direct_messages/sent.xml"
+#define TWITTER_DIRECT_MESSAGES_DESTROY_URL TWITTER_API_URL "/direct_messages/destroy/"
+
+/* Friendships URLs */
+#define TWITTER_FRIENDSHIPS_CREATE_URL TWITTER_API_URL "/friendships/create.xml"
+#define TWITTER_FRIENDSHIPS_DESTROY_URL TWITTER_API_URL "/friendships/destroy.xml"
+#define TWITTER_FRIENDSHIPS_SHOW_URL TWITTER_API_URL "/friendships/show.xml"
+
+/* Social graphs URLs */
+#define TWITTER_FRIENDS_IDS_URL TWITTER_API_URL "/friends/ids.xml"
+#define TWITTER_FOLLOWERS_IDS_URL TWITTER_API_URL "/followers/ids.xml"
+
+/* Account URLs */
+#define TWITTER_ACCOUNT_RATE_LIMIT_URL TWITTER_API_URL "/account/rate_limit_status.xml"
+
+/* Favorites URLs */
+#define TWITTER_FAVORITES_GET_URL TWITTER_API_URL "/favorites.xml"
+#define TWITTER_FAVORITE_CREATE_URL TWITTER_API_URL "/favorites/create/"
+#define TWITTER_FAVORITE_DESTROY_URL TWITTER_API_URL "/favorites/destroy/"
+
+/* Block URLs */
+#define TWITTER_BLOCKS_CREATE_URL TWITTER_API_URL "/blocks/create/"
+#define TWITTER_BLOCKS_DESTROY_URL TWITTER_API_URL "/blocks/destroy/"
+
+void twitter_get_friends_ids(struct im_connection *ic, int next_cursor);
+void twitter_get_home_timeline(struct im_connection *ic, int next_cursor);
+void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor);
+
+void twitter_post_status(struct im_connection *ic, char *msg);
+void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message);
+
+#endif //_TWITTER_LIB_H
+
Index: protocols/yahoo/yahoo.c
===================================================================
--- protocols/yahoo/yahoo.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ protocols/yahoo/yahoo.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -138,7 +138,12 @@
 	struct im_connection *ic = imcb_new( acc );
 	struct byahoo_data *yd = ic->proto_data = g_new0( struct byahoo_data, 1 );
+	char *s;
 	
 	yd->logged_in = FALSE;
 	yd->current_status = YAHOO_STATUS_AVAILABLE;
+	
+	if( ( s = strchr( acc->user, '@' ) ) && g_strcasecmp( s, "@yahoo.com" ) == 0 )
+		imcb_error( ic, "Your Yahoo! username should just be a username. "
+		                "Do not include any @domain part." );
 	
 	imcb_log( ic, "Connecting" );
@@ -681,5 +686,5 @@
 		
 		inp->d = d;
-		d->tag = inp->h = b_input_add( fd, GAIM_INPUT_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d );
+		d->tag = inp->h = b_input_add( fd, B_EV_IO_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d );
 	}
 	else if( cond == YAHOO_INPUT_WRITE )
@@ -692,5 +697,5 @@
 		
 		inp->d = d;
-		d->tag = inp->h = b_input_add( fd, GAIM_INPUT_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d );
+		d->tag = inp->h = b_input_add( fd, B_EV_IO_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d );
 	}
 	else
@@ -828,4 +833,8 @@
 	YList *m;
 	
+	if( g_strcasecmp( who, ic->acc->user ) == 0 )
+		/* WTF, Yahoo! seems to echo these now? */
+		return;
+	
 	inv = g_malloc( sizeof( struct byahoo_conf_invitation ) );
 	memset( inv, 0, sizeof( struct byahoo_conf_invitation ) );
Index: set.c
===================================================================
--- set.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ set.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -29,5 +29,5 @@
 char *SET_INVALID = "nee";
 
-set_t *set_add( set_t **head, char *key, char *def, set_eval eval, void *data )
+set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data )
 {
 	set_t *s = set_find( head, key );
@@ -63,5 +63,5 @@
 }
 
-set_t *set_find( set_t **head, char *key )
+set_t *set_find( set_t **head, const char *key )
 {
 	set_t *s = *head;
@@ -77,5 +77,5 @@
 }
 
-char *set_getstr( set_t **head, char *key )
+char *set_getstr( set_t **head, const char *key )
 {
 	set_t *s = set_find( head, key );
@@ -87,5 +87,5 @@
 }
 
-int set_getint( set_t **head, char *key )
+int set_getint( set_t **head, const char *key )
 {
 	char *s = set_getstr( head, key );
@@ -101,5 +101,5 @@
 }
 
-int set_getbool( set_t **head, char *key )
+int set_getbool( set_t **head, const char *key )
 {
 	char *s = set_getstr( head, key );
@@ -111,5 +111,5 @@
 }
 
-int set_setstr( set_t **head, char *key, char *value )
+int set_setstr( set_t **head, const char *key, char *value )
 {
 	set_t *s = set_find( head, key );
@@ -150,5 +150,5 @@
 }
 
-int set_setint( set_t **head, char *key, int value )
+int set_setint( set_t **head, const char *key, int value )
 {
 	char s[24];	/* Not quite 128-bit clean eh? ;-) */
@@ -158,5 +158,5 @@
 }
 
-void set_del( set_t **head, char *key )
+void set_del( set_t **head, const char *key )
 {
 	set_t *s = *head, *t = NULL;
@@ -182,5 +182,5 @@
 }
 
-int set_reset( set_t **head, char *key )
+int set_reset( set_t **head, const char *key )
 {
 	set_t *s;
@@ -211,4 +211,19 @@
 {
 	return is_bool( value ) ? value : SET_INVALID;
+}
+
+char *set_eval_list( set_t *set, char *value )
+{
+	GSList *options = set->eval_data, *opt;
+	
+	for( opt = options; opt; opt = opt->next )
+		if( strcmp( value, opt->data ) == 0 )
+			return value;
+	
+	/* TODO: It'd be nice to show the user a list of allowed values,
+	         but we don't have enough context here to do that. May
+	         want to fix that. */
+	
+	return NULL;
 }
 
Index: set.h
===================================================================
--- set.h	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ set.h	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -69,32 +69,36 @@
 	   set_setstr() should be able to free() the returned string! */
 	set_eval eval;
+	void *eval_data;
 	struct set *next;
 } set_t;
 
 /* Should be pretty clear. */
-set_t *set_add( set_t **head, char *key, char *def, set_eval eval, void *data );
+set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data );
 
 /* Returns the raw set_t. Might be useful sometimes. */
-set_t *set_find( set_t **head, char *key );
+set_t *set_find( set_t **head, const char *key );
 
 /* Returns a pointer to the string value of this setting. Don't modify the
    returned string, and don't free() it! */
-G_MODULE_EXPORT char *set_getstr( set_t **head, char *key );
+G_MODULE_EXPORT char *set_getstr( set_t **head, const char *key );
 
 /* Get an integer. In previous versions set_getint() was also used to read
    boolean values, but this SHOULD be done with set_getbool() now! */
-G_MODULE_EXPORT int set_getint( set_t **head, char *key );
-G_MODULE_EXPORT int set_getbool( set_t **head, char *key );
+G_MODULE_EXPORT int set_getint( set_t **head, const char *key );
+G_MODULE_EXPORT int set_getbool( set_t **head, const char *key );
 
 /* set_setstr() strdup()s the given value, so after using this function
    you can free() it, if you want. */
-int set_setstr( set_t **head, char *key, char *value );
-int set_setint( set_t **head, char *key, int value );
-void set_del( set_t **head, char *key );
-int set_reset( set_t **head, char *key );
+int set_setstr( set_t **head, const char *key, char *value );
+int set_setint( set_t **head, const char *key, int value );
+void set_del( set_t **head, const char *key );
+int set_reset( set_t **head, const char *key );
 
 /* Two very useful generic evaluators. */
 char *set_eval_int( set_t *set, char *value );
 char *set_eval_bool( set_t *set, char *value );
+
+/* Another more complicated one. */
+char *set_eval_list( set_t *set, char *value );
 
 /* Some not very generic evaluators that really shouldn't be here... */
Index: storage_xml.c
===================================================================
--- storage_xml.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ storage_xml.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -496,4 +496,5 @@
 		goto write_error;
 	
+	fsync( fd );
 	close( fd );
 	
Index: unix.c
===================================================================
--- unix.c	(revision ba7d16f3c90de2744243efe6373ccebe51cfcb5a)
+++ unix.c	(revision 51a799ef9c2a144bf3edc58130bfc1e275cdd53f)
@@ -56,4 +56,5 @@
 	
 	log_init();
+	
 	global.conf_file = g_strdup( CONF_FILE_DEF );
 	global.conf = conf_load( argc, argv );
@@ -62,8 +63,17 @@
 	
 	b_main_init();
-	nogaim_init();
 	
 	srand( time( NULL ) ^ getpid() );
+	
 	global.helpfile = g_strdup( HELP_FILE );
+	if( help_init( &global.help, global.helpfile ) == NULL )
+		log_message( LOGLVL_WARNING, "Error opening helpfile %s.", HELP_FILE );
+
+	global.storage = storage_init( global.conf->primary_storage, global.conf->migrate_storage );
+	if( global.storage == NULL )
+	{
+		log_message( LOGLVL_ERROR, "Unable to load storage backend '%s'", global.conf->primary_storage );
+		return( 1 );
+	}
 	
 	if( global.conf->runmode == RUNMODE_INETD )
@@ -73,17 +83,20 @@
 	
 		i = bitlbee_inetd_init();
-		log_message( LOGLVL_INFO, "Bitlbee %s starting in inetd mode.", BITLBEE_VERSION );
+		log_message( LOGLVL_INFO, "BitlBee %s starting in inetd mode.", BITLBEE_VERSION );
 
 	}
 	else if( global.conf->runmode == RUNMODE_DAEMON )
 	{
-		log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG );
-		log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG );
+		log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE );
+		log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE );
 
 		i = bitlbee_daemon_init();
-		log_message( LOGLVL_INFO, "Bitlbee %s starting in daemon mode.", BITLBEE_VERSION );
+		log_message( LOGLVL_INFO, "BitlBee %s starting in daemon mode.", BITLBEE_VERSION );
 	}
 	else if( global.conf->runmode == RUNMODE_FORKDAEMON )
 	{
+		log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE );
+		log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE );
+
 		/* In case the operator requests a restart, we need this. */
 		old_cwd = g_malloc( 256 );
@@ -96,5 +109,5 @@
 		
 		i = bitlbee_daemon_init();
-		log_message( LOGLVL_INFO, "Bitlbee %s starting in forking daemon mode.", BITLBEE_VERSION );
+		log_message( LOGLVL_INFO, "BitlBee %s starting in forking daemon mode.", BITLBEE_VERSION );
 	}
 	if( i != 0 )
@@ -113,11 +126,4 @@
 			setuid( pw->pw_uid );
 		}
-	}
-
-	global.storage = storage_init( global.conf->primary_storage, global.conf->migrate_storage );
-	if( global.storage == NULL )
-	{
-		log_message( LOGLVL_ERROR, "Unable to load storage backend '%s'", global.conf->primary_storage );
-		return( 1 );
 	}
  	
@@ -139,6 +145,4 @@
 	if( !getuid() || !geteuid() )
 		log_message( LOGLVL_WARNING, "BitlBee is running with root privileges. Why?" );
-	if( help_init( &global.help, global.helpfile ) == NULL )
-		log_message( LOGLVL_WARNING, "Error opening helpfile %s.", HELP_FILE );
 	
 	b_main_run();
