Index: Makefile
===================================================================
--- Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -10,6 +10,6 @@
 
 # Program variables
-objects = account.o bitlbee.o chat.o crypting.o help.o ipc.o irc.o irc_commands.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) user.o
-headers = account.h bitlbee.h commands.h conf.h config.h crypting.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h lib/events.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/nogaim.h
+objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS)
+headers = bitlbee.h commands.h conf.h config.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h lib/events.h lib/ftutil.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/account.h protocols/bee.h protocols/ft.h protocols/nogaim.h
 subdirs = lib protocols
 
@@ -26,5 +26,4 @@
 # Expansion of variables
 subdirobjs = $(foreach dir,$(subdirs),$(dir)/$(dir).o)
-CFLAGS += -Wall
 
 all: $(OUTFILE)
@@ -82,5 +81,6 @@
 install-dev:
 	mkdir -p $(DESTDIR)$(INCLUDEDIR)
-	install -m 0644 $(headers) $(DESTDIR)$(INCLUDEDIR)
+	install -m 0644 config.h $(DESTDIR)$(INCLUDEDIR)
+	for i in $(headers); do install -m 0644 $(SRCDIR)$$i $(DESTDIR)$(INCLUDEDIR); done
 	mkdir -p $(DESTDIR)$(PCDIR)
 	install -m 0644 bitlbee.pc $(DESTDIR)$(PCDIR)
@@ -93,6 +93,6 @@
 install-etc:
 	mkdir -p $(DESTDIR)$(ETCDIR)
-	install -m 0644 motd.txt $(DESTDIR)$(ETCDIR)/motd.txt
-	install -m 0644 bitlbee.conf $(DESTDIR)$(ETCDIR)/bitlbee.conf
+	install -m 0644 $(SRCDIR)motd.txt $(DESTDIR)$(ETCDIR)/motd.txt
+	install -m 0644 $(SRCDIR)bitlbee.conf $(DESTDIR)$(ETCDIR)/bitlbee.conf
 
 uninstall-etc:
@@ -110,5 +110,5 @@
 	@$(MAKE) -C $@ $(MAKECMDGOALS)
 
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
 	@echo '*' Compiling $<
 	@$(CC) -c $(CFLAGS) $< -o $@
@@ -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: count.c
===================================================================
--- account.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,359 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2010 Wilmer van der Gaast and others                *
-  \********************************************************************/
-
-/* Account management functions                                         */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-
-#define BITLBEE_CORE
-#include "bitlbee.h"
-#include "account.h"
-#include "chat.h"
-
-account_t *account_add( irc_t *irc, struct prpl *prpl, char *user, char *pass )
-{
-	account_t *a;
-	set_t *s;
-	
-	if( irc->accounts )
-	{
-		for( a = irc->accounts; a->next; a = a->next );
-		a = a->next = g_new0( account_t, 1 );
-	}
-	else
-	{
-		irc->accounts = a = g_new0 ( account_t, 1 );
-	}
-	
-	a->prpl = prpl;
-	a->user = g_strdup( user );
-	a->pass = g_strdup( pass );
-	a->auto_connect = 1;
-	a->irc = irc;
-	
-	s = set_add( &a->set, "auto_connect", "true", set_eval_account, a );
-	s->flags |= ACC_SET_NOSAVE;
-	
-	s = set_add( &a->set, "auto_reconnect", "true", set_eval_bool, a );
-	
-	s = set_add( &a->set, "nick_source", "handle", NULL, a );
-	
-	s = set_add( &a->set, "password", NULL, set_eval_account, a );
-	s->flags |= ACC_SET_NOSAVE | SET_NULL_OK;
-	
-	s = set_add( &a->set, "username", NULL, set_eval_account, a );
-	s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
-	set_setstr( &a->set, "username", user );
-	
-	a->nicks = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free );
-	
-	/* This function adds some more settings (and might want to do more
-	   things that have to be done now, although I can't think of anything. */
-	if( prpl->init )
-		prpl->init( a );
-	
-	s = set_add( &a->set, "away", NULL, set_eval_account, a );
-	s->flags |= SET_NULL_OK;
-	
-	if( a->flags & ACC_FLAG_STATUS_MESSAGE )
-	{
-		s = set_add( &a->set, "status", NULL, set_eval_account, a );
-		s->flags |= SET_NULL_OK;
-	}
-	
-	return a;
-}
-
-char *set_eval_account( set_t *set, char *value )
-{
-	account_t *acc = set->data;
-	
-	/* Double-check: We refuse to edit on-line accounts. */
-	if( set->flags & ACC_SET_OFFLINE_ONLY && acc->ic )
-		return SET_INVALID;
-	
-	if( strcmp( set->key, "server" ) == 0 )
-	{
-		g_free( acc->server );
-		if( value && *value )
-		{
-			acc->server = g_strdup( value );
-			return value;
-		}
-		else
-		{
-			acc->server = g_strdup( set->def );
-			return g_strdup( set->def );
-		}
-	}
-	else if( strcmp( set->key, "username" ) == 0 )
-	{
-		g_free( acc->user );
-		acc->user = g_strdup( value );
-		return value;
-	}
-	else if( strcmp( set->key, "password" ) == 0 )
-	{
-		if( value )
-		{
-			g_free( acc->pass );
-			acc->pass = g_strdup( value );
-			return NULL;	/* password shouldn't be visible in plaintext! */
-		}
-		else
-		{
-			/* NULL can (should) be stored in the set_t
-			   variable, but is otherwise not correct. */
-			return SET_INVALID;
-		}
-	}
-	else if( strcmp( set->key, "auto_connect" ) == 0 )
-	{
-		if( !is_bool( value ) )
-			return SET_INVALID;
-		
-		acc->auto_connect = bool2int( value );
-		return value;
-	}
-	else if( strcmp( set->key, "away" ) == 0 ||
-	         strcmp( set->key, "status" ) == 0 )
-	{
-		if( acc->ic && acc->ic->flags & OPT_LOGGED_IN )
-		{
-			/* If we're currently on-line, set the var now already
-			   (bit of a hack) and send an update. */
-			g_free( set->value );
-			set->value = g_strdup( value );
-			
-			imc_away_send_update( acc->ic );
-		}
-		
-		return value;
-	}
-	
-	return SET_INVALID;
-}
-
-account_t *account_get( irc_t *irc, char *id )
-{
-	account_t *a, *ret = NULL;
-	char *handle, *s;
-	int nr;
-	
-	/* This checks if the id string ends with (...) */
-	if( ( handle = strchr( id, '(' ) ) && ( s = strchr( handle, ')' ) ) && s[1] == 0 )
-	{
-		struct prpl *proto;
-		
-		*s = *handle = 0;
-		handle ++;
-		
-		if( ( proto = find_protocol( id ) ) )
-		{
-			for( a = irc->accounts; a; a = a->next )
-				if( a->prpl == proto &&
-				    a->prpl->handle_cmp( handle, a->user ) == 0 )
-					ret = a;
-		}
-		
-		/* Restore the string. */
-		handle --;
-		*handle = '(';
-		*s = ')';
-		
-		if( ret )
-			return ret;
-	}
-	
-	if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 )
-	{
-		for( a = irc->accounts; a; a = a->next )
-			if( ( nr-- ) == 0 )
-				return( a );
-		
-		return( NULL );
-	}
-	
-	for( a = irc->accounts; a; a = a->next )
-	{
-		if( g_strcasecmp( id, a->prpl->name ) == 0 )
-		{
-			if( !ret )
-				ret = a;
-			else
-				return( NULL ); /* We don't want to match more than one... */
-		}
-		else if( strstr( a->user, id ) )
-		{
-			if( !ret )
-				ret = a;
-			else
-				return( NULL );
-		}
-	}
-	
-	return( ret );
-}
-
-void account_del( irc_t *irc, account_t *acc )
-{
-	account_t *a, *l = NULL;
-	struct chat *c, *nc;
-	
-	if( acc->ic )
-		/* Caller should have checked, accounts still in use can't be deleted. */
-		return;
-	
-	for( a = irc->accounts; a; a = (l=a)->next )
-		if( a == acc )
-		{
-			if( l )
-				l->next = a->next;
-			else
-				irc->accounts = a->next;
-			
-			for( c = irc->chatrooms; c; c = nc )
-			{
-				nc = c->next;
-				if( acc == c->acc )
-					chat_del( irc, c );
-			}
-			
-			while( a->set )
-				set_del( &a->set, a->set->key );
-			
-			g_hash_table_destroy( a->nicks );
-			
-			g_free( a->user );
-			g_free( a->pass );
-			g_free( a->server );
-			if( a->reconnect )	/* This prevents any reconnect still queued to happen */
-				cancel_auto_reconnect( a );
-			g_free( a );
-			
-			break;
-		}
-}
-
-void account_on( irc_t *irc, account_t *a )
-{
-	if( a->ic )
-	{
-		/* Trying to enable an already-enabled account */
-		return;
-	}
-	
-	cancel_auto_reconnect( a );
-	
-	a->reconnect = 0;
-	a->prpl->login( a );
-}
-
-void account_off( irc_t *irc, account_t *a )
-{
-	imc_logout( a->ic, FALSE );
-	a->ic = NULL;
-	if( a->reconnect )
-	{
-		/* Shouldn't happen */
-		cancel_auto_reconnect( a );
-	}
-}
-
-struct account_reconnect_delay
-{
-	int start;
-	char op;
-	int step;
-	int max;
-};
-
-int account_reconnect_delay_parse( char *value, struct account_reconnect_delay *p )
-{
-	memset( p, 0, sizeof( *p ) );
-	/* A whole day seems like a sane "maximum maximum". */
-	p->max = 86400;
-	
-	/* Format: /[0-9]+([*+][0-9]+(<[0-9+])?)?/ */
-	while( *value && isdigit( *value ) )
-		p->start = p->start * 10 + *value++ - '0';
-	
-	/* Sure, call me evil for implementing my own fscanf here, but it's
-	   dead simple and I immediately know where to continue parsing. */
-	
-	if( *value == 0 )
-		/* If the string ends now, the delay is constant. */
-		return 1;
-	else if( *value != '+' && *value != '*' )
-		/* Otherwise allow either a + or a * */
-		return 0;
-	
-	p->op = *value++;
-	
-	/* + or * the delay by this number every time. */
-	while( *value && isdigit( *value ) )
-		p->step = p->step * 10 + *value++ - '0';
-	
-	if( *value == 0 )
-		/* Use the default maximum (one day). */
-		return 1;
-	else if( *value != '<' )
-		return 0;
-	
-	p->max = 0;
-	value ++;
-	while( *value && isdigit( *value ) )
-		p->max = p->max * 10 + *value++ - '0';
-	
-	return p->max > 0;
-}
-
-char *set_eval_account_reconnect_delay( set_t *set, char *value )
-{
-	struct account_reconnect_delay p;
-	
-	return account_reconnect_delay_parse( value, &p ) ? value : SET_INVALID;
-}
-
-int account_reconnect_delay( account_t *a )
-{
-	char *setting = set_getstr( &a->irc->set, "auto_reconnect_delay" );
-	struct account_reconnect_delay p;
-	
-	if( account_reconnect_delay_parse( setting, &p ) )
-	{
-		if( a->auto_reconnect_delay == 0 )
-			a->auto_reconnect_delay = p.start;
-		else if( p.op == '+' )
-			a->auto_reconnect_delay += p.step;
-		else if( p.op == '*' )
-			a->auto_reconnect_delay *= p.step;
-		
-		if( a->auto_reconnect_delay > p.max )
-			a->auto_reconnect_delay = p.max;
-	}
-	else
-	{
-		a->auto_reconnect_delay = 0;
-	}
-	
-	return a->auto_reconnect_delay;
-}
Index: count.h
===================================================================
--- account.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,72 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2004 Wilmer van der Gaast and others                *
-  \********************************************************************/
-
-/* Account management functions                                         */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-
-#ifndef _ACCOUNT_H
-#define _ACCOUNT_H
-
-typedef struct account
-{
-	struct prpl *prpl;
-	char *user;
-	char *pass;
-	char *server;
-	
-	int auto_connect;
-	int auto_reconnect_delay;
-	int reconnect;
-	int flags;
-	
-	set_t *set;
-	GHashTable *nicks;
-	
-	struct irc *irc;
-	struct im_connection *ic;
-	struct account *next;
-} account_t;
-
-account_t *account_add( irc_t *irc, struct prpl *prpl, char *user, char *pass );
-account_t *account_get( irc_t *irc, char *id );
-void account_del( irc_t *irc, account_t *acc );
-void account_on( irc_t *irc, account_t *a );
-void account_off( irc_t *irc, account_t *a );
-
-char *set_eval_account( set_t *set, char *value );
-char *set_eval_account_reconnect_delay( set_t *set, char *value );
-int account_reconnect_delay( account_t *a );
-
-typedef enum
-{
-	ACC_SET_NOSAVE = 0x01,          /* Don't save this setting (i.e. stored elsewhere). */
-	ACC_SET_OFFLINE_ONLY = 0x02,    /* Allow changes only if the acct is offline. */
-	ACC_SET_ONLINE_ONLY = 0x04,     /* Allow changes only if the acct is online. */
-} account_set_flag_t;
-
-typedef enum
-{
-	ACC_FLAG_AWAY_MESSAGE = 0x01,   /* Supports away messages instead of just states. */
-	ACC_FLAG_STATUS_MESSAGE = 0x02, /* Supports status messages (without being away). */
-} account_flag_t;
-
-#endif
Index: bitlbee.c
===================================================================
--- bitlbee.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ bitlbee.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 );
 }
@@ -283,8 +318,10 @@
 			struct bitlbee_child *child;
 			
+			/* TODO: Stuff like this belongs in ipc.c. */
 			child = g_new0( struct bitlbee_child, 1 );
 			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->to_fd = -1;
 			child_list = g_slist_append( child_list, child );
 			
@@ -314,5 +351,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] );
@@ -335,5 +372,6 @@
 	/* Try to save data for all active connections (if desired). */
 	while( irc_connection_list != NULL )
-		irc_free( irc_connection_list->data );
+		irc_abort( irc_connection_list->data, TRUE,
+		           "BitlBee server shutting down" );
 	
 	/* We'll only reach this point when not running in inetd mode: */
Index: bitlbee.h
===================================================================
--- bitlbee.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ bitlbee.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -35,11 +35,13 @@
 
 #define PACKAGE "BitlBee"
-#define BITLBEE_VERSION "1.2.5"
+#define BITLBEE_VERSION "1.3dev"
 #define VERSION BITLBEE_VERSION
+#define BITLBEE_VER(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+#define BITLBEE_VERSION_CODE BITLBEE_VER(1, 3, 0)
 
 #define MAX_STRING 511
 
 #if HAVE_CONFIG_H
-#include "config.h"
+#include <config.h>
 #endif
 
@@ -124,4 +126,5 @@
 #define CONF_FILE_DEF ETCDIR "bitlbee.conf"
 
+#include "bee.h"
 #include "irc.h"
 #include "storage.h"
@@ -158,6 +161,7 @@
 gboolean bitlbee_io_current_client_write( gpointer data, gint source, b_input_condition cond );
 
-void root_command_string( irc_t *irc, user_t *u, char *command, int flags );
+void root_command_string( irc_t *irc, char *command );
 void root_command( irc_t *irc, char *command[] );
+gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond );
 gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond );
 
Index: at.c
===================================================================
--- chat.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,192 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2008 Wilmer van der Gaast and others                *
-  \********************************************************************/
-
-/* Keep track of chatrooms the user is interested in                    */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-
-#include "bitlbee.h"
-#include "chat.h"
-
-struct chat *chat_add( irc_t *irc, account_t *acc, char *handle, char *channel )
-{
-	struct chat *c, *l;
-	set_t *s;
-
-	if( acc->prpl->chat_join == NULL || !chat_chanok( channel ) ||
-	    chat_chancmp( channel, irc->channel ) == 0 )
-	{
-		return NULL;
-	}
-	
-	for( c = irc->chatrooms; c; c = c->next )
-	{
-		if( chat_chancmp( channel, c->channel ) == 0 )
-			return NULL;
-		
-		if( acc == c->acc && g_strcasecmp( handle, c->handle ) == 0 )
-			return NULL;
-		
-		l = c;
-	}
-	
-	if( irc->chatrooms == NULL )
-		irc->chatrooms = c = g_new0( struct chat, 1 );
-	else
-		l->next = c = g_new0( struct chat, 1 );
-	
-	c->acc = acc;
-	c->handle = g_strdup( handle );
-	c->channel = g_strdup( channel );
-	
-	s = set_add( &c->set, "auto_join", "false", set_eval_bool, c );
-	/* s = set_add( &c->set, "auto_rejoin", "false", set_eval_bool, c ); */
-	s = set_add( &c->set, "nick", NULL, NULL, c );
-	s->flags |= SET_NULL_OK;
-	
-	return c;
-}
-
-struct chat *chat_byhandle( irc_t *irc, account_t *acc, char *handle )
-{
-	struct chat *c;
-	
-	for( c = irc->chatrooms; c; c = c->next )
-	{
-		if( acc == c->acc && g_strcasecmp( handle, c->handle ) == 0 )
-			break;
-	}
-	
-	return c;
-}
-
-struct chat *chat_bychannel( irc_t *irc, char *channel )
-{
-	struct chat *c;
-	
-	for( c = irc->chatrooms; c; c = c->next )
-	{
-		if( chat_chancmp( channel, c->channel ) == 0 )
-			break;
-	}
-	
-	return c;
-}
-
-struct chat *chat_get( irc_t *irc, char *id )
-{
-	struct chat *c, *ret = NULL;
-	int nr;
-	
-	if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 )
-	{
-		for( c = irc->chatrooms; c; c = c->next )
-			if( ( nr-- ) == 0 )
-				return c;
-		
-		return NULL;
-	}
-	
-	for( c = irc->chatrooms; c; c = c->next )
-	{
-		if( strstr( c->handle, id ) )
-		{
-			if( !ret )
-				ret = c;
-			else
-				return NULL;
-		}
-		else if( strstr( c->channel, id ) )
-		{
-			if( !ret )
-				ret = c;
-			else
-				return NULL;
-		}
-	}
-	
-	return ret;
-}
-
-int chat_del( irc_t *irc, struct chat *chat )
-{
-	struct chat *c, *l = NULL;
-	
-	for( c = irc->chatrooms; c; c = (l=c)->next )
-		if( c == chat )
-			break;
-	
-	if( c == NULL )
-		return 0;
-	else if( l == NULL )
-		irc->chatrooms = c->next;
-	else
-		l->next = c->next;
-	
-	while( c->set )
-		set_del( &c->set, c->set->key );
-	
-	g_free( c->handle );
-	g_free( c->channel );
-	g_free( c );
-	
-	return 1;
-}
-
-int chat_chancmp( char *a, char *b )
-{
-	if( !chat_chanok( a ) || !chat_chanok( b ) )
-		return 0;
-	
-	if( a[0] == b[0] )
-		return nick_cmp( a + 1, b + 1 );
-	else
-		return -1;
-}
-
-int chat_chanok( char *a )
-{
-	if( strchr( CTYPES, a[0] ) != NULL )
-		return nick_ok( a + 1 );
-	else
-		return 0;
-}
-
-int chat_join( irc_t *irc, struct chat *c, const char *password )
-{
-	struct groupchat *gc;
-	char *nick = set_getstr( &c->set, "nick" );
-
-	if( c->acc->ic == NULL || c->acc->prpl->chat_join == NULL )
-		return 0;
-	
-	if( nick == NULL )
-		nick = irc->nick;
-	
-	if( ( gc = c->acc->prpl->chat_join( c->acc->ic, c->handle, nick, password ) ) )
-	{
-		g_free( gc->channel );
-		gc->channel = g_strdup( c->channel );
-		return 1;
-	}
-	
-	return 0;
-}
Index: at.h
===================================================================
--- chat.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,51 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2008 Wilmer van der Gaast and others                *
-  \********************************************************************/
-
-/* Keep track of chatrooms the user is interested in                    */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-
-#ifndef _CHAT_H
-#define _CHAT_H
-
-struct chat
-{
-	account_t *acc;
-	
-	char *handle;
-	char *channel;
-	set_t *set;
-	
-	struct chat *next;
-};
-
-struct chat *chat_add( irc_t *irc, account_t *acc, char *handle, char *channel );
-struct chat *chat_byhandle( irc_t *irc, account_t *acc, char *handle );
-struct chat *chat_bychannel( irc_t *irc, char *channel );
-struct chat *chat_get( irc_t *irc, char *id );
-int chat_del( irc_t *irc, struct chat *chat );
-
-int chat_chancmp( char *a, char *b );
-int chat_chanok( char *a );
-
-int chat_join( irc_t *irc, struct chat *c, const char *password );
-
-#endif
Index: conf.c
===================================================================
--- conf.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ conf.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -63,4 +63,7 @@
 	conf->ping_timeout = 300;
 	conf->user = NULL;
+	conf->ft_max_size = SIZE_MAX;
+	conf->ft_max_kbps = G_MAXUINT;
+	conf->ft_listen = NULL;
 	conf->protocols = NULL;
 	proxytype = 0;
@@ -79,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:hu:V" ) ) >= 0 )
 	/*     ^^^^ Just to make sure we skip this step from the REHASH handler. */
 	{
@@ -145,13 +148,13 @@
 			        "  -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 == 'R' )
-		{
-			/* Backward compatibility; older BitlBees passed this
-			   info using a command-line flag. Allow people to
-			   upgrade from such a version for now. */
-			setenv( "_BITLBEE_RESTART_STATE", optarg, 0 );
+		else if( opt == 'V' )
+		{
+			printf( "BitlBee %s\nAPI version %06x\n",
+			        BITLBEE_VERSION, BITLBEE_VERSION_CODE );
+			return NULL;
 		}
 		else if( opt == 'u' )
@@ -308,4 +311,28 @@
 				conf->user = g_strdup( ini->value );
 			}
+			else if( g_strcasecmp( ini->key, "ft_max_size" ) == 0 )
+			{
+				size_t ft_max_size;
+				if( sscanf( ini->value, "%zu", &ft_max_size ) != 1 )
+				{
+					fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value );
+					return 0;
+				}
+				conf->ft_max_size = ft_max_size;
+			}
+			else if( g_strcasecmp( ini->key, "ft_max_kbps" ) == 0 )
+			{
+				if( sscanf( ini->value, "%d", &i ) != 1 )
+				{
+					fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value );
+					return 0;
+				}
+				conf->ft_max_kbps = i;
+			}
+			else if( g_strcasecmp( ini->key, "ft_listen" ) == 0 )
+			{
+				g_free( conf->ft_listen );
+				conf->ft_listen = g_strdup( ini->value );
+			}
 			else if( g_strcasecmp( ini->key, "protocols" ) == 0 )
 			{
@@ -342,5 +369,5 @@
 		if( g_strcasecmp( ini->section, "defaults" ) == 0 )
 		{
-			set_t *s = set_find( &irc->set, ini->key );
+			set_t *s = set_find( &irc->b->set, ini->key );
 			
 			if( s )
Index: conf.h
===================================================================
--- conf.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ conf.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -50,4 +50,7 @@
 	int ping_timeout;
 	char *user;
+	size_t ft_max_size;
+	int ft_max_kbps;
+	char *ft_listen;
 	char **protocols;
 } conf_t;
Index: configure
===================================================================
--- configure	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ configure	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -26,4 +26,7 @@
 oscar=1
 yahoo=1
+twitter=1
+twitter=1
+purple=0
 
 debug=0
@@ -66,4 +69,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
@@ -119,4 +125,27 @@
 EOF
 
+srcdir="$(dirname $0)"
+if [ "$srcdir" != "." ]; then
+	echo
+	echo "configure script run from a different directory. Will create some symlinks..."
+	if [ ! -e Makefile -o -L Makefile ]; then
+		COPYDIRS="doc lib protocols tests utils"
+		mkdir -p $(cd "$srcdir"; find $COPYDIRS -type d)
+		find . -name Makefile -type l -print0 | xargs -0 rm 2> /dev/null
+		dst="$PWD"
+		cd "$srcdir"
+		for i in $(find . -name Makefile -type f); do
+			ln -s "$PWD${i#.}" "$dst/$i";
+		done
+		cd "$dst"
+		rm -rf .bzr
+	fi
+	
+	echo "SRCDIR=$srcdir/" >> Makefile.settings
+	CFLAGS="$CFLAGS -I${dst}"
+else
+	srcdir=$PWD
+fi
+
 cat<<EOF>config.h
 /* BitlBee settings, generated by configure
@@ -155,6 +184,6 @@
 fi
 
-echo CFLAGS=$CFLAGS >> Makefile.settings
-echo CFLAGS+=-I`pwd` -I`pwd`/lib -I`pwd`/protocols -I. >> Makefile.settings
+echo CFLAGS=$CFLAGS $CPPFLAGS >> Makefile.settings
+echo CFLAGS+=-I${srcdir} -I${srcdir}/lib -I${srcdir}/protocols -I. >> Makefile.settings
 
 echo CFLAGS+=-DHAVE_CONFIG_H >> Makefile.settings
@@ -172,4 +201,8 @@
 
 echo "CC=$CC" >> Makefile.settings;
+if echo $CC | grep -qw gcc; then
+	# Apparently -Wall is gcc-specific?
+	echo CFLAGS+=-Wall >> Makefile.settings
+fi
 
 if [ -z "$LD" ]; then
@@ -267,5 +300,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 +328,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 +342,5 @@
 detect_resolv_static()
 {
-	TMPFILE=$(mktemp)
+	TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
 	ret=1
 	for i in $systemlibdirs; do
@@ -396,5 +429,5 @@
 fi
 
-STORAGES="text xml"
+STORAGES="xml"
 
 if [ "$ldap" = "auto" ]; then
@@ -476,4 +509,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 +539,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 +603,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: ypting.c
===================================================================
--- crypting.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,133 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2004 Sjoerd Hemminga and others                     *
-  \********************************************************************/
-
-/* A little bit of encryption for the users' passwords                  */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-
-/* [WvG] This file can also be compiled into a stand-alone program
-   which can encode/decode BitlBee account files. The main() will be
-   included if CRYPTING_MAIN is defined. Or just do "make decode" and
-   the programs will be built. */
-
-#include <bitlbee.h>
-#include "md5.h"
-#include "crypting.h"
-
-/*\
- * [SH] Do _not_ call this if it's not entirely sure that it will not cause
- * harm to another users file, since this does not check the password for
- * correctness.
-\*/
-
-int checkpass (const char *pass, const char *md5sum)
-{
-	md5_state_t md5state;
-	md5_byte_t digest[16];
-	int i, j;
-	char digits[3];
-	
-	md5_init (&md5state);
-	md5_append (&md5state, (unsigned char *)pass, strlen (pass));
-	md5_finish (&md5state, digest);
-	
-	for (i = 0, j = 0; i < 16; i++, j += 2) {
-		/* Check password for correctness */
-		g_snprintf (digits, sizeof (digits), "%02x\n", digest[i]);
-		
-		if (digits[0] != md5sum[j]) return (-1);
-		if (digits[1] != md5sum[j + 1]) return (-1);
-	}
-
-	return( 0 );
-}
-
-
-char *hashpass (const char *password)
-{
-	md5_state_t md5state;
-	md5_byte_t digest[16];
-	int i;
-	char digits[3];
-	char *rv;
-	
-	if (password == NULL) return (NULL);
-	
-	rv = g_new0 (char, 33);
-	
-	md5_init (&md5state);
-	md5_append (&md5state, (const unsigned char *)password, strlen (password));
-	md5_finish (&md5state, digest);
-	
-	for (i = 0; i < 16; i++) {
-		/* Build a hash of the pass */
-		g_snprintf (digits, sizeof (digits), "%02x", digest[i]);
-		strcat (rv, digits);
-	}
-	
-	return (rv);
-}
-
-char *obfucrypt (char *line, const char *password) 
-{
-	int i, j;
-	char *rv;
-	
-	if (password == NULL) return (NULL);
-	
-	rv = g_new0 (char, strlen (line) + 1);
-	
-	i = j = 0;
-	while (*line) {
-		/* Encrypt/obfuscate the line, using the password */
-		if (*(signed char*)line < 0) *line = - (*line);
-		
-		rv[j] = *line + password[i]; /* Overflow intended */
-		
-		line++;
-		if (!password[++i]) i = 0;
-		j++;
-	}
-	
-	return (rv);
-}
-
-char *deobfucrypt (char *line, const char *password) 
-{
-	int i, j;
-	char *rv;
-	
-	if (password == NULL) return (NULL);
-	
-	rv = g_new0 (char, strlen (line) + 1);
-	
-	i = j = 0;
-	while (*line) {
-		/* Decrypt/deobfuscate the line, using the pass */
-		rv[j] = *line - password[i]; /* Overflow intended */
-		
-		line++;
-		if (!password[++i]) i = 0;
-		j++;
-	}
-	
-	return (rv);
-}
Index: ypting.h
===================================================================
--- crypting.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,29 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2004 Sjoerd Hemminga and others                     *
-  \********************************************************************/
-
-/* A little bit of encryption for the users' passwords                  */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-
-int checkpass (const char *password, const char *md5sum);
-G_GNUC_MALLOC char *hashpass (const char *password);
-G_GNUC_MALLOC char *obfucrypt (char *line, const char *password);
-G_GNUC_MALLOC char *deobfucrypt (char *line, const char *password);
Index: dcc.c
===================================================================
--- dcc.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ dcc.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,566 @@
+/********************************************************************\
+* BitlBee -- An IRC to other IM-networks gateway                     *
+*                                                                    *
+* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                   *
+\********************************************************************/
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+#include "ft.h"
+#include "dcc.h"
+#include <netinet/tcp.h>
+#include <regex.h>
+#include "lib/ftutil.h"
+
+/* 
+ * Since that might be confusing a note on naming:
+ *
+ * Generic dcc functions start with 
+ *
+ * 	dcc_
+ *
+ * ,methods specific to DCC SEND start with
+ *
+ * 	dccs_
+ *
+ * . Since we can be on both ends of a DCC SEND,
+ * functions specific to one end are called
+ *
+ * 	dccs_send and dccs_recv
+ *
+ * ,respectively.
+ */
+
+
+/* 
+ * used to generate a unique local transfer id the user
+ * can use to reject/cancel transfers
+ */
+unsigned int local_transfer_id=1;
+
+/* 
+ * just for debugging the nr. of chunks we received from im-protocols and the total data
+ */
+unsigned int receivedchunks=0, receiveddata=0;
+
+void dcc_finish( file_transfer_t *file );
+void dcc_close( file_transfer_t *file );
+gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond );
+int dccs_send_request( struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr );
+gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond);
+gboolean dccs_recv_write_request( file_transfer_t *ft );
+gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond );
+gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... );
+
+dcc_file_transfer_t *dcc_alloc_transfer( const char *file_name, size_t file_size, struct im_connection *ic )
+{
+	file_transfer_t *file = g_new0( file_transfer_t, 1 );
+	dcc_file_transfer_t *df = file->priv = g_new0( dcc_file_transfer_t, 1 );
+	
+	file->file_size = file_size;
+	file->file_name = g_strdup( file_name );
+	file->local_id = local_transfer_id++;
+	file->ic = df->ic = ic;
+	df->ft = file;
+	
+	return df;
+}
+
+/* This is where the sending magic starts... */
+file_transfer_t *dccs_send_start( struct im_connection *ic, irc_user_t *iu, const char *file_name, size_t file_size )
+{
+	file_transfer_t *file;
+	dcc_file_transfer_t *df;
+	irc_t *irc = (irc_t *) ic->bee->ui_data;
+	struct sockaddr_storage saddr;
+	char *errmsg;
+	char host[HOST_NAME_MAX];
+	char port[6];
+
+	if( file_size > global.conf->ft_max_size )
+		return NULL;
+	
+	df = dcc_alloc_transfer( file_name, file_size, ic );
+	file = df->ft;
+	file->write = dccs_send_write;
+
+	/* listen and request */
+
+	if( ( df->fd = ft_listen( &saddr, host, port, irc->fd, TRUE, &errmsg ) ) == -1 )
+	{
+		dcc_abort( df, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg );
+		return NULL;
+	}
+
+	file->status = FT_STATUS_LISTENING;
+
+	if( !dccs_send_request( df, iu, &saddr ) )
+		return NULL;
+
+	/* watch */
+	df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_send_proto, df );
+
+	irc->file_transfers = g_slist_prepend( irc->file_transfers, file );
+
+	df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df );
+
+	imcb_log( ic, "File transfer request from %s for %s (%zd kb).\n"
+	              "Accept the file transfer if you'd like the file. If you don't, "
+	              "issue the 'transfer reject' command.",
+	              iu->nick, file_name, file_size / 1024 );
+
+	return file;
+}
+
+/* Used pretty much everywhere in the code to abort a transfer */
+gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... )
+{
+	file_transfer_t *file = df->ft;
+	va_list params;
+	va_start( params, reason );
+	char *msg = g_strdup_vprintf( reason, params );
+	va_end( params );
+	
+	file->status |= FT_STATUS_CANCELED;
+	
+	if( file->canceled )
+		file->canceled( file, msg );
+
+	imcb_log( df->ic, "File %s: DCC transfer aborted: %s", file->file_name, msg );
+
+	g_free( msg );
+
+	dcc_close( df->ft );
+
+	return FALSE;
+}
+
+gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond )
+{
+	struct dcc_file_transfer *df = data;
+
+	if( df->bytes_sent == df->progress_bytes_last )
+	{
+		/* no progress. cancel */
+		if( df->bytes_sent == 0 )
+			return dcc_abort( df, "Couldn't establish transfer within %d seconds", DCC_MAX_STALL );
+		else 
+			return dcc_abort( df, "Transfer stalled for %d seconds at %d kb", DCC_MAX_STALL, df->bytes_sent / 1024 );
+
+	}
+
+	df->progress_bytes_last = df->bytes_sent;
+
+	return TRUE;
+}
+
+/* used extensively for socket operations */
+#define ASSERTSOCKOP(op, msg) \
+	if( (op) == -1 ) \
+		return dcc_abort( df , msg ": %s", strerror( errno ) );
+
+/* Creates the "DCC SEND" line and sends it to the server */
+int dccs_send_request( struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr )
+{
+	char ipaddr[INET6_ADDRSTRLEN]; 
+	const void *netaddr;
+	int port;
+	char *cmd;
+
+	if( saddr->ss_family == AF_INET )
+	{
+		struct sockaddr_in *saddr_ipv4 = ( struct sockaddr_in *) saddr;
+
+		sprintf( ipaddr, "%d", 
+			 ntohl( saddr_ipv4->sin_addr.s_addr ) );
+		port = saddr_ipv4->sin_port;
+	}
+	else 
+	{
+		struct sockaddr_in6 *saddr_ipv6 = ( struct sockaddr_in6 *) saddr;
+
+		netaddr = &saddr_ipv6->sin6_addr.s6_addr;
+		port = saddr_ipv6->sin6_port;
+
+		/* 
+		 * Didn't find docs about this, but it seems that's the way irssi does it
+		 */
+		if( !inet_ntop( saddr->ss_family, netaddr, ipaddr, sizeof( ipaddr ) ) )
+			return dcc_abort( df, "inet_ntop failed: %s", strerror( errno ) );
+	}
+
+	port = ntohs( port );
+
+	cmd = g_strdup_printf( "\001DCC SEND %s %s %u %zu\001",
+				df->ft->file_name, ipaddr, port, df->ft->file_size );
+	
+	irc_send_msg_raw( iu, "PRIVMSG", iu->irc->user->nick, cmd );
+
+	g_free( cmd );
+
+	return TRUE;
+}
+
+/*
+ * After setup, the transfer itself is handled entirely by this function.
+ * There are basically four things to handle: connect, receive, send, and error.
+ */
+gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )
+{
+	dcc_file_transfer_t *df = data;
+	file_transfer_t *file = df->ft;
+	
+	if( ( cond & B_EV_IO_READ ) &&
+	    ( file->status & FT_STATUS_LISTENING ) )
+	{ 	
+		struct sockaddr *clt_addr;
+		socklen_t ssize = sizeof( clt_addr );
+
+		/* Connect */
+
+		ASSERTSOCKOP( df->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" );
+
+		closesocket( fd );
+		fd = df->fd;
+		file->status = FT_STATUS_TRANSFERRING;
+		sock_make_nonblocking( fd );
+
+		/* IM protocol callback */
+		if( file->accept )
+			file->accept( file );
+
+		/* reschedule for reading on new fd */
+		df->watch_in = b_input_add( fd, B_EV_IO_READ, dccs_send_proto, df );
+
+		return FALSE;
+	}
+
+	if( cond & B_EV_IO_READ ) 
+	{
+		int ret;
+		
+		ASSERTSOCKOP( ret = recv( fd, ( (char*) &df->acked ) + df->acked_len,
+			sizeof( df->acked ) - df->acked_len, 0 ), "Receiving" );
+
+		if( ret == 0 )
+			return dcc_abort( df, "Remote end closed connection" );
+		
+		/* How likely is it that a 32-bit integer gets split accross
+		   packet boundaries? Chances are rarely 0 so let's be sure. */
+		if( ( df->acked_len = ( df->acked_len + ret ) % 4 ) > 0 )
+			return TRUE;
+
+		df->acked = ntohl( df->acked );
+
+		/* If any of this is actually happening, the receiver should buy a new IRC client */
+
+		if ( df->acked > df->bytes_sent )
+			return dcc_abort( df, "Receiver magically received more bytes than sent ( %d > %d ) (BUG at receiver?)", df->acked, df->bytes_sent );
+
+		if ( df->acked < file->bytes_transferred )
+			return dcc_abort( df, "Receiver lost bytes? ( has %d, had %d ) (BUG at receiver?)", df->acked, file->bytes_transferred );
+		
+		file->bytes_transferred = df->acked;
+	
+		if( file->bytes_transferred >= file->file_size ) {
+			if( df->proto_finished )
+				dcc_finish( file );
+			return FALSE;
+		}
+	
+		return TRUE;
+	}
+
+	return TRUE;
+}
+
+gboolean dccs_recv_start( file_transfer_t *ft )
+{
+	dcc_file_transfer_t *df = ft->priv;
+	struct sockaddr_storage *saddr = &df->saddr;
+	int fd;
+	char ipaddr[INET6_ADDRSTRLEN]; 
+	socklen_t sa_len = saddr->ss_family == AF_INET ? 
+		sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 );
+	
+	if( !ft->write )
+		return dcc_abort( df, "BUG: protocol didn't register write()" );
+	
+	ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening Socket" );
+
+	sock_make_nonblocking( fd );
+
+	if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) &&
+	    ( errno != EINPROGRESS ) )
+		return dcc_abort( df, "Connecting to %s:%d : %s", 
+			inet_ntop( saddr->ss_family, 
+				saddr->ss_family == AF_INET ? 
+				    ( void* ) &( ( struct sockaddr_in *) saddr )->sin_addr.s_addr :
+				    ( void* ) &( ( struct sockaddr_in6 *) saddr )->sin6_addr.s6_addr,
+				ipaddr, 
+				sizeof( ipaddr ) ),
+			ntohs( saddr->ss_family == AF_INET ?
+			    ( ( struct sockaddr_in *) saddr )->sin_port :
+			    ( ( struct sockaddr_in6 *) saddr )->sin6_port ),
+			strerror( errno ) );
+
+	ft->status = FT_STATUS_CONNECTING;
+
+	/* watch */
+	df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_recv_proto, df );
+	ft->write_request = dccs_recv_write_request;
+
+	df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df );
+
+	return TRUE;
+}
+
+gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond )
+{
+	dcc_file_transfer_t *df = data;
+	file_transfer_t *ft = df->ft;
+
+	if( ( cond & B_EV_IO_WRITE ) &&
+	    ( ft->status & FT_STATUS_CONNECTING ) )
+	{
+		ft->status = FT_STATUS_TRANSFERRING;
+
+		//df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
+
+		df->watch_out = 0;
+		return FALSE;
+	}
+
+	if( cond & B_EV_IO_READ )
+	{
+		int ret, done;
+
+		ASSERTSOCKOP( ret = recv( fd, ft->buffer, sizeof( ft->buffer ), 0 ), "Receiving" );
+
+		if( ret == 0 )
+			return dcc_abort( df, "Remote end closed connection" );
+
+		if( !ft->write( df->ft, ft->buffer, ret ) )
+			return FALSE;
+
+		df->bytes_sent += ret;
+
+		done = df->bytes_sent >= ft->file_size;
+
+		if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) ||
+		    done )
+		{
+			guint32 ack = htonl( ft->bytes_transferred = df->bytes_sent );
+			int ackret;
+
+			ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" );
+			
+			if ( ackret != 4 )
+				return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret );
+		}
+		
+		if( df->bytes_sent == ret )
+			ft->started = time( NULL );
+
+		if( done )
+		{
+			if( df->watch_out )
+				b_event_remove( df->watch_out );
+
+			if( df->proto_finished )
+				dcc_finish( ft );
+
+			df->watch_in = 0;
+			return FALSE;
+		}
+
+		df->watch_in = 0;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean dccs_recv_write_request( file_transfer_t *ft )
+{
+	dcc_file_transfer_t *df = ft->priv;
+
+	if( df->watch_in )
+		return dcc_abort( df, "BUG: write_request() called while watching" );
+
+	df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
+
+	return TRUE;
+}
+
+gboolean dccs_send_can_write( gpointer data, gint fd, b_input_condition cond )
+{
+	struct dcc_file_transfer *df = data;
+	df->watch_out = 0;
+
+	df->ft->write_request( df->ft );
+	return FALSE;
+}
+
+/* 
+ * Incoming data.
+ * 
+ */
+gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_len )
+{
+	dcc_file_transfer_t *df = file->priv;
+	int ret;
+
+	receivedchunks++; receiveddata += data_len;
+
+	if( df->watch_out )
+		return dcc_abort( df, "BUG: write() called while watching" );
+
+	ASSERTSOCKOP( ret = send( df->fd, data, data_len, 0 ), "Sending data" );
+
+	if( ret == 0 )
+		return dcc_abort( df, "Remote end closed connection" );
+
+	/* TODO: this should really not be fatal */
+	if( ret < data_len )
+		return dcc_abort( df, "send() sent %d instead of %d", ret, data_len );
+
+	if( df->bytes_sent == 0 )
+		file->started = time( NULL );
+
+	df->bytes_sent += ret;
+
+	if( df->bytes_sent < df->ft->file_size )
+		df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_send_can_write, df );
+
+	return TRUE;
+}
+
+/*
+ * Cleans up after a transfer.
+ */
+void dcc_close( file_transfer_t *file )
+{
+	dcc_file_transfer_t *df = file->priv;
+	irc_t *irc = (irc_t *) df->ic->bee->ui_data;
+
+	if( file->free )
+		file->free( file );
+	
+	closesocket( df->fd );
+
+	if( df->watch_in )
+		b_event_remove( df->watch_in );
+
+	if( df->watch_out )
+		b_event_remove( df->watch_out );
+	
+	if( df->progress_timeout )
+		b_event_remove( df->progress_timeout );
+	
+	irc->file_transfers = g_slist_remove( irc->file_transfers, file );
+	
+	g_free( df );
+	g_free( file->file_name );
+	g_free( file );
+}
+
+void dcc_finish( file_transfer_t *file )
+{
+	dcc_file_transfer_t *df = file->priv;
+	time_t diff = time( NULL ) - file->started ? : 1;
+
+	file->status |= FT_STATUS_FINISHED;
+	
+	if( file->finished )
+		file->finished( file );
+
+	imcb_log( df->ic, "File %s transferred successfully at %d kb/s!" , file->file_name, (int) ( file->bytes_transferred / 1024 / diff ) );
+	dcc_close( file );
+}
+
+/* 
+ * DCC SEND <filename> <IP> <port> <filesize>
+ *
+ * filename can be in "" or not. If it is, " can probably be escaped...
+ * IP can be an unsigned int (IPV4) or something else (IPV6)
+ * 
+ */
+file_transfer_t *dcc_request( struct im_connection *ic, char* const* ctcp )
+{
+	irc_t *irc = (irc_t *) ic->bee->ui_data;
+	file_transfer_t *ft;
+	dcc_file_transfer_t *df;
+	int gret;
+	size_t filesize;
+	
+	if( ctcp[5] != NULL &&
+	    sscanf( ctcp[4], "%zd", &filesize ) == 1 && /* Just int. validation. */
+	    sscanf( ctcp[5], "%zd", &filesize ) == 1 )
+	{
+		char *filename, *host, *port;
+		struct addrinfo hints, *rp;
+		
+		filename = ctcp[2];
+		
+		host = ctcp[3];
+		while( *host && isdigit( *host ) ) host++; /* Just digits? */
+		if( *host == '\0' )
+		{
+			struct in_addr ipaddr = { .s_addr = htonl( atoll( ctcp[3] ) ) };
+			host = inet_ntoa( ipaddr );
+		} else
+		{
+			/* Contains non-numbers, hopefully an IPV6 address */
+			host = ctcp[3];
+		}
+
+		port = ctcp[4];
+		filesize = atoll( ctcp[5] );
+
+		memset( &hints, 0, sizeof ( struct addrinfo ) );
+		hints.ai_socktype = SOCK_STREAM;
+		hints.ai_flags = AI_NUMERICSERV;
+
+		if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) )
+		{
+			imcb_log( ic, "DCC: getaddrinfo() failed with %s "
+				  "when parsing incoming 'DCC SEND': "
+				  "host %s, port %s", 
+				  gai_strerror( gret ), host, port );
+			return NULL;
+		}
+
+		df = dcc_alloc_transfer( filename, filesize, ic );
+		ft = df->ft;
+		ft->sending = TRUE;
+		memcpy( &df->saddr, rp->ai_addr, rp->ai_addrlen );
+
+		freeaddrinfo( rp );
+
+		irc->file_transfers = g_slist_prepend( irc->file_transfers, ft );
+
+		return ft;
+	}
+	else
+		imcb_log( ic, "DCC: couldnt parse `DCC SEND' line" );
+
+	return NULL;
+}
Index: dcc.h
===================================================================
--- dcc.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ dcc.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,105 @@
+/********************************************************************\
+* BitlBee -- An IRC to other IM-networks gateway                     *
+*                                                                    *
+* Copyright 2006 Marijn Kruisselbrink and others                     *
+* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                   *
+\********************************************************************/
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/* 
+ * DCC SEND
+ *
+ * Historically, DCC means send 1024 Bytes and wait for a 4 byte reply
+ * acknowledging all transferred data. This is ridiculous for two reasons.  The
+ * first being that TCP is a stream oriented protocol that doesn't care much
+ * about your idea of a packet. The second reason being that TCP is a reliable
+ * transfer protocol with its own sophisticated ACK mechanism, making DCCs ACK
+ * mechanism look like a joke. For these reasons, DCCs requirements have
+ * (hopefully) been relaxed in most implementations and this implementation
+ * depends upon at least the following: The 1024 bytes need not be transferred
+ * at once, i.e. packets can be smaller. A second relaxation has apparently
+ * gotten the name "DCC SEND ahead" which basically means to not give a damn
+ * about those DCC ACKs and just send data as you please. This behaviour is
+ * enabled by default. Note that this also means that packets may be as large
+ * as the maximum segment size.
+ */ 
+
+#ifndef _DCC_H
+#define _DCC_H
+
+/* Send an ACK after receiving this amount of data */
+#define DCC_PACKET_SIZE 1024
+
+/* Time in seconds that a DCC transfer can be stalled before being aborted.
+ * By handling this here individual protocols don't have to think about this. */
+#define DCC_MAX_STALL 120
+
+typedef struct dcc_file_transfer {
+
+	struct im_connection *ic;
+
+	/*
+	 * Depending in the status of the file transfer, this is either the socket that is
+	 * being listened on for connections, or the socket over which the file transfer is
+	 * taking place.
+	 */
+	int fd;
+	
+	/*
+	 * IDs returned by b_input_add for watch_ing over the above socket.
+	 */
+	gint watch_in;   /* readable */
+	gint watch_out;  /* writable */
+	
+	/* the progress watcher cancels any file transfer if nothing happens within DCC_MAX_STALL */
+	gint progress_timeout;
+	size_t progress_bytes_last;
+
+	/*
+	 * The total amount of bytes that have been sent to the irc client.
+	 */
+	size_t bytes_sent;
+	
+	/*
+	 * Handle the wonderful sadly-not-deprecated ACKs.
+	 */
+	guint32 acked;
+	int acked_len;
+	
+	/* imc's handle */
+	file_transfer_t *ft;
+
+	/* if we're receiving, this is the sender's socket address */
+	struct sockaddr_storage saddr;
+
+	/* set to true if the protocol has finished 
+	 * (i.e. called imcb_file_finished)
+	 */
+	int proto_finished;
+} dcc_file_transfer_t;
+
+file_transfer_t *dccs_send_start( struct im_connection *ic, irc_user_t *iu, const char *file_name, size_t file_size );
+void dcc_canceled( file_transfer_t *file, char *reason );
+gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_size );
+file_transfer_t *dcc_request( struct im_connection *ic, char* const* ctcp );
+void dcc_finish( file_transfer_t *file );
+void dcc_close( file_transfer_t *file );
+gboolean dccs_recv_start( file_transfer_t *ft );
+
+#endif
Index: debian/bitlbee-common.config
===================================================================
--- debian/bitlbee-common.config	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ debian/bitlbee-common.config	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,25 @@
+#!/bin/sh -e
+
+. /usr/share/debconf/confmodule
+[ -f /etc/default/bitlbee ] && . /etc/default/bitlbee
+
+db_title BitlBee
+
+if [ -n "$BITLBEE_PORT" ]; then
+	db_set bitlbee/serveport "$BITLBEE_PORT"
+else
+	db_get bitlbee/serveport
+	if [ "$RET" = "stillhavetoask" ]; then
+		listens=$(netstat -ltn | awk '{print $4}')
+		for port in 6667 6666 6668 6669; do
+			if [ $(expr "$listens " : ".*:$port\s") = "0" ]; then
+				break
+			fi
+		done
+		db_set bitlbee/serveport $port;
+	fi
+fi
+
+if db_input medium bitlbee/serveport; then
+	db_go;
+fi
Index: debian/bitlbee-common.docs
===================================================================
--- debian/bitlbee-common.docs	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ debian/bitlbee-common.docs	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,6 @@
+doc/user-guide/user-guide.txt
+doc/user-guide/user-guide.html
+doc/AUTHORS
+doc/CREDITS
+doc/FAQ
+doc/README
Index: debian/bitlbee-common.examples
===================================================================
--- debian/bitlbee-common.examples	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ debian/bitlbee-common.examples	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,1 @@
+utils/*
Index: debian/bitlbee-common.templates
===================================================================
--- debian/bitlbee-common.templates	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ debian/bitlbee-common.templates	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,8 @@
+Template: bitlbee/serveport
+Type: string
+Default: stillhavetoask
+_Description: On what TCP port should BitlBee listen for connections?
+ BitlBee normally listens on the regular IRC port, 6667. This might not be
+ a very good idea when you're running a real IRC daemon as well. 6668 might
+ be a good alternative. Leaving this value blank means that BitlBee will not
+ be run automatically.
Index: debian/bitlbee.init
===================================================================
--- debian/bitlbee.init	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ debian/bitlbee.init	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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/bitlbee.postinst
===================================================================
--- debian/bitlbee.postinst	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ debian/bitlbee.postinst	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,102 @@
+#!/bin/sh -e
+
+. /usr/share/debconf/confmodule
+
+db_get bitlbee/serveport
+PORT="$RET"
+
+CONFDIR=/var/lib/bitlbee/
+
+update-rc.d bitlbee defaults > /dev/null 2>&1
+
+## Load default option. Don't want to put this in debconf (yet?)
+BITLBEE_OPTS=-F
+BITLBEE_DISABLED=0
+BITLBEE_UPGRADE_DONT_RESTART=0
+[ -r /etc/default/bitlbee ] && . /etc/default/bitlbee
+
+if [ "$BITLBEE_DISABLED" = "0" ] && type update-inetd > /dev/null 2> /dev/null &&
+   ( expr "$2" : '0\..*' > /dev/null || expr "$2" : '1\.0\..*' > /dev/null ); then
+	## Make sure the inetd entry is gone (can still be there from a
+	## previous version.
+	update-inetd --remove '.*/usr/sbin/bitlbee'
+	if grep -q /usr/sbin/bitlbee /etc/inetd.conf 2> /dev/null; then
+		# Thanks for breaking update-inetd! (bugs.debian.org/311111)
+		# I hope that it works at least with xinetd, because this
+		# emergency hack doesn't:
+		perl -pi -e 's:^[^#].*/usr/sbin/bitlbee$:## Now using daemon mode\: # $&:' /etc/inetd.conf
+		killall -HUP inetd || true
+	fi
+fi
+
+cat<<EOF>/etc/default/bitlbee
+## /etc/default/bitlbee: Auto-generated/updated script.
+##
+## If running in (fork)daemon mode, listen on this TCP port.
+BITLBEE_PORT="$PORT"
+
+## Use single-process or forking daemon mode? Can't be changed from debconf,
+## but maintainer scripts will save your changes here.
+BITLBEE_OPTS="$BITLBEE_OPTS"
+
+## In case you want to stick with inetd mode (or if you just want to disable
+## the init scripts for some other reason), you can disable the init script
+## here. (Just set it to 1)
+BITLBEE_DISABLED=$BITLBEE_DISABLED
+
+## As a server operator, you can use the RESTART command to restart only the
+## master process while keeping all the child processes and their IPC
+## connections. By enabling this, the maintainer scripts won't restart
+## BitlBee during upgrades so you can restart the master process by hand.
+BITLBEE_UPGRADE_DONT_RESTART=$BITLBEE_UPGRADE_DONT_RESTART
+EOF
+
+## Bye-bye DebConf, we don't need you anymore.
+db_stop
+
+## Restore the helpfile in case we weren't upgrading but just reconfiguring:
+if [ -e /usr/share/bitlbee/help.upgrading ]; then
+	if [ -e /usr/share/bitlbee/help.txt ]; then
+		rm -f /usr/share/bitlbee/help.upgrading
+	else
+		mv /usr/share/bitlbee/help.upgrading /usr/share/bitlbee/help.txt
+	fi
+fi
+
+if [ -n "$2" -a "$BITLBEE_UPGRADE_DONT_RESTART" != "1" ]; then
+	if which invoke-rc.d >/dev/null 2>&1; then
+		invoke-rc.d bitlbee restart
+	else
+		/etc/init.d/bitlbee restart
+	fi
+fi
+
+## If we're upgrading, we'll probably skip this next part
+if [ -d $CONFDIR ] && chown -R bitlbee: $CONFDIR; then
+	echo 'BitlBee (probably) already installed, skipping user/configdir installation'
+	exit 0
+fi
+
+adduser --system --group --disabled-login --disabled-password --home /var/lib/bitlbee/ bitlbee
+chmod 700 /var/lib/bitlbee/
+
+## Can't do this in packaging phase: Don't know the UID yet. Access to
+## the file should be limited, now that it stores passwords. Added
+## --group later for a little more security, but have to see if I can
+## apply this change to existing installations on upgrades. Will think
+## about that later.
+if getent group bitlbee > /dev/null; then
+	chmod 640 /etc/bitlbee/bitlbee.conf
+	chown root:bitlbee /etc/bitlbee/bitlbee.conf
+else
+	chmod 600 /etc/bitlbee/bitlbee.conf
+	chown bitlbee /etc/bitlbee/bitlbee.conf
+fi
+
+if [ -z "$2" ]; then
+	if which invoke-rc.d >/dev/null 2>&1; then
+		invoke-rc.d bitlbee start
+	else
+		/etc/init.d/bitlbee start
+	fi
+fi
Index: debian/bitlbee.postrm
===================================================================
--- debian/bitlbee.postrm	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ debian/bitlbee.postrm	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,14 @@
+#!/bin/sh -e
+
+[ "$1" = "purge" ] || exit 0
+
+if [ -e /usr/share/debconf/confmodule ]; then
+	. /usr/share/debconf/confmodule;
+	db_purge;
+fi
+
+update-rc.d bitlbee remove > /dev/null 2>&1 || true
+rm -f /etc/default/bitlbee
+
+deluser --system --remove-home bitlbee || true
+rm -rf /var/lib/bitlbee ## deluser doesn't seem to do this for homedirs in /var
Index: debian/bitlbee.prerm
===================================================================
--- debian/bitlbee.prerm	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ debian/bitlbee.prerm	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,17 @@
+#!/bin/sh -e
+
+if [ "$1" = "upgrade" ]; then
+	## To prevent the help function from breaking in currently running
+	## BitlBee processes. Have to do it like this because dpkg-reconfigure
+	## looks a lot like an upgrade and we don't want to lose help.txt...
+	if [ -e /usr/share/bitlbee/help.txt ]; then
+		rm -f /usr/share/bitlbee/help.upgrading
+		mv /usr/share/bitlbee/help.txt /usr/share/bitlbee/help.upgrading
+	fi
+else
+	if which invoke-rc.d >/dev/null 2>&1; then
+		invoke-rc.d bitlbee stop || exit 0
+	else
+		/etc/init.d/bitlbee stop || exit 0
+	fi
+fi
Index: debian/changelog
===================================================================
--- debian/changelog	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ debian/changelog	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -1,2 +1,33 @@
+bitlbee (1.3-0) unstable; urgency=low
+
+  * Setting some bogus version number, fix that later.
+  * Now using debhelper to improve maintainability.
+  * Added a bitlbee-libpurple package, and split off docs and stuff into
+    bitlbee-common.
+
+ -- Wilmer van der Gaast <wilmer@gaast.net>  Sat, 05 Jun 2010 15:16:38 +0100
+
+bitlbee (1.2.8-1) unstable; urgency=low
+
+  * New upstream version.
+
+ -- Wilmer van der Gaast <wilmer@gaast.net>  Sat, 10 Jul 2010 13:54:55 +0100
+
+bitlbee (1.2.7-1) unstable; urgency=high
+
+  * New upstream version.
+  * Fixes MSN Messenger login issues, so uploading at high priority.
+
+ -- Wilmer van der Gaast <wilmer@gaast.net>  Sat, 15 May 2010 16:15:57 +0100
+
+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/compat
===================================================================
--- debian/compat	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ debian/compat	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,1 @@
+6
Index: bian/conffiles
===================================================================
--- debian/conffiles	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,3 +1,0 @@
-/etc/bitlbee/motd.txt
-/etc/bitlbee/bitlbee.conf
-/etc/init.d/bitlbee
Index: bian/config
===================================================================
--- debian/config	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,25 +1,0 @@
-#!/bin/sh -e
-
-. /usr/share/debconf/confmodule
-[ -f /etc/default/bitlbee ] && . /etc/default/bitlbee
-
-db_title BitlBee
-
-if [ -n "$BITLBEE_PORT" ]; then
-	db_set bitlbee/serveport "$BITLBEE_PORT"
-else
-	db_get bitlbee/serveport
-	if [ "$RET" = "stillhavetoask" ]; then
-		listens=$(netstat -ltn | awk '{print $4}')
-		for port in 6667 6666 6668 6669; do
-			if [ $(expr "$listens " : ".*:$port\s") = "0" ]; then
-				break
-			fi
-		done
-		db_set bitlbee/serveport $port;
-	fi
-fi
-
-if db_input medium bitlbee/serveport; then
-	db_go;
-fi
Index: debian/control
===================================================================
--- debian/control	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ debian/control	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -4,6 +4,6 @@
 Maintainer: Wilmer van der Gaast <wilmer@gaast.net>
 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
+Standards-Version: 3.8.4
+Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), po-debconf, libpurple-dev, debhelper (>= 6)
 Homepage: http://www.bitlbee.org/
 Vcs-Bzr: http://code.bitlbee.org/bitlbee/
@@ -12,15 +12,49 @@
 Package: bitlbee
 Architecture: any
-Depends: ${shlibs:Depends}, adduser, net-tools, ${debconf-depends}, debianutils (>= 1.16)
-Description: An IRC to other chat networks gateway
+Depends: ${shlibs:Depends}, adduser, debianutils (>= 1.16), bitlbee-common (= ${bee:Version})
+Conflicts: bitlbee-libpurple
+Replaces: bitlbee-libpurple
+Description: An IRC to other chat networks gateway (default version)
  This program can be used as an IRC server which forwards everything you
- say to people on other chat networks: Jabber, ICQ, AIM, MSN and Yahoo.
+ say to people on other chat networks: Jabber (which includes Google Talk
+ and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net.
+
+Package: bitlbee-libpurple
+Architecture: any
+Depends: ${shlibs:Depends}, adduser, debianutils (>= 1.16), bitlbee-common (= ${bee:Version})
+Conflicts: bitlbee
+Replaces: bitlbee
+Description: An IRC to other chat networks gateway (using libpurple)
+ This program can be used as an IRC server which forwards everything you
+ say to people on other chat networks: Jabber (which includes Google Talk
+ and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net.
+ .
+ This package contains a version of BitlBee that uses the libpurple instant
+ messaging library instead of built-in code, which adds support for more IM
+ protocols (all protocols supported by Pidgin/Finch) and features (like file
+ transfers), at the price of being less lightweight.
+ .
+ This variant may not be very suitable for BitlBee instances used by many
+ (tens or hundreds) of clients.
+
+Package: bitlbee-common
+Architecture: all
+Depends: ${misc:Depends}, net-tools
+Replaces: bitlbee
+Description: An IRC to other chat networks gateway (common files/docs)
+ This program can be used as an IRC server which forwards everything you
+ say to people on other chat networks: Jabber (which includes Google Talk
+ and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net.
+ .
+ This package contains common files (mostly documentation) for bitlbee and
+ bitlbee-libpurple.
 
 Package: bitlbee-dev
 Architecture: all
-Depends: bitlbee (>= ${source:Version}), bitlbee (<< ${source:Version}.1~)
-Description: An IRC to other chat networks gateway
+Depends: ${misc:Depends}, bitlbee (>= ${bee:Version}), bitlbee (<< ${bee:Version}.1~)
+Description: An IRC to other chat networks gateway (dev files)
  This program can be used as an IRC server which forwards everything you
- say to people on other chat networks: Jabber, ICQ, AIM, MSN and Yahoo.
+ say to people on other chat networks: Jabber (which includes Google Talk
+ and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net.
  .
  This package holds development stuff for compiling plug-ins.
Index: debian/patches/bitlbee.conf.diff
===================================================================
--- debian/patches/bitlbee.conf.diff	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ debian/patches/bitlbee.conf.diff	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -1,4 +1,4 @@
---- debian/bitlbee/etc/bitlbee/bitlbee.conf	2009-06-01 00:20:24.000000000 +0100
-+++ debian/bitlbee/etc/bitlbee/bitlbee.conf	2009-06-07 21:16:19.000000000 +0100
+--- bitlbee.conf	2009-06-01 00:20:24.000000000 +0100
++++ bitlbee.conf	2009-06-07 21:16:19.000000000 +0100
 @@ -23,13 +23,18 @@
  ## If BitlBee is started by root as a daemon, it can drop root privileges,
Index: debian/po/POTFILES.in
===================================================================
--- debian/po/POTFILES.in	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ debian/po/POTFILES.in	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -1,1 +1,1 @@
-[type: gettext/rfc822deb] templates
+[type: gettext/rfc822deb] bitlbee-common.templates
Index: bian/postinst
===================================================================
--- debian/postinst	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,102 +1,0 @@
-#!/bin/sh -e
-
-. /usr/share/debconf/confmodule
-
-db_get bitlbee/serveport
-PORT="$RET"
-
-CONFDIR=/var/lib/bitlbee/
-
-update-rc.d bitlbee defaults > /dev/null 2>&1
-
-## Load default option. Don't want to put this in debconf (yet?)
-BITLBEE_OPTS=-F
-BITLBEE_DISABLED=0
-BITLBEE_UPGRADE_DONT_RESTART=0
-[ -r /etc/default/bitlbee ] && . /etc/default/bitlbee
-
-if [ "$BITLBEE_DISABLED" = "0" ] && type update-inetd > /dev/null 2> /dev/null &&
-   ( expr "$2" : '0\..*' > /dev/null || expr "$2" : '1\.0\..*' > /dev/null ); then
-	## Make sure the inetd entry is gone (can still be there from a
-	## previous version.
-	update-inetd --remove '.*/usr/sbin/bitlbee'
-	if grep -q /usr/sbin/bitlbee /etc/inetd.conf 2> /dev/null; then
-		# Thanks for breaking update-inetd! (bugs.debian.org/311111)
-		# I hope that it works at least with xinetd, because this
-		# emergency hack doesn't:
-		perl -pi -e 's:^[^#].*/usr/sbin/bitlbee$:## Now using daemon mode\: # $&:' /etc/inetd.conf
-		killall -HUP inetd || true
-	fi
-fi
-
-cat<<EOF>/etc/default/bitlbee
-## /etc/default/bitlbee: Auto-generated/updated script.
-##
-## If running in (fork)daemon mode, listen on this TCP port.
-BITLBEE_PORT="$PORT"
-
-## Use single-process or forking daemon mode? Can't be changed from debconf,
-## but maintainer scripts will save your changes here.
-BITLBEE_OPTS="$BITLBEE_OPTS"
-
-## In case you want to stick with inetd mode (or if you just want to disable
-## the init scripts for some other reason), you can disable the init script
-## here. (Just set it to 1)
-BITLBEE_DISABLED=$BITLBEE_DISABLED
-
-## As a server operator, you can use the RESTART command to restart only the
-## master process while keeping all the child processes and their IPC
-## connections. By enabling this, the maintainer scripts won't restart
-## BitlBee during upgrades so you can restart the master process by hand.
-BITLBEE_UPGRADE_DONT_RESTART=$BITLBEE_UPGRADE_DONT_RESTART
-EOF
-
-## Bye-bye DebConf, we don't need you anymore.
-db_stop
-
-## Restore the helpfile in case we weren't upgrading but just reconfiguring:
-if [ -e /usr/share/bitlbee/help.upgrading ]; then
-	if [ -e /usr/share/bitlbee/help.txt ]; then
-		rm -f /usr/share/bitlbee/help.upgrading
-	else
-		mv /usr/share/bitlbee/help.upgrading /usr/share/bitlbee/help.txt
-	fi
-fi
-
-if [ -n "$2" -a "$BITLBEE_UPGRADE_DONT_RESTART" != "1" ]; then
-	if which invoke-rc.d >/dev/null 2>&1; then
-		invoke-rc.d bitlbee restart
-	else
-		/etc/init.d/bitlbee restart
-	fi
-fi
-
-## If we're upgrading, we'll probably skip this next part
-if [ -d $CONFDIR ] && chown -R bitlbee: $CONFDIR; then
-	echo 'BitlBee (probably) already installed, skipping user/configdir installation'
-	exit 0
-fi
-
-adduser --system --group --disabled-login --disabled-password --home /var/lib/bitlbee/ bitlbee
-chmod 700 /var/lib/bitlbee/
-
-## Can't do this in packaging phase: Don't know the UID yet. Access to
-## the file should be limited, now that it stores passwords. Added
-## --group later for a little more security, but have to see if I can
-## apply this change to existing installations on upgrades. Will think
-## about that later.
-if getent group bitlbee > /dev/null; then
-	chmod 640 /etc/bitlbee/bitlbee.conf
-	chown root:bitlbee /etc/bitlbee/bitlbee.conf
-else
-	chmod 600 /etc/bitlbee/bitlbee.conf
-	chown bitlbee /etc/bitlbee/bitlbee.conf
-fi
-
-if [ -z "$2" ]; then
-	if which invoke-rc.d >/dev/null 2>&1; then
-		invoke-rc.d bitlbee start
-	else
-		/etc/init.d/bitlbee start
-	fi
-fi
Index: bian/postrm
===================================================================
--- debian/postrm	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,14 +1,0 @@
-#!/bin/sh -e
-
-[ "$1" = "purge" ] || exit 0
-
-if [ -e /usr/share/debconf/confmodule ]; then
-	. /usr/share/debconf/confmodule;
-	db_purge;
-fi
-
-update-rc.d bitlbee remove > /dev/null 2>&1 || true
-rm -f /etc/default/bitlbee
-
-deluser --system --remove-home bitlbee || true
-rm -rf /var/lib/bitlbee ## deluser doesn't seem to do this for homedirs in /var
Index: bian/prerm
===================================================================
--- debian/prerm	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,17 +1,0 @@
-#!/bin/sh -e
-
-if [ "$1" = "upgrade" ]; then
-	## To prevent the help function from breaking in currently running
-	## BitlBee processes. Have to do it like this because dpkg-reconfigure
-	## looks a lot like an upgrade and we don't want to lose help.txt...
-	if [ -e /usr/share/bitlbee/help.txt ]; then
-		rm -f /usr/share/bitlbee/help.upgrading
-		mv /usr/share/bitlbee/help.txt /usr/share/bitlbee/help.upgrading
-	fi
-else
-	if which invoke-rc.d >/dev/null 2>&1; then
-		invoke-rc.d bitlbee stop || exit 0
-	else
-		/etc/init.d/bitlbee stop || exit 0
-	fi
-fi
Index: debian/rules
===================================================================
--- debian/rules	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ debian/rules	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -1,109 +1,104 @@
 #!/usr/bin/make -f
+#
+# Finally switching to debhelper.
+#
+# Not using debhelper was an exercise suggested to me by my AM (Gergely
+# Nagy). It was educating at the time but I finally decided that the
+# exercise is over now.
+#
 
+BITLBEE_CONFIGURE_FLAGS ?=
 DEBUG ?= 0
 
-ifdef BITLBEE_VERSION
-BITLBEE_FORCE_VERSION=1
-else
+ifndef BITLBEE_VERSION
 # Want to use the full package version number instead of just the release.
-BITLBEE_VERSION ?= "$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}')"
-export BITLBEE_VERSION
+BITLBEE_CONFIGURE_VERSION ?= BITLBEE_VERSION=\"$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}')\"
 endif
 
-build-arch: build-arch-stamp
-build-arch-stamp:
-	[ -d debian ]
-	./configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent
-	$(MAKE)
-#	$(MAKE) -C doc/ all
-	touch build-arch-stamp
+build: build-stamp
+build-stamp:
+	dh_testdir
+
+	mkdir -p debian/build-native
+	ROOT=$$PWD; cd debian/build-native; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent $(BITLBEE_CONFIGURE_FLAGS)
+	$(MAKE) -C debian/build-native
+
+	mkdir -p debian/build-libpurple
+	ROOT=$$PWD; cd debian/build-libpurple; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --purple=1 $(BITLBEE_CONFIGURE_FLAGS)
+	$(MAKE) -C debian/build-libpurple
+
+	$(MAKE) -C doc
+
+	touch build-stamp
 
 clean:
-	[ "`whoami`" = "root" -a -d debian ]
-	rm -rf build-arch-stamp debian/bitlbee debian/*.substvars debian/files debian/bitlbee-dev
+	dh_testdir
+	dh_testroot
+	rm -f build-stamp
+
+	rm -rf build-arch-stamp debian/build-*
 	$(MAKE) distclean
-#	-$(MAKE) -C doc/ clean
-				
 
-install-arch: build-arch
-	[ "`whoami`" = "root" -a -d debian ]
-	mkdir -p debian/bitlbee/DEBIAN/
-	$(MAKE) install install-etc DESTDIR=`pwd`/debian/bitlbee
+	dh_clean
 
-	mkdir -p debian/bitlbee/usr/share/doc/bitlbee/
-	cp doc/user-guide/user-guide.txt debian/bitlbee/usr/share/doc/bitlbee/
-	cp doc/user-guide/user-guide.html debian/bitlbee/usr/share/doc/bitlbee/
+install: build
+	dh_testdir
+	dh_testroot
+	dh_clean -k
+	dh_installdirs
 
-install-indep: install-arch
-	[ "`whoami`" = "root" -a -d debian ]
-	mkdir -p debian/bitlbee-dev/DEBIAN/
-	$(MAKE) install-dev DESTDIR=`pwd`/debian/bitlbee-dev
+	$(MAKE) -C debian/build-native install install-etc DESTDIR=`pwd`/debian/bitlbee
+	$(MAKE) -C debian/build-libpurple install install-etc DESTDIR=`pwd`/debian/bitlbee-libpurple
+	$(MAKE) -C debian/build-native install-dev DESTDIR=`pwd`/debian/bitlbee-dev
 
-	mkdir -p debian/bitlbee-dev/usr/share/doc/bitlbee-dev/
+	patch debian/bitlbee/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff
+	patch debian/bitlbee-libpurple/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff
 
-binary-arch: build-arch install-arch
-	[ "`whoami`" = "root" -a -d debian ]
+	mkdir -p debian/bitlbee-common/usr
+	mv debian/bitlbee/usr/share debian/bitlbee-common/usr
+	rm -rf debian/bitlbee-libpurple/usr/share
 
-	chmod 755 debian/post* debian/pre* debian/config debian/bitlbee.init
+binary-common:
+	dh_testdir
+	dh_testroot
 
-	mkdir -p debian/bitlbee/usr/share/doc/bitlbee/examples/ debian/bitlbee/etc/init.d/
-	-cp doc/RELEASE-SPEECH* debian/bitlbee/usr/share/doc/bitlbee/ && gzip -9 debian/bitlbee/usr/share/doc/bitlbee/RELEASE-SPEECH*
-	cp doc/CREDITS doc/AUTHORS doc/README doc/FAQ debian/README.Debian debian/bitlbee/usr/share/doc/bitlbee/
-	cp debian/changelog debian/bitlbee/usr/share/doc/bitlbee/changelog.Debian
-	cp debian/copyright debian/bitlbee/usr/share/doc/bitlbee/copyright
-	cp doc/CHANGES debian/bitlbee/usr/share/doc/bitlbee/changelog
-	cp utils/* debian/bitlbee/usr/share/doc/bitlbee/examples/
-	cp debian/bitlbee.init debian/bitlbee/etc/init.d/bitlbee
-	patch -p0 < debian/patches/bitlbee.conf.diff
-	cd debian/bitlbee/usr/share/; \
-		gzip -9 doc/bitlbee/changelog.Debian doc/bitlbee/changelog doc/bitlbee/user-guide.txt \
-		        doc/bitlbee/examples/* man/man8/bitlbee.8 man/man5/bitlbee.conf.5
-	
-	chown -R root:root debian/bitlbee/
-	find debian/bitlbee/usr/share/ -type d -exec chmod 755 {} \;
-	find debian/bitlbee/usr/share/ -type f -exec chmod 644 {} \;
-	
-	cp debian/prerm debian/bitlbee/DEBIAN/
-	cp debian/postinst debian/bitlbee/DEBIAN/
-	cp debian/postrm debian/bitlbee/DEBIAN/
-	cp debian/config debian/bitlbee/DEBIAN/
+	dh_installchangelogs doc/CHANGES
+	dh_installexamples
+	dh_installdocs #--link-doc=bitlbee-common
+	# TODO: Restore --link-doc up here and remove the hack below once
+	# Hardy and Lenny are deprecated.
+	for p in bitlbee bitlbee-libpurple bitlbee-dev; do rm -rf debian/$$p/usr/share/doc/$$p; ln -s bitlbee-common debian/$$p/usr/share/doc/$$p; done
+	dh_installdebconf
+	dh_installinit
+ifeq ($(DH_OPTIONS),-a)
+	cp -a debian/bitlbee/etc debian/bitlbee-libpurple
+endif
+	dh_installman
+	dh_strip
+	dh_link
+	dh_compress
+	dh_fixperms
+	dh_installdeb
+ifeq ($(DH_OPTIONS),-a)
+	cp -a debian/bitlbee/DEBIAN/post* debian/bitlbee/DEBIAN/pre* debian/bitlbee-libpurple/DEBIAN
+endif
+	dh_shlibdeps
+ifdef BITLBEE_VERSION
+	dh_gencontrol -- -v1:$(BITLBEE_VERSION)-0  -Vbee:Version=1:$(BITLBEE_VERSION)-0
+else
+	dh_gencontrol -- -Vbee:Version=$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}' | sed -e 's/+b[0-9]\+$$//')
+endif
+	dh_md5sums
+	dh_builddeb
 
-	po2debconf debian/templates > debian/bitlbee/DEBIAN/templates
-	cp debian/conffiles debian/bitlbee/DEBIAN/
-	
-	if [ "$(DEBUG)" = "0" ]; then strip -R .comment -R .note debian/bitlbee/usr/sbin/bitlbee; fi
+binary-indep: build install
+	$(MAKE) -f debian/rules DH_OPTIONS=-i binary-common
 
-	cd debian/bitlbee; \
-		find usr -type f -exec md5sum {} \; > DEBIAN/md5sums
-	dpkg-shlibdeps -Tdebian/bitlbee.substvars -dDepends debian/bitlbee/usr/sbin/bitlbee
-ifdef BITLBEE_FORCE_VERSION
-	dpkg-gencontrol -ldebian/changelog -isp -pbitlbee -Tdebian/bitlbee.substvars -Pdebian/bitlbee -v1:$(BITLBEE_VERSION)-0 -V'debconf-depends=debconf (>= 1.2.0) | debconf-2.0'
-else
-	dpkg-gencontrol -ldebian/changelog -isp -pbitlbee -Tdebian/bitlbee.substvars -Pdebian/bitlbee -V'debconf-depends=debconf (>= 1.2.0) | debconf-2.0'
-endif
+binary-arch: build install
+	$(MAKE) -f debian/rules DH_OPTIONS=-a binary-common
 
-	dpkg --build debian/bitlbee ..
+binary-%: build install
+	make -f debian/rules binary-common DH_OPTIONS=-p$*
 
-binary-indep: install-indep
-	[ "`whoami`" = "root" -a -d debian ]
-
-	chown -R root.root debian/bitlbee-dev/
-	find debian/bitlbee-dev/usr/share/ -type d -exec chmod 755 {} \;
-	find debian/bitlbee-dev/usr/share/ -type f -exec chmod 644 {} \;
-
-	cp debian/changelog debian/bitlbee-dev/usr/share/doc/bitlbee-dev/changelog.Debian
-	gzip -9 debian/bitlbee-dev/usr/share/doc/bitlbee-dev/changelog.Debian
-	cp debian/copyright debian/bitlbee-dev/usr/share/doc/bitlbee-dev/copyright
-
-	cd debian/bitlbee-dev; \
-		find usr -type f -exec md5sum {} \; > DEBIAN/md5sums
-
-	dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev
-
-	dpkg --build debian/bitlbee-dev ..
-
-binary: binary-arch binary-indep
-build: build-arch
-install: install-arch install-indep
-
-.PHONY: build-arch build clean binary-arch binary install-arch install binary-indep install-indep
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary-common binary install
Index: bian/templates
===================================================================
--- debian/templates	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,8 +1,0 @@
-Template: bitlbee/serveport
-Type: string
-Default: stillhavetoask
-_Description: On what TCP port should BitlBee listen for connections?
- BitlBee normally listens on the regular IRC port, 6667. This might not be
- a very good idea when you're running a real IRC daemon as well. 6668 might
- be a good alternative. Leaving this value blank means that BitlBee will not
- be run automatically.
Index: doc/CHANGES
===================================================================
--- doc/CHANGES	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/CHANGES	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -3,4 +3,121 @@
 
 http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on
+
+Version 1.3dev:
+- For the first time since 2007, a dev snapshot. Like then, this is pretty
+  stable already (running on testing.bitlbee.org for weeks by now), but not
+  all planned features for the next major release are ready yet, and there
+  may be some rough edges in the interface and documentation.
+- Loads of new stuff, mostly ready for a new major release, but starting with
+  a dev snapshot. A few changes that were planned for a long time already:
+- Rewrote the IRC core, which brings:
+  * Support for multiple (control) channels, so you can have one channel per
+    buddy group, per IM account/protocol, or for example a &offline with all
+    offline contacts. See "help channels" for information on how to use this.
+  * You can also leave and rejoin all channels. Being in &bitlbee is no
+    longer required.
+  * Now you can start groupchats by just joining a new channel and inviting
+    contacts. (The "chat with" command still works as well.)
+  * You can change your nickname, whenever you want.
+  * Root commands:
+    + To hopefully resolve confusion about the "account set" syntax, the
+      ordering was changed slightly: "account set acc/setting value"
+      becomes "account acc set setting value". Obviously the same order
+      should now be used for other account subcommands.
+    + Shortcuts: Instead of "account list" you can say "acc li".
+  * /whois shows idle/login times of your contacts when available.
+  * paste_buffer (previously known as buddy_sendbuffer) now works for chats
+    as well.
+  * The nick_source setting was replaced with a nick_format setting, which
+    looks more or less like a format string, and lets you tweak how nicknames
+    for contacts are generated in more detail. It also tries to convert non-
+    ASCII characters (i.e. accents) properly.
+  * A per-channel show_users setting lets you configure exactly which kinds
+    of contacts (online/away/offline) should show up in a channel and what
+    modes (none/voice/etc) they should have. Like show_offline, but together
+    with other new features it can for example create an &offline channel
+    with just offline contacts.
+  * If you connect (and identify) to a BitlBee server you're already
+    connected to, you can take over the existing session instead of starting
+    a new one.
+  * More correct validation of channel names: They can contain pretty much
+    any character, unlike nicknames.
+- Support for using libpurple instead of BitlBee's built-in IM protocol
+  modules. This can be enabled by passing --purple=1 to the configure script.
+  * This adds support for many more IM protocols to BitlBee.
+  * And new functionality to existing protocols.
+  * This is and will always be optional and using it on public servers is
+    *not* recommended. It should be pretty stable, but costs more RAM/etc.
+  * Switching to libpurple should be pretty transparent. See "help purple"
+    for a list of supported protocols (works only in libpurple-enabled
+    binaries).
+- Support for file transfers, in and out. /DCC SEND a file to a contact and
+  it becomes a file transfer, and incoming file transfers become /DCC SENDs
+  to you. Note that this is mostly useful when combined with libpurple, as
+  only the Jabber native protocol module currently supports file transfers.
+- Updated Yahoo! module to be in sync again with libyahoo2. This mostly
+  fixes issues with authorization requests.
+
+Finished 6 Aug 2010
+
+Version 1.2.8:
+- Now always using the AIM-style authentication method for OSCAR connections,
+  even when connecting to ICQ. This solves login issues some people were
+  having. (If you have problems, try changing the old_icq_auth setting.)
+- Twitter:
+  * Allow changing the Twitter API base URL so the module can also be used
+    for identi.ca or any other compatible network.
+  * Fetch the full list of Twitter contacts instead of slowly adding all
+    contacts as they post a message.
+  * Fixed message length counting.
+  * Allow following/unfollowing people using the usual add/remove commands.
+  * Better error reporting.
+- Added a user_agent setting to the Jabber module to get around artificial
+  client restrictions.
+- Allow nick changes (although only before register/identify).
+- Some more minor bugfixes/etc.
+
+Finished 4 Jul 2010
+
+Version 1.2.7:
+- Fixed problems with MSN Messenger authentication. ("Could not parse
+  Passport server response")
+- Fixed broken typing notifications when talking to GTalk contacts.
+- Fixed an issue with non-anonymous Jabber chatrooms polluting the nick
+  namespace, sometimes generating odd warning messages.
+- Restored ability to start groupchats on ICQ.
+- Added show_offline setting that will also show offline contacts in the
+  control channel.
+- OAuth support for Twitter: This means the module will keep working after
+  June (this also changes "via API" under your tweets into "via BitlBee").
+
+Finished 15 May 2010
+
+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 +140,5 @@
   the main client).
 
-Fixed 17 Mar 2010
+Finished 17 Mar 2010
 
 Version 1.2.4:
Index: doc/HACKING
===================================================================
--- doc/HACKING	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ doc/HACKING	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,125 @@
+BitlBee post-1.x "architecture"
+
+DISCLAIMER: The messy architecture is being cleaned up. Although lots of
+progress was made already, this is still a work in progress, and possibly
+parts of this document aren't entirely accurate anymore by the time you
+read this.
+
+It's been a while since BitlBee started, as a semi-fork of Gaim (version
+0.58 at the time). Some people believe nothing changed, but fortunately,
+many things have.
+
+The API is gone for a while already - which wasn't incredibly intrusive,
+just a few functions renamed for slightly better consistency, added some
+calls and arguments where that seemed useful, etc.
+
+However, up to late in the 1.2 series, the IRC core was still spread accross
+several files, mostly irc.c + irc_commands.c and pieces and bits in
+nogaim.c. If you're looking for a textbook example of layer violation, start
+there.
+
+This was all finally redone. Most of the IRC protocol code was rewritten,
+as was most of the glue between that and the IM modules.
+
+The core of BitlBee is now protocols/bee*. Some pieces are still left in
+protocols/nogaim*. Most stuff in the "root" directory belongs to the IRC
+UI, which should be considered "a" frontend (although currently, and
+possibly forever, the only one). Every subdirectory of protocols/ is another
+IM protocol backend (including purple/ which uses libpurple to define
+many different protocols).
+
+
+/
+
+The IRC core has code to show an IRC interface to a user, with contacts,
+channels, etc. To make channels and contacts do something, you add event
+handlers (that translate a message sent to a nick into an instant message
+to an IM contact, or translates joining a channel into joining an IM
+chatroom).
+
+To get events back from the BitlBee core, the bee_t object has a bunch of
+functions (struct bee_ui_funcs) that catch them and convert them back to
+IRC.
+
+Short description of what all irc*.c files (and some related ones) do:
+
+bitlbee.c: BitlBee bootstrap code, doing bits of I/O as well.
+ipc.c: For inter-process communication - communication between BitlBee
+    sessions. Also used in daemon mode (in which it's not so much inter-
+    process).
+irc.c: The main core, with bits of I/O handling, parsing, etc.
+irc_channel.c: Most things related to standard channels (also defines some
+    of the control channel behaviour).
+irc_commands.c: Defines all IRC commands (JOIN, NICK, PRIVMSG, etc.).
+irc_im.c: Most of the glue between IRC and the IM core live here. This is
+    where instant messages are converted to IRC and vice versa, contacts
+    coming online is translated to one or more joins and/or mode changes.
+irc_send.c: Simple functions that send pieces of IRC output. Somewhat
+    random, but if an IRC response is slightly more complicated than just a
+    simple line, make it a function here.
+irc_user.c: Defines all IRC user details. Mostly defines the user "object".
+irc_util.c: Misc. stuff. Not much ATM.
+nick.c: Handling of nicknames: compare, ucase/lcase, generating and storing
+    nicks for IM contacts.
+set.c: Settings management, used for user-changeable global/account/channel
+    settings. Should really be considered part of the core.
+storage*.c: Storing user accounts. (The stuff you normally find in
+    /var/lib/bitlbee)
+
+
+/protocols
+
+The IM core lives in protocols/. Whenever you write code there, try to avoid
+using any IRCisms there.
+
+Most header files in there have some of their details explained in comments.
+bee*.c and nogaim.c are the layer between the IM modules and the IRC
+frontend. They keep track of IM accounts, contacts and their status,
+groupchats, etc.
+
+You can control them by calling functions in there if available, and
+otherwise by just calling the functions exported via the prpl struct. Most
+of these functions are briefly explained in the header files, otherwise the
+best documentation is sadly in irc_im.c and root_commands.c.
+
+Events from the IM module go back to the core + frontend via imcb_*
+functions defined in bee*.c and nogaim.c. They're all described in the
+header files.
+
+
+/lib
+
+BitlBee uses GLib, which is a pretty nifty library adding a bunch of things
+that make life of a C coder better. Please try to not use features from
+recent GLib versions as whenever this happens, some people get cranky. :>
+
+There's also a whole bunch of nice stuff in lib/ that you can use:
+
+arc.c: ARC4 encryption, mostly used for encrypting IM passwords in the XML
+    storage module.
+base64.c
+events_*.c: Event handling, using either GLib (default) or libevent (may
+    make non-forking daemon mode with many users a little bit more efficient).
+ftutil.c: Some small utility functions currently just used for file transfers.
+http_client.c: A simple (but asynchronous) HTTP(S) client, used by the MSN,
+    Yahoo! and Twitter module by now.
+ini.c: Simple INI file parser, used to parse bitlbee.conf.
+md5.c
+misc.c: What the name says, really.
+oauth.c: What the name says. If you don't know what OAuth is, ask Google.
+    Currently only used by the Twitter module. Implements just version 1a ATM.
+proxy.c: Used together with events_*.c for asynchronous I/O directly or via
+    proxies.
+sha1.c
+ssl_*.c: SSL client stuff, using GnuTLS (preferred) or OpenSSL. Other modules
+    aren't working well ATM.
+url.c: URL parser.
+xmltree.c: Uses the GLib stream parser to build XML parse trees from a stream
+    and convert the same structs back into XML. Good enough to do Jabber but
+    not very aware of stuff like XML namespaces. Used for Jabber and Twitter.
+
+This, together with the headerfile comments, is most of the documentation we
+have right now. If you're trying to make some changes to BitlBee, do feel
+free to join #bitlbee on irc.oftc.net to ask any question you have. 
+Suggestions for specific parts to document a little bit more are also
+welcome.
Index: doc/Makefile
===================================================================
--- doc/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -1,3 +1,6 @@
 -include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)doc/
+endif
 
 all:
@@ -7,6 +10,6 @@
 install:
 	mkdir -p $(DESTDIR)$(MANDIR)/man8/ $(DESTDIR)$(MANDIR)/man5/
-	install -m 0644 bitlbee.8 $(DESTDIR)$(MANDIR)/man8/
-	install -m 0644 bitlbee.conf.5 $(DESTDIR)$(MANDIR)/man5/
+	install -m 0644 $(SRCDIR)bitlbee.8 $(DESTDIR)$(MANDIR)/man8/
+	install -m 0644 $(SRCDIR)bitlbee.conf.5 $(DESTDIR)$(MANDIR)/man5/
 	$(MAKE) -C user-guide $@
 
Index: doc/README
===================================================================
--- doc/README	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/README	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -9,5 +9,23 @@
 make install will move all the files to the right places.
 
---- inetd installation
+--- (Fork)Daemon mode
+
+These days ForkDaemon mode is the recommended way of running BitlBee. The
+difference between Daemon and ForkDaemon mode is that in the latter, a
+separate process is spawned for every user. This costs a little bit more
+memory, but means that if one user hits a bug in the code, not all other
+users get disconnected with him/her.
+
+To use BitlBee in any daemon mode, just start it with the right flags or
+enable it in bitlbee.conf (see the RunMode option). You probably want to
+write an init script to start BitlBee automatically after a reboot. (This
+is where you realise using a package from your distro would've been a better
+idea. :-P)
+
+Please do make sure that the user BitlBee runs as (not root, please!) is
+able to read from and write to the /var/lib/bitlbee directory to save your
+settings!
+
+--- inetd installation (more or less deprecated)
 
 After installation you have to set up inetd (you got that one running,
@@ -27,5 +45,5 @@
 you choose), add it and use that name in the inetd.conf entry.
 
--- xinetd installation
+-- xinetd installation (equally deprecated)
 
 Most machines use xinetd instead of inetd these days. If your machine runs
@@ -41,23 +59,4 @@
 by default) and chown it to the UID BitlBee is running as. Make sure this
 directory is read-/writable by this user only.
-
---- (Fork)Daemon mode
-
-If you don't want to run any inetd daemon, you can run BitlBee in Daemon
-mode. Right now, daemon mode may be a bad idea on servers with multiple
-users, since possible fatal BitlBee bugs will crash the BitlBee process and
-disconnect all connected users at once. Instead, you can use ForkDaemon
-mode, which serves every user from a separate process, without depending on
-an inetd daemon.
-
-To use BitlBee in daemon mode, just start it with the right flags or enable
-it in bitlbee.conf. You probably want to write an init script to start
-BitlBee automatically after a reboot. (This is where you realise using
-a package from your distro would've been a better idea. :-P)
-
-Note that the BitlBee code is getting stable enough for daemon mode to be
-useful. Some public servers use it, and it saves a lot of memory by serving
-tens of users from a single process. One crash affects all users, but these
-are becoming quite rare.
 
 
@@ -139,6 +138,9 @@
 http://bugs.bitlbee.org/
 
-Our version control system is Bazaar-NG. Our repository is at:
+Our version control system is Bazaar. Our repository is at:
 http://code.bitlbee.org/
+
+More documentation on the Wiki:
+http://wiki.bitlbee.org/
 
 
@@ -186,4 +188,4 @@
 	BitlBee - An IRC to other chat networks gateway 
 	          <http://www.bitlbee.org/>
-	Copyright (C) 2002-2007  Wilmer van der Gaast <wilmer@gaast.net>
+	Copyright (C) 2002-2010  Wilmer van der Gaast <wilmer@gaast.net>
 	                         and others
Index: doc/bitlbee.8
===================================================================
--- doc/bitlbee.8	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/bitlbee.8	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -39,7 +39,6 @@
 networks and acts as a gateway. Users can connect to the server
 with any normal IRC client and see their 'buddy list' in
-&bitlbee. BitlBee's protocol support is based on the gaim 
-protocol plugins. BitlBee currently supports Oscar (aim and icq), 
-MSN, Jabber and Yahoo.
+&bitlbee. It currently supports Oscar (AIM and ICQ),
+MSN, Jabber, Yahoo! and Twitter.
 
 \fBbitlbee\fP should be called by
Index: doc/user-guide/Makefile
===================================================================
--- doc/user-guide/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/user-guide/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -1,3 +1,7 @@
 -include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)doc/user-guide/
+endif
+
 EXTRAPARANEWLINE = 1
 # EXTRAPARANEWLINE = 0
@@ -38,5 +42,5 @@
 	chmod 0755 $(DESTDIR)$(DATADIR)
 	rm -f $(DESTDIR)$(DATADIR)/help.txt # Prevent help function from breaking in running sessions
-	install -m 0644 help.txt $(DESTDIR)$(DATADIR)/help.txt
+	install -m 0644 $(SRCDIR)help.txt $(DESTDIR)$(DATADIR)/help.txt
 
 uninstall:
Index: doc/user-guide/commands.xml
===================================================================
--- doc/user-guide/commands.xml	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/user-guide/commands.xml	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -6,5 +6,5 @@
 	<bitlbee-command name="account">
 		<short-description>IM-account list maintenance</short-description>
-		<syntax>account &lt;action&gt; [&lt;arguments&gt;]</syntax>
+		<syntax>account [&lt;account id&gt;] &lt;action&gt; [&lt;arguments&gt;]</syntax>
 
 		<description>
@@ -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">
@@ -77,5 +99,5 @@
 
 		<bitlbee-command name="del">
-			<syntax>account del &lt;account id&gt;</syntax>
+			<syntax>account &lt;account id&gt; del</syntax>
 
 			<description>
@@ -86,5 +108,5 @@
 
 				<para>
-					The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection.
+					The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection.
 				</para>
 			</description>
@@ -92,5 +114,5 @@
 
 		<bitlbee-command name="on">
-			<syntax>account on [&lt;account id&gt;]</syntax>
+			<syntax>account [&lt;account id&gt;] on</syntax>
 
 			<description>
@@ -100,5 +122,5 @@
 
 				<para>
-					The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection.
+					The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection.
 				</para>
 			</description>
@@ -107,5 +129,5 @@
 
 		<bitlbee-command name="off">
-			<syntax>account off [&lt;account id&gt;]</syntax>
+			<syntax>account [&lt;account id&gt;] off</syntax>
 
 			<description>
@@ -115,5 +137,5 @@
 
 				<para>
-					The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection.
+					The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection.
 				</para>
 			</description>
@@ -131,12 +153,12 @@
 
 		<bitlbee-command name="set">
-			<syntax>account set &lt;account id&gt;</syntax>
-			<syntax>account set &lt;account id&gt;/&lt;setting&gt;</syntax>
-			<syntax>account set &lt;account id&gt;/&lt;setting&gt; &lt;value&gt;</syntax>
-			<syntax>account set -del &lt;account id&gt;/&lt;setting&gt;</syntax>
+			<syntax>account &lt;account id&gt; set</syntax>
+			<syntax>account &lt;account id&gt; set &lt;setting&gt;</syntax>
+			<syntax>account &lt;account id&gt; set &lt;setting&gt; &lt;value&gt;</syntax>
+			<syntax>account &lt;account id&gt; set -del &lt;setting&gt;</syntax>
 
 			<description>
 				<para>
-					This command can be used to change various settings for IM accounts. For all protocols, this command can be used to change the handle or the password BitlBee uses to log in and if it should be logged in automatically. Some protocols have additional settings. You can see the settings available for a connection by typing <emphasis>account set &lt;account id&gt;</emphasis>.
+					This command can be used to change various settings for IM accounts. For all protocols, this command can be used to change the handle or the password BitlBee uses to log in and if it should be logged in automatically. Some protocols have additional settings. You can see the settings available for a connection by typing <emphasis>account &lt;account id&gt; set</emphasis>.
 				</para>
 				
@@ -146,8 +168,67 @@
 				
 				<para>
-					The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection.
+					The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection.
 				</para>
 			</description>
 		</bitlbee-command>
+	</bitlbee-command>
+
+	<bitlbee-command name="channel">
+		<short-description>Channel list maintenance</short-description>
+		<syntax>channel [&lt;account id&gt;] &lt;action&gt; [&lt;arguments&gt;]</syntax>
+
+		<description>
+			<para>
+				Available actions: del, list, set. See <emphasis>help chat &lt;action&gt;</emphasis> for more information.
+			</para>
+			
+			<para>
+				There is no <emphasis>channel add</emphasis> command. To create a new channel, just use the IRC <emphasis>/join</emphasis> command. See also <emphasis>help channels</emphasis> and <emphasis>help groupchats</emphasis>.
+			</para>
+		</description>
+
+		<bitlbee-command name="del">
+			<syntax>channel &lt;channel id&gt; del</syntax>
+
+			<description>
+				<para>
+					Remove a channel and forget all its settings. You can only remove channels you're not currently in, and can't remove the main control channel. (You can, however, leave it.)
+				</para>
+			</description>
+
+		</bitlbee-command>
+
+		<bitlbee-command name="list">
+			<syntax>channel list</syntax>
+
+			<description>
+				<para>
+					This command gives you a list of all the channels you configured.
+				</para>
+			</description>
+
+		</bitlbee-command>
+
+		<bitlbee-command name="set">
+			<syntax>channel [&lt;channel id&gt;] set</syntax>
+			<syntax>channel [&lt;channel id&gt;] set &lt;setting&gt;</syntax>
+			<syntax>channel [&lt;channel id&gt;] set &lt;setting&gt; &lt;value&gt;</syntax>
+			<syntax>channel [&lt;channel id&gt;] set -del &lt;setting&gt;</syntax>
+
+			<description>
+				<para>
+					This command can be used to change various settings for channels. Different channel types support different settings. You can see the settings available for a channel by typing <emphasis>channel &lt;channel id&gt; set</emphasis>.
+				</para>
+				
+				<para>
+					For more infomation about a setting, see <emphasis>help set &lt;setting&gt;</emphasis>.
+				</para>
+				
+				<para>
+					The channel ID can be a number (see <emphasis>channel list</emphasis>), or (part of) its name, as long as it matches only one channel. If you want to change settings of the current channel, you can omit the channel ID.
+				</para>
+			</description>
+		</bitlbee-command>
+
 	</bitlbee-command>
 
@@ -159,5 +240,5 @@
 
 			<para>
-				Available actions: add, del, list, with and set. See <emphasis>help chat &lt;action&gt;</emphasis> for more information.
+				Available actions: add, with. See <emphasis>help chat &lt;action&gt;</emphasis> for more information.
 			</para>
 
@@ -165,5 +246,5 @@
 
 		<bitlbee-command name="add">
-			<syntax>chat add &lt;account&gt; &lt;room&gt; [&lt;channel&gt;]</syntax>
+			<syntax>chat add &lt;account id&gt; &lt;room&gt; [&lt;channel&gt;]</syntax>
 
 			<description>
@@ -183,64 +264,19 @@
 		</bitlbee-command>
 
-		<bitlbee-command name="del">
-			<syntax>chat del &lt;chat id&gt;</syntax>
+		<bitlbee-command name="with">
+			<syntax>chat with &lt;nickname&gt;</syntax>
 
 			<description>
 				<para>
-					This commands deletes an chatroom from your list.
-				</para>
-
-				<para>
-					The room ID can be a number (see <emphasis>chat list</emphasis>), or (part of) the name of the room/channel.
+					While most <emphasis>chat</emphasis> subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what <emphasis>/join #nickname</emphasis> used to do in older BitlBee versions.
 				</para>
 			</description>
 		</bitlbee-command>
-
-		<bitlbee-command name="list">
-			<syntax>chat list</syntax>
-
-			<description>
-				<para>
-					This command gives you a list of all the chatrooms known by BitlBee.
-				</para>
-			</description>
-		</bitlbee-command>
-
-		<bitlbee-command name="with">
-			<syntax>chat with &lt;nickname&gt;</syntax>
-
-			<description>
-				<para>
-					While most <emphasis>chat</emphasis> subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what <emphasis>/join #nickname</emphasis> used to do in older BitlBee versions.
-				</para>
-			</description>
-		</bitlbee-command>
-
-		<bitlbee-command name="set">
-			<syntax>chat set &lt;chat id&gt;</syntax>
-			<syntax>chat set &lt;chat id&gt;/&lt;setting&gt;</syntax>
-			<syntax>chat set &lt;chat id&gt;/&lt;setting&gt; &lt;value&gt;</syntax>
-			<syntax>chat set -del &lt;chat id&gt;/&lt;setting&gt;</syntax>
-
-			<description>
-				<para>
-					This command can be used to change various settings for chatrooms.
-				</para>
-				
-				<para>
-					For more infomation about a setting, see <emphasis>help set &lt;setting&gt;</emphasis>.
-				</para>
-				
-				<para>
-					The room ID can be a number (see <emphasis>chat list</emphasis>), or (part of) the name of the room/channel.
-				</para>
-			</description>
-		</bitlbee-command>
 	</bitlbee-command>
 
 	<bitlbee-command name="add">
 		<short-description>Add a buddy to your contact list</short-description>
-		<syntax>add &lt;connection&gt; &lt;handle&gt; [&lt;nick&gt;]</syntax>
-		<syntax>add -tmp &lt;connection&gt; &lt;handle&gt; [&lt;nick&gt;]</syntax>
+		<syntax>add &lt;account id&gt; &lt;handle&gt; [&lt;nick&gt;]</syntax>
+		<syntax>add -tmp &lt;account id&gt; &lt;handle&gt; [&lt;nick&gt;]</syntax>
 
 		<description>
@@ -251,4 +287,8 @@
 			<para>
 				If you want, you can also tell BitlBee what nick to give the new contact. The -tmp option adds the buddy to the internal BitlBee structures only, not to the real contact list (like done by <emphasis>set handle_unknown add</emphasis>). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications.
+			</para>
+
+			<para>
+				If you use this command in a control channel containing people from only one group, the new contact will be added to that group automatically.
 			</para>
 		</description>
@@ -376,4 +416,23 @@
 	</bitlbee-command>
 
+	<bitlbee-setting name="account" type="string" scope="channel">
+
+		<description>
+			<para>
+				For control channels with <emphasis>fill_by</emphasis> set to <emphasis>account</emphasis>: Set this setting to the account id (numeric, or part of the username) of the account containing the contacts you want to see in this channel.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="allow_takeover" type="boolean" scope="global">
+		<default>true</default>
+
+		<description>
+			<para>
+				When you're already connected to a BitlBee server and you connect (and identify) again, BitlBee will offer to migrate your existing session to the new connection. If for whatever reason you don't want this, you can disable this setting.
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="auto_connect" type="boolean" scope="both">
 		<default>true</default>
@@ -390,10 +449,10 @@
 	</bitlbee-setting>
 
-	<bitlbee-setting name="auto_join" type="boolean" scope="chat">
+	<bitlbee-setting name="auto_join" type="boolean" scope="channel">
 		<default>false</default>
 
 		<description>
 			<para>
-				With this option enabled, BitlBee will automatically join this chatroom when you log in.
+				With this option enabled, BitlBee will automatically join this channel when you log in.
 			</para>
 		</description>
@@ -401,5 +460,5 @@
 
 	<bitlbee-setting name="auto_reconnect" type="boolean" scope="both">
-		<default>false</default>
+		<default>true</default>
 
 		<description>
@@ -436,4 +495,22 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="auto_reply_timeout" type="integer" scope="account">
+		<default>10800</default>
+
+		<description>
+			<para>
+				For Twitter accounts: If you respond to Tweets IRC-style (like "nickname: reply"), this will automatically be converted to the usual Twitter format ("@screenname reply").
+			</para>
+
+			<para>
+				By default, BitlBee will then also add a reference to that person's most recent Tweet, unless that message is older than the value of this setting in seconds.
+			</para>
+
+			<para>
+				If you want to disable this feature, just set this to 0. Alternatively, if you want to write a message once that is <emphasis>not</emphasis> a reply, use the Twitter reply syntax (@screenname).
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="away" type="string" scope="both">
 		<description>
@@ -459,36 +536,39 @@
 				With this option enabled, the root user devoices people when they go away (just away, not offline) and gives the voice back when they come back. You might dislike the voice-floods you'll get if your contact list is huge, so this option can be disabled.
 			</para>
-		</description>
-	</bitlbee-setting>
-
-	<bitlbee-setting name="buddy_sendbuffer" type="boolean" scope="global">
-		<default>false</default>
-
-		<description>
-			<para>
-				By default, when you send a message to someone, BitlBee forwards this message to the user immediately. When you paste a large number of lines, the lines will be sent in separate messages, which might not be very nice to read. If you enable this setting, BitlBee will buffer your messages and wait for more data.
-			</para>
-
-			<para>
-				Using the <emphasis>buddy_sendbuffer_delay</emphasis> setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent.
-			</para>
-
-			<para>
-				Please note that if you remove a buddy from your list (or if the connection to that user drops) and there's still data in the buffer, this data will be lost. BitlBee will not try to send the message to the user in those cases.
-			</para>
-		</description>
-	</bitlbee-setting>
-
-	<bitlbee-setting name="buddy_sendbuffer_delay" type="integer" scope="global">
-		<default>200</default>
-
-		<description>
-
-			<para>
-				Tell BitlBee after how many (mili)seconds a buffered message should be sent. Values greater than 5 will be interpreted as miliseconds, 5 and lower as seconds.
-			</para>
-
-			<para>
-				See also the <emphasis>buddy_sendbuffer</emphasis> setting.
+			
+			<para>
+				Replaced with the <emphasis>show_users</emphasis> setting. See <emphasis>help show_users</emphasis>.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="away_reply_timeout" type="integer" scope="global">
+		<default>3600</default>
+
+		<description>
+			<para>
+				Most IRC servers send a user's away message every time s/he gets a private message, to inform the sender that they may not get a response immediately. With this setting set to 0, BitlBee will also behave like this.
+			</para>
+
+			<para>
+				Since not all IRC clients do an excellent job at suppressing these messages, this setting lets BitlBee do it instead. BitlBee will wait this many seconds (or until the away state/message changes) before re-informing you that the person's away.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="base_url" type="string" scope="account">
+		<default>http://twitter.com</default>
+
+		<description>
+			<para>
+				There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations.
+			</para>
+
+			<para>
+				For example, set this setting to <emphasis>http://identi.ca/api</emphasis> to use Identi.ca.
+			</para>
+
+			<para>
+				Keep two things in mind: When not using Twitter, you <emphasis>must</emphasis> also disable the <emphasis>oauth</emphasis> setting as it currently only works with Twitter. If you're still having issues, make sure there is <emphasis>no</emphasis> slash at the end of the URL you enter here.
 			</para>
 		</description>
@@ -521,4 +601,45 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="chat_type" type="string" scope="channel">
+		<default>groupchat</default>
+		<possible-values>groupchat, room</possible-values>
+
+		<description>
+			<para>
+				There are two kinds of chat channels: simple groupchats (basically normal IM chats with more than two participants) and names chatrooms, more similar to IRC channels.
+			</para>
+			
+			<para>
+				BitlBee supports both types. With this setting set to <emphasis>groupchat</emphasis> (the default), you can just invite people into the room and start talking.
+			</para>
+			
+			<para>
+				For setting up named chatrooms, it's currently easier to just use the <emphasis>chat add</emphasis> command.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="commands" type="boolean" scope="account">
+		<default>true</default>
+
+		<description>
+			<para>
+				With this setting enabled, you can use some commands in your Twitter channel/query. The commands are simple and not documented in too much detail:
+			</para>
+
+			<variablelist>
+				<varlistentry><term>undo [&lt;id&gt;]</term><listitem><para>Delete your last Tweet (or one with the given ID)</para></listitem></varlistentry>
+				<varlistentry><term>rt &lt;screenname|id&gt;</term><listitem><para>Retweet someone's last Tweet (or one with the given ID)</para></listitem></varlistentry>
+				<varlistentry><term>follow &lt;screenname&gt;</term><listitem><para>Start following a person</para></listitem></varlistentry>
+				<varlistentry><term>unfollow &lt;screenname&gt;</term><listitem><para>Stop following a person</para></listitem></varlistentry>
+				<varlistentry><term>post &lt;message&gt;</term><listitem><para>Post a tweet</para></listitem></varlistentry>
+			</variablelist>
+
+			<para>
+				Anything that doesn't look like a command will be treated as a tweet. Watch out for typos! :-)
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="debug" type="boolean" scope="global">
 		<default>false</default>
@@ -560,6 +681,48 @@
 	</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>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="fill_by" type="string" scope="channel">
+		<default>all</default>
+		<possible-values>all, group, account, protocol</possible-values>
+
+		<description>
+			<para>
+				For control channels only: This setting determines which contacts the channel gets populated with.
+			</para>
+
+			<para>
+				By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol.
+			</para>
+			
+			<para>
+				Change this setting and the corresponding <emphasis>account</emphasis>/<emphasis>group</emphasis>/<emphasis>protocol</emphasis> setting to set up this selection.
+			</para>
+			
+			<para>
+				Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See <emphasis>help channels</emphasis>.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="group" type="string" scope="channel">
+
+		<description>
+			<para>
+				For control channels with <emphasis>fill_by</emphasis> set to <emphasis>group</emphasis>: Set this setting to the name of the group containing the contacts you want to see in this channel.
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="handle_unknown" type="string" scope="global">
-		<default>root</default>
+		<default>add_channel</default>
 		<possible-values>root, add, add_private, add_channel, ignore</possible-values>
 
@@ -587,4 +750,15 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="ignore_auth_requests" type="boolean" scope="account">
+		<default>false</default>
+
+		<description>
+			<para>
+				Only supported by OSCAR so far, you can use this setting to ignore ICQ authorization requests, which are hardly used for legitimate (i.e. non-spam) reasons anymore.
+			</para>
+		</description>
+
+	</bitlbee-setting>
+
 	<bitlbee-setting name="lcnicks" type="boolean" scope="global">
 		<default>true</default>
@@ -598,4 +772,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>
@@ -609,9 +794,73 @@
 	</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>
+
+	</bitlbee-setting>
+
 	<bitlbee-setting name="nick" type="string" scope="chat">
-
 		<description>
 			<para>
 				You can use this option to set your nickname in a chatroom. You won't see this nickname yourself, but other people in the room will. By default, BitlBee will use your username as the chatroom nickname.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="nick_format" type="string" scope="both">
+		<default>%-@nick</default>
+
+		<description>
+			<para>
+				By default, BitlBee tries to derive sensible nicknames for all your contacts from their IM handles. In some cases, IM modules (ICQ for example) will provide a nickname suggestion, which will then be used instead. This setting lets you change this behaviour.
+			</para>
+
+			<para>
+				Whenever this setting is set for an account, it will be used for all its contacts. If it's not set, the global value will be used.
+			</para>
+
+			<para>
+				It's easier to describe this setting using a few examples:
+			</para>
+
+			<para>
+				FB-%full_name will make all nicknames start with "FB-", followed by the person's full name. For example you can set this format for your Facebook account so all Facebook contacts are clearly marked.
+			</para>
+
+			<para>
+				[%group]%-@nick will make all nicknames start with the group the contact is in between square brackets, followed by the nickname suggestions from the IM module if available, or otherwise the handle. Because of the "-@" part, everything from the first @ will be stripped.
+			</para>
+
+			<para>
+				See <emphasis>help nick_format</emphasis> for more information.
 			</para>
 		</description>
@@ -633,4 +882,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>
@@ -663,4 +931,37 @@
 		</description>
 	</bitlbee-setting>
+
+	<bitlbee-setting name="paste_buffer" type="boolean" scope="global">
+		<default>false</default>
+
+		<description>
+			<para>
+				By default, when you send a message to someone, BitlBee forwards this message to the user immediately. When you paste a large number of lines, the lines will be sent in separate messages, which might not be very nice to read. If you enable this setting, BitlBee will buffer your messages and wait for more data.
+			</para>
+
+			<para>
+				Using the <emphasis>paste_buffer_delay</emphasis> setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent.
+			</para>
+
+			<para>
+				Please note that if you remove a buddy from your list (or if the connection to that user drops) and there's still data in the buffer, this data will be lost. BitlBee will not try to send the message to the user in those cases.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="paste_buffer_delay" type="integer" scope="global">
+		<default>200</default>
+
+		<description>
+
+			<para>
+				Tell BitlBee after how many (mili)seconds a buffered message should be sent. Values greater than 5 will be interpreted as miliseconds, 5 and lower as seconds.
+			</para>
+
+			<para>
+				See also the <emphasis>paste_buffer</emphasis> setting.
+			</para>
+		</description>
+	</bitlbee-setting>
 	
 	<bitlbee-setting name="port" type="integer" scope="account">
@@ -696,4 +997,13 @@
 			<para>
 				This setting is remembered (during one session) per-user, this setting only changes the default state. This option takes effect as soon as you reconnect.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="protocol" type="string" scope="channel">
+
+		<description>
+			<para>
+				For control channels with <emphasis>fill_by</emphasis> set to <emphasis>protocol</emphasis>: Set this setting to the name of the IM protocol of all contacts you want to see in this channel.
 			</para>
 		</description>
@@ -768,4 +1078,40 @@
 	</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>
+			
+			<para>
+				Replaced with the <emphasis>show_users</emphasis> setting. See <emphasis>help show_users</emphasis>.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="show_users" type="string" scope="channel">
+		<default>online+,away</default>
+
+		<description>
+			<para>
+				Comma-separated list of statuses of users you want in the channel,
+				and any modes they should have. The following statuses are currently
+				recognised: <emphasis>online</emphasis> (i.e. available, not
+				away), <emphasis>away</emphasis>, and <emphasis>offline</emphasis>.
+			</para>
+			
+			<para>
+				If a status is followed by a valid channel mode character
+				(@, % or +), it will be given to users with that status.
+				For example, <emphasis>online@,away+,offline</emphasis> will
+				show all users in the channel. Online people will
+				have +o, people who are online but away will have +v,
+				and others will have no special modes.
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="simulate_netsplit" type="boolean" scope="global">
 		<default>true</default>
@@ -817,4 +1163,49 @@
 	</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="tag" type="string" scope="account">
+		<description>
+			<para>
+				For every account you have, you can set a tag you can use to uniquely identify that account. This tag can be used instead of the account number (or protocol name, or part of the screenname) when using commands like <emphasis>account</emphasis>, <emphasis>add</emphasis>, etc. You can't have two accounts with one and the same account tag.
+			</para>
+
+			<para>
+				By default, it will be set to the name of the IM protocol. Once you add a second account on an IM network, a numeric suffix will be added, starting with 2.
+			</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>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="tls" type="boolean" scope="account">
 		<default>try</default>
@@ -845,4 +1236,33 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="translate_to_nicks" type="boolean" scope="channel">
+		<default>true</default>
+
+		<description>
+			<para>
+				IRC's nickname namespace is quite limited compared to most IM protocols. Not any non-ASCII characters are allowed, in fact nicknames have to be mostly alpha-numeric. Also, BitlBee has to add underscores sometimes to avoid nickname collisions.
+			</para>
+
+			<para>
+				While normally the BitlBee user is the only one seeing these names, they may be exposed to other chatroom participants for example when addressing someone in the channel (with or without tab completion). By default BitlBee will translate these stripped nicknames back to the original nick. If you don't want this, disable this setting.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="type" type="string" scope="channel">
+		<default>control</default>
+		<possible-values>control, chat</possible-values>
+
+		<description>
+			<para>
+				BitlBee supports two kinds of channels: control channels (usually with a name starting with a &amp;) and chatroom channels (name usually starts with a #).
+			</para>
+
+			<para>
+				See <emphasis>help channels</emphasis> for a full description of channel types in BitlBee.
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="typing_notice" type="boolean" scope="global">
 		<default>false</default>
@@ -851,4 +1271,18 @@
 			<para>
 				Sends you a /notice when a user starts typing a message (if supported by the IM protocol and the user's client). To use this, you most likely want to use a script in your IRC client to show this information in a more sensible way.
+			</para>
+		</description>
+	</bitlbee-setting>
+
+	<bitlbee-setting name="user_agent" type="string" scope="account">
+		<default>BitlBee</default>
+
+		<description>
+			<para>
+				Some Jabber servers are configured to only allow a few (or even just one) kinds of XMPP clients to connect to them.
+			</para>
+			
+			<para>
+				You can change this setting to make BitlBee present itself as a different client, so that you can still connect to these servers.
 			</para>
 		</description>
@@ -963,5 +1397,5 @@
 
 	<bitlbee-command name="identify">
-		<syntax>identify &lt;password&gt;</syntax>
+		<syntax>identify [-noload|-force] &lt;password&gt;</syntax>
 		<short-description>Identify yourself with your password</short-description>
 
@@ -973,4 +1407,12 @@
 			<para>
 				Once you're registered, you can change your password using <emphasis>set password &lt;password&gt;</emphasis>.
+			</para>
+
+			<para>
+				The <emphasis>-noload</emphasis> and <emphasis>-force</emphasis> flags can be used to identify when you're logged into some IM accounts already. <emphasis>-force</emphasis> will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already).
+			</para>
+			
+			<para>
+				<emphasis>-noload</emphasis> will log you in but not load any accounts and settings saved under your current nickname. These will be overwritten once you save your settings (i.e. when you disconnect).
 			</para>
 		</description>
@@ -1000,20 +1442,56 @@
 	</bitlbee-command>
 
-	<bitlbee-command name="nick">
-		<short-description>Change friendly name, nick</short-description>
-		<syntax>nick &lt;connection&gt; [&lt;new nick&gt;]</syntax>
-		<syntax>nick &lt;connection&gt;</syntax>
-
-		<description>
-			<para>
-				Deprecated: Use the per-account <emphasis>display_name</emphasis> setting to read and change this information.
-			</para>
-		</description>
-
-		<ircexample>
-			<ircline nick="wouter">account set 1/display_name "The majestik møøse"</ircline>
-			<ircline nick="root">display_name = `The majestik møøse'</ircline>
-		</ircexample>
-
-	</bitlbee-command>
+	<bitlbee-command name="group">
+		<short-description>Contact group management</short-description>
+		<syntax>group list</syntax>
+
+		<description>
+			<para>
+				Only the <emphasis>group list</emphasis> command is supported at the moment, which shows a list of all groups defined so far.
+			</para>
+		</description>
+	</bitlbee-command>
+	
+	<bitlbee-command name="transfers">
+		<short-description>Monitor, cancel, or reject file transfers</short-description>
+		<syntax>transfers [&lt;cancel&gt; id | &lt;reject&gt;]</syntax>
+		
+		<description>
+			<para>
+				Without parameters the currently pending file transfers and their status will be listed. Available actions are <emphasis>cancel</emphasis> and <emphasis>reject</emphasis>. See <emphasis>help transfers &lt;action&gt;</emphasis> for more information.
+			</para>
+
+			<ircexample>
+				<ircline nick="ulim">transfers</ircline>
+			</ircexample>
+		</description>
+		
+		<bitlbee-command name="cancel">
+			<short-description>Cancels the file transfer with the given id</short-description>
+			<syntax>transfers &lt;cancel&gt; id</syntax>
+
+			<description>
+				<para>Cancels the file transfer with the given id</para>
+			</description>
+
+			<ircexample>
+				<ircline nick="ulim">transfers cancel 1</ircline>
+				<ircline nick="root">Canceling file transfer for test</ircline>
+			</ircexample>
+		</bitlbee-command>
+
+		<bitlbee-command name="reject">
+			<short-description>Rejects all incoming transfers</short-description>
+			<syntax>transfers &lt;reject&gt;</syntax>
+
+			<description>
+				<para>Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is neccessary. Or is it?</para>
+			</description>
+
+			<ircexample>
+				<ircline nick="ulim">transfers reject</ircline>
+			</ircexample>
+		</bitlbee-command>
+	</bitlbee-command>
+	
 </chapter>
Index: doc/user-guide/help.xml
===================================================================
--- doc/user-guide/help.xml	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/user-guide/help.xml	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -14,7 +14,9 @@
   <varlistentry><term>quickstart</term><listitem><para>A short introduction into BitlBee</para></listitem></varlistentry>
   <varlistentry><term>commands</term><listitem><para>All available commands and settings</para></listitem></varlistentry>
+  <varlistentry><term>channels</term><listitem><para>About creating and customizing channels</para></listitem></varlistentry>
   <varlistentry><term>away</term><listitem><para>About setting away states</para></listitem></varlistentry>
+  <varlistentry><term>groupchats</term><listitem><para>How to work with groupchats on BitlBee</para></listitem></varlistentry>
+  <varlistentry><term>nick_changes</term><listitem><para>Changing your nickname without losing any settings</para></listitem></varlistentry>
   <varlistentry><term>smileys</term><listitem><para>A summary of some non-standard smileys you might find and fail to understand</para></listitem></varlistentry>
-  <varlistentry><term>groupchats</term><listitem><para>How to work with groupchats on BitlBee</para></listitem></varlistentry>
 </variablelist>
 
@@ -43,7 +45,9 @@
   <varlistentry><term>quickstart</term><listitem><para>A short introduction into BitlBee</para></listitem></varlistentry>
   <varlistentry><term>commands</term><listitem><para>All available commands and settings</para></listitem></varlistentry>
+  <varlistentry><term>channels</term><listitem><para>About creating and customizing channels</para></listitem></varlistentry>
   <varlistentry><term>away</term><listitem><para>About setting away states</para></listitem></varlistentry>
+  <varlistentry><term>groupchats</term><listitem><para>How to work with groupchats on BitlBee</para></listitem></varlistentry>
+  <varlistentry><term>nick_changes</term><listitem><para>Changing your nickname without losing any settings</para></listitem></varlistentry>
   <varlistentry><term>smileys</term><listitem><para>A summary of some non-standard smileys you might find and fail to understand</para></listitem></varlistentry>
-  <varlistentry><term>groupchats</term><listitem><para>How to work with groupchats on BitlBee</para></listitem></varlistentry>
 </variablelist>
 
Index: doc/user-guide/misc.xml
===================================================================
--- doc/user-guide/misc.xml	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/user-guide/misc.xml	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -117,3 +117,120 @@
 </sect1>
 
+<sect1 id="nick_changes">
+<title>Changing your nickname</title>
+
+<para>
+BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated.
+</para>
+
+<para>
+The restriction no longer exists now though. When you change your nick (just using the <emphasis>/nick</emphasis> command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved.
+</para>
+
+<para>
+To restore your logged-in status, you need to either use the <emphasis>register</emphasis> command to create an account under the new nickname, or use <emphasis>identify -noload</emphasis> to re-identify yourself under the new nickname. The <emphasis>-noload</emphasis> flag tells the command to verify your password and log you in, but not load any new settings. See <emphasis>help identify</emphasis> for more information.
+</para>
+
+</sect1>
+
+<sect1 id="channels">
+<title>Dealing with channels</title>
+
+<para>
+You can have as many channels in BitlBee as you want. You maintain your channel list using the <emphasis>channel</emphasis> command. You can create new channels by just joining them, like on regular IRC networks.
+</para>
+
+<para>
+You can create two kinds of channels. Control channels, and groupchat channels. By default, BitlBee will set up new channels as control channels if their name starts with an &amp;, and as chat channels if it starts with a #.
+</para>
+
+<para>
+Control channels are where you see your contacts. By default, you will have one control channel called &amp;bitlbee, containing all your contacts. But you can create more, if you want, and divide your contact list accross several channels.
+</para>
+
+<para>
+For example, you can have one channel with all contacts from your MSN Messenger account in it. Or all contacts from the group called "Work".
+</para>
+
+<para>
+Type <emphasis>help channels2</emphasis> to read more.
+</para>
+
+</sect1>
+
+<sect1 id="channels2">
+<title>Creating a channel</title>
+
+<para>
+When you create a new channel, BitlBee will try to guess from its name which contacts to fill it with. For example, if the channel name (excluding the &amp;) matches the name of a group in which you have one or more contacts, the channel will contain all those contacts.
+</para>
+
+<para>
+Any valid account ID (so a number, protocol name or part of screenname, as long as it's unique) can also be used as a channel name. So if you just join &amp;msn, it will contain all your MSN contacts. And if you have a Facebook account set up, you can see its contacts by just joining &amp;facebook.
+</para>
+
+<para>
+To start a simple group chat, you simply join a channel which a name starting with #, and invite people into it. All people you invite have to be on the same IM network and contact list.
+</para>
+
+<para>
+If you want to configure your own channels, you can use the <emphasis>channel set</emphasis>.
+</para>
+
+</sect1>
+
+<sect1 id="nick_format">
+<title>Nickname formatting</title>
+
+<para>
+The <emphasis>nick_format</emphasis> setting can be set globally using
+the <emphasis>set</emphasis> command, or per account using <emphasis>account
+set</emphasis> (so that you can set a per-account suffix/prefix or have 
+nicknames generated from full names for certain accounts).
+</para>
+
+<para>
+The setting is basically some kind of format string. It can contain normal
+text that will be copied to the nick, combined with several variables:
+</para>
+
+<variablelist>
+	<varlistentry><term>%nick</term><listitem><para>Nickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested.</para></listitem></varlistentry>
+	<varlistentry><term>%handle</term><listitem><para>The handle/screenname of the contact.</para></listitem></varlistentry>
+	<varlistentry><term>%full_name</term><listitem><para>The full name of the contact.</para></listitem></varlistentry>
+	<varlistentry><term>%first_name</term><listitem><para>The first name of the contact (the full name up to the first space).</para></listitem></varlistentry>
+	<varlistentry><term>%group</term><listitem><para>The name of the group this contact is a member of</para></listitem></varlistentry>
+	<varlistentry><term>%account</term><listitem><para>Account tag of the contact</para></listitem></varlistentry>
+</variablelist>
+
+<para>
+Invalid characters (like spaces) will always be stripped. Depending on your
+locale settings, characters with accents will be converted to ASCII.
+</para>
+
+<para>
+See <emphasis>set nick_format2</emphasis> for some more information.
+</para>
+
+</sect1>
+
+<sect1 id="nick_format2">
+<title>Nickname formatting - modifiers</title>
+
+<para>
+Two modifiers are currently available: You can include only the first few
+characters of a variable by putting a number right after the %. For
+example, <emphasis>[%3group]%-@nick</emphasis> will include only the first
+three characters of the group name in the nick.
+</para>
+
+<para>
+Also, you can truncate variables from a certain character using
+the <emphasis>-</emphasis> modifier. For example, you may want to leave out
+everything after the @. <emphasis>%-@handle</emphasis> will expand to
+everything in the handle up to the first @.
+</para>
+
+</sect1>
+
 </chapter>
Index: doc/user-guide/quickstart.xml
===================================================================
--- doc/user-guide/quickstart.xml	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ doc/user-guide/quickstart.xml	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -3,5 +3,5 @@
 
 <para>
-Welcome to BitlBee, your IRC gateway to ICQ, MSN, AOL, Jabber and Yahoo Instant Messaging Systems.
+Welcome to BitlBee, your IRC gateway to ICQ, MSN, AOL, Jabber, Yahoo! and Twitter.
 </para>
 
@@ -43,5 +43,5 @@
 
 <para>
-Other available IM protocols are msn, oscar, and yahoo. OSCAR is the protocol used by ICQ and AOL. For more information about the <emphasis>account add</emphasis> command, see <emphasis>help account add</emphasis>.
+Other available IM protocols are msn, oscar, yahoo and twitter. OSCAR is the protocol used by ICQ and AOL. For more information about the <emphasis>account add</emphasis> command, see <emphasis>help account add</emphasis>.
 </para>
 
Index: help.c
===================================================================
--- help.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ help.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ help.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ ipc.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -2,5 +2,5 @@
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2006 Wilmer van der Gaast and others                *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
   \********************************************************************/
 
@@ -29,8 +29,13 @@
 #include "commands.h"
 #ifndef _WIN32
+#include <sys/uio.h>
 #include <sys/un.h>
 #endif
 
 GSList *child_list = NULL;
+static int ipc_child_recv_fd = -1;
+
+static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both );
+static gboolean ipc_send_fd( int fd, int send_fd );
 
 static void ipc_master_cmd_client( irc_t *data, char **cmd )
@@ -49,4 +54,5 @@
 	}
 	
+	/* CLIENT == On initial connects, HELLO is after /RESTARTs. */
 	if( g_strcasecmp( cmd[0], "CLIENT" ) == 0 )
 		ipc_to_children_str( "OPERMSG :Client connecting (PID=%d): %s@%s (%s)\r\n",
@@ -54,4 +60,15 @@
 }
 
+static void ipc_master_cmd_nick( irc_t *data, char **cmd )
+{
+	struct bitlbee_child *child = (void*) data;
+	
+	if( child && cmd[1] )
+	{
+		g_free( child->nick );
+		child->nick = g_strdup( cmd[1] );
+	}
+}
+
 static void ipc_master_cmd_die( irc_t *data, char **cmd )
 {
@@ -110,4 +127,99 @@
 	global.restart = -1;
 	bitlbee_shutdown( NULL, -1, 0 );
+}
+
+void ipc_master_cmd_identify( irc_t *data, char **cmd )
+{
+	struct bitlbee_child *child = (void*) data, *old = NULL;
+	char *resp;
+	GSList *l;
+	
+	if( !child || !child->nick || strcmp( child->nick, cmd[1] ) != 0 )
+		return;
+	
+	g_free( child->password );
+	child->password = g_strdup( cmd[2] );
+	
+	for( l = child_list; l; l = l->next )
+	{
+		old = l->data;
+		if( child != old &&
+		    old->nick && nick_cmp( old->nick, child->nick ) == 0 &&
+		    old->password && strcmp( old->password, child->password ) == 0 )
+			break;
+	}
+	
+	if( l && !child->to_child && !old->to_child )
+	{
+		resp = "TAKEOVER INIT\r\n";
+		child->to_child = old;
+		old->to_child = child;
+	}
+	else
+	{
+		/* Won't need the fd since we can't send it anywhere. */
+		closesocket( child->to_fd );
+		child->to_fd = -1;
+		resp = "TAKEOVER NO\r\n";
+	}
+	
+	if( write( child->ipc_fd, resp, strlen( resp ) ) != strlen( resp ) )
+		ipc_master_free_one( child );
+}
+
+
+void ipc_master_cmd_takeover( irc_t *data, char **cmd )
+{
+	struct bitlbee_child *child = (void*) data;
+	char *fwd = NULL;
+	
+	/* Normal daemon mode doesn't keep these and has simplified code for
+	   takeovers. */
+	if( child == NULL )
+		return;
+	
+	if( child->to_child == NULL ||
+	    g_slist_find( child_list, child->to_child ) == NULL )
+		return ipc_master_takeover_fail( child, FALSE );
+	
+	if( strcmp( cmd[1], "AUTH" ) == 0 )
+	{
+		/* New connection -> Master */
+		if( child->to_child &&
+		    child->nick && child->to_child->nick && cmd[2] &&
+		    child->password && child->to_child->password && cmd[3] &&
+		    strcmp( child->nick, child->to_child->nick ) == 0 &&
+		    strcmp( child->nick, cmd[2] ) == 0 &&
+		    strcmp( child->password, child->to_child->password ) == 0 &&
+		    strcmp( child->password, cmd[3] ) == 0 )
+		{
+			ipc_send_fd( child->to_child->ipc_fd, child->to_fd );
+			
+			fwd = irc_build_line( cmd );
+			if( write( child->to_child->ipc_fd, fwd, strlen( fwd ) ) != strlen( fwd ) )
+				ipc_master_free_one( child );
+			g_free( fwd );
+		}
+		else
+			return ipc_master_takeover_fail( child, TRUE );
+	}
+	else if( strcmp( cmd[1], "DONE" ) == 0 || strcmp( cmd[1], "FAIL" ) == 0 )
+	{
+		/* Old connection -> Master */
+		int fd;
+		
+		/* The copy was successful (or not), we don't need it anymore. */
+		closesocket( child->to_fd );
+		child->to_fd = -1;
+		
+		/* Pass it through to the other party, and flush all state. */
+		fwd = irc_build_line( cmd );
+		fd = child->to_child->ipc_fd;
+		child->to_child->to_child = NULL;
+		child->to_child = NULL;
+		if( write( fd, fwd, strlen( fwd ) ) != strlen( fwd ) )
+			ipc_master_free_one( child );
+		g_free( fwd );
+	}
 }
 
@@ -115,4 +227,5 @@
 	{ "client",     3, ipc_master_cmd_client,     0 },
 	{ "hello",      0, ipc_master_cmd_client,     0 },
+	{ "nick",       1, ipc_master_cmd_nick,       0 },
 	{ "die",        0, ipc_master_cmd_die,        0 },
 	{ "deaf",       0, ipc_master_cmd_deaf,       0 },
@@ -123,4 +236,6 @@
 	{ "kill",       2, NULL,                      IPC_CMD_TO_CHILDREN },
 	{ "restart",    0, ipc_master_cmd_restart,    0 },
+	{ "identify",   2, ipc_master_cmd_identify,   0 },
+	{ "takeover",   1, ipc_master_cmd_takeover,   0 },
 	{ NULL }
 };
@@ -138,5 +253,5 @@
 	
 	if( strchr( irc->umode, 'w' ) )
-		irc_write( irc, ":%s WALLOPS :%s", irc->myhost, cmd[1] );
+		irc_write( irc, ":%s WALLOPS :%s", irc->root->host, cmd[1] );
 }
 
@@ -147,5 +262,5 @@
 	
 	if( strchr( irc->umode, 's' ) )
-		irc_write( irc, ":%s NOTICE %s :%s", irc->myhost, irc->nick, cmd[1] );
+		irc_write( irc, ":%s NOTICE %s :%s", irc->root->host, irc->user->nick, cmd[1] );
 }
 
@@ -156,5 +271,5 @@
 	
 	if( strchr( irc->umode, 'o' ) )
-		irc_write( irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->myhost, irc->nick, cmd[1] );
+		irc_write( irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->root->host, irc->user->nick, cmd[1] );
 }
 
@@ -176,8 +291,8 @@
 		return;
 	
-	if( nick_cmp( cmd[1], irc->nick ) != 0 )
+	if( nick_cmp( cmd[1], irc->user->nick ) != 0 )
 		return;		/* It's not for us. */
 	
-	irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->mynick, irc->mynick, irc->myhost, irc->nick, cmd[2] );
+	irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->root->nick, irc->root->nick, irc->root->host, irc->user->nick, cmd[2] );
 	irc_abort( irc, 0, "Killed by operator: %s", cmd[2] );
 }
@@ -188,5 +303,129 @@
 		ipc_to_master_str( "HELLO\r\n" );
 	else
-		ipc_to_master_str( "HELLO %s %s :%s\r\n", irc->host, irc->nick, irc->realname );
+		ipc_to_master_str( "HELLO %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname );
+}
+
+static void ipc_child_cmd_takeover_yes( void *data );
+static void ipc_child_cmd_takeover_no( void *data );
+
+static void ipc_child_cmd_takeover( irc_t *irc, char **cmd )
+{
+	if( strcmp( cmd[1], "NO" ) == 0 )
+	{
+		/* Master->New connection */
+		/* No takeover, finish the login. */
+	}
+	else if( strcmp( cmd[1], "INIT" ) == 0 )
+	{
+		/* Master->New connection */
+		if( !set_getbool( &irc->b->set, "allow_takeover" ) )
+		{
+			ipc_child_cmd_takeover_no( irc );
+			return;
+		}
+		
+		/* Offer to take over the old session, unless for some reason
+		   we're already logging into IM connections. */
+		if( irc->login_source_id != -1 )
+			query_add( irc, NULL,
+			           "You're already connected to this server. "
+			           "Would you like to take over this session?",
+			           ipc_child_cmd_takeover_yes,
+		        	   ipc_child_cmd_takeover_no, NULL, irc );
+		
+		/* This one's going to connect to accounts, avoid that. */
+		b_event_remove( irc->login_source_id );
+		irc->login_source_id = -1;
+	}
+	else if( strcmp( cmd[1], "AUTH" ) == 0 )
+	{
+		/* Master->Old connection */
+		if( irc->password && cmd[2] && cmd[3] &&
+		    ipc_child_recv_fd != -1 &&
+		    strcmp( irc->user->nick, cmd[2] ) == 0 &&
+		    strcmp( irc->password, cmd[3] ) == 0 &&
+		    set_getbool( &irc->b->set, "allow_takeover" ) )
+		{
+			irc_switch_fd( irc, ipc_child_recv_fd );
+			irc_sync( irc );
+			irc_usermsg( irc, "You've successfully taken over your old session" );
+			ipc_child_recv_fd = -1;
+			
+			ipc_to_master_str( "TAKEOVER DONE\r\n" );
+		}
+		else
+		{
+			ipc_to_master_str( "TAKEOVER FAIL\r\n" );
+		}
+	}
+	else if( strcmp( cmd[1], "DONE" ) == 0 ) 
+	{
+		/* Master->New connection (now taken over by old process) */
+		irc_free( irc );
+	}
+	else if( strcmp( cmd[1], "FAIL" ) == 0 ) 
+	{
+		/* Master->New connection */
+		irc_usermsg( irc, "Could not take over old session" );
+	}
+}
+
+static void ipc_child_cmd_takeover_yes( void *data )
+{
+	irc_t *irc = data, *old = NULL;
+	char *to_auth[] = { "TAKEOVER", "AUTH", irc->user->nick, irc->password, NULL };
+	
+	/* Master->New connection */
+	ipc_to_master_str( "TAKEOVER AUTH %s :%s\r\n",
+	                   irc->user->nick, irc->password );
+	
+	if( global.conf->runmode == RUNMODE_DAEMON )
+	{
+		GSList *l;
+		
+		for( l = irc_connection_list; l; l = l->next )
+		{
+			old = l->data;
+			
+			if( irc != old &&
+			    irc->user->nick && old->user->nick &&
+			    irc->password && old->password &&
+			    strcmp( irc->user->nick, old->user->nick ) == 0 &&
+			    strcmp( irc->password, old->password ) == 0 )
+				break;
+		}
+		if( l == NULL )
+		{
+			to_auth[1] = "FAIL";
+			ipc_child_cmd_takeover( irc, to_auth );
+			return;
+		}
+	}
+	
+	/* Drop credentials, we'll shut down soon and shouldn't overwrite
+	   any settings. */
+	irc_usermsg( irc, "Trying to take over existing session" );
+	
+	irc_desync( irc );
+	
+	if( old )
+	{
+		ipc_child_recv_fd = dup( irc->fd );
+		ipc_child_cmd_takeover( old, to_auth );
+	}
+	
+	/* TODO: irc_setpass() should do all of this. */
+	irc_setpass( irc, NULL );
+	irc->status &= ~USTATUS_IDENTIFIED;
+	irc_umode_set( irc, "-R", 1 );
+	
+	if( old )
+		irc_abort( irc, FALSE, NULL );
+}
+
+static void ipc_child_cmd_takeover_no( void *data )
+{
+	ipc_to_master_str( "TAKEOVER NO\r\n" );
+	cmd_identify_finish( data, 0, 0 );
 }
 
@@ -199,7 +438,71 @@
 	{ "kill",       2, ipc_child_cmd_kill,        0 },
 	{ "hello",      0, ipc_child_cmd_hello,       0 },
+	{ "takeover",   1, ipc_child_cmd_takeover,    0 },
 	{ NULL }
 };
 
+gboolean ipc_child_identify( irc_t *irc )
+{
+	if( global.conf->runmode == RUNMODE_FORKDAEMON )
+	{
+		if( !ipc_send_fd( global.listen_socket, irc->fd ) )
+			ipc_child_disable();
+	
+		ipc_to_master_str( "IDENTIFY %s :%s\r\n", irc->user->nick, irc->password );
+		
+		return TRUE;
+	}
+	else if( global.conf->runmode == RUNMODE_DAEMON )
+	{
+		GSList *l;
+		irc_t *old;
+		char *to_init[] = { "TAKEOVER", "INIT", NULL };
+		
+		for( l = irc_connection_list; l; l = l->next )
+		{
+			old = l->data;
+			
+			if( irc != old &&
+			    irc->user->nick && old->user->nick &&
+			    irc->password && old->password &&
+			    strcmp( irc->user->nick, old->user->nick ) == 0 &&
+			    strcmp( irc->password, old->password ) == 0 )
+				break;
+		}
+		if( l == NULL ||
+		    !set_getbool( &irc->b->set, "allow_takeover" ) ||
+		    !set_getbool( &old->b->set, "allow_takeover" ) )
+			return FALSE;
+		
+		ipc_child_cmd_takeover( irc, to_init );
+		
+		return TRUE;
+	}
+	else
+		return FALSE;
+}
+
+static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both )
+{
+	if( child == NULL || g_slist_find( child_list, child ) == NULL )
+		return;
+	
+	if( both && child->to_child != NULL )
+		ipc_master_takeover_fail( child->to_child, FALSE );
+	
+	if( child->to_fd > -1 )
+	{
+		/* Send this error only to the new connection, which can be
+		   recognised by to_fd being set. */
+		if( write( child->ipc_fd, "TAKEOVER FAIL\r\n", 15 ) != 15 )
+		{
+			ipc_master_free_one( child );
+			return;
+		}
+		close( child->to_fd );
+		child->to_fd = -1;
+	}
+	child->to_child = NULL;
+}
 
 static void ipc_command_exec( void *data, char **cmd, const command_t *commands )
@@ -230,6 +533,10 @@
 /* Return just one line. Returns NULL if something broke, an empty string
    on temporary "errors" (EAGAIN and friends). */
-static char *ipc_readline( int fd )
-{
+static char *ipc_readline( int fd, int *recv_fd )
+{
+	struct msghdr msg;
+	struct iovec iov;
+	char ccmsg[CMSG_SPACE(sizeof(recv_fd))];
+	struct cmsghdr *cmsg;
 	char buf[513], *eol;
 	int size;
@@ -253,20 +560,48 @@
 		size = eol - buf + 2;
 	
-	if( recv( fd, buf, size, 0 ) != size )
+	iov.iov_base = buf;
+	iov.iov_len = size;
+	
+	memset( &msg, 0, sizeof( msg ) );
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = ccmsg;
+	msg.msg_controllen = sizeof( ccmsg );
+	
+	if( recvmsg( fd, &msg, 0 ) != size )
 		return NULL;
-	else
-		return g_strndup( buf, size - 2 );
+	
+	if( recv_fd )
+		for( cmsg = CMSG_FIRSTHDR( &msg ); cmsg; cmsg = CMSG_NXTHDR( &msg, cmsg ) )
+			if( cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS )
+			{
+				/* Getting more than one shouldn't happen but if it does,
+				   make sure we don't leave them around. */
+				if( *recv_fd != -1 )
+					close( *recv_fd );
+				
+				*recv_fd = *(int*) CMSG_DATA( cmsg );
+				/*
+				fprintf( stderr, "pid %d received fd %d\n", (int) getpid(), *recv_fd );
+				*/
+			}
+	
+	/*
+	fprintf( stderr, "pid %d received: %s", (int) getpid(), buf );
+	*/
+	return g_strndup( buf, size - 2 );
 }
 
 gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond )
 {
+	struct bitlbee_child *child = data;
 	char *buf, **cmd;
 	
-	if( ( buf = ipc_readline( source ) ) )
+	if( ( buf = ipc_readline( source, &child->to_fd ) ) )
 	{
 		cmd = irc_parse_line( buf );
 		if( cmd )
 		{
-			ipc_command_exec( data, cmd, ipc_master_commands );
+			ipc_command_exec( child, cmd, ipc_master_commands );
 			g_free( cmd );
 		}
@@ -285,5 +620,5 @@
 	char *buf, **cmd;
 	
-	if( ( buf = ipc_readline( source ) ) )
+	if( ( buf = ipc_readline( source, &ipc_child_recv_fd ) ) )
 	{
 		cmd = irc_parse_line( buf );
@@ -392,8 +727,5 @@
 			next = l->next;
 			if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 )
-			{
 				ipc_master_free_one( c );
-				child_list = g_slist_remove( child_list, c );
-			}
 		}
 	}
@@ -413,13 +745,55 @@
 }
 
+static gboolean ipc_send_fd( int fd, int send_fd )
+{
+	struct msghdr msg;
+	struct iovec iov;
+	char ccmsg[CMSG_SPACE(sizeof(fd))];
+	struct cmsghdr *cmsg;
+	
+	memset( &msg, 0, sizeof( msg ) );
+	iov.iov_base = "0x90\r\n";         /* Ja, noppes */
+	iov.iov_len = 6;
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	
+	msg.msg_control = ccmsg;
+	msg.msg_controllen = sizeof( ccmsg );
+	cmsg = CMSG_FIRSTHDR( &msg );
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN( sizeof( send_fd ) );
+	*(int*)CMSG_DATA( cmsg ) = send_fd;
+	msg.msg_controllen = cmsg->cmsg_len;
+	
+	return sendmsg( fd, &msg, 0 ) == 6;
+}
+
 void ipc_master_free_one( struct bitlbee_child *c )
 {
+	GSList *l;
+	
 	b_event_remove( c->ipc_inpa );
 	closesocket( c->ipc_fd );
+	
+	if( c->to_fd != -1 )
+		close( c->to_fd );
 	
 	g_free( c->host );
 	g_free( c->nick );
 	g_free( c->realname );
+	g_free( c->password );
 	g_free( c );
+	
+	child_list = g_slist_remove( child_list, c );
+	
+	/* Also, if any child has a reference to this one, remove it. */
+	for( l = child_list; l; l = l->next )
+	{
+		struct bitlbee_child *oc = l->data;
+		
+		if( oc->to_child == c )
+			ipc_master_takeover_fail( oc, FALSE );
+	}
 }
 
@@ -435,5 +809,4 @@
 		{
 			ipc_master_free_one( c );
-			child_list = g_slist_remove( child_list, c );
 			break;
 		}
@@ -443,11 +816,6 @@
 void ipc_master_free_all()
 {
-	GSList *l;
-	
-	for( l = child_list; l; l = l->next )
-		ipc_master_free_one( l->data );
-	
-	g_slist_free( child_list );
-	child_list = NULL;
+	while( child_list )
+		ipc_master_free_one( child_list->data );
 }
 
@@ -506,6 +874,6 @@
 	struct bitlbee_child *child = g_new0( struct bitlbee_child, 1 );
 	
+	child->to_fd = -1;
 	child->ipc_fd = accept( serversock, NULL, 0 );
-	
 	if( child->ipc_fd == -1 )
 	{
@@ -514,7 +882,7 @@
 	}
 		
-	child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
-	
-	child_list = g_slist_append( child_list, child );
+	child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
+	
+	child_list = g_slist_prepend( child_list, child );
 	
 	return TRUE;
@@ -552,5 +920,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,7 +965,8 @@
 			return 0;
 		}
-		child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
-		
-		child_list = g_slist_append( child_list, child );
+		child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
+		child->to_fd = -1;
+		
+		child_list = g_slist_prepend( child_list, child );
 	}
 	
Index: ipc.h
===================================================================
--- ipc.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ ipc.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -2,5 +2,5 @@
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
   \********************************************************************/
 
@@ -37,4 +37,10 @@
 	char *nick;
 	char *realname;
+	
+	char *password;
+	
+	/* For takeovers: */
+	struct bitlbee_child *to_child;
+	int to_fd;
 };
 
@@ -48,4 +54,6 @@
 
 void ipc_child_disable();
+
+gboolean ipc_child_identify( irc_t *irc );
 
 void ipc_to_master( char **cmd );
Index: irc.c
===================================================================
--- irc.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ irc.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -5,5 +5,5 @@
   \********************************************************************/
 
-/* The big hairy IRCd part of the project                               */
+/* The IRC-based UI (for now the only one)                              */
 
 /*
@@ -24,76 +24,14 @@
 */
 
-#define BITLBEE_CORE
 #include "bitlbee.h"
-#include "sock.h"
-#include "crypting.h"
 #include "ipc.h"
-
-static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond );
-
-GSList *irc_connection_list = NULL;
-
-static char *set_eval_password( set_t *set, char *value )
-{
-	irc_t *irc = set->data;
-	
-	if( irc->status & USTATUS_IDENTIFIED && value )
-	{
-		irc_setpass( irc, value );
-		return NULL;
-	}
-	else
-	{
-		return SET_INVALID;
-	}
-}
-
-static char *set_eval_charset( set_t *set, char *value )
-{
-	irc_t *irc = set->data;
-	GIConv ic, oc;
-
-	if( g_strcasecmp( value, "none" ) == 0 )
-		value = g_strdup( "utf-8" );
-
-	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 );
-		return NULL;
-	}
-	
-	if( irc->iconv != (GIConv) -1 )
-		g_iconv_close( irc->iconv );
-	if( irc->oconv != (GIConv) -1 )
-		g_iconv_close( irc->oconv );
-	
-	irc->iconv = ic;
-	irc->oconv = oc;
-
-	return value;
-}
-
-static char *set_eval_away_status( set_t *set, char *value )
-{
-	irc_t *irc = set->data;
-	account_t *a;
-	
-	g_free( set->value );
-	set->value = g_strdup( value );
-	
-	for( a = irc->accounts; a; a = a->next )
-	{
-		struct im_connection *ic = a->ic;
-		
-		if( ic && ic->flags & OPT_LOGGED_IN )
-			imc_away_send_update( ic );
-	}
-	
-	return value;
-}
+#include "dcc.h"
+
+GSList *irc_connection_list;
+
+static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond );
+static char *set_eval_charset( set_t *set, char *value );
+static char *set_eval_password( set_t *set, char *value );
+static char *set_eval_bw_compat( set_t *set, char *value );
 
 irc_t *irc_new( int fd )
@@ -102,5 +40,8 @@
 	struct sockaddr_storage sock;
 	socklen_t socklen = sizeof( sock );
+	char *host = NULL, *myhost = NULL;
+	irc_user_t *iu;
 	set_t *s;
+	bee_t *b;
 	
 	irc = g_new0( irc_t, 1 );
@@ -109,15 +50,11 @@
 	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;
 	irc->last_pong = gettime();
 	
-	irc->userhash = g_hash_table_new( g_str_hash, g_str_equal );
+	irc->nick_user_hash = g_hash_table_new( g_str_hash, g_str_equal );
 	irc->watches = g_hash_table_new( g_str_hash, g_str_equal );
-	
-	strcpy( irc->umode, UMODE );
-	irc->mynick = g_strdup( ROOT_NICK );
-	irc->channel = g_strdup( ROOT_CHAN );
 	
 	irc->iconv = (GIConv) -1;
@@ -126,5 +63,5 @@
 	if( global.conf->hostname )
 	{
-		irc->myhost = g_strdup( global.conf->hostname );
+		myhost = g_strdup( global.conf->hostname );
 	}
 	else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) 
@@ -135,5 +72,5 @@
 		                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
 		{
-			irc->myhost = g_strdup( ipv6_unwrap( buf ) );
+			myhost = g_strdup( ipv6_unwrap( buf ) );
 		}
 	}
@@ -146,55 +83,81 @@
 		                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
 		{
-			irc->host = g_strdup( ipv6_unwrap( buf ) );
-		}
-	}
-	
-	if( irc->host == NULL )
-		irc->host = g_strdup( "localhost.localdomain" );
-	if( irc->myhost == NULL )
-		irc->myhost = g_strdup( "localhost.localdomain" );
+			host = g_strdup( ipv6_unwrap( buf ) );
+		}
+	}
+	
+	if( host == NULL )
+		host = g_strdup( "localhost.localdomain" );
+	if( myhost == NULL )
+		myhost = g_strdup( "localhost.localdomain" );
 	
 	if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 )
 		irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc );
-	
-	irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost, "BitlBee-IRCd initialized, please go on" );
 
 	irc_connection_list = g_slist_append( irc_connection_list, irc );
 	
-	s = set_add( &irc->set, "away", NULL,  set_eval_away_status, irc );
+	b = irc->b = bee_new();
+	b->ui_data = irc;
+	b->ui = &irc_ui_funcs;
+	
+	s = set_add( &b->set, "allow_takeover", "true", set_eval_bool, irc );
+	s = set_add( &b->set, "away_devoice", "true", set_eval_bw_compat, irc );
+	s = set_add( &b->set, "away_reply_timeout", "3600", set_eval_int, irc );
+	s = set_add( &b->set, "charset", "utf-8", set_eval_charset, irc );
+	s = set_add( &b->set, "default_target", "root", NULL, irc );
+	s = set_add( &b->set, "display_namechanges", "false", set_eval_bool, irc );
+	s = set_add( &b->set, "display_timestamps", "true", set_eval_bool, irc );
+	s = set_add( &b->set, "handle_unknown", "add_channel", NULL, irc );
+	s = set_add( &b->set, "lcnicks", "true", set_eval_bool, irc );
+	s = set_add( &b->set, "nick_format", "%-@nick", NULL, irc );
+	s = set_add( &b->set, "offline_user_quits", "true", set_eval_bool, irc );
+	s = set_add( &b->set, "ops", "both", set_eval_irc_channel_ops, irc );
+	s = set_add( &b->set, "paste_buffer", "false", set_eval_bool, irc );
+	s->old_key = g_strdup( "buddy_sendbuffer" );
+	s = set_add( &b->set, "paste_buffer_delay", "200", set_eval_int, irc );
+	s->old_key = g_strdup( "buddy_sendbuffer_delay" );
+	s = set_add( &b->set, "password", NULL, set_eval_password, irc );
 	s->flags |= SET_NULL_OK;
-	s = set_add( &irc->set, "away_devoice", "true",  set_eval_away_devoice, irc );
-	s = set_add( &irc->set, "auto_connect", "true", set_eval_bool, irc );
-	s = set_add( &irc->set, "auto_reconnect", "true", set_eval_bool, irc );
-	s = set_add( &irc->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, irc );
-	s = set_add( &irc->set, "buddy_sendbuffer", "false", set_eval_bool, irc );
-	s = set_add( &irc->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc );
-	s = set_add( &irc->set, "charset", "utf-8", set_eval_charset, irc );
-	s = set_add( &irc->set, "control_channel", irc->channel, set_eval_control_channel, irc );
-	s = set_add( &irc->set, "debug", "false", set_eval_bool, irc );
-	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, "handle_unknown", "root", NULL, irc );
-	s = set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc );
-	s = set_add( &irc->set, "ops", "both", set_eval_ops, irc );
-	s = set_add( &irc->set, "password", NULL, set_eval_password, irc );
-	s->flags |= SET_NULL_OK;
-	s = set_add( &irc->set, "private", "true", set_eval_bool, irc );
-	s = set_add( &irc->set, "query_order", "lifo", NULL, irc );
-	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, "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, "to_char", ": ", set_eval_to_char, irc );
-	s = set_add( &irc->set, "typing_notice", "false", set_eval_bool, irc );
+	s = set_add( &b->set, "private", "true", set_eval_bool, irc );
+	s = set_add( &b->set, "query_order", "lifo", NULL, irc );
+	s = set_add( &b->set, "root_nick", ROOT_NICK, set_eval_root_nick, irc );
+	s = set_add( &b->set, "show_offline", "false", set_eval_bw_compat, irc );
+	s = set_add( &b->set, "simulate_netsplit", "true", set_eval_bool, irc );
+	s = set_add( &b->set, "timezone", "local", set_eval_timezone, irc );
+	s = set_add( &b->set, "to_char", ": ", set_eval_to_char, irc );
+	s = set_add( &b->set, "typing_notice", "false", set_eval_bool, irc );
+
+	irc->root = iu = irc_user_new( irc, ROOT_NICK );
+	iu->host = g_strdup( myhost );
+	iu->fullname = g_strdup( ROOT_FN );
+	iu->f = &irc_user_root_funcs;
+	
+	iu = irc_user_new( irc, NS_NICK );
+	iu->host = g_strdup( myhost );
+	iu->fullname = g_strdup( ROOT_FN );
+	iu->f = &irc_user_root_funcs;
+	
+	irc->user = g_new0( irc_user_t, 1 );
+	irc->user->host = g_strdup( host );
 	
 	conf_loaddefaults( irc );
 	
 	/* Evaluator sets the iconv/oconv structures. */
-	set_eval_charset( set_find( &irc->set, "charset" ), set_getstr( &irc->set, "charset" ) );
-	
-	return( irc );
+	set_eval_charset( set_find( &b->set, "charset" ), set_getstr( &b->set, "charset" ) );
+	
+	irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "BitlBee-IRCd initialized, please go on" );
+	if( isatty( irc->fd ) )
+		irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host,
+		           "If you read this, you most likely accidentally "
+		           "started BitlBee in inetd mode on the command line. "
+		           "You probably want to run it in (Fork)Daemon mode. "
+		           "See doc/README for more information." );
+	
+	g_free( myhost );
+	g_free( host );
+	
+	nogaim_init();
+	
+	return irc;
 }
 
@@ -204,107 +167,62 @@
 void irc_abort( irc_t *irc, int immed, char *format, ... )
 {
+	char *reason = NULL;
+	
 	if( format != NULL )
 	{
 		va_list params;
-		char *reason;
 		
 		va_start( params, format );
 		reason = g_strdup_vprintf( format, params );
 		va_end( params );
-		
-		if( !immed )
-			irc_write( irc, "ERROR :Closing link: %s", reason );
-		
-		ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
-	                           irc->nick ? irc->nick : "(NONE)", irc->host, reason );
-	     	
-		g_free( reason );
+	}
+	
+	irc_write( irc, "ERROR :Closing link: %s", reason ? : "" );
+	
+	ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
+	                   irc->user->nick ? irc->user->nick : "(NONE)",
+	                   irc->user->host, reason ? : "" );
+	
+	g_free( reason );
+	
+	irc_flush( irc );
+	if( immed )
+	{
+		irc_free( irc );
 	}
 	else
 	{
-		if( !immed )
-			irc_write( irc, "ERROR :Closing link" );
-		
-		ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
-	        	           irc->nick ? irc->nick : "(NONE)", irc->host, "No reason given" );
-	}
-	
+		b_event_remove( irc->ping_source_id );
+		irc->ping_source_id = b_timeout_add( 1, (b_event_handler) irc_free, irc );
+	}
+}
+
+static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data );
+
+void irc_free( irc_t * irc )
+{
 	irc->status |= USTATUS_SHUTDOWN;
-	if( irc->sendbuffer && !immed )
-	{
-		/* Set up a timeout event that should shut down the connection
-		   in a second, just in case ..._write doesn't do it first. */
-		
-		b_event_remove( irc->r_watch_source_id );
-		irc->r_watch_source_id = 0;
-		
-		b_event_remove( irc->ping_source_id );
-		irc->ping_source_id = b_timeout_add( 1000, (b_event_handler) irc_free, irc );
-	}
-	else
-	{
-		irc_free( irc );
-	}
-}
-
-static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
-{
-	g_free( key );
-	
-	return( TRUE );
-}
-
-/* Because we have no garbage collection, this is quite annoying */
-void irc_free( irc_t * irc )
-{
-	user_t *user, *usertmp;
 	
 	log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd );
 	
-	if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->set, "save_on_quit" ) ) 
+	if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->b->set, "save_on_quit" ) ) 
 		if( storage_save( irc, NULL, TRUE ) != STORAGE_OK )
-			irc_usermsg( irc, "Error while saving settings!" );
+			log_message( LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick );
 	
 	irc_connection_list = g_slist_remove( irc_connection_list, irc );
-	
-	while( irc->accounts )
-	{
-		if( irc->accounts->ic )
-			imc_logout( irc->accounts->ic, FALSE );
-		else if( irc->accounts->reconnect )
-			cancel_auto_reconnect( irc->accounts );
-		
-		if( irc->accounts->ic == NULL )
-			account_del( irc, irc->accounts );
-		else
-			/* Nasty hack, but account_del() doesn't work in this
-			   case and we don't want infinite loops, do we? ;-) */
-			irc->accounts = irc->accounts->next;
-	}
 	
 	while( irc->queries != NULL )
 		query_del( irc, irc->queries );
 	
-	while( irc->set )
-		set_del( &irc->set, irc->set->key );
-	
-	if (irc->users != NULL)
-	{
-		user = irc->users;
-		while( user != NULL )
-		{
-			g_free( user->nick );
-			g_free( user->away );
-			g_free( user->handle );
-			if( user->user != user->nick ) g_free( user->user );
-			if( user->host != user->nick ) g_free( user->host );
-			if( user->realname != user->nick ) g_free( user->realname );
-			b_event_remove( user->sendbuf_timer );
-					
-			usertmp = user;
-			user = user->next;
-			g_free( usertmp );
-		}
-	}
+	/* This is a little bit messy: bee_free() frees all b->users which
+	   calls us back to free the corresponding irc->users. So do this
+	   before we clear the remaining ones ourselves. */
+	bee_free( irc->b );
+	
+	while( irc->users )
+		irc_user_free( irc, (irc_user_t *) irc->users->data );
+	
+	while( irc->channels )
+		irc_channel_free( irc->channels->data );
 	
 	if( irc->ping_source_id > 0 )
@@ -318,6 +236,6 @@
 	irc->fd = -1;
 	
-	g_hash_table_foreach_remove( irc->userhash, irc_free_hashkey, NULL );
-	g_hash_table_destroy( irc->userhash );
+	g_hash_table_foreach_remove( irc->nick_user_hash, irc_free_hashkey, NULL );
+	g_hash_table_destroy( irc->nick_user_hash );
 	
 	g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL );
@@ -331,17 +249,5 @@
 	g_free( irc->sendbuffer );
 	g_free( irc->readbuffer );
-	
-	g_free( irc->nick );
-	g_free( irc->user );
-	g_free( irc->host );
-	g_free( irc->realname );
 	g_free( irc->password );
-	
-	g_free( irc->myhost );
-	g_free( irc->mynick );
-	
-	g_free( irc->channel );
-	
-	g_free( irc->last_target );
 	
 	g_free( irc );
@@ -355,7 +261,14 @@
 }
 
+static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
+{
+	g_free( key );
+	
+	return( TRUE );
+}
+
 /* USE WITH CAUTION!
    Sets pass without checking */
-void irc_setpass (irc_t *irc, const char *pass) 
+void irc_setpass (irc_t *irc, const char *pass)
 {
 	g_free (irc->password);
@@ -368,4 +281,21 @@
 }
 
+static char *set_eval_password( set_t *set, char *value )
+{
+	irc_t *irc = set->data;
+	
+	if( irc->status & USTATUS_IDENTIFIED && value )
+	{
+		irc_setpass( irc, value );
+		return NULL;
+	}
+	else
+	{
+		return SET_INVALID;
+	}
+}
+
+static char **irc_splitlines( char *buffer );
+
 void irc_process( irc_t *irc )
 {
@@ -375,5 +305,5 @@
 	if( irc->readbuffer != NULL )
 	{
-		lines = irc_tokenize( irc->readbuffer );
+		lines = irc_splitlines( irc->readbuffer );
 		
 		for( i = 0; *lines[i] != '\0'; i ++ )
@@ -412,5 +342,5 @@
 						                  "`help set charset' for more information. Your "
 						                  "message was ignored.",
-						                  set_getstr( &irc->set, "charset" ) );
+						                  set_getstr( &irc->b->set, "charset" ) );
 						
 						g_free( conv );
@@ -419,5 +349,5 @@
 					else
 					{
-						irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost,
+						irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host,
 						           "Warning: invalid characters received at login time." );
 						
@@ -457,7 +387,9 @@
 }
 
-/* Splits a long string into separate lines. The array is NULL-terminated and, unless the string
-   contains an incomplete line at the end, ends with an empty string. */
-char **irc_tokenize( char *buffer )
+/* Splits a long string into separate lines. The array is NULL-terminated
+   and, unless the string contains an incomplete line at the end, ends with
+   an empty string. Could use g_strsplit() but this one does it in-place.
+   (So yes, it's destructive.) */
+static char **irc_splitlines( char *buffer )
 {
 	int i, j, n = 3;
@@ -590,34 +522,4 @@
 }
 
-void irc_reply( irc_t *irc, int code, char *format, ... )
-{
-	char text[IRC_MAX_LINE];
-	va_list params;
-	
-	va_start( params, format );
-	g_vsnprintf( text, IRC_MAX_LINE, format, params );
-	va_end( params );
-	irc_write( irc, ":%s %03d %s %s", irc->myhost, code, irc->nick?irc->nick:"*", text );
-	
-	return;
-}
-
-int irc_usermsg( irc_t *irc, char *format, ... )
-{
-	char text[1024];
-	va_list params;
-	char is_private = 0;
-	user_t *u;
-	
-	u = user_find( irc, irc->mynick );
-	is_private = u->is_private;
-	
-	va_start( params, format );
-	g_vsnprintf( text, sizeof( text ), format, params );
-	va_end( params );
-	
-	return( irc_msgfrom( irc, u->nick, text ) );
-}
-
 void irc_write( irc_t *irc, char *format, ... ) 
 {
@@ -630,4 +532,33 @@
 	return;
 }
+
+void irc_write_all( int now, char *format, ... )
+{
+	va_list params;
+	GSList *temp;	
+	
+	va_start( params, format );
+	
+	temp = irc_connection_list;
+	while( temp != NULL )
+	{
+		irc_t *irc = temp->data;
+		
+		if( now )
+		{
+			g_free( irc->sendbuffer );
+			irc->sendbuffer = g_strdup( "\r\n" );
+		}
+		irc_vawrite( temp->data, format, params );
+		if( now )
+		{
+			bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE );
+		}
+		temp = temp->next;
+	}
+	
+	va_end( params );
+	return;
+} 
 
 void irc_vawrite( irc_t *irc, char *format, va_list params )
@@ -677,8 +608,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 );
 	}
 	
@@ -686,103 +617,141 @@
 }
 
-void irc_write_all( int now, char *format, ... )
-{
-	va_list params;
-	GSList *temp;	
-	
-	va_start( params, format );
-	
-	temp = irc_connection_list;
-	while( temp != NULL )
-	{
-		irc_t *irc = temp->data;
-		
-		if( now )
-		{
-			g_free( irc->sendbuffer );
-			irc->sendbuffer = g_strdup( "\r\n" );
-		}
-		irc_vawrite( temp->data, format, params );
-		if( now )
-		{
-			bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
-		}
-		temp = temp->next;
-	}
-	
-	va_end( params );
-	return;
-} 
-
-void irc_names( irc_t *irc, char *channel )
-{
-	user_t *u;
-	char namelist[385] = "";
-	struct groupchat *c = NULL;
-	char *ops = set_getstr( &irc->set, "ops" );
-	
-	/* RFCs say there is no error reply allowed on NAMES, so when the
-	   channel is invalid, just give an empty reply. */
-	
-	if( g_strcasecmp( channel, irc->channel ) == 0 )
-	{
-		for( u = irc->users; u; u = u->next ) if( u->online )
-		{
-			if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
+/* Flush sendbuffer if you can. If it fails, fail silently and let some
+   I/O event handler clean up. */
+void irc_flush( irc_t *irc )
+{
+	ssize_t n;
+	size_t len;
+	
+	if( irc->sendbuffer == NULL )
+		return;
+	
+	len = strlen( irc->sendbuffer );
+	if( ( n = send( irc->fd, irc->sendbuffer, len, 0 ) ) == len )
+	{
+		g_free( irc->sendbuffer );
+		irc->sendbuffer = NULL;
+		
+		b_event_remove( irc->w_watch_source_id );
+		irc->w_watch_source_id = 0;
+	}
+	else if( n > 0 )
+	{
+		char *s = g_strdup( irc->sendbuffer + n );
+		g_free( irc->sendbuffer );
+		irc->sendbuffer = s;
+	}
+	/* Otherwise something went wrong and we don't currently care
+	   what the error was. We may or may not succeed later, we
+	   were just trying to flush the buffer immediately. */
+}
+
+/* Meant for takeover functionality. Transfer an IRC connection to a different
+   socket. */
+void irc_switch_fd( irc_t *irc, int fd )
+{
+	irc_write( irc, "ERROR :Transferring session to a new connection" );
+	irc_flush( irc ); /* Write it now or forget about it forever. */
+	
+	if( irc->sendbuffer )
+	{
+		b_event_remove( irc->w_watch_source_id );
+		irc->w_watch_source_id = 0;
+		g_free( irc->sendbuffer );
+		irc->sendbuffer = NULL;
+	}
+	
+	b_event_remove( irc->r_watch_source_id );
+	closesocket( irc->fd );
+	irc->fd = fd;
+	irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc );
+}
+
+void irc_sync( irc_t *irc )
+{
+	GSList *l;
+	
+	irc_write( irc, ":%s!%s@%s MODE %s :+%s", irc->user->nick,
+	           irc->user->user, irc->user->host, irc->user->nick,
+	           irc->umode );
+	
+	for( l = irc->channels; l; l = l->next )
+	{
+		irc_channel_t *ic = l->data;
+		if( ic->flags & IRC_CHANNEL_JOINED )
+			irc_send_join( ic, irc->user );
+	}
+}
+
+void irc_desync( irc_t *irc )
+{
+	GSList *l;
+	
+	for( l = irc->channels; l; l = l->next )
+		irc_channel_del_user( l->data, irc->user, IRC_CDU_KICK,
+		                      "Switching to old session" );
+	
+	irc_write( irc, ":%s!%s@%s MODE %s :-%s", irc->user->nick,
+	           irc->user->user, irc->user->host, irc->user->nick,
+	           irc->umode );
+}
+
+int irc_check_login( irc_t *irc )
+{
+	if( irc->user->user && irc->user->nick )
+	{
+		if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
+		{
+			irc_send_num( irc, 464, ":This server is password-protected." );
+			return 0;
+		}
+		else
+		{
+			irc_channel_t *ic;
+			irc_user_t *iu = irc->user;
+			
+			irc->user = irc_user_new( irc, iu->nick );
+			irc->user->user = iu->user;
+			irc->user->host = iu->host;
+			irc->user->fullname = iu->fullname;
+			irc->user->f = &irc_user_self_funcs;
+			g_free( iu->nick );
+			g_free( iu );
+			
+			if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
+				ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname );
+			
+			irc->status |= USTATUS_LOGGED_IN;
+			
+			irc_send_login( irc );
+			
+			irc->umode[0] = '\0';
+			irc_umode_set( irc, "+" UMODE, TRUE );
+			
+			ic = irc->default_channel = irc_channel_new( irc, ROOT_CHAN );
+			irc_channel_set_topic( ic, CONTROL_TOPIC, irc->root );
+			set_setstr( &ic->set, "auto_join", "true" );
+			irc_channel_auto_joins( irc, NULL );
+			
+			irc->root->last_channel = irc->default_channel;
+			
+			irc_usermsg( irc,
+			             "Welcome to the BitlBee gateway!\n\n"
+			             "If you've never used BitlBee before, please do read the help "
+			             "information using the \x02help\x02 command. Lots of FAQs are "
+			             "answered there.\n"
+			             "If you already have an account on this server, just use the "
+			             "\x02identify\x02 command to identify yourself." );
+			
+			/* This is for bug #209 (use PASS to identify to NickServ). */
+			if( irc->password != NULL )
 			{
-				irc_reply( irc, 353, "= %s :%s", channel, namelist );
-				*namelist = 0;
+				char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL };
+				
+				irc_setpass( irc, NULL );
+				root_command( irc, send_cmd );
+				g_free( send_cmd[1] );
 			}
 			
-			if( u->ic && !u->away && set_getbool( &irc->set, "away_devoice" ) )
-				strcat( namelist, "+" );
-			else if( ( strcmp( u->nick, irc->mynick ) == 0 && ( strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) == 0 ) ) ||
-			         ( strcmp( u->nick, irc->nick ) == 0 && ( strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) == 0 ) ) )
-				strcat( namelist, "@" );
-			
-			strcat( namelist, u->nick );
-			strcat( namelist, " " );
-		}
-	}
-	else if( ( c = irc_chat_by_channel( irc, channel ) ) )
-	{
-		GList *l;
-		
-		/* root and the user aren't in the channel userlist but should
-		   show up in /NAMES, so list them first: */
-		sprintf( namelist, "%s%s %s%s ", strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->mynick,
-		                                 strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->nick );
-		
-		for( l = c->in_room; l; l = l->next ) if( ( u = user_findhandle( c->ic, l->data ) ) )
-		{
-			if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
-			{
-				irc_reply( irc, 353, "= %s :%s", channel, namelist );
-				*namelist = 0;
-			}
-			
-			strcat( namelist, u->nick );
-			strcat( namelist, " " );
-		}
-	}
-	
-	if( *namelist )
-		irc_reply( irc, 353, "= %s :%s", channel, namelist );
-	
-	irc_reply( irc, 366, "%s :End of /NAMES list", channel );
-}
-
-int irc_check_login( irc_t *irc )
-{
-	if( irc->user && irc->nick )
-	{
-		if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
-		{
-			irc_reply( irc, 464, ":This server is password-protected." );
-			return 0;
-		}
-		else
-		{
-			irc_login( irc );
 			return 1;
 		}
@@ -795,133 +764,12 @@
 }
 
-void irc_login( irc_t *irc )
-{
-	user_t *u;
-	
-	irc_reply( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->nick );
-	irc_reply( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->myhost );
-	irc_reply( irc,   3, ":%s", IRCD_INFO );
-	irc_reply( irc,   4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
-	irc_reply( irc,   5, "PREFIX=(ov)@+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee "
-	                     "CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server",
-	                     CTYPES, CMODES, MAX_NICK_LENGTH - 1 );
-	irc_motd( irc );
-	irc->umode[0] = '\0';
-	irc_umode_set( irc, "+" UMODE, 1 );
-
-	u = user_add( irc, irc->mynick );
-	u->host = g_strdup( irc->myhost );
-	u->realname = g_strdup( ROOT_FN );
-	u->online = 1;
-	u->send_handler = root_command_string;
-	u->is_private = 0; /* [SH] The channel is root's personal playground. */
-	irc_spawn( irc, u );
-	
-	u = user_add( irc, NS_NICK );
-	u->host = g_strdup( irc->myhost );
-	u->realname = g_strdup( ROOT_FN );
-	u->online = 0;
-	u->send_handler = root_command_string;
-	u->is_private = 1; /* [SH] NickServ is not in the channel, so should always /query. */
-	
-	u = user_add( irc, irc->nick );
-	u->user = g_strdup( irc->user );
-	u->host = g_strdup( irc->host );
-	u->realname = g_strdup( irc->realname );
-	u->online = 1;
-	irc_spawn( irc, u );
-	
-	irc_usermsg( irc, "Welcome to the BitlBee gateway!\n\n"
-	                  "If you've never used BitlBee before, please do read the help "
-	                  "information using the \x02help\x02 command. Lots of FAQs are "
-	                  "answered there.\n"
-	                  "If you already have an account on this server, just use the "
-	                  "\x02identify\x02 command to identify yourself." );
-	
-	if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
-		ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->host, irc->nick, irc->realname );
-	
-	irc->status |= USTATUS_LOGGED_IN;
-	
-	/* This is for bug #209 (use PASS to identify to NickServ). */
-	if( irc->password != NULL )
-	{
-		char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL };
-		
-		irc_setpass( irc, NULL );
-		root_command( irc, send_cmd );
-		g_free( send_cmd[1] );
-	}
-}
-
-void irc_motd( irc_t *irc )
-{
-	int fd;
-	
-	fd = open( global.conf->motdfile, O_RDONLY );
-	if( fd == -1 )
-	{
-		irc_reply( irc, 422, ":We don't need MOTDs." );
-	}
-	else
-	{
-		char linebuf[80];	/* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */
-		char *add, max;
-		int len;
-		
-		linebuf[79] = len = 0;
-		max = sizeof( linebuf ) - 1;
-		
-		irc_reply( irc, 375, ":- %s Message Of The Day - ", irc->myhost );
-		while( read( fd, linebuf + len, 1 ) == 1 )
-		{
-			if( linebuf[len] == '\n' || len == max )
-			{
-				linebuf[len] = 0;
-				irc_reply( irc, 372, ":- %s", linebuf );
-				len = 0;
-			}
-			else if( linebuf[len] == '%' )
-			{
-				read( fd, linebuf + len, 1 );
-				if( linebuf[len] == 'h' )
-					add = irc->myhost;
-				else if( linebuf[len] == 'v' )
-					add = BITLBEE_VERSION;
-				else if( linebuf[len] == 'n' )
-					add = irc->nick;
-				else
-					add = "%";
-				
-				strncpy( linebuf + len, add, max - len );
-				while( linebuf[++len] );
-			}
-			else if( len < max )
-			{
-				len ++;
-			}
-		}
-		irc_reply( irc, 376, ":End of MOTD" );
-		close( fd );
-	}
-}
-
-void irc_topic( irc_t *irc, char *channel )
-{
-	struct groupchat *c = irc_chat_by_channel( irc, channel );
-	
-	if( c && c->topic )
-		irc_reply( irc, 332, "%s :%s", channel, c->topic );
-	else if( g_strcasecmp( channel, irc->channel ) == 0 )
-		irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC );
-	else
-		irc_reply( irc, 331, "%s :No topic for this channel", channel );
-}
-
-void irc_umode_set( irc_t *irc, char *s, int allow_priv )
+/* TODO: This is a mess, but this function is a bit too complicated to be
+   converted to something more generic. */
+void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv )
 {
 	/* allow_priv: Set to 0 if s contains user input, 1 if you want
 	   to set a "privileged" mode (+o, +R, etc). */
-	char m[256], st = 1, *t;
+	char m[128], st = 1;
+	const char *t;
 	int i;
 	char changes[512], *p, st2 = 2;
@@ -931,6 +779,7 @@
 	
 	for( t = irc->umode; *t; t ++ )
-		m[(int)*t] = 1;
-
+		if( *t < sizeof( m ) )
+			m[(int)*t] = 1;
+	
 	p = changes;
 	for( t = s; *t; t ++ )
@@ -938,5 +787,7 @@
 		if( *t == '+' || *t == '-' )
 			st = *t == '+';
-		else if( st == 0 || ( strchr( UMODES, *t ) || ( allow_priv && strchr( UMODES_PRIV, *t ) ) ) )
+		else if( ( st == 0 && ( !strchr( UMODES_KEEP, *t ) || allow_priv ) ) ||
+		         ( st == 1 && strchr( UMODES, *t ) ) ||
+		         ( st == 1 && allow_priv && strchr( UMODES_PRIV, *t ) ) )
 		{
 			if( m[(int)*t] != st)
@@ -955,326 +806,14 @@
 	memset( irc->umode, 0, sizeof( irc->umode ) );
 	
-	for( i = 0; i < 256 && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
+	for( i = 'A'; i <= 'z' && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
 		if( m[i] )
 			irc->umode[strlen(irc->umode)] = i;
 	
 	if( badflag )
-		irc_reply( irc, 501, ":Unknown MODE flag" );
-	/* Deliberately no !user@host on the prefix here */
+		irc_send_num( irc, 501, ":Unknown MODE flag" );
 	if( *changes )
-		irc_write( irc, ":%s MODE %s %s", irc->nick, irc->nick, changes );
-}
-
-void irc_spawn( irc_t *irc, user_t *u )
-{
-	irc_join( irc, u, irc->channel );
-}
-
-void irc_join( irc_t *irc, user_t *u, char *channel )
-{
-	char *nick;
-	
-	if( ( g_strcasecmp( channel, irc->channel ) != 0 ) || user_find( irc, irc->nick ) )
-		irc_write( irc, ":%s!%s@%s JOIN :%s", u->nick, u->user, u->host, channel );
-	
-	if( nick_cmp( u->nick, irc->nick ) == 0 )
-	{
-		irc_write( irc, ":%s MODE %s +%s", irc->myhost, channel, CMODE );
-		irc_names( irc, channel );
-		irc_topic( irc, channel );
-	}
-	
-	nick = g_strdup( u->nick );
-	nick_lc( nick );
-	if( g_hash_table_lookup( irc->watches, nick ) )
-	{
-		irc_reply( irc, 600, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged online" );
-	}
-	g_free( nick );
-}
-
-void irc_part( irc_t *irc, user_t *u, char *channel )
-{
-	irc_write( irc, ":%s!%s@%s PART %s :%s", u->nick, u->user, u->host, channel, "" );
-}
-
-void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker )
-{
-	irc_write( irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, channel, u->nick, "" );
-}
-
-void irc_kill( irc_t *irc, user_t *u )
-{
-	char *nick, *s;
-	char reason[128];
-	
-	if( u->ic && u->ic->flags & OPT_LOGGING_OUT && set_getbool( &irc->set, "simulate_netsplit" ) )
-	{
-		if( u->ic->acc->server )
-			g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
-			            u->ic->acc->server );
-		else if( ( s = strchr( u->ic->acc->user, '@' ) ) )
-			g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
-			            s + 1 );
-		else
-			g_snprintf( reason, sizeof( reason ), "%s %s.%s", irc->myhost,
-			            u->ic->acc->prpl->name, irc->myhost );
-		
-		/* proto_opt might contain garbage after the : */
-		if( ( s = strchr( reason, ':' ) ) )
-			*s = 0;
-	}
-	else
-	{
-		strcpy( reason, "Leaving..." );
-	}
-	
-	irc_write( irc, ":%s!%s@%s QUIT :%s", u->nick, u->user, u->host, reason );
-	
-	nick = g_strdup( u->nick );
-	nick_lc( nick );
-	if( g_hash_table_lookup( irc->watches, nick ) )
-	{
-		irc_reply( irc, 601, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged offline" );
-	}
-	g_free( nick );
-}
-
-int irc_send( irc_t *irc, char *nick, char *s, int flags )
-{
-	struct groupchat *c = NULL;
-	user_t *u = NULL;
-	
-	if( strchr( CTYPES, *nick ) )
-	{
-		if( !( c = irc_chat_by_channel( irc, nick ) ) )
-		{
-			irc_reply( irc, 403, "%s :Channel does not exist", nick );
-			return( 0 );
-		}
-	}
-	else
-	{
-		u = user_find( irc, nick );
-		
-		if( !u )
-		{
-			if( irc->is_private )
-				irc_reply( irc, 401, "%s :Nick does not exist", nick );
-			else
-				irc_usermsg( irc, "Nick `%s' does not exist!", nick );
-			return( 0 );
-		}
-	}
-	
-	if( *s == 1 && s[strlen(s)-1] == 1 )
-	{
-		if( g_strncasecmp( s + 1, "ACTION", 6 ) == 0 )
-		{
-			if( s[7] == ' ' ) s ++;
-			s += 3;
-			*(s++) = '/';
-			*(s++) = 'm';
-			*(s++) = 'e';
-			*(s++) = ' ';
-			s -= 4;
-			s[strlen(s)-1] = 0;
-		}
-		else if( g_strncasecmp( s + 1, "VERSION", 7 ) == 0 )
-		{
-			u = user_find( irc, irc->mynick );
-			irc_privmsg( irc, u, "NOTICE", irc->nick, "", "\001VERSION BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\001" );
-			return( 1 );
-		}
-		else if( g_strncasecmp( s + 1, "PING", 4 ) == 0 )
-		{
-			u = user_find( irc, irc->mynick );
-			irc_privmsg( irc, u, "NOTICE", irc->nick, "", s );
-			return( 1 );
-		}
-		else if( g_strncasecmp( s + 1, "TYPING", 6 ) == 0 )
-		{
-			if( u && u->ic && u->ic->acc->prpl->send_typing && strlen( s ) >= 10 )
-			{
-				time_t current_typing_notice = time( NULL );
-				
-				if( current_typing_notice - u->last_typing_notice >= 5 )
-				{
-					u->ic->acc->prpl->send_typing( u->ic, u->handle, ( s[8] - '0' ) << 8 );
-					u->last_typing_notice = current_typing_notice;
-				}
-			}
-			return( 1 );
-		}
-		else
-		{
-			irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" );
-			return( 0 );
-		}
-	}
-	
-	if( u )
-	{
-		/* For the next message, we probably do have to send new notices... */
-		u->last_typing_notice = 0;
-		u->is_private = irc->is_private;
-		
-		if( u->is_private )
-		{
-			if( !u->online )
-				irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
-			else if( u->away )
-				irc_reply( irc, 301, "%s :%s", u->nick, u->away );
-		}
-		
-		if( u->send_handler )
-		{
-			u->send_handler( irc, u, s, flags );
-			return 1;
-		}
-	}
-	else if( c && c->ic && c->ic->acc && c->ic->acc->prpl )
-	{
-		return( imc_chat_msg( c, s, 0 ) );
-	}
-	
-	return( 0 );
-}
-
-static gboolean buddy_send_handler_delayed( gpointer data, gint fd, b_input_condition cond )
-{
-	user_t *u = data;
-	
-	/* Shouldn't happen, but just to be sure. */
-	if( u->sendbuf_len < 2 )
-		return FALSE;
-	
-	u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */
-	imc_buddy_msg( u->ic, u->handle, u->sendbuf, u->sendbuf_flags );
-	
-	g_free( u->sendbuf );
-	u->sendbuf = NULL;
-	u->sendbuf_len = 0;
-	u->sendbuf_timer = 0;
-	u->sendbuf_flags = 0;
-	
-	return FALSE;
-}
-
-void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags )
-{
-	if( !u || !u->ic ) return;
-	
-	if( set_getbool( &irc->set, "buddy_sendbuffer" ) && set_getint( &irc->set, "buddy_sendbuffer_delay" ) > 0 )
-	{
-		int delay;
-		
-		if( u->sendbuf_len > 0 && u->sendbuf_flags != flags)
-		{
-			/* Flush the buffer */
-			b_event_remove( u->sendbuf_timer );
-			buddy_send_handler_delayed( u, -1, 0 );
-		}
-
-		if( u->sendbuf_len == 0 )
-		{
-			u->sendbuf_len = strlen( msg ) + 2;
-			u->sendbuf = g_new( char, u->sendbuf_len );
-			u->sendbuf[0] = 0;
-			u->sendbuf_flags = flags;
-		}
-		else
-		{
-			u->sendbuf_len += strlen( msg ) + 1;
-			u->sendbuf = g_renew( char, u->sendbuf, u->sendbuf_len );
-		}
-		
-		strcat( u->sendbuf, msg );
-		strcat( u->sendbuf, "\n" );
-		
-		delay = set_getint( &irc->set, "buddy_sendbuffer_delay" );
-		if( delay <= 5 )
-			delay *= 1000;
-		
-		if( u->sendbuf_timer > 0 )
-			b_event_remove( u->sendbuf_timer );
-		u->sendbuf_timer = b_timeout_add( delay, buddy_send_handler_delayed, u );
-	}
-	else
-	{
-		imc_buddy_msg( u->ic, u->handle, msg, flags );
-	}
-}
-
-int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg )
-{
-	char last = 0;
-	char *s = msg, *line = msg;
-	
-	/* The almighty linesplitter .. woohoo!! */
-	while( !last )
-	{
-		if( *s == '\r' && *(s+1) == '\n' )
-			*(s++) = 0;
-		if( *s == '\n' )
-		{
-			last = s[1] == 0;
-			*s = 0;
-		}
-		else
-		{
-			last = s[0] == 0;
-		}
-		if( *s == 0 )
-		{
-			if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 )
-			{
-				irc_write( irc, ":%s!%s@%s %s %s :\001ACTION %s\001", u->nick, u->user, u->host,
-				           type, to, line + 4 );
-			}
-			else
-			{
-				irc_write( irc, ":%s!%s@%s %s %s :%s%s", u->nick, u->user, u->host,
-				           type, to, prefix ? prefix : "", line );
-			}
-			line = s + 1;
-		}
-		s ++;
-	}
-	
-	return( 1 );
-}
-
-int irc_msgfrom( irc_t *irc, char *nick, char *msg )
-{
-	user_t *u = user_find( irc, nick );
-	static char *prefix = NULL;
-	
-	if( !u ) return( 0 );
-	if( prefix && *prefix ) g_free( prefix );
-	
-	if( !u->is_private && nick_cmp( u->nick, irc->mynick ) != 0 )
-	{
-		int len = strlen( irc->nick) + 3;
-		prefix = g_new (char, len );
-		g_snprintf( prefix, len, "%s%s", irc->nick, set_getstr( &irc->set, "to_char" ) );
-		prefix[len-1] = 0;
-	}
-	else
-	{
-		prefix = "";
-	}
-	
-	return( irc_privmsg( irc, u, "PRIVMSG", u->is_private ? irc->nick : irc->channel, prefix, msg ) );
-}
-
-int irc_noticefrom( irc_t *irc, char *nick, char *msg )
-{
-	user_t *u = user_find( irc, nick );
-	
-	if( u )
-		return( irc_privmsg( irc, u, "NOTICE", irc->nick, "", msg ) );
-	else
-		return( 0 );
+		irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->user->nick,
+		           irc->user->user, irc->user->host, irc->user->nick,
+		           changes );
 }
 
@@ -1315,25 +854,75 @@
 }
 
-struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel )
-{
-	struct groupchat *c;
-	account_t *a;
-	
-	/* This finds the connection which has a conversation which belongs to this channel */
-	for( a = irc->accounts; a; a = a->next )
-	{
-		if( a->ic == NULL )
-			continue;
-		
-		c = a->ic->groupchats;
-		while( c )
-		{
-			if( c->channel && g_strcasecmp( c->channel, channel ) == 0 )
-				return c;
-			
-			c = c->next;
-		}
-	}
-	
-	return NULL;
-}
+static char *set_eval_charset( set_t *set, char *value )
+{
+	irc_t *irc = (irc_t*) set->data;
+	char *test;
+	gsize test_bytes = 0;
+	GIConv ic, oc;
+
+	if( g_strcasecmp( value, "none" ) == 0 )
+		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 )
+	{
+		g_iconv_close( oc );
+		return NULL;
+	}
+	
+	if( irc->iconv != (GIConv) -1 )
+		g_iconv_close( irc->iconv );
+	if( irc->oconv != (GIConv) -1 )
+		g_iconv_close( irc->oconv );
+	
+	irc->iconv = ic;
+	irc->oconv = oc;
+
+	return value;
+}
+
+/* Mostly meant for upgrades. If one of these is set to the non-default,
+   set show_users of all channels to something with the same effect. */
+static char *set_eval_bw_compat( set_t *set, char *value )
+{
+	irc_t *irc = set->data;
+	char *val;
+	GSList *l;
+	
+	irc_usermsg( irc, "Setting `%s' is obsolete, use the `show_users' "
+	             "channel setting instead.", set->key );
+	
+	if( strcmp( set->key, "away_devoice" ) == 0 && !bool2int( value ) )
+		val = "online,away";
+	else if( strcmp( set->key, "show_offline" ) == 0 && bool2int( value ) )
+		val = "online@,away+,offline";
+	else
+		return SET_INVALID;
+	
+	for( l = irc->channels; l; l = l->next )
+	{
+		irc_channel_t *ic = l->data;
+		/* No need to check channel type, if the setting doesn't exist it
+		   will just be ignored. */
+		set_setstr( &ic->set, "show_users", val );
+	}
+	
+	return SET_INVALID;
+}
Index: irc.h
===================================================================
--- irc.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ irc.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -2,8 +2,8 @@
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
   \********************************************************************/
 
-/* The big hairy IRCd part of the project                               */
+/* The IRC-based UI (for now the only one)                              */
 
 /*
@@ -33,19 +33,24 @@
 #define IRC_PING_STRING "PinglBee"
 
-#define UMODES "abisw"
-#define UMODES_PRIV "Ro"
-#define CMODES "nt"
-#define CMODE "t"
-#define UMODE "s"
-#define CTYPES "&#"
+#define UMODES "abisw"     /* Allowed umodes (although they mostly do nothing) */
+#define UMODES_PRIV "Ro"   /* Allowed, but not by user directly */
+#define UMODES_KEEP "R"    /* Don't allow unsetting using /MODE */
+#define CMODES "ntC"       /* Allowed modes */
+#define CMODE "t"          /* Default mode */
+#define UMODE "s"          /* Default mode */
+
+#define CTYPES "&#"        /* Valid channel name prefixes */
 
 typedef enum
 {
 	USTATUS_OFFLINE = 0,
-	USTATUS_AUTHORIZED = 1,
-	USTATUS_LOGGED_IN = 2,
-	USTATUS_IDENTIFIED = 4,
-	USTATUS_SHUTDOWN = 8
+	USTATUS_AUTHORIZED = 1, /* Gave the correct server password (PASS). */
+	USTATUS_LOGGED_IN = 2,  /* USER+NICK(+PASS) finished. */
+	USTATUS_IDENTIFIED = 4, /* To NickServ (root). */
+	USTATUS_SHUTDOWN = 8,   /* Now used to indicate we're shutting down.
+	                           Currently just blocks irc_vawrite(). */
 } irc_status_t;
+
+struct irc_user;
 
 typedef struct irc
@@ -59,43 +64,155 @@
 	GIConv iconv, oconv;
 
-	int sentbytes;
-	time_t oldtime;
-
+	struct irc_user *root;
+	struct irc_user *user;
+	
+	char *password; /* HACK: Used to save the user's password, but before
+	                   logging in, this may contain a password we should
+	                   send to identify after USER/NICK are received. */
+
+	char umode[8];
+	
+	struct query *queries;
+	GSList *file_transfers;
+	
+	GSList *users, *channels;
+	struct irc_channel *default_channel;
+	GHashTable *nick_user_hash;
+	GHashTable *watches; /* See irc_cmd_watch() */
+
+	gint r_watch_source_id;
+	gint w_watch_source_id;
+	gint ping_source_id;
+	gint login_source_id; /* To slightly delay some events at login time. */
+	
+	struct bee *b;
+} irc_t;
+
+typedef enum
+{
+	/* Replaced with iu->last_channel IRC_USER_PRIVATE = 1, */
+	IRC_USER_AWAY = 2,
+} irc_user_flags_t;
+
+typedef struct irc_user
+{
+	irc_t *irc;
+	
 	char *nick;
 	char *user;
 	char *host;
-	char *realname;
-	char *password; /* HACK: Used to save the user's password, but before
-	                   logging in, this may contain a password we should
-	                   send to identify after USER/NICK are received. */
-
-	char umode[8];
-	
-	char *myhost;
-	char *mynick;
-
-	char *channel;
-	int c_id;
-
-	char is_private;		/* Not too nice... */
-	char *last_target;
-	
-	struct query *queries;
-	struct account *accounts;
-	struct chat *chatrooms;
-	
-	struct __USER *users;
-	GHashTable *userhash;
-	GHashTable *watches;
-	struct __NICK *nicks;
+	char *fullname;
+	
+	/* Nickname in lowercase for case insensitive searches */
+	char *key;
+	
+	irc_user_flags_t flags;
+	struct irc_channel *last_channel;
+	
+	GString *pastebuf; /* Paste buffer (combine lines into a multiline msg). */
+	guint pastebuf_timer;
+	time_t away_reply_timeout; /* Only send a 301 if this time passed. */
+	
+	struct bee_user *bu;
+	
+	const struct irc_user_funcs *f;
+} irc_user_t;
+
+struct irc_user_funcs
+{
+	gboolean (*privmsg)( irc_user_t *iu, const char *msg );
+	gboolean (*ctcp)( irc_user_t *iu, char * const* ctcp );
+};
+
+extern const struct irc_user_funcs irc_user_root_funcs;
+extern const struct irc_user_funcs irc_user_self_funcs;
+
+typedef enum
+{
+	IRC_CHANNEL_JOINED = 1, /* The user is currently in the channel. */
+	IRC_CHANNEL_TEMP = 2,   /* Erase the channel when the user leaves,
+	                           and don't save it. */
+	
+	/* Hack: Set this flag right before jumping into IM when we expect
+	   a call to imcb_chat_new(). */
+	IRC_CHANNEL_CHAT_PICKME = 0x10000,
+} irc_channel_flags_t;
+
+typedef struct irc_channel
+{
+	irc_t *irc;
+	char *name;
+	char mode[8];
+	int flags;
+	
+	char *topic;
+	char *topic_who;
+	time_t topic_time;
+	
+	GSList *users; /* struct irc_channel_user */
+	struct irc_user *last_target;
 	struct set *set;
-
-	gint r_watch_source_id;
-	gint w_watch_source_id;
-	gint ping_source_id;
-} irc_t;
-
-#include "user.h"
-
+	
+	GString *pastebuf; /* Paste buffer (combine lines into a multiline msg). */
+	guint pastebuf_timer;
+	
+	const struct irc_channel_funcs *f;
+	void *data;
+} irc_channel_t;
+
+struct irc_channel_funcs
+{
+	gboolean (*privmsg)( irc_channel_t *ic, const char *msg );
+	gboolean (*join)( irc_channel_t *ic );
+	gboolean (*part)( irc_channel_t *ic, const char *msg );
+	gboolean (*topic)( irc_channel_t *ic, const char *new );
+	gboolean (*invite)( irc_channel_t *ic, irc_user_t *iu );
+	
+	gboolean (*_init)( irc_channel_t *ic );
+	gboolean (*_free)( irc_channel_t *ic );
+};
+
+typedef enum
+{
+	IRC_CHANNEL_USER_OP = 1,
+	IRC_CHANNEL_USER_HALFOP = 2,
+	IRC_CHANNEL_USER_VOICE = 4,
+	IRC_CHANNEL_USER_NONE = 8,
+} irc_channel_user_flags_t;
+
+typedef struct irc_channel_user
+{
+	irc_user_t *iu;
+	int flags;
+} irc_channel_user_t;
+
+typedef enum
+{
+	IRC_CC_TYPE_DEFAULT,
+	IRC_CC_TYPE_REST,
+	IRC_CC_TYPE_GROUP,
+	IRC_CC_TYPE_ACCOUNT,
+	IRC_CC_TYPE_PROTOCOL,
+} irc_control_channel_type_t;
+
+struct irc_control_channel
+{
+	irc_control_channel_type_t type;
+	struct bee_group *group;
+	struct account *account;
+	struct prpl *protocol;
+	char modes[4];
+};
+
+extern const struct bee_ui_funcs irc_ui_funcs;
+
+typedef enum
+{
+	IRC_CDU_SILENT,
+	IRC_CDU_PART,
+	IRC_CDU_KICK,
+} irc_channel_del_user_type_t;
+
+/* irc.c */
 extern GSList *irc_connection_list;
 
@@ -103,40 +220,81 @@
 void irc_abort( irc_t *irc, int immed, char *format, ... ) G_GNUC_PRINTF( 3, 4 );
 void irc_free( irc_t *irc );
-
-void irc_exec( irc_t *irc, char **cmd );
+void irc_setpass (irc_t *irc, const char *pass);
+
 void irc_process( irc_t *irc );
 char **irc_parse_line( char *line );
 char *irc_build_line( char **cmd );
 
-void irc_vawrite( irc_t *irc, char *format, va_list params );
 void irc_write( irc_t *irc, char *format, ... ) G_GNUC_PRINTF( 2, 3 );
 void irc_write_all( int now, char *format, ... ) G_GNUC_PRINTF( 2, 3 );
-void irc_reply( irc_t *irc, int code, char *format, ... ) G_GNUC_PRINTF( 3, 4 );
-G_MODULE_EXPORT int irc_usermsg( irc_t *irc, char *format, ... ) G_GNUC_PRINTF( 2, 3 );
-char **irc_tokenize( char *buffer );
-
-void irc_login( irc_t *irc );
+void irc_vawrite( irc_t *irc, char *format, va_list params );
+
+void irc_flush( irc_t *irc );
+void irc_switch_fd( irc_t *irc, int fd );
+void irc_sync( irc_t *irc );
+void irc_desync( irc_t *irc );
+
 int irc_check_login( irc_t *irc );
-void irc_motd( irc_t *irc );
-void irc_names( irc_t *irc, char *channel );
-void irc_topic( irc_t *irc, char *channel );
-void irc_umode_set( irc_t *irc, char *s, int allow_priv );
-void irc_who( irc_t *irc, char *channel );
-void irc_spawn( irc_t *irc, user_t *u );
-void irc_join( irc_t *irc, user_t *u, char *channel );
-void irc_part( irc_t *irc, user_t *u, char *channel );
-void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker );
-void irc_kill( irc_t *irc, user_t *u );
-void irc_invite( irc_t *irc, char *nick, char *channel );
-void irc_whois( irc_t *irc, char *nick );
-void irc_setpass( irc_t *irc, const char *pass ); /* USE WITH CAUTION! */
-
-int irc_send( irc_t *irc, char *nick, char *s, int flags );
-int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg );
-int irc_msgfrom( irc_t *irc, char *nick, char *msg );
-int irc_noticefrom( irc_t *irc, char *nick, char *msg );
-
-void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags );
-struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel );
+
+void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv );
+
+/* irc_channel.c */
+irc_channel_t *irc_channel_new( irc_t *irc, const char *name );
+irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name );
+irc_channel_t *irc_channel_get( irc_t *irc, char *id );
+int irc_channel_free( irc_channel_t *ic );
+void irc_channel_free_soon( irc_channel_t *ic );
+int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu );
+int irc_channel_del_user( irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg );
+irc_channel_user_t *irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu );
+int irc_channel_set_topic( irc_channel_t *ic, const char *topic, const irc_user_t *who );
+void irc_channel_user_set_mode( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags );
+void irc_channel_set_mode( irc_channel_t *ic, const char *s );
+void irc_channel_auto_joins( irc_t *irc, struct account *acc );
+void irc_channel_printf( irc_channel_t *ic, char *format, ... );
+gboolean irc_channel_name_ok( const char *name );
+void irc_channel_name_strip( char *name );
+int irc_channel_name_cmp( const char *a_, const char *b_ );
+void irc_channel_update_ops( irc_channel_t *ic, char *value );
+char *set_eval_irc_channel_ops( struct set *set, char *value );
+
+/* irc_commands.c */
+void irc_exec( irc_t *irc, char **cmd );
+
+/* irc_send.c */
+void irc_send_num( irc_t *irc, int code, char *format, ... ) G_GNUC_PRINTF( 3, 4 );
+void irc_send_login( irc_t *irc );
+void irc_send_motd( irc_t *irc );
+void irc_usermsg( irc_t *irc, char *format, ... );
+void irc_send_join( irc_channel_t *ic, irc_user_t *iu );
+void irc_send_part( irc_channel_t *ic, irc_user_t *iu, const char *reason );
+void irc_send_quit( irc_user_t *iu, const char *reason );
+void irc_send_kick( irc_channel_t *ic, irc_user_t *iu, irc_user_t *kicker, const char *reason );
+void irc_send_names( irc_channel_t *ic );
+void irc_send_topic( irc_channel_t *ic, gboolean topic_change );
+void irc_send_whois( irc_user_t *iu );
+void irc_send_who( irc_t *irc, GSList *l, const char *channel );
+void irc_send_msg( irc_user_t *iu, const char *type, const char *dst, const char *msg, const char *prefix );
+void irc_send_msg_raw( irc_user_t *iu, const char *type, const char *dst, const char *msg );
+void irc_send_msg_f( irc_user_t *iu, const char *type, const char *dst, const char *format, ... ) G_GNUC_PRINTF( 4, 5 );
+void irc_send_nick( irc_user_t *iu, const char *new );
+void irc_send_channel_user_mode_diff( irc_channel_t *ic, irc_user_t *iu,
+                                      irc_channel_user_flags_t old, irc_channel_user_flags_t new );
+
+/* irc_user.c */
+irc_user_t *irc_user_new( irc_t *irc, const char *nick );
+int irc_user_free( irc_t *irc, irc_user_t *iu );
+irc_user_t *irc_user_by_name( irc_t *irc, const char *nick );
+int irc_user_set_nick( irc_user_t *iu, const char *new );
+gint irc_user_cmp( gconstpointer a_, gconstpointer b_ );
+const char *irc_user_get_away( irc_user_t *iu );
+void irc_user_quit( irc_user_t *iu, const char *msg );
+
+/* irc_util.c */
+char *set_eval_timezone( struct set *set, char *value );
+char *irc_format_timestamp( irc_t *irc, time_t msg_ts );
+
+/* irc_im.c */
+void bee_irc_channel_update( irc_t *irc, irc_channel_t *ic, irc_user_t *iu );
 
 #endif
Index: irc_channel.c
===================================================================
--- irc_channel.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ irc_channel.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,755 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* The IRC-based UI - Representing (virtual) channels.                  */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "bitlbee.h"
+
+static char *set_eval_channel_type( set_t *set, char *value );
+static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ );
+static const struct irc_channel_funcs control_channel_funcs;
+
+extern const struct irc_channel_funcs irc_channel_im_chat_funcs;
+
+irc_channel_t *irc_channel_new( irc_t *irc, const char *name )
+{
+	irc_channel_t *ic;
+	
+	if( !irc_channel_name_ok( name ) || irc_channel_by_name( irc, name ) )
+		return NULL;
+	
+	ic = g_new0( irc_channel_t, 1 );
+	ic->irc = irc;
+	ic->name = g_strdup( name );
+	strcpy( ic->mode, CMODE );
+	
+	irc_channel_add_user( ic, irc->root );
+	
+	irc->channels = g_slist_append( irc->channels, ic );
+	
+	set_add( &ic->set, "auto_join", "false", set_eval_bool, ic );
+	set_add( &ic->set, "type", "control", set_eval_channel_type, ic );
+	
+	if( name[0] == '&' )
+		set_setstr( &ic->set, "type", "control" );
+	else /* if( name[0] == '#' ) */
+		set_setstr( &ic->set, "type", "chat" );
+	
+	return ic;
+}
+
+irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name )
+{
+	GSList *l;
+	
+	for( l = irc->channels; l; l = l->next )
+	{
+		irc_channel_t *ic = l->data;
+		
+		if( irc_channel_name_cmp( name, ic->name ) == 0 )
+			return ic;
+	}
+	
+	return NULL;
+}
+
+irc_channel_t *irc_channel_get( irc_t *irc, char *id )
+{
+	irc_channel_t *ic, *ret = NULL;
+	GSList *l;
+	int nr;
+	
+	if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 )
+	{
+		for( l = irc->channels; l; l = l->next )
+		{
+			ic = l->data;
+			if( ( nr-- ) == 0 ) 
+				return ic;
+		}
+		
+		return NULL;
+	}
+	
+	/* Exact match first: Partial match only sucks if there's a channel
+	   #aa and #aabb */
+	if( ( ret = irc_channel_by_name( irc, id ) ) )
+		return ret;
+	
+	for( l = irc->channels; l; l = l->next )
+	{
+		ic = l->data;
+		
+		if( strstr( ic->name, id ) )
+		{
+			/* Make sure it's a unique match. */
+			if( !ret )
+				ret = ic;
+			else
+				return NULL;
+		}
+	}
+	
+	return ret;
+}
+
+int irc_channel_free( irc_channel_t *ic )
+{
+	irc_t *irc = ic->irc;
+	GSList *l;
+	
+	if( ic->flags & IRC_CHANNEL_JOINED )
+		irc_channel_del_user( ic, irc->user, IRC_CDU_KICK, "Cleaning up channel" );
+	
+	if( ic->f->_free )
+		ic->f->_free( ic );
+	
+	while( ic->set )
+		set_del( &ic->set, ic->set->key );
+	
+	irc->channels = g_slist_remove( irc->channels, ic );
+	while( ic->users )
+	{
+		g_free( ic->users->data );
+		ic->users = g_slist_remove( ic->users, ic->users->data );
+	}
+	
+	for( l = irc->users; l; l = l->next )
+	{
+		irc_user_t *iu = l->data;
+		
+		if( iu->last_channel == ic )
+			iu->last_channel = irc->default_channel;
+	}
+	
+	g_free( ic->name );
+	g_free( ic->topic );
+	g_free( ic->topic_who );
+	g_free( ic );
+	
+	return 1;
+}
+
+struct irc_channel_free_data
+{
+	irc_t *irc;
+	irc_channel_t *ic;
+	char *name;
+};
+
+static gboolean irc_channel_free_callback( gpointer data, gint fd, b_input_condition cond )
+{
+	struct irc_channel_free_data *d = data;
+	
+	if( g_slist_find( irc_connection_list, d->irc ) &&
+	    irc_channel_by_name( d->irc, d->name ) == d->ic &&
+	    !( d->ic->flags & IRC_CHANNEL_JOINED ) )
+		irc_channel_free( d->ic );
+
+	g_free( d->name );
+	g_free( d );
+	return FALSE;
+}
+
+/* Free the channel, but via the event loop, so after finishing whatever event
+   we're currently handling. */
+void irc_channel_free_soon( irc_channel_t *ic )
+{
+	struct irc_channel_free_data *d = g_new0( struct irc_channel_free_data, 1 );
+	
+	d->irc = ic->irc;
+	d->ic = ic;
+	d->name = g_strdup( ic->name );
+	
+	b_timeout_add( 0, irc_channel_free_callback, d );
+}
+
+static char *set_eval_channel_type( set_t *set, char *value )
+{
+	struct irc_channel *ic = set->data;
+	const struct irc_channel_funcs *new;
+	
+	if( strcmp( value, "control" ) == 0 )
+		new = &control_channel_funcs;
+	else if( strcmp( value, "chat" ) == 0 )
+		new = &irc_channel_im_chat_funcs;
+	else
+		return SET_INVALID;
+	
+	/* TODO: Return values. */
+	if( ic->f && ic->f->_free )
+		ic->f->_free( ic );
+	
+	ic->f = new;
+	
+	if( ic->f && ic->f->_init )
+		ic->f->_init( ic );
+	
+	return value;
+}
+
+int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu )
+{
+	irc_channel_user_t *icu;
+	
+	if( irc_channel_has_user( ic, iu ) )
+		return 0;
+	
+	icu = g_new0( irc_channel_user_t, 1 );
+	icu->iu = iu;
+	
+	ic->users = g_slist_insert_sorted( ic->users, icu, irc_channel_user_cmp );
+	
+	irc_channel_update_ops( ic, set_getstr( &ic->irc->b->set, "ops" ) );
+	
+	if( iu == ic->irc->user || ic->flags & IRC_CHANNEL_JOINED )
+	{
+		ic->flags |= IRC_CHANNEL_JOINED;
+		irc_send_join( ic, iu );
+	}
+	
+	return 1;
+}
+
+int irc_channel_del_user( irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg )
+{
+	irc_channel_user_t *icu;
+	
+	if( !( icu = irc_channel_has_user( ic, iu ) ) )
+		return 0;
+	
+	ic->users = g_slist_remove( ic->users, icu );
+	g_free( icu );
+	
+	if( !( ic->flags & IRC_CHANNEL_JOINED ) || type == IRC_CDU_SILENT ) {}
+		/* Do nothing. The caller should promise it won't screw
+		   up state of the IRC client. :-) */
+	else if( type == IRC_CDU_PART )
+		irc_send_part( ic, iu, msg );
+	else if( type == IRC_CDU_KICK )
+		irc_send_kick( ic, iu, ic->irc->root, msg );
+	
+	if( iu == ic->irc->user )
+	{
+		ic->flags &= ~IRC_CHANNEL_JOINED;
+		
+		if( ic->irc->status & USTATUS_SHUTDOWN )
+		{
+			/* Don't do anything fancy when we're shutting down anyway. */
+		}
+		else if( ic->flags & IRC_CHANNEL_TEMP )
+		{
+			irc_channel_free_soon( ic );
+		}
+		else
+		{
+			/* Flush userlist now. The user won't see it anyway. */
+			while( ic->users )
+			{
+				g_free( ic->users->data );
+				ic->users = g_slist_remove( ic->users, ic->users->data );
+			}
+			irc_channel_add_user( ic, ic->irc->root );
+		}
+	}
+	
+	return 1;
+}
+
+irc_channel_user_t *irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu )
+{
+	GSList *l;
+	
+	for( l = ic->users; l; l = l->next )
+	{
+		irc_channel_user_t *icu = l->data;
+		
+		if( icu->iu == iu )
+			return icu;
+	}
+	
+	return NULL;
+}
+
+int irc_channel_set_topic( irc_channel_t *ic, const char *topic, const irc_user_t *iu )
+{
+	g_free( ic->topic );
+	ic->topic = g_strdup( topic );
+	
+	g_free( ic->topic_who );
+	if( iu )
+		ic->topic_who = g_strdup_printf( "%s!%s@%s", iu->nick, iu->user, iu->host );
+	else
+		ic->topic_who = NULL;
+	
+	ic->topic_time = time( NULL );
+	
+	if( ic->flags & IRC_CHANNEL_JOINED )
+		irc_send_topic( ic, TRUE );
+	
+	return 1;
+}
+
+void irc_channel_user_set_mode( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags )
+{
+	irc_channel_user_t *icu = irc_channel_has_user( ic, iu );
+	
+	if( !icu || icu->flags == flags )
+		return;
+	
+	if( ic->flags & IRC_CHANNEL_JOINED )
+		irc_send_channel_user_mode_diff( ic, iu, icu->flags, flags );
+	
+	icu->flags = flags;
+}
+
+void irc_channel_set_mode( irc_channel_t *ic, const char *s )
+{
+	irc_t *irc = ic->irc;
+	char m[128], st = 1;
+	const char *t;
+	int i;
+	char changes[512], *p, st2 = 2;
+	
+	memset( m, 0, sizeof( m ) );
+	
+	for( t = ic->mode; *t; t ++ )
+		if( *t < sizeof( m ) )
+			m[(int)*t] = 1;
+	
+	p = changes;
+	for( t = s; *t; t ++ )
+	{
+		if( *t == '+' || *t == '-' )
+			st = *t == '+';
+		else if( strchr( CMODES, *t ) )
+		{
+			if( m[(int)*t] != st)
+			{
+				if( st != st2 )
+					st2 = st, *p++ = st ? '+' : '-';
+				*p++ = *t;
+			}
+			m[(int)*t] = st;
+		}
+	}
+	*p = '\0';
+	
+	memset( ic->mode, 0, sizeof( ic->mode ) );
+	
+	for( i = 'A'; i <= 'z' && strlen( ic->mode ) < ( sizeof( ic->mode ) - 1 ); i ++ )
+		if( m[i] )
+			ic->mode[strlen(ic->mode)] = i;
+	
+	if( *changes && ( ic->flags & IRC_CHANNEL_JOINED ) )
+		irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->root->nick,
+		           irc->root->user, irc->root->host, ic->name,
+		           changes );
+}
+
+void irc_channel_auto_joins( irc_t *irc, account_t *acc )
+{
+	GSList *l;
+	
+	for( l = irc->channels; l; l = l->next )
+	{
+		irc_channel_t *ic = l->data;
+		gboolean aj = set_getbool( &ic->set, "auto_join" );
+		char *type;
+		
+		if( acc &&
+		    ( type = set_getstr( &ic->set, "chat_type" ) ) &&
+		    strcmp( type, "room" ) == 0 )
+		{
+			/* Bit of an ugly special case: Handle chatrooms here, we
+			   can only auto-join them if their account is online. */
+			char *acc_s;
+			
+			if( !aj && !( ic->flags & IRC_CHANNEL_JOINED ) )
+				/* Only continue if this one's marked as auto_join
+				   or if we're in it already. (Possible if the
+				   client auto-rejoined it before identyfing.) */
+				continue;
+			else if( !( acc_s = set_getstr( &ic->set, "account" ) ) )
+				continue;
+			else if( account_get( irc->b, acc_s ) != acc )
+				continue;
+			else if( acc->ic == NULL || !( acc->ic->flags & OPT_LOGGED_IN ) )
+				continue;
+			else
+				ic->f->join( ic );
+		}
+		else if( aj )
+		{
+			irc_channel_add_user( ic, irc->user );
+		}
+	}
+}
+
+void irc_channel_printf( irc_channel_t *ic, char *format, ... )
+{
+	va_list params;
+	char *text;
+	
+	va_start( params, format );
+	text = g_strdup_vprintf( format, params );
+	va_end( params );
+	
+	irc_send_msg( ic->irc->root, "PRIVMSG", ic->name, text, NULL );
+	g_free( text );
+}
+
+gboolean irc_channel_name_ok( const char *name_ )
+{
+	const unsigned char *name = (unsigned char*) name_;
+	int i;
+	
+	if( name_[0] == '\0' )
+		return FALSE;
+	
+	/* Check if the first character is in CTYPES (#&) */
+	if( strchr( CTYPES, name_[0] ) == NULL )
+		return FALSE;
+	
+	/* RFC 1459 keeps amazing me: While only a "few" chars are allowed
+	   in nicknames, channel names can be pretty much anything as long
+	   as they start with # or &. I'll be a little bit more strict and
+	   disallow all non-printable characters. */
+	for( i = 1; name[i]; i ++ )
+		if( name[i] <= ' ' || name[i] == ',' )
+			return FALSE;
+	
+	return TRUE;
+}
+
+void irc_channel_name_strip( char *name )
+{
+	int i, j;
+	
+	for( i = j = 0; name[i]; i ++ )
+		if( name[i] > ' ' && name[i] != ',' )
+			name[j++] = name[i];
+	
+	name[j] = '\0';
+}
+
+int irc_channel_name_cmp( const char *a_, const char *b_ )
+{
+	static unsigned char case_map[256];
+	const unsigned char *a = (unsigned char*) a_, *b = (unsigned char*) b_;
+	int i;
+	
+	if( case_map['A'] == '\0' )
+	{
+		for( i = 33; i < 256; i ++ )
+			if( i != ',' )
+				case_map[i] = i;
+		
+		for( i = 0; i < 26; i ++ )
+			case_map['A'+i] = 'a' + i;
+		
+		case_map['['] = '{';
+		case_map[']'] = '}';
+		case_map['~'] = '`';
+		case_map['\\'] = '|';
+	}
+	
+	if( !irc_channel_name_ok( a_ ) || !irc_channel_name_ok( b_ ) )
+		return -1;
+	
+	for( i = 0; a[i] && b[i] && case_map[a[i]] && case_map[b[i]]; i ++ )
+	{
+		if( case_map[a[i]] == case_map[b[i]] )
+			continue;
+		else
+			return case_map[a[i]] - case_map[b[i]];
+	}
+	
+	return case_map[a[i]] - case_map[b[i]];
+}
+
+static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ )
+{
+	const irc_channel_user_t *a = a_, *b = b_;
+	
+	return irc_user_cmp( a->iu, b->iu );
+}
+
+void irc_channel_update_ops( irc_channel_t *ic, char *value )
+{
+	irc_channel_user_set_mode( ic, ic->irc->root,
+		( strcmp( value, "both" ) == 0 ||
+		  strcmp( value, "root" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 );
+	irc_channel_user_set_mode( ic, ic->irc->user,
+		( strcmp( value, "both" ) == 0 ||
+		  strcmp( value, "user" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 );
+}
+
+char *set_eval_irc_channel_ops( set_t *set, char *value )
+{
+	irc_t *irc = set->data;
+	GSList *l;
+	
+	if( strcmp( value, "both" ) != 0 && strcmp( value, "none" ) != 0 && 
+	    strcmp( value, "user" ) != 0 && strcmp( value, "root" ) != 0 )
+		return SET_INVALID;
+	
+	for( l = irc->channels; l; l = l->next )
+		irc_channel_update_ops( l->data, value );
+	
+	return value;
+}
+
+/* Channel-type dependent functions, for control channels: */
+static gboolean control_channel_privmsg( irc_channel_t *ic, const char *msg )
+{
+	irc_t *irc = ic->irc;
+	irc_user_t *iu;
+	const char *s;
+	
+	/* Scan for non-whitespace chars followed by a colon: */
+	for( s = msg; *s && !isspace( *s ) && *s != ':' && *s != ','; s ++ ) {}
+	
+	if( *s == ':' || *s == ',' )
+	{
+		char to[s-msg+1];
+		
+		memset( to, 0, sizeof( to ) );
+		strncpy( to, msg, s - msg );
+		while( *(++s) && isspace( *s ) ) {}
+		msg = s;
+		
+		if( !( iu = irc_user_by_name( irc, to ) ) )
+			irc_channel_printf( ic, "User does not exist: %s", to );
+		else
+			ic->last_target = iu;
+	}
+	else if( g_strcasecmp( set_getstr( &irc->b->set, "default_target" ), "last" ) == 0 &&
+	         ic->last_target && g_slist_find( irc->users, ic->last_target ) )
+		iu = ic->last_target;
+	else
+		iu = irc->root;
+	
+	if( iu && iu->f->privmsg )
+	{
+		iu->last_channel = ic;
+		iu->f->privmsg( iu, msg );
+	}
+	
+	return TRUE;
+}
+
+static gboolean control_channel_invite( irc_channel_t *ic, irc_user_t *iu )
+{
+	struct irc_control_channel *icc = ic->data;
+	bee_user_t *bu = iu->bu;
+	
+	if( bu == NULL )
+		return FALSE;
+	
+	if( icc->type != IRC_CC_TYPE_GROUP )
+	{
+		irc_send_num( ic->irc, 482, "%s :Invitations are only possible to fill_by=group channels", ic->name );
+		return FALSE;
+	}
+	
+	bu->ic->acc->prpl->add_buddy( bu->ic, bu->handle,
+	                              icc->group ? icc->group->name : NULL );
+	
+	return TRUE;
+}
+
+static char *set_eval_by_account( set_t *set, char *value );
+static char *set_eval_fill_by( set_t *set, char *value );
+static char *set_eval_by_group( set_t *set, char *value );
+static char *set_eval_by_protocol( set_t *set, char *value );
+static char *set_eval_show_users( set_t *set, char *value );
+
+static gboolean control_channel_init( irc_channel_t *ic )
+{
+	struct irc_control_channel *icc;
+	
+	set_add( &ic->set, "account", NULL, set_eval_by_account, ic );
+	set_add( &ic->set, "fill_by", "all", set_eval_fill_by, ic );
+	set_add( &ic->set, "group", NULL, set_eval_by_group, ic );
+	set_add( &ic->set, "protocol", NULL, set_eval_by_protocol, ic );
+	
+	/* When changing the default, also change it below. */
+	set_add( &ic->set, "show_users", "online+,away", set_eval_show_users, ic );
+	
+	ic->data = icc = g_new0( struct irc_control_channel, 1 );
+	icc->type = IRC_CC_TYPE_DEFAULT;
+	
+	/* Have to run the evaluator to initialize icc->modes. */
+	set_setstr( &ic->set, "show_users", "online+,away" );
+	
+	/* For scripts that care. */
+	irc_channel_set_mode( ic, "+C" );
+	
+	return TRUE;
+}
+
+static gboolean control_channel_join( irc_channel_t *ic )
+{
+	bee_irc_channel_update( ic->irc, ic, NULL );
+	
+	return TRUE;
+}
+
+static char *set_eval_by_account( set_t *set, char *value )
+{
+	struct irc_channel *ic = set->data;
+	struct irc_control_channel *icc = ic->data;
+	account_t *acc;
+	
+	if( !( acc = account_get( ic->irc->b, value ) ) )
+		return SET_INVALID;
+	
+	icc->account = acc;
+	if( icc->type == IRC_CC_TYPE_ACCOUNT )
+		bee_irc_channel_update( ic->irc, ic, NULL );
+	
+	return g_strdup( acc->tag );
+}
+
+static char *set_eval_fill_by( set_t *set, char *value )
+{
+	struct irc_channel *ic = set->data;
+	struct irc_control_channel *icc = ic->data;
+	
+	if( strcmp( value, "all" ) == 0 )
+		icc->type = IRC_CC_TYPE_DEFAULT;
+	else if( strcmp( value, "rest" ) == 0 )
+		icc->type = IRC_CC_TYPE_REST;
+	else if( strcmp( value, "group" ) == 0 )
+		icc->type = IRC_CC_TYPE_GROUP;
+	else if( strcmp( value, "account" ) == 0 )
+		icc->type = IRC_CC_TYPE_ACCOUNT;
+	else if( strcmp( value, "protocol" ) == 0 )
+		icc->type = IRC_CC_TYPE_PROTOCOL;
+	else
+		return SET_INVALID;
+	
+	bee_irc_channel_update( ic->irc, ic, NULL );
+	return value;
+}
+
+static char *set_eval_by_group( set_t *set, char *value )
+{
+	struct irc_channel *ic = set->data;
+	struct irc_control_channel *icc = ic->data;
+	
+	icc->group = bee_group_by_name( ic->irc->b, value, TRUE );
+	if( icc->type == IRC_CC_TYPE_GROUP )
+		bee_irc_channel_update( ic->irc, ic, NULL );
+	
+	return g_strdup( icc->group->name );
+}
+
+static char *set_eval_by_protocol( set_t *set, char *value )
+{
+	struct irc_channel *ic = set->data;
+	struct irc_control_channel *icc = ic->data;
+	struct prpl *prpl;
+	
+	if( !( prpl = find_protocol( value ) ) )
+		return SET_INVALID;
+	
+	icc->protocol = prpl;
+	if( icc->type == IRC_CC_TYPE_PROTOCOL )
+		bee_irc_channel_update( ic->irc, ic, NULL );
+	
+	return value;
+}
+
+static char *set_eval_show_users( set_t *set, char *value )
+{
+	struct irc_channel *ic = set->data;
+	struct irc_control_channel *icc = ic->data;
+	char **parts = g_strsplit( value, ",", 0 ), **part;
+	char modes[4];
+	
+	memset( modes, 0, 4 );
+	for( part = parts; *part; part ++ )
+	{
+		char last, modechar = IRC_CHANNEL_USER_NONE;
+		
+		if( **part == '\0' )
+			goto fail;
+		
+		last = (*part)[strlen(*part+1)];
+		if( last == '+' )
+			modechar = IRC_CHANNEL_USER_VOICE;
+		else if( last == '%' )
+			modechar = IRC_CHANNEL_USER_HALFOP;
+		else if( last == '@' )
+			modechar = IRC_CHANNEL_USER_OP;
+		
+		if( strncmp( *part, "offline", 7 ) == 0 )
+			modes[0] = modechar;
+		else if( strncmp( *part, "away", 4 ) == 0 )
+			modes[1] = modechar;
+		else if( strncmp( *part, "online", 6 ) == 0 )
+			modes[2] = modechar;
+		else
+			goto fail;
+	}
+	memcpy( icc->modes, modes, 4 );
+	bee_irc_channel_update( ic->irc, ic, NULL );
+	
+	g_strfreev( parts );
+	return value;
+	
+fail:
+	g_strfreev( parts );
+	return SET_INVALID;	
+}
+
+static gboolean control_channel_free( irc_channel_t *ic )
+{
+	struct irc_control_channel *icc = ic->data;
+	
+	set_del( &ic->set, "account" );
+	set_del( &ic->set, "fill_by" );
+	set_del( &ic->set, "group" );
+	set_del( &ic->set, "protocol" );
+	
+	g_free( icc );
+	ic->data = NULL;
+	
+	/* For scripts that care. */
+	irc_channel_set_mode( ic, "-C" );
+	
+	return TRUE;
+}
+
+static const struct irc_channel_funcs control_channel_funcs = {
+	control_channel_privmsg,
+	control_channel_join,
+	NULL,
+	NULL,
+	control_channel_invite,
+	
+	control_channel_init,
+	control_channel_free,
+};
Index: irc_commands.c
===================================================================
--- irc_commands.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ irc_commands.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -2,5 +2,5 @@
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2006 Wilmer van der Gaast and others                *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
   \********************************************************************/
 
@@ -27,5 +27,4 @@
 #include "bitlbee.h"
 #include "ipc.h"
-#include "chat.h"
 
 static void irc_cmd_pass( irc_t *irc, char **cmd )
@@ -39,5 +38,6 @@
 		   out we don't require it, which will break this feature.)
 		   Try to identify using the given password. */
-		return root_command( irc, send_cmd );
+		root_command( irc, send_cmd );
+		return;
 	}
 	/* Handling in pre-logged-in state, first see if this server is
@@ -53,5 +53,5 @@
 	else if( global.conf->auth_pass )
 	{
-		irc_reply( irc, 464, ":Incorrect password" );
+		irc_send_num( irc, 464, ":Incorrect password" );
 	}
 	else
@@ -65,6 +65,6 @@
 static void irc_cmd_user( irc_t *irc, char **cmd )
 {
-	irc->user = g_strdup( cmd[1] );
-	irc->realname = g_strdup( cmd[4] );
+	irc->user->user = g_strdup( cmd[1] );
+	irc->user->fullname = g_strdup( cmd[4] );
 	
 	irc_check_login( irc );
@@ -73,21 +73,34 @@
 static void irc_cmd_nick( irc_t *irc, char **cmd )
 {
-	if( irc->nick )
-	{
-		irc_reply( irc, 438, ":The hand of the deity is upon thee, thy nick may not change" );
-	}
-	/* This is not clean, but for now it'll have to be like this... */
-	else if( ( nick_cmp( cmd[1], irc->mynick ) == 0 ) || ( nick_cmp( cmd[1], NS_NICK ) == 0 ) )
-	{
-		irc_reply( irc, 433, ":This nick is already in use" );
+	irc_user_t *iu;
+	
+	if( ( iu = irc_user_by_name( irc, cmd[1] ) ) && iu != irc->user )
+	{
+		irc_send_num( irc, 433, "%s :This nick is already in use", cmd[1] );
 	}
 	else if( !nick_ok( cmd[1] ) )
 	{
 		/* [SH] Invalid characters. */
-		irc_reply( irc, 432, ":This nick contains invalid characters" );
-	}
-	else
-	{
-		irc->nick = g_strdup( cmd[1] );
+		irc_send_num( irc, 432, "%s :This nick contains invalid characters", cmd[1] );
+	}
+	else if( irc->status & USTATUS_LOGGED_IN )
+	{
+		if( irc->status & USTATUS_IDENTIFIED )
+		{
+			irc_setpass( irc, NULL );
+			irc->status &= ~USTATUS_IDENTIFIED;
+			irc_umode_set( irc, "-R", 1 );
+			irc_usermsg( irc, "Changing nicks resets your identify status. "
+			             "Re-identify or register a new account if you want "
+			             "your configuration to be saved. See \x02help "
+			             "nick_changes\x02." );
+		}
+		
+		irc_user_set_nick( irc->user, cmd[1] );
+	}
+	else
+	{
+		g_free( irc->user->nick );
+		irc->user->nick = g_strdup( cmd[1] );
 		
 		irc_check_login( irc );
@@ -105,6 +118,275 @@
 static void irc_cmd_ping( irc_t *irc, char **cmd )
 {
-	irc_write( irc, ":%s PONG %s :%s", irc->myhost, irc->myhost, cmd[1]?cmd[1]:irc->myhost );
-}
+	irc_write( irc, ":%s PONG %s :%s", irc->root->host,
+	           irc->root->host, cmd[1]?cmd[1]:irc->root->host );
+}
+
+static void irc_cmd_pong( irc_t *irc, char **cmd )
+{
+	/* We could check the value we get back from the user, but in
+	   fact we don't care, we're just happy s/he's still alive. */
+	irc->last_pong = gettime();
+	irc->pinging = 0;
+}
+
+static void irc_cmd_join( irc_t *irc, char **cmd )
+{
+	char *comma, *s = cmd[1];
+	
+	while( s )
+	{
+		irc_channel_t *ic;
+		
+		if( ( comma = strchr( s, ',' ) ) )
+			*comma = '\0';
+		
+		if( ( ic = irc_channel_by_name( irc, s ) ) == NULL &&
+		    ( ic = irc_channel_new( irc, s ) ) )
+		{
+			if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 )
+			{
+				/* Autoconfiguration is for control channels only ATM. */
+			}
+			else if( bee_group_by_name( ic->irc->b, ic->name + 1, FALSE ) )
+			{
+				set_setstr( &ic->set, "group", ic->name + 1 );
+				set_setstr( &ic->set, "fill_by", "group" );
+			}
+			else if( set_setstr( &ic->set, "protocol", ic->name + 1 ) )
+			{
+				set_setstr( &ic->set, "fill_by", "protocol" );
+			}
+			else if( set_setstr( &ic->set, "account", ic->name + 1 ) )
+			{
+				set_setstr( &ic->set, "fill_by", "account" );
+			}
+			else
+			{
+				/* The set commands above will run this already,
+				   but if we didn't hit any, we have to fill the
+				   channel with the default population. */
+				bee_irc_channel_update( ic->irc, ic, NULL );
+			}
+		}
+		else if( ic == NULL )
+		{
+			irc_send_num( irc, 479, "%s :Invalid channel name", s );
+			goto next;
+		}
+		
+		if( ic->flags & IRC_CHANNEL_JOINED )
+			/* Dude, you're already there...
+			   RFC doesn't have any reply for that though? */
+			goto next;
+		
+		if( ic->f->join && !ic->f->join( ic ) )
+			/* The story is: FALSE either means the handler
+			   showed an error message, or is doing some work
+			   before the join should be confirmed. (In the
+			   latter case, the caller should take care of that
+			   confirmation.) TRUE means all's good, let the
+			   user join the channel right away. */
+			goto next;
+		
+		irc_channel_add_user( ic, irc->user );
+		
+next:
+		if( comma )
+		{
+			s = comma + 1;
+			*comma = ',';
+		}
+		else
+			break;
+	}
+}
+
+static void irc_cmd_names( irc_t *irc, char **cmd )
+{
+	irc_channel_t *ic;
+	
+	if( cmd[1] && ( ic = irc_channel_by_name( irc, cmd[1] ) ) )
+		irc_send_names( ic );
+	/* With no args, we should show /names of all chans. Make the code
+	   below work well if necessary.
+	else
+	{
+		GSList *l;
+		
+		for( l = irc->channels; l; l = l->next )
+			irc_send_names( l->data );
+	}
+	*/
+}
+
+static void irc_cmd_part( irc_t *irc, char **cmd )
+{
+	irc_channel_t *ic;
+	
+	if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL )
+	{
+		irc_send_num( irc, 403, "%s :No such channel", cmd[1] );
+	}
+	else if( irc_channel_del_user( ic, irc->user, IRC_CDU_PART, cmd[2] ) )
+	{
+		if( ic->f->part )
+			ic->f->part( ic, NULL );
+	}
+	else
+	{
+		irc_send_num( irc, 442, "%s :You're not on that channel", cmd[1] );
+	}
+}
+
+static void irc_cmd_whois( irc_t *irc, char **cmd )
+{
+	char *nick = cmd[1];
+	irc_user_t *iu = irc_user_by_name( irc, nick );
+	
+	if( iu )
+		irc_send_whois( iu );
+	else
+		irc_send_num( irc, 401, "%s :Nick does not exist", nick );
+}
+
+static void irc_cmd_whowas( irc_t *irc, char **cmd )
+{
+	/* For some reason irssi tries a whowas when whois fails. We can
+	   ignore this, but then the user never gets a "user not found"
+	   message from irssi which is a bit annoying. So just respond
+	   with not-found and irssi users will get better error messages */
+	
+	irc_send_num( irc, 406, "%s :Nick does not exist", cmd[1] );
+	irc_send_num( irc, 369, "%s :End of WHOWAS", cmd[1] );
+}
+
+static void irc_cmd_motd( irc_t *irc, char **cmd )
+{
+	irc_send_motd( irc );
+}
+
+static void irc_cmd_mode( irc_t *irc, char **cmd )
+{
+	if( irc_channel_name_ok( cmd[1] ) )
+	{
+		irc_channel_t *ic;
+		
+		if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL )
+			irc_send_num( irc, 403, "%s :No such channel", cmd[1] );
+		else if( cmd[2] )
+		{
+			if( *cmd[2] == '+' || *cmd[2] == '-' )
+				irc_send_num( irc, 477, "%s :Can't change channel modes", cmd[1] );
+			else if( *cmd[2] == 'b' )
+				irc_send_num( irc, 368, "%s :No bans possible", cmd[1] );
+		}
+		else
+			irc_send_num( irc, 324, "%s +%s", cmd[1], ic->mode );
+	}
+	else
+	{
+		if( nick_cmp( cmd[1], irc->user->nick ) == 0 )
+		{
+			if( cmd[2] )
+				irc_umode_set( irc, cmd[2], 0 );
+			else
+				irc_send_num( irc, 221, "+%s", irc->umode );
+		}
+		else
+			irc_send_num( irc, 502, ":Don't touch their modes" );
+	}
+}
+
+static void irc_cmd_who( irc_t *irc, char **cmd )
+{
+	char *channel = cmd[1];
+	irc_channel_t *ic;
+	
+	if( !channel || *channel == '0' || *channel == '*' || !*channel )
+		irc_send_who( irc, irc->users, "**" );
+	else if( ( ic = irc_channel_by_name( irc, channel ) ) )
+		irc_send_who( irc, ic->users, channel );
+	else
+		irc_send_num( irc, 403, "%s :No such channel", channel );
+}
+
+static void irc_cmd_privmsg( irc_t *irc, char **cmd )
+{
+	irc_channel_t *ic;
+	irc_user_t *iu;
+	
+	if( !cmd[2] ) 
+	{
+		irc_send_num( irc, 412, ":No text to send" );
+		return;
+	}
+	
+	/* Don't treat CTCP actions as real CTCPs, just convert them right now. */
+	if( g_strncasecmp( cmd[2], "\001ACTION", 7 ) == 0 )
+	{
+		cmd[2] += 4;
+		memcpy( cmd[2], "/me", 3 );
+		if( cmd[2][strlen(cmd[2])-1] == '\001' )
+			cmd[2][strlen(cmd[2])-1] = '\0';
+	}
+	
+	if( irc_channel_name_ok( cmd[1] ) &&
+	    ( ic = irc_channel_by_name( irc, cmd[1] ) ) )
+	{
+		if( cmd[2][0] == '\001' )
+		{
+			/* CTCPs to channels? Nah. Maybe later. */
+		}
+		else if( ic->f->privmsg )
+			ic->f->privmsg( ic, cmd[2] );
+	}
+	else if( ( iu = irc_user_by_name( irc, cmd[1] ) ) )
+	{
+		if( cmd[2][0] == '\001' )
+		{
+			char **ctcp;
+			
+			if( iu->f->ctcp == NULL )
+				return;
+			if( cmd[2][strlen(cmd[2])-1] == '\001' )
+				cmd[2][strlen(cmd[2])-1] = '\0';
+			
+			ctcp = split_command_parts( cmd[2] + 1 );
+			iu->f->ctcp( iu, ctcp );
+		}
+		else if( iu->f->privmsg )
+		{
+			iu->last_channel = NULL;
+			iu->f->privmsg( iu, cmd[2] );
+		}
+	}
+	else
+	{
+		irc_send_num( irc, 401, "%s :No such nick/channel", cmd[1] );
+	}
+}
+
+static void irc_cmd_notice( irc_t *irc, char **cmd )
+{
+	if( !cmd[2] ) 
+	{
+		irc_send_num( irc, 412, ":No text to send" );
+		return;
+	}
+	
+	/* At least for now just echo. IIRC some IRC clients use self-notices
+	   for lag checks, so try to support that. */
+	if( nick_cmp( cmd[1], irc->user->nick ) == 0 )
+		irc_send_msg( irc->user, "NOTICE", irc->user->nick, cmd[2], NULL );
+}
+
+static void irc_cmd_nickserv( irc_t *irc, char **cmd )
+{
+	/* [SH] This aliases the NickServ command to PRIVMSG root */
+	/* [TV] This aliases the NS command to PRIVMSG root as well */
+	root_command( irc, cmd + 1 );
+}
+
+
 
 static void irc_cmd_oper( irc_t *irc, char **cmd )
@@ -116,90 +398,9 @@
 	{
 		irc_umode_set( irc, "+o", 1 );
-		irc_reply( irc, 381, ":Password accepted" );
-	}
-	else
-	{
-		irc_reply( irc, 432, ":Incorrect password" );
-	}
-}
-
-static void irc_cmd_mode( irc_t *irc, char **cmd )
-{
-	if( strchr( CTYPES, *cmd[1] ) )
-	{
-		if( cmd[2] )
-		{
-			if( *cmd[2] == '+' || *cmd[2] == '-' )
-				irc_reply( irc, 477, "%s :Can't change channel modes", cmd[1] );
-			else if( *cmd[2] == 'b' )
-				irc_reply( irc, 368, "%s :No bans possible", cmd[1] );
-		}
-		else
-			irc_reply( irc, 324, "%s +%s", cmd[1], CMODE );
-	}
-	else
-	{
-		if( nick_cmp( cmd[1], irc->nick ) == 0 )
-		{
-			if( cmd[2] )
-				irc_umode_set( irc, cmd[2], 0 );
-			else
-				irc_reply( irc, 221, "+%s", irc->umode );
-		}
-		else
-			irc_reply( irc, 502, ":Don't touch their modes" );
-	}
-}
-
-static void irc_cmd_names( irc_t *irc, char **cmd )
-{
-	irc_names( irc, cmd[1]?cmd[1]:irc->channel );
-}
-
-static void irc_cmd_part( irc_t *irc, char **cmd )
-{
-	struct groupchat *c;
-	
-	if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
-	{
-		user_t *u = user_find( irc, irc->nick );
-		
-		/* Not allowed to leave control channel */
-		irc_part( irc, u, irc->channel );
-		irc_join( irc, u, irc->channel );
-	}
-	else if( ( c = irc_chat_by_channel( irc, cmd[1] ) ) )
-	{
-		user_t *u = user_find( irc, irc->nick );
-		
-		irc_part( irc, u, c->channel );
-		
-		if( c->ic )
-		{
-			c->joined = 0;
-			c->ic->acc->prpl->chat_leave( c );
-		}
-	}
-	else
-	{
-		irc_reply( irc, 403, "%s :No such channel", cmd[1] );
-	}
-}
-
-static void irc_cmd_join( irc_t *irc, char **cmd )
-{
-	if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
-		; /* Dude, you're already there...
-		     RFC doesn't have any reply for that though? */
-	else if( cmd[1] )
-	{
-		struct chat *c;
-		
-		if( strchr( CTYPES, cmd[1][0] ) == NULL || cmd[1][1] == 0 )
-			irc_reply( irc, 479, "%s :Invalid channel name", cmd[1] );
-		else if( ( c = chat_bychannel( irc, cmd[1] ) ) && c->acc && c->acc->ic )
-			chat_join( irc, c, cmd[2] );
-		else
-			irc_reply( irc, 403, "%s :No such channel", cmd[1] );
+		irc_send_num( irc, 381, ":Password accepted" );
+	}
+	else
+	{
+		irc_send_num( irc, 432, ":Incorrect password" );
 	}
 }
@@ -207,105 +408,26 @@
 static void irc_cmd_invite( irc_t *irc, char **cmd )
 {
-	char *nick = cmd[1], *channel = cmd[2];
-	struct groupchat *c = irc_chat_by_channel( irc, channel );
-	user_t *u = user_find( irc, nick );
-	
-	if( u && c && ( u->ic == c->ic ) )
-		if( c->ic && c->ic->acc->prpl->chat_invite )
-		{
-			c->ic->acc->prpl->chat_invite( c, u->handle, NULL );
-			irc_reply( irc, 341, "%s %s", nick, channel );
-			return;
-		}
-	
-	irc_reply( irc, 482, "%s :Invite impossible; User/Channel non-existent or incompatible", channel );
-}
-
-static void irc_cmd_privmsg( irc_t *irc, char **cmd )
-{
-	if ( !cmd[2] ) 
-	{
-		irc_reply( irc, 412, ":No text to send" );
-	}
-	else if ( irc->nick && g_strcasecmp( cmd[1], irc->nick ) == 0 ) 
-	{
-		irc_write( irc, ":%s!%s@%s %s %s :%s", irc->nick, irc->user, irc->host, cmd[0], cmd[1], cmd[2] ); 
-	}
-	else 
-	{
-		if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
-		{
-			unsigned int i;
-			char *t = set_getstr( &irc->set, "default_target" );
-			
-			if( g_strcasecmp( t, "last" ) == 0 && irc->last_target )
-				cmd[1] = irc->last_target;
-			else if( g_strcasecmp( t, "root" ) == 0 )
-				cmd[1] = irc->mynick;
-			
-			for( i = 0; i < strlen( cmd[2] ); i ++ )
-			{
-				if( cmd[2][i] == ' ' ) break;
-				if( cmd[2][i] == ':' || cmd[2][i] == ',' )
-				{
-					cmd[1] = cmd[2];
-					cmd[2] += i;
-					*cmd[2] = 0;
-					while( *(++cmd[2]) == ' ' );
-					break;
-				}
-			}
-			
-			irc->is_private = 0;
-			
-			if( cmd[1] != irc->last_target )
-			{
-				g_free( irc->last_target );
-				irc->last_target = g_strdup( cmd[1] );
-			}
-		}
-		else
-		{
-			irc->is_private = 1;
-		}
-		irc_send( irc, cmd[1], cmd[2], ( g_strcasecmp( cmd[0], "NOTICE" ) == 0 ) ? OPT_AWAY : 0 );
-	}
-}
-
-static void irc_cmd_who( irc_t *irc, char **cmd )
-{
-	char *channel = cmd[1];
-	user_t *u = irc->users;
-	struct groupchat *c;
-	GList *l;
-	
-	if( !channel || *channel == '0' || *channel == '*' || !*channel )
-		while( u )
-		{
-			irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", u->online ? irc->channel : "*", u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname );
-			u = u->next;
-		}
-	else if( g_strcasecmp( channel, irc->channel ) == 0 )
-		while( u )
-		{
-			if( u->online )
-				irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
-			u = u->next;
-		}
-	else if( ( c = irc_chat_by_channel( irc, channel ) ) )
-		for( l = c->in_room; l; l = l->next )
-		{
-			if( ( u = user_findhandle( c->ic, l->data ) ) )
-				irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
-		}
-	else if( ( u = user_find( irc, channel ) ) )
-		irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname );
-	
-	irc_reply( irc, 315, "%s :End of /WHO list", channel?channel:"**" );
+	irc_channel_t *ic;
+	irc_user_t *iu;
+	
+	if( ( iu = irc_user_by_name( irc, cmd[1] ) ) == NULL )
+	{
+		irc_send_num( irc, 401, "%s :No such nick", cmd[1] );
+		return;
+	}
+	else if( ( ic = irc_channel_by_name( irc, cmd[2] ) ) == NULL )
+	{
+		irc_send_num( irc, 403, "%s :No such channel", cmd[2] );
+		return;
+	}
+	
+	if( !ic->f->invite )
+		irc_send_num( irc, 482, "%s :Can't invite people here", cmd[2] );
+	else if( ic->f->invite( ic, iu ) )
+		irc_send_num( irc, 341, "%s %s", iu->nick, ic->name );
 }
 
 static void irc_cmd_userhost( irc_t *irc, char **cmd )
 {
-	user_t *u;
 	int i;
 	
@@ -317,16 +439,16 @@
 	
 	for( i = 1; cmd[i]; i ++ )
-		if( ( u = user_find( irc, cmd[i] ) ) )
-		{
-			if( u->online && u->away )
-				irc_reply( irc, 302, ":%s=-%s@%s", u->nick, u->user, u->host );
-			else
-				irc_reply( irc, 302, ":%s=+%s@%s", u->nick, u->user, u->host );
-		}
+	{
+		irc_user_t *iu = irc_user_by_name( irc, cmd[i] );
+		
+		if( iu )
+			irc_send_num( irc, 302, ":%s=%c%s@%s", iu->nick,
+			              irc_user_get_away( iu ) ? '-' : '+',
+			              iu->user, iu->host );
+	}
 }
 
 static void irc_cmd_ison( irc_t *irc, char **cmd )
 {
-	user_t *u;
 	char buff[IRC_MAX_LINE];
 	int lenleft, i;
@@ -344,15 +466,18 @@
 		while( *this )
 		{
+			irc_user_t *iu;
+			
 			if( ( next = strchr( this, ' ' ) ) )
 				*next = 0;
 			
-			if( ( u = user_find( irc, this ) ) && u->online )
-			{
-				lenleft -= strlen( u->nick ) + 1;
+			if( ( iu = irc_user_by_name( irc, this ) ) &&
+			    iu->bu && iu->bu->flags & BEE_USER_ONLINE )
+			{
+				lenleft -= strlen( iu->nick ) + 1;
 				
 				if( lenleft < 0 )
 					break;
 				
-				strcat( buff, u->nick );
+				strcat( buff, iu->nick );
 				strcat( buff, " " );
 			}
@@ -377,5 +502,5 @@
 		buff[strlen(buff)-1] = '\0';
 	
-	irc_reply( irc, 303, ":%s", buff );
+	irc_send_num( irc, 303, ":%s", buff );
 }
 
@@ -390,5 +515,5 @@
 	{
 		char *nick;
-		user_t *u;
+		irc_user_t *iu;
 		
 		if( !cmd[i][0] || !cmd[i][1] )
@@ -398,5 +523,5 @@
 		nick_lc( nick );
 		
-		u = user_find( irc, nick );
+		iu = irc_user_by_name( irc, nick );
 		
 		if( cmd[i][0] == '+' )
@@ -405,8 +530,10 @@
 				g_hash_table_insert( irc->watches, nick, nick );
 			
-			if( u && u->online )
-				irc_reply( irc, 604, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "is online" );
+			if( iu && iu->bu && iu->bu->flags & BEE_USER_ONLINE )
+				irc_send_num( irc, 604, "%s %s %s %d :%s", iu->nick, iu->user,
+				              iu->host, (int) time( NULL ), "is online" );
 			else
-				irc_reply( irc, 605, "%s %s %s %d :%s", nick, "*", "*", (int) time( NULL ), "is offline" );
+				irc_send_num( irc, 605, "%s %s %s %d :%s", nick, "*", "*",
+				              (int) time( NULL ), "is offline" );
 		}
 		else if( cmd[i][0] == '-' )
@@ -419,5 +546,5 @@
 				g_free( okey );
 				
-				irc_reply( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" );
+				irc_send_num( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" );
 			}
 		}
@@ -427,18 +554,21 @@
 static void irc_cmd_topic( irc_t *irc, char **cmd )
 {
-	char *channel = cmd[1];
-	char *topic = cmd[2];
-	
-	if( topic )
-	{
-		/* Send the topic */
-		struct groupchat *c = irc_chat_by_channel( irc, channel );
-		if( c && c->ic && c->ic->acc->prpl->chat_topic )
-			c->ic->acc->prpl->chat_topic( c, topic );
-	}
-	else
-	{
-		/* Get the topic */
-		irc_topic( irc, channel );
+	irc_channel_t *ic = irc_channel_by_name( irc, cmd[1] );
+	const char *new = cmd[2];
+	
+	if( ic == NULL )
+	{
+		irc_send_num( irc, 403, "%s :No such channel", cmd[1] );
+	}
+	else if( new )
+	{
+		if( ic->f->topic == NULL )
+			irc_send_num( irc, 482, "%s :Can't change this channel's topic", ic->name );
+		else if( ic->f->topic( ic, new ) )
+			irc_send_topic( ic, TRUE );
+	}
+	else
+	{
+		irc_send_topic( ic, FALSE );
 	}
 }
@@ -446,121 +576,64 @@
 static void irc_cmd_away( irc_t *irc, char **cmd )
 {
-	user_t *u = user_find( irc, irc->nick );
-	char *away = cmd[1];
-	
-	if( !u ) return;
-	
-	if( away && *away )
-	{
+	if( cmd[1] && *cmd[1] )
+	{
+		char away[strlen(cmd[1])+1];
 		int i, j;
 		
 		/* Copy away string, but skip control chars. Mainly because
 		   Jabber really doesn't like them. */
-		u->away = g_malloc( strlen( away ) + 1 );
-		for( i = j = 0; away[i]; i ++ )
-			if( ( u->away[j] = away[i] ) >= ' ' )
+		for( i = j = 0; cmd[1][i]; i ++ )
+			if( ( away[j] = cmd[1][i] ) >= ' ' )
 				j ++;
-		u->away[j] = 0;
-		
-		irc_reply( irc, 306, ":You're now away: %s", u->away );
-		/* irc_umode_set( irc, irc->myhost, "+a" ); */
-	}
-	else
-	{
-		if( u->away ) g_free( u->away );
-		u->away = NULL;
-		/* irc_umode_set( irc, irc->myhost, "-a" ); */
-		irc_reply( irc, 305, ":Welcome back" );
-	}
-	
-	set_setstr( &irc->set, "away", u->away );
-}
-
-static void irc_cmd_whois( irc_t *irc, char **cmd )
-{
-	char *nick = cmd[1];
-	user_t *u = user_find( irc, nick );
-	
-	if( u )
-	{
-		irc_reply( irc, 311, "%s %s %s * :%s", u->nick, u->user, u->host, u->realname );
-		
-		if( u->ic )
-			irc_reply( irc, 312, "%s %s.%s :%s network", u->nick, u->ic->acc->user,
-			           u->ic->acc->server && *u->ic->acc->server ? u->ic->acc->server : "",
-			           u->ic->acc->prpl->name );
-		else
-			irc_reply( irc, 312, "%s %s :%s", u->nick, irc->myhost, IRCD_INFO );
-		
-		if( !u->online )
-			irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
-		else if( u->away )
-			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, 318, "%s :End of /WHOIS list", nick );
-	}
-	else
-	{
-		irc_reply( irc, 401, "%s :Nick does not exist", nick );
-	}
-}
-
-static void irc_cmd_whowas( irc_t *irc, char **cmd )
-{
-	/* For some reason irssi tries a whowas when whois fails. We can
-	   ignore this, but then the user never gets a "user not found"
-	   message from irssi which is a bit annoying. So just respond
-	   with not-found and irssi users will get better error messages */
-	
-	irc_reply( irc, 406, "%s :Nick does not exist", cmd[1] );
-	irc_reply( irc, 369, "%s :End of WHOWAS", cmd[1] );
-}
-
-static void irc_cmd_nickserv( irc_t *irc, char **cmd )
-{
-	/* [SH] This aliases the NickServ command to PRIVMSG root */
-	/* [TV] This aliases the NS command to PRIVMSG root as well */
-	root_command( irc, cmd + 1 );
-}
-
-static void irc_cmd_motd( irc_t *irc, char **cmd )
-{
-	irc_motd( irc );
-}
-
-static void irc_cmd_pong( irc_t *irc, char **cmd )
-{
-	/* We could check the value we get back from the user, but in
-	   fact we don't care, we're just happy he's still alive. */
-	irc->last_pong = gettime();
-	irc->pinging = 0;
+		away[j] = '\0';
+		
+		irc_send_num( irc, 306, ":You're now away: %s", away );
+		set_setstr( &irc->b->set, "away", away );
+	}
+	else
+	{
+		irc_send_num( irc, 305, ":Welcome back" );
+		set_setstr( &irc->b->set, "away", NULL );
+	}
+}
+
+static void irc_cmd_list( irc_t *irc, char **cmd )
+{
+	GSList *l;
+	
+	for( l = irc->channels; l; l = l->next )
+	{
+		irc_channel_t *ic = l->data;
+		
+		irc_send_num( irc, 322, "%s %d :%s",
+		              ic->name, g_slist_length( ic->users ), ic->topic ? : "" );
+	}
+	irc_send_num( irc, 323, ":%s", "End of /LIST" );
 }
 
 static void irc_cmd_version( irc_t *irc, char **cmd )
 {
-	irc_reply( irc, 351, "bitlbee-%s. %s :%s/%s ", BITLBEE_VERSION, irc->myhost, ARCH, CPU );
+	irc_send_num( irc, 351, "bitlbee-%s. %s :%s/%s ",
+	              BITLBEE_VERSION, irc->root->host, ARCH, CPU );
 }
 
 static void irc_cmd_completions( irc_t *irc, char **cmd )
 {
-	user_t *u = user_find( irc, irc->mynick );
 	help_t *h;
 	set_t *s;
 	int i;
 	
-	irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "OK" );
+	irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK" );
 	
 	for( i = 0; commands[i].command; i ++ )
-		irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", commands[i].command );
+		irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", commands[i].command );
 	
 	for( h = global.help; h; h = h->next )
-		irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS help ", h->title );
-	
-	for( s = irc->set; s; s = s->next )
-		irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS set ", s->key );
-	
-	irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "END" );
+		irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title );
+	
+	for( s = irc->b->set; s; s = s->next )
+		irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS set %s", s->key );
+	
+	irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END" );
 }
 
@@ -572,5 +645,5 @@
 		ipc_to_master( cmd );
 	
-	irc_reply( irc, 382, "%s :Rehashing", global.conf_file );
+	irc_send_num( irc, 382, "%s :Rehashing", global.conf_file );
 }
 
@@ -581,26 +654,27 @@
 	{ "quit",        0, irc_cmd_quit,        0 },
 	{ "ping",        0, irc_cmd_ping,        0 },
-	{ "oper",        2, irc_cmd_oper,        IRC_CMD_LOGGED_IN },
+	{ "pong",        0, irc_cmd_pong,        IRC_CMD_LOGGED_IN },
+	{ "join",        1, irc_cmd_join,        IRC_CMD_LOGGED_IN },
+	{ "names",       1, irc_cmd_names,       IRC_CMD_LOGGED_IN },
+	{ "part",        1, irc_cmd_part,        IRC_CMD_LOGGED_IN },
+	{ "whois",       1, irc_cmd_whois,       IRC_CMD_LOGGED_IN },
+	{ "whowas",      1, irc_cmd_whowas,      IRC_CMD_LOGGED_IN },
+	{ "motd",        0, irc_cmd_motd,        IRC_CMD_LOGGED_IN },
 	{ "mode",        1, irc_cmd_mode,        IRC_CMD_LOGGED_IN },
-	{ "names",       0, irc_cmd_names,       IRC_CMD_LOGGED_IN },
-	{ "part",        1, irc_cmd_part,        IRC_CMD_LOGGED_IN },
-	{ "join",        1, irc_cmd_join,        IRC_CMD_LOGGED_IN },
-	{ "invite",      2, irc_cmd_invite,      IRC_CMD_LOGGED_IN },
+	{ "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
 	{ "privmsg",     1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
-	{ "notice",      1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
-	{ "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
+	{ "notice",      1, irc_cmd_notice,      IRC_CMD_LOGGED_IN },
+	{ "nickserv",    1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
+	{ "ns",          1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
+	{ "away",        0, irc_cmd_away,        IRC_CMD_LOGGED_IN },
+	{ "version",     0, irc_cmd_version,     IRC_CMD_LOGGED_IN },
+	{ "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
 	{ "userhost",    1, irc_cmd_userhost,    IRC_CMD_LOGGED_IN },
 	{ "ison",        1, irc_cmd_ison,        IRC_CMD_LOGGED_IN },
 	{ "watch",       1, irc_cmd_watch,       IRC_CMD_LOGGED_IN },
+	{ "invite",      2, irc_cmd_invite,      IRC_CMD_LOGGED_IN },
 	{ "topic",       1, irc_cmd_topic,       IRC_CMD_LOGGED_IN },
-	{ "away",        0, irc_cmd_away,        IRC_CMD_LOGGED_IN },
-	{ "whois",       1, irc_cmd_whois,       IRC_CMD_LOGGED_IN },
-	{ "whowas",      1, irc_cmd_whowas,      IRC_CMD_LOGGED_IN },
-	{ "nickserv",    1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
-	{ "ns",          1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
-	{ "motd",        0, irc_cmd_motd,        IRC_CMD_LOGGED_IN },
-	{ "pong",        0, irc_cmd_pong,        IRC_CMD_LOGGED_IN },
-	{ "version",     0, irc_cmd_version,     IRC_CMD_LOGGED_IN },
-	{ "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
+	{ "oper",        2, irc_cmd_oper,        IRC_CMD_LOGGED_IN },
+	{ "list",        0, irc_cmd_list,        IRC_CMD_LOGGED_IN },
 	{ "die",         0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
 	{ "deaf",        0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
@@ -628,17 +702,17 @@
 			if( irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN )
 			{
-				irc_reply( irc, 462, ":Only allowed before logging in" );
+				irc_send_num( irc, 462, ":Only allowed before logging in" );
 			}
 			else if( irc_commands[i].flags & IRC_CMD_LOGGED_IN && !( irc->status & USTATUS_LOGGED_IN ) )
 			{
-				irc_reply( irc, 451, ":Register first" );
+				irc_send_num( irc, 451, ":Register first" );
 			}
 			else if( irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr( irc->umode, 'o' ) )
 			{
-				irc_reply( irc, 481, ":Permission denied - You're not an IRC operator" );
+				irc_send_num( irc, 481, ":Permission denied - You're not an IRC operator" );
 			}
 			else if( n_arg < irc_commands[i].required_parameters )
 			{
-				irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+				irc_send_num( irc, 461, "%s :Need more parameters", cmd[0] );
 			}
 			else if( irc_commands[i].flags & IRC_CMD_TO_MASTER )
@@ -657,4 +731,4 @@
 	
 	if( irc->status >= USTATUS_LOGGED_IN )
-		irc_reply( irc, 421, "%s :Unknown command", cmd[0] );
-}
+		irc_send_num( irc, 421, "%s :Unknown command", cmd[0] );
+}
Index: irc_im.c
===================================================================
--- irc_im.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ irc_im.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,928 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Some glue to put the IRC and the IM stuff together.                  */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "bitlbee.h"
+#include "dcc.h"
+
+/* IM->IRC callbacks: Simple IM/buddy-related stuff. */
+
+static const struct irc_user_funcs irc_user_im_funcs;
+
+static void bee_irc_imc_connected( struct im_connection *ic )
+{
+	irc_t *irc = (irc_t*) ic->bee->ui_data;
+	
+	irc_channel_auto_joins( irc, ic->acc );
+}
+
+static void bee_irc_imc_disconnected( struct im_connection *ic )
+{
+	/* Maybe try to send /QUITs here instead of later on. */
+}
+
+static gboolean bee_irc_user_new( bee_t *bee, bee_user_t *bu )
+{
+	irc_user_t *iu;
+	irc_t *irc = (irc_t*) bee->ui_data;
+	char nick[MAX_NICK_LENGTH+1], *s;
+	
+	memset( nick, 0, MAX_NICK_LENGTH + 1 );
+	strcpy( nick, nick_get( bu ) );
+	
+	bu->ui_data = iu = irc_user_new( irc, nick );
+	iu->bu = bu;
+	
+	if( ( s = strchr( bu->handle, '@' ) ) )
+	{
+		iu->host = g_strdup( s + 1 );
+		iu->user = g_strndup( bu->handle, s - bu->handle );
+	}
+	else if( bu->ic->acc->server )
+	{
+		iu->host = g_strdup( bu->ic->acc->server );
+		iu->user = g_strdup( bu->handle );
+		
+		/* s/ /_/ ... important for AOL screennames */
+		for( s = iu->user; *s; s ++ )
+			if( *s == ' ' )
+				*s = '_';
+	}
+	else
+	{
+		iu->host = g_strdup( bu->ic->acc->prpl->name );
+		iu->user = g_strdup( bu->handle );
+	}
+	
+	if( bu->flags & BEE_USER_LOCAL )
+	{
+		char *s = set_getstr( &bee->set, "handle_unknown" );
+		
+		if( strcmp( s, "add_private" ) == 0 )
+			iu->last_channel = NULL;
+		else if( strcmp( s, "add_channel" ) == 0 )
+			iu->last_channel = irc->default_channel;
+	}
+	
+	iu->f = &irc_user_im_funcs;
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_user_free( bee_t *bee, bee_user_t *bu )
+{
+	return irc_user_free( bee->ui_data, (irc_user_t *) bu->ui_data );
+}
+
+static gboolean bee_irc_user_status( bee_t *bee, bee_user_t *bu, bee_user_t *old )
+{
+	irc_t *irc = bee->ui_data;
+	irc_user_t *iu = bu->ui_data;
+	
+	/* Do this outside the if below since away state can change without
+	   the online state changing. */
+	iu->flags &= ~IRC_USER_AWAY;
+	if( bu->flags & BEE_USER_AWAY || !( bu->flags & BEE_USER_ONLINE ) )
+		iu->flags |= IRC_USER_AWAY;
+	
+	if( ( bu->flags & BEE_USER_ONLINE ) != ( old->flags & BEE_USER_ONLINE ) )
+	{
+		if( bu->flags & BEE_USER_ONLINE )
+		{
+			if( g_hash_table_lookup( irc->watches, iu->key ) )
+				irc_send_num( irc, 600, "%s %s %s %d :%s", iu->nick, iu->user,
+				              iu->host, (int) time( NULL ), "logged online" );
+		}
+		else
+		{
+			if( g_hash_table_lookup( irc->watches, iu->key ) )
+				irc_send_num( irc, 601, "%s %s %s %d :%s", iu->nick, iu->user,
+				              iu->host, (int) time( NULL ), "logged offline" );
+			
+			/* Send a QUIT since those will also show up in any
+			   query windows the user may have, plus it's only
+			   one QUIT instead of possibly many (in case of
+			   multiple control chans). If there's a channel that
+			   shows offline people, a JOIN will follow. */
+			if( set_getbool( &bee->set, "offline_user_quits" ) )
+				irc_user_quit( iu, "Leaving..." );
+		}
+	}
+	
+	/* Reset this one since the info may have changed. */
+	iu->away_reply_timeout = 0;
+	
+	bee_irc_channel_update( irc, NULL, iu );
+	
+	return TRUE;
+}
+
+void bee_irc_channel_update( irc_t *irc, irc_channel_t *ic, irc_user_t *iu )
+{
+	struct irc_control_channel *icc;
+	GSList *l;
+	gboolean match = FALSE;
+	
+	if( ic == NULL )
+	{
+		for( l = irc->channels; l; l = l->next )
+		{
+			ic = l->data;
+			/* TODO: Just add a type flag or so.. */
+			if( ic->f == irc->default_channel->f &&
+			    ( ic->flags & IRC_CHANNEL_JOINED ) )
+				bee_irc_channel_update( irc, ic, iu );
+		}
+		return;
+	}
+	if( iu == NULL )
+	{
+		for( l = irc->users; l; l = l->next )
+		{
+			iu = l->data;
+			if( iu->bu )
+				bee_irc_channel_update( irc, ic, l->data );
+		}
+		return;
+	}
+	
+	icc = ic->data;
+	
+	if( icc->type == IRC_CC_TYPE_DEFAULT )
+		match = TRUE;
+	else if( icc->type == IRC_CC_TYPE_GROUP )
+		match = iu->bu->group == icc->group;
+	else if( icc->type == IRC_CC_TYPE_ACCOUNT )
+		match = iu->bu->ic->acc == icc->account;
+	else if( icc->type == IRC_CC_TYPE_PROTOCOL )
+		match = iu->bu->ic->acc->prpl == icc->protocol;
+	
+	if( !match )
+	{
+		irc_channel_del_user( ic, iu, IRC_CDU_PART, NULL );
+	}
+	else
+	{
+		int mode = 0;
+		
+		if( !( iu->bu->flags & BEE_USER_ONLINE ) )
+			mode = icc->modes[0];
+		else if( iu->bu->flags & BEE_USER_AWAY )
+			mode = icc->modes[1];
+		else
+			mode = icc->modes[2];
+		
+		if( !mode )
+			irc_channel_del_user( ic, iu, IRC_CDU_PART, NULL );
+		else
+		{
+			irc_channel_add_user( ic, iu );
+			irc_channel_user_set_mode( ic, iu, mode );
+		}
+	}
+}
+
+static gboolean bee_irc_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at )
+{
+	irc_t *irc = bee->ui_data;
+	irc_user_t *iu = (irc_user_t *) bu->ui_data;
+	char *dst, *prefix = NULL;
+	char *wrapped, *ts = NULL;
+	irc_channel_t *ic = NULL;
+	
+	if( sent_at > 0 && set_getbool( &irc->b->set, "display_timestamps" ) )
+		ts = irc_format_timestamp( irc, sent_at );
+	
+	/* Too similar to irc_usermsg()... */
+	if( iu->last_channel )
+	{
+		if( iu->last_channel->flags & IRC_CHANNEL_JOINED )
+			ic = iu->last_channel;
+		else if( irc->default_channel->flags & IRC_CHANNEL_JOINED )
+			ic = irc->default_channel;
+	}
+	
+	if( ic )
+	{
+		dst = ic->name;
+		prefix = g_strdup_printf( "%s%s%s", irc->user->nick, set_getstr( &bee->set, "to_char" ), ts ? : "" );
+	}
+	else
+	{
+		dst = irc->user->nick;
+		prefix = ts;
+		ts = NULL;
+	}
+	
+	wrapped = word_wrap( msg, 425 );
+	irc_send_msg( iu, "PRIVMSG", dst, wrapped, prefix );
+	
+	g_free( wrapped );
+	g_free( prefix );
+	g_free( ts );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_user_typing( bee_t *bee, bee_user_t *bu, uint32_t flags )
+{
+	irc_t *irc = (irc_t *) bee->ui_data;
+	
+	if( set_getbool( &bee->set, "typing_notice" ) )
+		irc_send_msg_f( (irc_user_t *) bu->ui_data, "PRIVMSG", irc->user->nick,
+		                "\001TYPING %d\001", ( flags >> 8 ) & 3 );
+	else
+		return FALSE;
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_user_nick_update( irc_user_t *iu );
+
+static gboolean bee_irc_user_fullname( bee_t *bee, bee_user_t *bu )
+{
+	irc_user_t *iu = (irc_user_t *) bu->ui_data;
+	irc_t *irc = (irc_t *) bee->ui_data;
+	char *s;
+	
+	if( iu->fullname != iu->nick )
+		g_free( iu->fullname );
+	iu->fullname = g_strdup( bu->fullname );
+	
+	/* Strip newlines (unlikely, but IRC-unfriendly so they must go)
+	   TODO(wilmer): Do the same with away msgs again! */
+	for( s = iu->fullname; *s; s ++ )
+		if( isspace( *s ) ) *s = ' ';
+	
+	if( ( bu->ic->flags & OPT_LOGGED_IN ) && set_getbool( &bee->set, "display_namechanges" ) )
+	{
+		char *msg = g_strdup_printf( "<< \002BitlBee\002 - Changed name to `%s' >>", iu->fullname );
+		irc_send_msg( iu, "NOTICE", irc->user->nick, msg, NULL );
+	}
+	
+	bee_irc_user_nick_update( iu );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_user_nick_hint( bee_t *bee, bee_user_t *bu, const char *hint )
+{
+	bee_irc_user_nick_update( (irc_user_t*) bu->ui_data );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_user_group( bee_t *bee, bee_user_t *bu )
+{
+	irc_user_t *iu = (irc_user_t *) bu->ui_data;
+	irc_t *irc = (irc_t *) bee->ui_data;
+	bee_user_flags_t online;
+	
+	/* Take the user offline temporarily so we can change the nick (if necessary). */
+	if( ( online = bu->flags & BEE_USER_ONLINE ) )
+		bu->flags &= ~BEE_USER_ONLINE;
+	
+	bee_irc_channel_update( irc, NULL, iu );
+	bee_irc_user_nick_update( iu );
+	
+	if( online )
+	{
+		bu->flags |= online;
+		bee_irc_channel_update( irc, NULL, iu );
+	}
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_user_nick_update( irc_user_t *iu )
+{
+	bee_user_t *bu = iu->bu;
+	char *newnick;
+	
+	if( bu->flags & BEE_USER_ONLINE )
+		/* Ignore if the user is visible already. */
+		return TRUE;
+	
+	if( nick_saved( bu ) )
+		/* The user already assigned a nickname to this person. */
+		return TRUE;
+	
+	newnick = nick_get( bu );
+	
+	if( strcmp( iu->nick, newnick ) != 0 )
+	{
+		nick_dedupe( bu, newnick );
+		irc_user_set_nick( iu, newnick );
+	}
+	
+	return TRUE;
+}
+
+/* IRC->IM calls */
+
+static gboolean bee_irc_user_privmsg_cb( gpointer data, gint fd, b_input_condition cond );
+
+static gboolean bee_irc_user_privmsg( irc_user_t *iu, const char *msg )
+{
+	const char *away;
+	
+	if( iu->bu == NULL )
+		return FALSE;
+	
+	if( ( away = irc_user_get_away( iu ) ) &&
+	    time( NULL ) >= iu->away_reply_timeout )
+	{
+		irc_send_num( iu->irc, 301, "%s :%s", iu->nick, away );
+		iu->away_reply_timeout = time( NULL ) +
+			set_getint( &iu->irc->b->set, "away_reply_timeout" );
+	}
+	
+	if( set_getbool( &iu->irc->b->set, "paste_buffer" ) )
+	{
+		int delay;
+		
+		if( iu->pastebuf == NULL )
+			iu->pastebuf = g_string_new( msg );
+		else
+		{
+			b_event_remove( iu->pastebuf_timer );
+			g_string_append_printf( iu->pastebuf, "\n%s", msg );
+		}
+		
+		if( ( delay = set_getint( &iu->irc->b->set, "paste_buffer_delay" ) ) <= 5 )
+			delay *= 1000;
+		
+		iu->pastebuf_timer = b_timeout_add( delay, bee_irc_user_privmsg_cb, iu );
+		
+		return TRUE;
+	}
+	else
+		return bee_user_msg( iu->irc->b, iu->bu, msg, 0 );
+}
+
+static gboolean bee_irc_user_privmsg_cb( gpointer data, gint fd, b_input_condition cond )
+{
+	irc_user_t *iu = data;
+	
+	bee_user_msg( iu->irc->b, iu->bu, iu->pastebuf->str, 0 );
+	
+	g_string_free( iu->pastebuf, TRUE );
+	iu->pastebuf = 0;
+	iu->pastebuf_timer = 0;
+	
+	return FALSE;
+}
+
+static gboolean bee_irc_user_ctcp( irc_user_t *iu, char *const *ctcp )
+{
+	if( ctcp[1] && g_strcasecmp( ctcp[0], "DCC" ) == 0
+	            && g_strcasecmp( ctcp[1], "SEND" ) == 0 )
+	{
+		if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request )
+		{
+			file_transfer_t *ft = dcc_request( iu->bu->ic, ctcp );
+			if ( ft )
+				iu->bu->ic->acc->prpl->transfer_request( iu->bu->ic, ft, iu->bu->handle );
+			
+			return TRUE;
+		}
+	}
+	else if( g_strcasecmp( ctcp[0], "TYPING" ) == 0 )
+	{
+		if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->send_typing && ctcp[1] )
+		{
+			int st = ctcp[1][0];
+			if( st >= '0' && st <= '2' )
+			{
+				st <<= 8;
+				iu->bu->ic->acc->prpl->send_typing( iu->bu->ic, iu->bu->handle, st );
+			}
+			
+			return TRUE;
+		}
+	}
+	
+	return FALSE;
+}
+
+static const struct irc_user_funcs irc_user_im_funcs = {
+	bee_irc_user_privmsg,
+	bee_irc_user_ctcp,
+};
+
+
+/* IM->IRC: Groupchats */
+const struct irc_channel_funcs irc_channel_im_chat_funcs;
+
+static gboolean bee_irc_chat_new( bee_t *bee, struct groupchat *c )
+{
+	irc_t *irc = bee->ui_data;
+	irc_channel_t *ic;
+	char *topic;
+	GSList *l;
+	int i;
+	
+	/* Try to find a channel that expects to receive a groupchat.
+	   This flag is set earlier in our current call trace. */
+	for( l = irc->channels; l; l = l->next )
+	{
+		ic = l->data;
+		if( ic->flags & IRC_CHANNEL_CHAT_PICKME )
+			break;
+	}
+	
+	/* If we found none, just generate some stupid name. */
+	if( l == NULL ) for( i = 0; i <= 999; i ++ )
+	{
+		char name[16];
+		sprintf( name, "#chat_%03d", i );
+		if( ( ic = irc_channel_new( irc, name ) ) )
+			break;
+	}
+	
+	if( ic == NULL )
+		return FALSE;
+	
+	c->ui_data = ic;
+	ic->data = c;
+	
+	topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title );
+	irc_channel_set_topic( ic, topic, irc->root );
+	g_free( topic );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_chat_free( bee_t *bee, struct groupchat *c )
+{
+	irc_channel_t *ic = c->ui_data;
+	
+	if( ic == NULL )
+		return FALSE;
+	
+	if( ic->flags & IRC_CHANNEL_JOINED )
+		irc_channel_printf( ic, "Cleaning up channel, bye!" );
+	
+	ic->data = NULL;
+	irc_channel_del_user( ic, ic->irc->user, IRC_CDU_KICK, "Chatroom closed by server" );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_chat_log( bee_t *bee, struct groupchat *c, const char *text )
+{
+	irc_channel_t *ic = c->ui_data;
+	
+	if( ic == NULL )
+		return FALSE;
+	
+	irc_channel_printf( ic, "%s", text );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_chat_msg( bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at )
+{
+	irc_t *irc = bee->ui_data;
+	irc_user_t *iu = bu->ui_data;
+	irc_channel_t *ic = c->ui_data;
+	char *ts = NULL;
+	
+	if( ic == NULL )
+		return FALSE;
+	
+	if( sent_at > 0 && set_getbool( &bee->set, "display_timestamps" ) )
+		ts = irc_format_timestamp( irc, sent_at );
+	
+	irc_send_msg( iu, "PRIVMSG", ic->name, msg, ts );
+	g_free( ts );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_chat_add_user( bee_t *bee, struct groupchat *c, bee_user_t *bu )
+{
+	irc_t *irc = bee->ui_data;
+	irc_channel_t *ic = c->ui_data;
+	
+	if( ic == NULL )
+		return FALSE;
+	
+	irc_channel_add_user( ic, bu == bee->user ? irc->user : bu->ui_data );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_chat_remove_user( bee_t *bee, struct groupchat *c, bee_user_t *bu )
+{
+	irc_t *irc = bee->ui_data;
+	irc_channel_t *ic = c->ui_data;
+	
+	if( ic == NULL )
+		return FALSE;
+	
+	/* TODO: Possible bug here: If a module removes $user here instead of just
+	   using imcb_chat_free() and the channel was IRC_CHANNEL_TEMP, we get into
+	   a broken state around here. */
+	irc_channel_del_user( ic, bu == bee->user ? irc->user : bu->ui_data, IRC_CDU_PART, NULL );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_chat_topic( bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu )
+{
+	irc_channel_t *ic = c->ui_data;
+	irc_t *irc = bee->ui_data;
+	irc_user_t *iu;
+	
+	if( ic == NULL )
+		return FALSE;
+	
+	if( bu == NULL )
+		iu = irc->root;
+	else if( bu == bee->user )
+		iu = irc->user;
+	else
+		iu = bu->ui_data;
+	
+	irc_channel_set_topic( ic, new, iu );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_chat_name_hint( bee_t *bee, struct groupchat *c, const char *name )
+{
+	irc_t *irc = bee->ui_data;
+	irc_channel_t *ic = c->ui_data, *oic;
+	char stripped[MAX_NICK_LENGTH+1], *full_name;
+	
+	if( ic == NULL )
+		return FALSE;
+	
+	/* Don't rename a channel if the user's in it already. */
+	if( ic->flags & IRC_CHANNEL_JOINED )
+		return FALSE;
+	
+	strncpy( stripped, name, MAX_NICK_LENGTH );
+	stripped[MAX_NICK_LENGTH] = '\0';
+	irc_channel_name_strip( stripped );
+	if( set_getbool( &bee->set, "lcnicks" ) )
+		nick_lc( stripped );
+	
+	if( stripped[0] == '\0' )
+		return FALSE;
+	
+	full_name = g_strdup_printf( "#%s", stripped );
+	if( ( oic = irc_channel_by_name( irc, full_name ) ) )
+	{
+		char *type, *chat_type;
+		
+		type = set_getstr( &oic->set, "type" );
+		chat_type = set_getstr( &oic->set, "chat_type" );
+		
+		if( type && chat_type && oic->data == FALSE &&
+		    strcmp( type, "chat" ) == 0 &&
+		    strcmp( chat_type, "groupchat" ) == 0 )
+		{
+			/* There's a channel with this name already, but it looks
+			   like it's not in use yet. Most likely the IRC client
+			   rejoined the channel after a reconnect. Remove it so
+			   we can reuse its name. */
+			irc_channel_free( oic );
+		}
+		else
+		{
+			g_free( full_name );
+			return FALSE;
+		}
+	}
+	
+	g_free( ic->name );
+	ic->name = full_name;
+	
+	return TRUE;
+}
+
+/* IRC->IM */
+static gboolean bee_irc_channel_chat_privmsg_cb( gpointer data, gint fd, b_input_condition cond );
+
+static gboolean bee_irc_channel_chat_privmsg( irc_channel_t *ic, const char *msg )
+{
+	struct groupchat *c = ic->data;
+	char *trans = NULL, *s;
+	
+	if( c == NULL )
+		return FALSE;
+	
+	if( set_getbool( &ic->set, "translate_to_nicks" ) )
+	{
+		char nick[MAX_NICK_LENGTH+1];
+		irc_user_t *iu;
+		
+		strncpy( nick, msg, MAX_NICK_LENGTH );
+		nick[MAX_NICK_LENGTH] = '\0';
+		if( ( s = strchr( nick, ':' ) ) || ( s = strchr( nick, ',' ) ) )
+		{
+			*s = '\0';
+			if( ( iu = irc_user_by_name( ic->irc, nick ) ) &&
+			    iu->bu->nick && irc_channel_has_user( ic, iu ) )
+			{
+				trans = g_strconcat( iu->bu->nick, msg + ( s - nick ), NULL );
+				msg = trans;
+			}
+		}
+	}
+	
+	if( set_getbool( &ic->irc->b->set, "paste_buffer" ) )
+	{
+		int delay;
+		
+		if( ic->pastebuf == NULL )
+			ic->pastebuf = g_string_new( msg );
+		else
+		{
+			b_event_remove( ic->pastebuf_timer );
+			g_string_append_printf( ic->pastebuf, "\n%s", msg );
+		}
+		
+		if( ( delay = set_getint( &ic->irc->b->set, "paste_buffer_delay" ) ) <= 5 )
+			delay *= 1000;
+		
+		ic->pastebuf_timer = b_timeout_add( delay, bee_irc_channel_chat_privmsg_cb, ic );
+		
+		g_free( trans );
+		return TRUE;
+	}
+	else
+		bee_chat_msg( ic->irc->b, c, msg, 0 );
+	
+	g_free( trans );
+	return TRUE;
+}
+
+static gboolean bee_irc_channel_chat_privmsg_cb( gpointer data, gint fd, b_input_condition cond )
+{
+	irc_channel_t *ic = data;
+	
+	bee_chat_msg( ic->irc->b, ic->data, ic->pastebuf->str, 0 );
+	
+	g_string_free( ic->pastebuf, TRUE );
+	ic->pastebuf = 0;
+	ic->pastebuf_timer = 0;
+	
+	return FALSE;
+}
+
+static gboolean bee_irc_channel_chat_join( irc_channel_t *ic )
+{
+	char *acc_s, *room;
+	account_t *acc;
+	
+	if( strcmp( set_getstr( &ic->set, "chat_type" ), "room" ) != 0 )
+		return TRUE;
+	
+	if( ( acc_s = set_getstr( &ic->set, "account" ) ) &&
+	    ( room = set_getstr( &ic->set, "room" ) ) &&
+	    ( acc = account_get( ic->irc->b, acc_s ) ) &&
+	    acc->ic && acc->prpl->chat_join )
+	{
+		char *nick;
+		
+		if( !( nick = set_getstr( &ic->set, "nick" ) ) )
+			nick = ic->irc->user->nick;
+		
+		ic->flags |= IRC_CHANNEL_CHAT_PICKME;
+		acc->prpl->chat_join( acc->ic, room, nick, NULL, &ic->set );
+		ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
+		
+		return FALSE;
+	}
+	else
+	{
+		irc_send_num( ic->irc, 403, "%s :Can't join channel, account offline?", ic->name );
+		return FALSE;
+	}
+}
+
+static gboolean bee_irc_channel_chat_part( irc_channel_t *ic, const char *msg )
+{
+	struct groupchat *c = ic->data;
+	
+	if( c && c->ic->acc->prpl->chat_leave )
+		c->ic->acc->prpl->chat_leave( c );
+	
+	return TRUE;
+}
+
+static gboolean bee_irc_channel_chat_topic( irc_channel_t *ic, const char *new )
+{
+	struct groupchat *c = ic->data;
+	
+	if( c == NULL )
+		return FALSE;
+	
+	if( c->ic->acc->prpl->chat_topic == NULL )
+		irc_send_num( ic->irc, 482, "%s :IM network does not support channel topics", ic->name );
+	else
+	{
+		/* TODO: Need more const goodness here, sigh */
+		char *topic = g_strdup( new );
+		c->ic->acc->prpl->chat_topic( c, topic );
+		g_free( topic );
+		return TRUE;
+	}
+		
+	return FALSE;
+}
+
+static gboolean bee_irc_channel_chat_invite( irc_channel_t *ic, irc_user_t *iu )
+{
+	struct groupchat *c = ic->data;
+	bee_user_t *bu = iu->bu;
+	
+	if( bu == NULL )
+		return FALSE;
+	
+	if( c )
+	{
+		if( iu->bu->ic != c->ic )
+			irc_send_num( ic->irc, 482, "%s :Can't mix different IM networks in one groupchat", ic->name );
+		else if( c->ic->acc->prpl->chat_invite )
+			c->ic->acc->prpl->chat_invite( c, iu->bu->handle, NULL );
+		else
+			irc_send_num( ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name );
+	}
+	else if( bu->ic->acc->prpl->chat_with &&
+	         strcmp( set_getstr( &ic->set, "chat_type" ), "groupchat" ) == 0 )
+	{
+		ic->flags |= IRC_CHANNEL_CHAT_PICKME;
+		iu->bu->ic->acc->prpl->chat_with( bu->ic, bu->handle );
+		ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
+	}
+	else
+	{
+		irc_send_num( ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name );
+	}
+	
+	return TRUE;
+}
+
+static char *set_eval_room_account( set_t *set, char *value );
+static char *set_eval_chat_type( set_t *set, char *value );
+
+static gboolean bee_irc_channel_init( irc_channel_t *ic )
+{
+	set_add( &ic->set, "account", NULL, set_eval_room_account, ic );
+	set_add( &ic->set, "chat_type", "groupchat", set_eval_chat_type, ic );
+	set_add( &ic->set, "nick", NULL, NULL, ic );
+	set_add( &ic->set, "room", NULL, NULL, ic );
+	set_add( &ic->set, "translate_to_nicks", "true", set_eval_bool, ic );
+	
+	/* chat_type == groupchat */
+	ic->flags |= IRC_CHANNEL_TEMP;
+	
+	return TRUE;
+}
+
+static char *set_eval_room_account( set_t *set, char *value )
+{
+	struct irc_channel *ic = set->data;
+	account_t *acc, *oa;
+	
+	if( !( acc = account_get( ic->irc->b, value ) ) )
+		return SET_INVALID;
+	else if( !acc->prpl->chat_join )
+	{
+		irc_usermsg( ic->irc, "Named chatrooms not supported on that account." );
+		return SET_INVALID;
+	}
+	
+	if( set->value && ( oa = account_get( ic->irc->b, set->value ) ) &&
+	    oa->prpl->chat_free_settings )
+		oa->prpl->chat_free_settings( oa, &ic->set );
+	
+	if( acc->prpl->chat_add_settings )
+		acc->prpl->chat_add_settings( acc, &ic->set );
+	
+	return g_strdup( acc->tag );
+}
+
+static char *set_eval_chat_type( set_t *set, char *value )
+{
+	struct irc_channel *ic = set->data;
+	
+	if( strcmp( value, "groupchat" ) == 0 )
+		ic->flags |= IRC_CHANNEL_TEMP;
+	else if( strcmp( value, "room" ) == 0 )
+		ic->flags &= ~IRC_CHANNEL_TEMP;
+	else
+		return NULL;
+	
+	return value;
+}
+
+static gboolean bee_irc_channel_free( irc_channel_t *ic )
+{
+	struct groupchat *c = ic->data;
+	
+	set_del( &ic->set, "account" );
+	set_del( &ic->set, "chat_type" );
+	set_del( &ic->set, "nick" );
+	set_del( &ic->set, "room" );
+	set_del( &ic->set, "translate_to_nicks" );
+	
+	ic->flags &= ~IRC_CHANNEL_TEMP;
+	
+	/* That one still points at this channel. Don't. */
+	if( c )
+		c->ui_data = NULL;
+	
+	return TRUE;
+}
+
+const struct irc_channel_funcs irc_channel_im_chat_funcs = {
+	bee_irc_channel_chat_privmsg,
+	bee_irc_channel_chat_join,
+	bee_irc_channel_chat_part,
+	bee_irc_channel_chat_topic,
+	bee_irc_channel_chat_invite,
+
+	bee_irc_channel_init,
+	bee_irc_channel_free,
+};
+
+
+/* IM->IRC: File transfers */
+static file_transfer_t *bee_irc_ft_in_start( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size )
+{
+	return dccs_send_start( bu->ic, (irc_user_t *) bu->ui_data, file_name, file_size );
+}
+
+static gboolean bee_irc_ft_out_start( struct im_connection *ic, file_transfer_t *ft )
+{
+	return dccs_recv_start( ft );
+}
+
+static void bee_irc_ft_close( struct im_connection *ic, file_transfer_t *ft )
+{
+	return dcc_close( ft );
+}
+
+static void bee_irc_ft_finished( struct im_connection *ic, file_transfer_t *file )
+{
+	dcc_file_transfer_t *df = file->priv;
+
+	if( file->bytes_transferred >= file->file_size )
+		dcc_finish( file );
+	else
+		df->proto_finished = TRUE;
+}
+
+const struct bee_ui_funcs irc_ui_funcs = {
+	bee_irc_imc_connected,
+	bee_irc_imc_disconnected,
+	
+	bee_irc_user_new,
+	bee_irc_user_free,
+	bee_irc_user_fullname,
+	bee_irc_user_nick_hint,
+	bee_irc_user_group,
+	bee_irc_user_status,
+	bee_irc_user_msg,
+	bee_irc_user_typing,
+	
+	bee_irc_chat_new,
+	bee_irc_chat_free,
+	bee_irc_chat_log,
+	bee_irc_chat_msg,
+	bee_irc_chat_add_user,
+	bee_irc_chat_remove_user,
+	bee_irc_chat_topic,
+	bee_irc_chat_name_hint,
+	
+	bee_irc_ft_in_start,
+	bee_irc_ft_out_start,
+	bee_irc_ft_close,
+	bee_irc_ft_finished,
+};
Index: irc_send.c
===================================================================
--- irc_send.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ irc_send.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,398 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* The IRC-based UI - Sending responses to commands/etc.                */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "bitlbee.h"
+
+void irc_send_num( irc_t *irc, int code, char *format, ... )
+{
+	char text[IRC_MAX_LINE];
+	va_list params;
+	
+	va_start( params, format );
+	g_vsnprintf( text, IRC_MAX_LINE, format, params );
+	va_end( params );
+	
+	irc_write( irc, ":%s %03d %s %s", irc->root->host, code, irc->user->nick ? : "*", text );
+}
+
+void irc_send_login( irc_t *irc )
+{
+	irc_send_num( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->user->nick );
+	irc_send_num( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->root->host );
+	irc_send_num( irc,   3, ":%s", IRCD_INFO );
+	irc_send_num( irc,   4, "%s %s %s %s", irc->root->host, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
+	irc_send_num( irc,   5, "PREFIX=(ov)@+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee "
+	                        "CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server",
+	                        CTYPES, CMODES, MAX_NICK_LENGTH - 1 );
+	irc_send_motd( irc );
+}
+
+void irc_send_motd( irc_t *irc )
+{
+	int fd;
+	
+	fd = open( global.conf->motdfile, O_RDONLY );
+	if( fd == -1 )
+	{
+		irc_send_num( irc, 422, ":We don't need MOTDs." );
+	}
+	else
+	{
+		char linebuf[80];	/* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */
+		char *add, max;
+		int len;
+		
+		linebuf[79] = len = 0;
+		max = sizeof( linebuf ) - 1;
+		
+		irc_send_num( irc, 375, ":- %s Message Of The Day - ", irc->root->host );
+		while( read( fd, linebuf + len, 1 ) == 1 )
+		{
+			if( linebuf[len] == '\n' || len == max )
+			{
+				linebuf[len] = 0;
+				irc_send_num( irc, 372, ":- %s", linebuf );
+				len = 0;
+			}
+			else if( linebuf[len] == '%' )
+			{
+				read( fd, linebuf + len, 1 );
+				if( linebuf[len] == 'h' )
+					add = irc->root->host;
+				else if( linebuf[len] == 'v' )
+					add = BITLBEE_VERSION;
+				else if( linebuf[len] == 'n' )
+					add = irc->user->nick;
+				else
+					add = "%";
+				
+				strncpy( linebuf + len, add, max - len );
+				while( linebuf[++len] );
+			}
+			else if( len < max )
+			{
+				len ++;
+			}
+		}
+		irc_send_num( irc, 376, ":End of MOTD" );
+		close( fd );
+	}
+}
+
+void irc_usermsg( irc_t *irc, char *format, ... )
+{
+	irc_channel_t *ic = NULL;
+	irc_user_t *iu = irc->root;
+	char text[1024];
+	va_list params;
+	char *dst;
+	
+	va_start( params, format );
+	g_vsnprintf( text, sizeof( text ), format, params );
+	va_end( params );
+	
+	/* Too similar to bee_irc_user_msg()... */
+	if( iu->last_channel )
+	{
+		if( iu->last_channel->flags & IRC_CHANNEL_JOINED )
+			ic = iu->last_channel;
+		else if( irc->default_channel->flags & IRC_CHANNEL_JOINED )
+			ic = irc->default_channel;
+	}
+	
+	if( ic )
+		dst = ic->name;
+	else
+		dst = irc->user->nick;
+	
+	irc_send_msg( irc->root, "PRIVMSG", dst, text, NULL );
+}
+
+void irc_send_join( irc_channel_t *ic, irc_user_t *iu )
+{
+	irc_t *irc = ic->irc;
+	
+	irc_write( irc, ":%s!%s@%s JOIN :%s", iu->nick, iu->user, iu->host, ic->name );
+	
+	if( iu == irc->user )
+	{
+		irc_write( irc, ":%s MODE %s +%s", irc->root->host, ic->name, ic->mode );
+		irc_send_names( ic );
+		if( ic->topic && *ic->topic )
+			irc_send_topic( ic, FALSE );
+	}
+}
+
+void irc_send_part( irc_channel_t *ic, irc_user_t *iu, const char *reason )
+{
+	irc_write( ic->irc, ":%s!%s@%s PART %s :%s", iu->nick, iu->user, iu->host, ic->name, reason ? : "" );
+}
+
+void irc_send_quit( irc_user_t *iu, const char *reason )
+{
+	irc_write( iu->irc, ":%s!%s@%s QUIT :%s", iu->nick, iu->user, iu->host, reason ? : "" );
+}
+
+void irc_send_kick( irc_channel_t *ic, irc_user_t *iu, irc_user_t *kicker, const char *reason )
+{
+	irc_write( ic->irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user,
+	           kicker->host, ic->name, iu->nick, reason ? : "" );
+}
+
+void irc_send_names( irc_channel_t *ic )
+{
+	GSList *l;
+	char namelist[385] = "";
+	
+	/* RFCs say there is no error reply allowed on NAMES, so when the
+	   channel is invalid, just give an empty reply. */
+	for( l = ic->users; l; l = l->next )
+	{
+		irc_channel_user_t *icu = l->data;
+		irc_user_t *iu = icu->iu;
+		
+		if( strlen( namelist ) + strlen( iu->nick ) > sizeof( namelist ) - 4 )
+		{
+			irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist );
+			*namelist = 0;
+		}
+		
+		if( icu->flags & IRC_CHANNEL_USER_OP )
+			strcat( namelist, "@" );
+		else if( icu->flags & IRC_CHANNEL_USER_HALFOP )
+			strcat( namelist, "%" );
+		else if( icu->flags & IRC_CHANNEL_USER_VOICE )
+			strcat( namelist, "+" );
+		
+		strcat( namelist, iu->nick );
+		strcat( namelist, " " );
+	}
+	
+	if( *namelist )
+		irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist );
+	
+	irc_send_num( ic->irc, 366, "%s :End of /NAMES list", ic->name );
+}
+
+void irc_send_topic( irc_channel_t *ic, gboolean topic_change )
+{
+	if( topic_change && ic->topic_who )
+	{
+		irc_write( ic->irc, ":%s TOPIC %s :%s", ic->topic_who, 
+		           ic->name, ic->topic && *ic->topic ? ic->topic : "" );
+	}
+	else if( ic->topic )
+	{
+		irc_send_num( ic->irc, 332, "%s :%s", ic->name, ic->topic );
+		if( ic->topic_who )
+			irc_send_num( ic->irc, 333, "%s %s %d",
+			              ic->name, ic->topic_who, (int) ic->topic_time );
+	}
+	else
+		irc_send_num( ic->irc, 331, "%s :No topic for this channel", ic->name );
+}
+
+void irc_send_whois( irc_user_t *iu )
+{
+	irc_t *irc = iu->irc;
+	
+	irc_send_num( irc, 311, "%s %s %s * :%s",
+	              iu->nick, iu->user, iu->host, iu->fullname );
+	
+	if( iu->bu )
+	{
+		bee_user_t *bu = iu->bu;
+		
+		irc_send_num( irc, 312, "%s %s.%s :%s network", iu->nick, bu->ic->acc->user,
+		           bu->ic->acc->server && *bu->ic->acc->server ? bu->ic->acc->server : "",
+		           bu->ic->acc->prpl->name );
+		
+		if( ( bu->status && *bu->status ) ||
+		    ( bu->status_msg && *bu->status_msg ) )
+		{
+			int num = bu->flags & BEE_USER_AWAY ? 301 : 320;
+			
+			if( bu->status && bu->status_msg )
+				irc_send_num( irc, num, "%s :%s (%s)", iu->nick, bu->status, bu->status_msg );
+			else
+				irc_send_num( irc, num, "%s :%s", iu->nick, bu->status ? : bu->status_msg );
+		}
+		else if( !( bu->flags & BEE_USER_ONLINE ) )
+		{
+			irc_send_num( irc, 301, "%s :%s", iu->nick, "User is offline" );
+		}
+		
+		if( bu->idle_time || bu->login_time )
+		{
+			irc_send_num( irc, 317, "%s %d %d :seconds idle, signon time",
+			              iu->nick,
+			              bu->idle_time ? (int) ( time( NULL ) - bu->idle_time ) : 0,
+			              (int) bu->login_time );
+		}
+	}
+	else
+	{
+		irc_send_num( irc, 312, "%s %s :%s", iu->nick, irc->root->host, IRCD_INFO );
+	}
+	
+	irc_send_num( irc, 318, "%s :End of /WHOIS list", iu->nick );
+}
+
+void irc_send_who( irc_t *irc, GSList *l, const char *channel )
+{
+	gboolean is_channel = strcmp( channel, "**" ) != 0;
+	
+	while( l )
+	{
+		irc_user_t *iu = l->data;
+		if( is_channel )
+			iu = ((irc_channel_user_t*)iu)->iu;
+		/* TODO(wilmer): Restore away/channel information here */
+		irc_send_num( irc, 352, "%s %s %s %s %s %c :0 %s",
+		              channel ? : "*", iu->user, iu->host, irc->root->host,
+		              iu->nick, iu->flags & IRC_USER_AWAY ? 'G' : 'H',
+		              iu->fullname );
+		l = l->next;
+	}
+	
+	irc_send_num( irc, 315, "%s :End of /WHO list", channel );
+}
+
+void irc_send_msg( irc_user_t *iu, const char *type, const char *dst, const char *msg, const char *prefix )
+{
+	char last = 0;
+	const char *s = msg, *line = msg;
+	char raw_msg[strlen(msg)+1024];
+	
+	while( !last )
+	{
+		if( *s == '\r' && *(s+1) == '\n' )
+			s++;
+		if( *s == '\n' )
+		{
+			last = s[1] == 0;
+		}
+		else
+		{
+			last = s[0] == 0;
+		}
+		if( *s == 0 || *s == '\n' )
+		{
+			if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) &&
+			    g_strcasecmp( type, "PRIVMSG" ) == 0 )
+			{
+				strcpy( raw_msg, "\001ACTION " );
+				strncat( raw_msg, line + 4, s - line - 4 );
+				strcat( raw_msg, "\001" );
+				irc_send_msg_raw( iu, type, dst, raw_msg );
+			}
+			else
+			{
+				*raw_msg = '\0';
+				if( prefix && *prefix )
+					strcpy( raw_msg, prefix );
+				strncat( raw_msg, line, s - line );
+				irc_send_msg_raw( iu, type, dst, raw_msg );
+			}
+			line = s + 1;
+		}
+		s ++;
+	}
+}
+
+void irc_send_msg_raw( irc_user_t *iu, const char *type, const char *dst, const char *msg )
+{
+	irc_write( iu->irc, ":%s!%s@%s %s %s :%s",
+	           iu->nick, iu->user, iu->host, type, dst, msg );
+}
+
+void irc_send_msg_f( irc_user_t *iu, const char *type, const char *dst, const char *format, ... )
+{
+	char text[IRC_MAX_LINE];
+	va_list params;
+	
+	va_start( params, format );
+	g_vsnprintf( text, IRC_MAX_LINE, format, params );
+	va_end( params );
+	
+	irc_write( iu->irc, ":%s!%s@%s %s %s :%s",
+	           iu->nick, iu->user, iu->host, type, dst, text );
+}
+
+void irc_send_nick( irc_user_t *iu, const char *new )
+{
+	irc_write( iu->irc, ":%s!%s@%s NICK %s",
+	           iu->nick, iu->user, iu->host, new );
+}
+
+/* Send an update of a user's mode inside a channel, compared to what it was. */
+void irc_send_channel_user_mode_diff( irc_channel_t *ic, irc_user_t *iu,
+	irc_channel_user_flags_t old, irc_channel_user_flags_t new )
+{
+	char changes[3*(5+strlen(iu->nick))];
+	char from[strlen(ic->irc->root->nick)+strlen(ic->irc->root->user)+strlen(ic->irc->root->host)+3];
+	int n;
+	
+	*changes = '\0'; n = 0;
+	if( ( old & IRC_CHANNEL_USER_OP ) != ( new & IRC_CHANNEL_USER_OP ) )
+	{
+		n ++;
+		if( new & IRC_CHANNEL_USER_OP )
+			strcat( changes, "+o" );
+		else
+			strcat( changes, "-o" );
+	}
+	if( ( old & IRC_CHANNEL_USER_HALFOP ) != ( new & IRC_CHANNEL_USER_HALFOP ) )
+	{
+		n ++;
+		if( new & IRC_CHANNEL_USER_HALFOP )
+			strcat( changes, "+h" );
+		else
+			strcat( changes, "-h" );
+	}
+	if( ( old & IRC_CHANNEL_USER_VOICE ) != ( new & IRC_CHANNEL_USER_VOICE ) )
+	{
+		n ++;
+		if( new & IRC_CHANNEL_USER_VOICE )
+			strcat( changes, "+v" );
+		else
+			strcat( changes, "-v" );
+	}
+	while( n )
+	{
+		strcat( changes, " " );
+		strcat( changes, iu->nick );
+		n --;
+	}
+	
+	if( set_getbool( &ic->irc->b->set, "simulate_netsplit" ) )
+		g_snprintf( from, sizeof( from ), "%s", ic->irc->root->host );
+	else
+		g_snprintf( from, sizeof( from ), "%s!%s@%s", ic->irc->root->nick,
+		            ic->irc->root->user, ic->irc->root->host );
+	
+	if( *changes )
+		irc_write( ic->irc, ":%s MODE %s %s", from, ic->name, changes );
+}
Index: irc_user.c
===================================================================
--- irc_user.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ irc_user.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,261 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Stuff to handle, save and search IRC buddies                         */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "bitlbee.h"
+#include "ipc.h"
+
+irc_user_t *irc_user_new( irc_t *irc, const char *nick )
+{
+	irc_user_t *iu = g_new0( irc_user_t, 1 );
+	
+	iu->irc = irc;
+	iu->nick = g_strdup( nick );
+	iu->user = iu->host = iu->fullname = iu->nick;
+	
+	if( set_getbool( &irc->b->set, "private" ) )
+		iu->last_channel = NULL;
+	else
+		iu->last_channel = irc->default_channel;
+	
+	iu->key = g_strdup( nick );
+	nick_lc( iu->key );
+	/* Using the hash table for speed and irc->users for easy iteration
+	   through the list (since the GLib API doesn't have anything sane
+	   for that.) */
+	g_hash_table_insert( irc->nick_user_hash, iu->key, iu );
+	irc->users = g_slist_insert_sorted( irc->users, iu, irc_user_cmp );
+	
+	return iu;
+}
+
+int irc_user_free( irc_t *irc, irc_user_t *iu )
+{
+	static struct im_connection *last_ic;
+	static char *msg;
+	
+	if( !iu )
+		return 0;
+	
+	if( iu->bu &&
+	    ( iu->bu->ic->flags & OPT_LOGGING_OUT ) &&
+	    iu->bu->ic != last_ic )
+	{
+		char host_prefix[] = "bitlbee.";
+		char *s;
+		
+		/* Irssi recognises netsplits by quitmsgs with two
+		   hostnames, where a hostname is a "word" with one
+		   of more dots. Mangle no-dot hostnames a bit. */
+		if( strchr( irc->root->host, '.' ) )
+			*host_prefix = '\0';
+		
+		last_ic = iu->bu->ic;
+		g_free( msg );
+		if( !set_getbool( &irc->b->set, "simulate_netsplit" ) )
+			msg = g_strdup( "Account off-line" );
+		else if( ( s = strchr( iu->bu->ic->acc->user, '@' ) ) )
+			msg = g_strdup_printf( "%s%s %s", host_prefix,
+			        irc->root->host, s + 1 );
+		else
+			msg = g_strdup_printf( "%s%s %s.%s",
+				host_prefix, irc->root->host,
+				iu->bu->ic->acc->prpl->name, irc->root->host );
+	}
+	else if( !iu->bu || !( iu->bu->ic->flags & OPT_LOGGING_OUT ) )
+	{
+		g_free( msg );
+		msg = g_strdup( "Removed" );
+		last_ic = NULL;
+	}
+	irc_user_quit( iu, msg );
+	
+	irc->users = g_slist_remove( irc->users, iu );
+	g_hash_table_remove( irc->nick_user_hash, iu->key );
+	
+	g_free( iu->nick );
+	if( iu->nick != iu->user ) g_free( iu->user );
+	if( iu->nick != iu->host ) g_free( iu->host );
+	if( iu->nick != iu->fullname ) g_free( iu->fullname );
+	g_free( iu->pastebuf );
+	if( iu->pastebuf_timer ) b_event_remove( iu->pastebuf_timer );
+	g_free( iu->key );
+	g_free( iu );
+	
+	return 1;
+}
+
+irc_user_t *irc_user_by_name( irc_t *irc, const char *nick )
+{
+	char key[strlen(nick)+1];
+	
+	strcpy( key, nick );
+	if( nick_lc( key ) )
+		return g_hash_table_lookup( irc->nick_user_hash, key );
+	else
+		return NULL;
+}
+
+int irc_user_set_nick( irc_user_t *iu, const char *new )
+{
+	irc_t *irc = iu->irc;
+	irc_user_t *new_iu;
+	char key[strlen(new)+1];
+	GSList *cl;
+	
+	strcpy( key, new );
+	if( iu == NULL || !nick_lc( key ) ||
+	    ( ( new_iu = irc_user_by_name( irc, new ) ) && new_iu != iu ) )
+		return 0;
+	
+	for( cl = irc->channels; cl; cl = cl->next )
+	{
+		irc_channel_t *ic = cl->data;
+		
+		/* Send a NICK update if we're renaming our user, or someone
+		   who's in the same channel like our user. */
+		if( iu == irc->user ||
+		    ( ( ic->flags & IRC_CHANNEL_JOINED ) &&
+		      irc_channel_has_user( ic, iu ) ) )
+		{
+			irc_send_nick( iu, new );
+			break;
+		}
+	}
+	
+	irc->users = g_slist_remove( irc->users, iu );
+	g_hash_table_remove( irc->nick_user_hash, iu->key );
+	
+	if( iu->nick == iu->user ) iu->user = NULL;
+	if( iu->nick == iu->host ) iu->host = NULL;
+	if( iu->nick == iu->fullname ) iu->fullname = NULL;
+	g_free( iu->nick );
+	iu->nick = g_strdup( new );
+	if( iu->user == NULL ) iu->user = g_strdup( iu->nick );
+	if( iu->host == NULL ) iu->host = g_strdup( iu->nick );
+	if( iu->fullname == NULL ) iu->fullname = g_strdup( iu->nick );
+	
+	iu->key = g_strdup( key );
+	g_hash_table_insert( irc->nick_user_hash, iu->key, iu );
+	irc->users = g_slist_insert_sorted( irc->users, iu, irc_user_cmp );
+	
+	if( iu == irc->user )
+		ipc_to_master_str( "NICK :%s\r\n", new );
+	
+	return 1;
+}
+
+gint irc_user_cmp( gconstpointer a_, gconstpointer b_ )
+{
+	const irc_user_t *a = a_, *b = b_;
+	
+	return strcmp( a->key, b->key );
+}
+
+const char *irc_user_get_away( irc_user_t *iu )
+{
+	irc_t *irc = iu->irc;
+	bee_user_t *bu = iu->bu;
+	
+	if( iu == irc->user )
+		return set_getstr( &irc->b->set, "away" );
+	else if( bu )
+	{
+		if( !bu->flags & BEE_USER_ONLINE )
+			return "Offline";
+		else if( bu->flags & BEE_USER_AWAY )
+		{
+			if( bu->status_msg )
+			{
+				static char ret[MAX_STRING];
+				g_snprintf( ret, MAX_STRING - 1, "%s (%s)",
+				            bu->status ? : "Away", bu->status_msg );
+				return ret;
+			}
+			else
+				return bu->status ? : "Away";
+		}
+	}
+	
+	return NULL;
+}
+
+void irc_user_quit( irc_user_t *iu, const char *msg )
+{
+	GSList *l;
+	gboolean send_quit = FALSE;
+	
+	if( !iu )
+		return;
+	
+	for( l = iu->irc->channels; l; l = l->next )
+		send_quit |= irc_channel_del_user( (irc_channel_t*) l->data, iu, IRC_CDU_SILENT, NULL );
+	
+	if( send_quit )
+		irc_send_quit( iu, msg );
+}
+
+/* User-type dependent functions, for root/NickServ: */
+static gboolean root_privmsg( irc_user_t *iu, const char *msg )
+{
+	char cmd[strlen(msg)+1];
+	
+	strcpy( cmd, msg );
+	root_command_string( iu->irc, cmd );
+	
+	return TRUE;
+}
+
+static gboolean root_ctcp( irc_user_t *iu, char * const *ctcp )
+{
+	if( g_strcasecmp( ctcp[0], "VERSION" ) == 0 )
+	{
+		irc_send_msg_f( iu, "NOTICE", iu->irc->user->nick, "\001%s %s\001",
+		                ctcp[0], "BitlBee " BITLBEE_VERSION " " ARCH "/" CPU );
+	}
+	else if( g_strcasecmp( ctcp[0], "PING" ) == 0 )
+	{
+		irc_send_msg_f( iu, "NOTICE", iu->irc->user->nick, "\001%s %s\001",
+		                ctcp[0], ctcp[1] ? : "" );
+	}
+	
+	return TRUE;
+}
+
+const struct irc_user_funcs irc_user_root_funcs = {
+	root_privmsg,
+	root_ctcp,
+};
+
+/* Echo to yourself: */
+static gboolean self_privmsg( irc_user_t *iu, const char *msg )
+{
+	irc_send_msg( iu, "PRIVMSG", iu->nick, msg, NULL );
+	
+	return TRUE;
+}
+
+const struct irc_user_funcs irc_user_self_funcs = {
+	self_privmsg,
+};
Index: irc_util.c
===================================================================
--- irc_util.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ irc_util.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,115 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Some stuff that doesn't belong anywhere else.                        */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "bitlbee.h"
+
+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;
+}
+
+char *irc_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->b->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 );
+}
Index: lib/Makefile
===================================================================
--- lib/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -8,9 +8,11 @@
 
 -include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)lib/
+endif
 
 # [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
+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
 LFLAGS += -r
 
@@ -37,5 +39,5 @@
 $(objects): ../Makefile.settings Makefile
 
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
 	@echo '*' Compiling $<
 	@$(CC) -c $(CFLAGS) $< -o $@
Index: lib/events.h
===================================================================
--- lib/events.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/events.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/events_glib.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/events_libevent.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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/ftutil.c
===================================================================
--- lib/ftutil.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ lib/ftutil.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,144 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Utility functions for file transfer                                      *
+*                                                                           *
+*  Copyright 2008 Uli Meis <a.sporto+bee@gmail.com>                         *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+#include <poll.h>
+#include <netinet/tcp.h>
+#include "lib/ftutil.h"
+
+#define ASSERTSOCKOP(op, msg) \
+	if( (op) == -1 ) {\
+		g_snprintf( errmsg, sizeof( errmsg ), msg ": %s", strerror( errno ) ); \
+		return -1; }
+
+/*
+ * Creates a listening socket and returns it in saddr_ptr.
+ */
+int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int copy_fd, int for_bitlbee_client, char **errptr )
+{
+	int fd, gret, saddrlen;
+	struct addrinfo hints, *rp;
+	socklen_t ssize = sizeof( struct sockaddr_storage );
+	struct sockaddr_storage saddrs, *saddr = &saddrs;
+	static char errmsg[1024];
+	char *ftlisten = global.conf->ft_listen;
+
+	if( errptr )
+		*errptr = errmsg;
+
+	strcpy( port, "0" );
+
+	/* Format is <IP-A>[:<Port-A>];<IP-B>[:<Port-B>] where
+	 * A is for connections with the bitlbee client (DCC)
+	 * and B is for connections with IM peers.
+	 */
+	if( ftlisten )
+	{
+		char *scolon = strchr( ftlisten, ';' );
+		char *colon;
+
+		if( scolon )
+		{
+			if( for_bitlbee_client )
+			{
+				*scolon = '\0';
+				strncpy( host, ftlisten, HOST_NAME_MAX );
+				*scolon = ';';
+			}
+			else
+			{
+				strncpy( host, scolon + 1, HOST_NAME_MAX );
+			}
+		}
+		else
+		{
+			strncpy( host, ftlisten, HOST_NAME_MAX );
+		}
+
+		if( ( colon = strchr( host, ':' ) ) )
+		{
+			*colon = '\0';
+			strncpy( port, colon + 1, 5 );
+		}
+	}
+	else if( copy_fd >= 0 && getsockname( copy_fd, (struct sockaddr*) &saddrs, &ssize ) == 0 &&
+	         ( saddrs.ss_family == AF_INET || saddrs.ss_family == AF_INET6 ) &&
+	         getnameinfo( (struct sockaddr*) &saddrs, ssize, host, HOST_NAME_MAX,
+	                      NULL, 0, NI_NUMERICHOST ) == 0 )
+	{
+		/* We just took our local address on copy_fd, which is likely to be a
+		   sensible address from which we can do a file transfer now - the
+		   most sensible we can get easily. */
+	}
+	else
+	{
+		ASSERTSOCKOP( gethostname( host, HOST_NAME_MAX + 1 ), "gethostname()" );
+	}
+
+	memset( &hints, 0, sizeof( struct addrinfo ) );
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_NUMERICSERV;
+
+	if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) != 0 )
+	{
+		sprintf( errmsg, "getaddrinfo() failed: %s", gai_strerror( gret ) );
+		return -1;
+	}
+
+	saddrlen = rp->ai_addrlen;
+
+	memcpy( saddr, rp->ai_addr, saddrlen );
+
+	freeaddrinfo( rp );
+
+	ASSERTSOCKOP( fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" );
+	ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, saddrlen ), "Binding socket" );
+	ASSERTSOCKOP( listen( fd, 1 ), "Making socket listen" );
+
+	if ( !inet_ntop( saddr->ss_family, saddr->ss_family == AF_INET ?
+			( void * )&( ( struct sockaddr_in * ) saddr )->sin_addr.s_addr :
+			( void * )&( ( struct sockaddr_in6 * ) saddr )->sin6_addr.s6_addr,
+			host, HOST_NAME_MAX ) )
+	{
+		strcpy( errmsg, "inet_ntop failed on listening socket" );
+		return -1;
+	}
+
+	ssize = sizeof( struct sockaddr_storage );
+	ASSERTSOCKOP( getsockname( fd, ( struct sockaddr *)saddr, &ssize ), "Getting socket name" );
+
+	if( saddr->ss_family == AF_INET )
+		g_snprintf( port, 6, "%d", ntohs( ( (struct sockaddr_in *) saddr )->sin_port ) );
+	else
+		g_snprintf( port, 6, "%d", ntohs( ( (struct sockaddr_in6 *) saddr )->sin6_port ) );
+
+	if( saddr_ptr )
+		memcpy( saddr_ptr, saddr, saddrlen );
+
+	/* I hate static-length strings.. */
+	host[HOST_NAME_MAX] = '\0';
+	port[5] = '\0';
+	
+	return fd;
+}
Index: lib/ftutil.h
===================================================================
--- lib/ftutil.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ lib/ftutil.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,40 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Utility functions for file transfer                                      *
+*                                                                           *
+*  Copyright 2008 Uli Meis <a.sporto+bee@gmail.com>                         *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#ifndef AI_NUMERICSERV
+#define AI_NUMERICSERV 0x0400   /* Don't use name resolution.  */
+#endif
+
+/* Some ifdefs for ulibc and apparently also BSD (Thanks to Whoopie) */
+#ifndef HOST_NAME_MAX
+#include <sys/param.h>
+#ifdef MAXHOSTNAMELEN
+#define HOST_NAME_MAX MAXHOSTNAMELEN
+#else
+#define HOST_NAME_MAX 255
+#endif
+#endif
+
+/* This function should be used with care. host should be AT LEAST a
+   char[HOST_NAME_MAX+1] and port AT LEAST a char[6]. */
+int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int copy_fd, int for_bitlbee_client, char **errptr );
Index: lib/http_client.c
===================================================================
--- lib/http_client.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/http_client.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -35,7 +35,8 @@
 static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond );
 static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond );
-
-
-void *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data )
+static void http_free( struct http_request *req );
+
+
+struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data )
 {
 	struct http_request *req;
@@ -67,9 +68,10 @@
 	req->request = g_strdup( request );
 	req->request_length = strlen( request );
+	req->redir_ttl = 3;
 	
 	return( req );
 }
 
-void *http_dorequest_url( char *url_string, http_input_function func, gpointer data )
+struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data )
 {
 	url_t *url = g_new0( url_t, 1 );
@@ -149,8 +151,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 +236,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 );
 	
@@ -311,5 +313,5 @@
 	}
 	
-	if( req->status_code == 301 || req->status_code == 302 )
+	if( ( req->status_code == 301 || req->status_code == 302 ) && req->redir_ttl-- > 0 )
 	{
 		char *loc, *new_request, *new_host;
@@ -445,5 +447,5 @@
 }
 
-void http_free( struct http_request *req )
+static void http_free( struct http_request *req )
 {
 	g_free( req->request );
Index: lib/http_client.h
===================================================================
--- lib/http_client.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/http_client.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -61,4 +61,6 @@
 	int finished;           /* Set to non-0 if the request was completed
 	                           successfully. */
+	int redir_ttl;          /* You can set it to 0 if you don't want
+	                           http_client to follow them. */
 	
 	http_input_function func;
@@ -79,6 +81,4 @@
    you want to add some extra headers. As you can see, HTTPS connections
    are also supported (using ssl_client). */
-void *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data );
-void *http_dorequest_url( char *url_string, http_input_function func, gpointer data );
-
-void http_free( struct http_request *req );
+struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data );
+struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data );
Index: lib/md5.h
===================================================================
--- lib/md5.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/md5.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -27,5 +27,9 @@
 #include <sys/types.h>
 #include <gmodule.h>
+#if(__sun)
+#include <inttypes.h>
+#else
 #include <stdint.h>
+#endif
 
 typedef uint8_t md5_byte_t;
Index: lib/misc.c
===================================================================
--- lib/misc.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/misc.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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;
 }
 
@@ -119,9 +154,9 @@
 {
 	char *start = in;
-	char *out = g_malloc( strlen( in ) + 1 );
+	char out[strlen(in)+1];
 	char *s = out, *cs;
 	int i, matched;
 	
-	memset( out, 0, strlen( in ) + 1 );
+	memset( out, 0, sizeof( out ) );
 	
 	while( *in )
@@ -185,5 +220,4 @@
 	
 	strcpy( start, out );
-	g_free( out );
 }
 
@@ -270,13 +304,16 @@
 	for( i = j = 0; t[i]; i ++, j ++ )
 	{
-		/* if( t[i] <= ' ' || ((unsigned char *)t)[i] >= 128 || t[i] == '%' ) */
-		if( !isalnum( t[i] ) )
+		/* Warning: isalnum() is locale-aware, so don't use it here! */
+		if( ( t[i] >= 'A' && t[i] <= 'Z' ) ||
+		    ( t[i] >= 'a' && t[i] <= 'z' ) ||
+		    ( t[i] >= '0' && t[i] <= '9' ) ||
+		    strchr( "._-~", t[i] ) )
+		{
+			s[j] = t[i];
+		}
+		else
 		{
 			sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] );
 			j += 2;
-		}
-		else
-		{
-			s[j] = t[i];
 		}
 	}
@@ -611,2 +648,53 @@
 	return ret;
 }
+
+/* Split commands (root-style, *not* IRC-style). Handles "quoting of"
+   white\ space in 'various ways'. Returns a NULL-terminated static
+   char** so watch out with nested use! Definitely not thread-safe. */
+char **split_command_parts( char *command )
+{
+	static char *cmd[IRC_MAX_ARGS+1];
+	char *s, q = 0;
+	int k;
+	
+	memset( cmd, 0, sizeof( cmd ) );
+	cmd[0] = command;
+	k = 1;
+	for( s = command; *s && k < IRC_MAX_ARGS; s ++ )
+		if( *s == ' ' && !q )
+		{
+			*s = 0;
+			while( *++s == ' ' );
+			if( *s == '"' || *s == '\'' )
+			{
+				q = *s;
+				s ++;
+			}
+			if( *s )
+			{
+				cmd[k++] = s;
+				s --;
+			}
+			else
+			{
+				break;
+			}
+		}
+		else if( *s == '\\' && ( ( !q && s[1] ) || ( q && q == s[1] ) ) )
+		{
+			char *cpy;
+			
+			for( cpy = s; *cpy; cpy ++ )
+				cpy[0] = cpy[1];
+		}
+		else if( *s == q )
+		{
+			q = *s = 0;
+		}
+	
+	/* Full zero-padding for easier argc checking. */
+	while( k <= IRC_MAX_ARGS )
+		cmd[k++] = NULL;
+	
+	return cmd;
+}
Index: lib/misc.h
===================================================================
--- lib/misc.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/misc.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 );
 
@@ -68,3 +69,5 @@
 G_MODULE_EXPORT int md5_verify_password( char *password, char *hash );
 
+G_MODULE_EXPORT char **split_command_parts( char *command );
+
 #endif
Index: lib/oauth.c
===================================================================
--- lib/oauth.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ lib/oauth.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ? 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 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ lib/oauth.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/proxy.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -58,25 +58,4 @@
 };
 
-
-
-static struct sockaddr_in *gaim_gethostbyname(const char *host, int port)
-{
-	static struct sockaddr_in sin;
-
-	if (!inet_aton(host, &sin.sin_addr)) {
-		struct hostent *hp;
-		if (!(hp = gethostbyname(host))) {
-			return NULL;
-		}
-		memset(&sin, 0, sizeof(struct sockaddr_in));
-		memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
-		sin.sin_family = hp->h_addrtype;
-	} else
-		sin.sin_family = AF_INET;
-	sin.sin_port = htons(port);
-
-	return &sin;
-}
-
 static gboolean gaim_io_connected(gpointer data, gint source, b_input_condition cond)
 {
@@ -91,7 +70,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,54 +81,78 @@
 	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);
-		g_free(phb);
-	}
-	
-	return FALSE;
-}
-
-static int proxy_connect_none(const char *host, unsigned short port, struct PHB *phb)
-{
-	struct sockaddr_in *sin;
+		phb->func(phb->data, source, B_EV_IO_READ);
+		g_free(phb);
+	}
+	
+	return FALSE;
+}
+
+static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb)
+{
 	struct sockaddr_in me;
 	int fd = -1;
-
-	if (!(sin = gaim_gethostbyname(host, port))) {
-		g_free(phb);
-		return -1;
-	}
-
-	if ((fd = socket(sin->sin_family, SOCK_STREAM, 0)) < 0) {
-		g_free(phb);
-		return -1;
-	}
-
-	sock_make_nonblocking(fd);
-	
-	if( global.conf->iface_out )
+	int ret;
+	char port[6];
+	struct addrinfo hints;
+	struct addrinfo* result;
+
+	g_snprintf(port, sizeof(port), "%d", port_);
+
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
+
+	if (!(ret = getaddrinfo(host, port, &hints, &result)))
 	{
-		me.sin_family = AF_INET;
-		me.sin_port = 0;
-		me.sin_addr.s_addr = inet_addr( global.conf->iface_out );
-		
-		if( bind( fd, (struct sockaddr *) &me, sizeof( me ) ) != 0 )
-			event_debug( "bind( %d, \"%s\" ) failure\n", fd, global.conf->iface_out );
-	}
-	
-	event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port, fd);
-	
-	if (connect(fd, (struct sockaddr *)sin, sizeof(*sin)) < 0 && !sockerr_again()) {
-		closesocket(fd);
-		g_free(phb);
-		
-		return -1;
-	} else {
-		phb->inpa = b_input_add(fd, GAIM_INPUT_WRITE, gaim_io_connected, phb);
-		phb->fd = fd;
-		
-		return fd;
-	}
+		struct addrinfo* rp;
+
+		for (rp = result; rp; rp = rp->ai_next)
+		{
+			if ((fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0) {
+				event_debug( "socket failed: %d\n", errno);
+				continue;
+			}
+
+			sock_make_nonblocking(fd);
+
+			if (global.conf->iface_out)
+			{
+				me.sin_family = AF_INET;
+				me.sin_port = 0;
+				me.sin_addr.s_addr = inet_addr( global.conf->iface_out );
+				
+				if (bind(fd, (struct sockaddr *) &me, sizeof(me)) != 0)
+					event_debug("bind( %d, \"%s\" ) failure\n", fd, global.conf->iface_out);
+			}
+
+			event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port, fd);
+	
+			if (connect(fd, rp->ai_addr, rp->ai_addrlen) < 0 && !sockerr_again()) {
+				event_debug( "connect failed: %s\n", strerror(errno));
+				closesocket(fd);
+				fd = -1;
+				continue;
+			} else {
+				phb->inpa = b_input_add(fd, B_EV_IO_WRITE, gaim_io_connected, phb);
+				phb->fd = fd;
+				
+				break;
+			}
+		}
+
+		freeaddrinfo(result);
+	}
+	else
+	{
+		event_debug("gai(): %s\n", gai_strerror(ret));
+	}
+	
+	if(fd < 0)
+		g_free(phb);
+
+	return fd;
 }
 
@@ -179,5 +182,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 +189,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 +207,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 +218,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 +233,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 +243,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 +276,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 +283,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 +302,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 +312,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 +329,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 +362,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 +369,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 +399,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 +405,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 +417,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 +425,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 +445,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 +453,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 +468,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 +474,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 +494,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 +516,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/sha1.h
===================================================================
--- lib/sha1.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/sha1.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -24,5 +24,9 @@
 #define _SHA1_H_
 
+#if(__sun)
+#include <inttypes.h>
+#else
 #include <stdint.h>
+#endif
 #include <gmodule.h>
 
Index: lib/ssl_bogus.c
===================================================================
--- lib/ssl_bogus.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/ssl_bogus.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/ssl_client.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/ssl_gnutls.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/ssl_nss.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/ssl_openssl.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/ssl_sspi.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/url.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/url.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ lib/xmltree.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -215,5 +215,8 @@
 	
 	if( node == NULL )
-		return xt_cleanup( xt, xt->root, depth );
+	{
+		xt_cleanup( xt, xt->root, depth );
+		return;
+	}
 	
 	if( node->flags & XT_SEEN && node == xt->root )
@@ -449,5 +452,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 +468,5 @@
 {
 	int i;
+	char *colon;
 	
 	if( !node )
@@ -468,4 +476,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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ log.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -172,4 +172,6 @@
 		fprintf(stdout, "Debug: %s\n", message);
 #endif
+	/* Always log stuff in syslogs too. */
+	log_syslog(level, message);
 	return;
 }
Index: nick.c
===================================================================
--- nick.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ nick.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -2,5 +2,5 @@
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2007 Wilmer van der Gaast and others                *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
   \********************************************************************/
 
@@ -27,4 +27,10 @@
 #include "bitlbee.h"
 
+/* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's.
+   With one difference, we allow dashes. These are used to do uc/lc conversions
+   and strip invalid chars. */
+static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|";
+static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\";
+
 /* Store handles in lower case and strip spaces, because AIM is braindead. */
 static char *clean_handle( const char *orig )
@@ -42,10 +48,10 @@
 }
 
-void nick_set( account_t *acc, const char *handle, const char *nick )
+void nick_set_raw( account_t *acc, const char *handle, const char *nick )
 {
 	char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 );
 	
 	store_handle = clean_handle( handle );
-	store_nick[MAX_NICK_LENGTH] = 0;
+	store_nick[MAX_NICK_LENGTH] = '\0';
 	strncpy( store_nick, nick, MAX_NICK_LENGTH );
 	nick_strip( store_nick );
@@ -54,5 +60,10 @@
 }
 
-char *nick_get( account_t *acc, const char *handle )
+void nick_set( bee_user_t *bu, const char *nick )
+{
+	nick_set_raw( bu->ic->acc, bu->handle, nick );
+}
+
+char *nick_get( bee_user_t *bu )
 {
 	static char nick[MAX_NICK_LENGTH+1];
@@ -61,16 +72,22 @@
 	memset( nick, 0, MAX_NICK_LENGTH + 1 );
 	
-	store_handle = clean_handle( handle );
+	store_handle = clean_handle( bu->handle );
 	/* Find out if we stored a nick for this person already. If not, try
 	   to generate a sane nick automatically. */
-	if( ( found_nick = g_hash_table_lookup( acc->nicks, store_handle ) ) )
+	if( ( found_nick = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ) ) )
 	{
 		strncpy( nick, found_nick, MAX_NICK_LENGTH );
 	}
+	else if( ( found_nick = nick_gen( bu ) ) )
+	{
+		strncpy( nick, found_nick, MAX_NICK_LENGTH );
+		g_free( found_nick );
+	}
 	else
 	{
+		/* Keep this fallback since nick_gen() can return NULL in some cases. */
 		char *s;
 		
-		g_snprintf( nick, MAX_NICK_LENGTH, "%s", handle );
+		g_snprintf( nick, MAX_NICK_LENGTH, "%s", bu->handle );
 		if( ( s = strchr( nick, '@' ) ) )
 			while( *s )
@@ -78,5 +95,5 @@
 		
 		nick_strip( nick );
-		if( set_getbool( &acc->irc->set, "lcnicks" ) )
+		if( set_getbool( &bu->bee->set, "lcnicks" ) )
 			nick_lc( nick );
 	}
@@ -85,16 +102,130 @@
 	/* Make sure the nick doesn't collide with an existing one by adding
 	   underscores and that kind of stuff, if necessary. */
-	nick_dedupe( acc, handle, nick );
+	nick_dedupe( bu, nick );
 	
 	return nick;
 }
 
-void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] )
-{
+char *nick_gen( bee_user_t *bu )
+{
+	gboolean ok = FALSE; /* Set to true once the nick contains something unique. */
+	GString *ret = g_string_new( "" );
+	char *fmt = set_getstr( &bu->ic->acc->set, "nick_format" ) ? :
+	            set_getstr( &bu->bee->set, "nick_format" );
+	
+	while( fmt && *fmt && ret->len < MAX_NICK_LENGTH )
+	{
+		char *part = NULL, chop = '\0', *asc = NULL;
+		int len = MAX_NICK_LENGTH;
+		
+		if( *fmt != '%' )
+		{
+			g_string_append_c( ret, *fmt );
+			fmt ++;
+			continue;
+		}
+		
+		fmt ++;
+		while( *fmt )
+		{
+			/* -char means chop off everything from char */
+			if( *fmt == '-' )
+			{
+				chop = fmt[1];
+				if( chop == '\0' )
+					return NULL;
+				fmt += 2;
+			}
+			else if( isdigit( *fmt ) )
+			{
+				len = 0;
+				/* Grab a number. */
+				while( isdigit( *fmt ) )
+					len = len * 10 + ( *(fmt++) - '0' );
+			}
+			else if( g_strncasecmp( fmt, "nick", 4 ) == 0 )
+			{
+				part = bu->nick ? : bu->handle;
+				fmt += 4;
+				ok |= TRUE;
+				break;
+			}
+			else if( g_strncasecmp( fmt, "handle", 6 ) == 0 )
+			{
+				part = bu->handle;
+				fmt += 6;
+				ok |= TRUE;
+				break;
+			}
+			else if( g_strncasecmp( fmt, "full_name", 9 ) == 0 )
+			{
+				part = bu->fullname;
+				fmt += 9;
+				ok |= part && *part;
+				break;
+			}
+			else if( g_strncasecmp( fmt, "first_name", 10 ) == 0 )
+			{
+				part = bu->fullname;
+				fmt += 10;
+				ok |= part && *part;
+				chop = ' ';
+				break;
+			}
+			else if( g_strncasecmp( fmt, "group", 5 ) == 0 )
+			{
+				part = bu->group ? bu->group->name : NULL;
+				fmt += 5;
+				break;
+			}
+			else if( g_strncasecmp( fmt, "account", 7 ) == 0 )
+			{
+				part = bu->ic->acc->tag;
+				fmt += 7;
+				break;
+			}
+			else
+			{
+				return NULL;
+			}
+		}
+		
+		/* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT
+		   should do lossy/approximate conversions, so letters with
+		   accents don't just get stripped. Note that it depends on
+		   LC_CTYPE being set to something other than C/POSIX. */
+		if( part )
+			part = asc = g_convert( part, -1, "ASCII//TRANSLIT//IGNORE",
+			                        "UTF-8", NULL, NULL, NULL );
+		
+		if( ret->len == 0 && part && isdigit( *part ) )
+			g_string_append_c( ret, '_' );
+		
+		while( part && *part && *part != chop && len > 0 )
+		{
+			if( strchr( nick_lc_chars, *part ) ||
+			    strchr( nick_uc_chars, *part ) )
+				g_string_append_c( ret, *part );
+			
+			part ++;
+			len --;
+		}
+		g_free( asc );
+	}
+	
+	/* This returns NULL if the nick is empty or otherwise not ok. */
+	return g_string_free( ret, ret->len == 0 || !ok );
+}
+
+void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] )
+{
+	irc_t *irc = (irc_t*) bu->bee->ui_data;
 	int inf_protection = 256;
+	irc_user_t *iu;
 	
 	/* Now, find out if the nick is already in use at the moment, and make
 	   subtle changes to make it unique. */
-	while( !nick_ok( nick ) || user_find( acc->irc, nick ) )
+	while( !nick_ok( nick ) ||
+	       ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) )
 	{
 		if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) )
@@ -112,17 +243,17 @@
 			int i;
 			
-			irc_usermsg( acc->irc, "Warning: Almost had an infinite loop in nick_get()! "
-			                       "This used to be a fatal BitlBee bug, but we tried to fix it. "
-			                       "This message should *never* appear anymore. "
-			                       "If it does, please *do* send us a bug report! "
-			                       "Please send all the following lines in your report:" );
-			
-			irc_usermsg( acc->irc, "Trying to get a sane nick for handle %s", handle );
+			irc_usermsg( irc, "Warning: Almost had an infinite loop in nick_get()! "
+			                  "This used to be a fatal BitlBee bug, but we tried to fix it. "
+			                  "This message should *never* appear anymore. "
+			                  "If it does, please *do* send us a bug report! "
+			                  "Please send all the following lines in your report:" );
+			
+			irc_usermsg( irc, "Trying to get a sane nick for handle %s", bu->handle );
 			for( i = 0; i < MAX_NICK_LENGTH; i ++ )
-				irc_usermsg( acc->irc, "Char %d: %c/%d", i, nick[i], nick[i] );
-			
-			irc_usermsg( acc->irc, "FAILED. Returning an insane nick now. Things might break. "
-			                       "Good luck, and please don't forget to paste the lines up here "
-			                       "in #bitlbee on OFTC or in a mail to wilmer@gaast.net" );
+				irc_usermsg( irc, "Char %d: %c/%d", i, nick[i], nick[i] );
+			
+			irc_usermsg( irc, "FAILED. Returning an insane nick now. Things might break. "
+			                  "Good luck, and please don't forget to paste the lines up here "
+			                  "in #bitlbee on OFTC or in a mail to wilmer@gaast.net" );
 			
 			g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() );
@@ -135,10 +266,10 @@
 /* Just check if there is a nickname set for this buddy or if we'd have to
    generate one. */
-int nick_saved( account_t *acc, const char *handle )
+int nick_saved( bee_user_t *bu )
 {
 	char *store_handle, *found;
 	
-	store_handle = clean_handle( handle );
-	found = g_hash_table_lookup( acc->nicks, store_handle );
+	store_handle = clean_handle( bu->handle );
+	found = g_hash_table_lookup( bu->ic->acc->nicks, store_handle );
 	g_free( store_handle );
 	
@@ -146,15 +277,9 @@
 }
 
-void nick_del( account_t *acc, const char *handle )
-{
-	g_hash_table_remove( acc->nicks, handle );
-}
-
-
-/* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's.
-   With one difference, we allow dashes. */
-
-static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|";
-static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\";
+void nick_del( bee_user_t *bu )
+{
+	g_hash_table_remove( bu->ic->acc->nicks, bu->handle );
+}
+
 
 void nick_strip( char *nick )
Index: nick.h
===================================================================
--- nick.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ nick.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -24,9 +24,11 @@
 */
 
-void nick_set( account_t *acc, const char *handle, const char *nick );
-char *nick_get( account_t *acc, const char *handle );
-void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] );
-int nick_saved( account_t *acc, const char *handle );
-void nick_del( account_t *acc, const char *handle );
+void nick_set_raw( account_t *acc, const char *handle, const char *nick );
+void nick_set( bee_user_t *bu, const char *nick );
+char *nick_get( bee_user_t *bu );
+char *nick_gen( bee_user_t *bu );
+void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] );
+int nick_saved( bee_user_t *bu );
+void nick_del( bee_user_t *bu );
 void nick_strip( char *nick );
 
Index: protocols/Makefile
===================================================================
--- protocols/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -8,7 +8,11 @@
 
 -include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/
+endif
 
 # [SH] Program variables
-objects = nogaim.o
+objects = account.o bee.o bee_chat.o bee_ft.o bee_user.o nogaim.o
+
 
 # [SH] The next two lines should contain the directory name (in $(subdirs))
@@ -21,5 +25,4 @@
 # Expansion of variables
 subdirobjs := $(join $(subdirs),$(addprefix /,$(subdirobjs)))
-CFLAGS += -Wall
 LFLAGS += -r
 
@@ -49,5 +52,5 @@
 $(objects): ../Makefile.settings Makefile
 
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
 	@echo '*' Compiling $<
 	@$(CC) -c $(CFLAGS) $< -o $@
Index: protocols/account.c
===================================================================
--- protocols/account.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/account.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,444 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Account management functions                                         */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+#include "account.h"
+
+static char *set_eval_nick_source( set_t *set, char *value );
+
+account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass )
+{
+	account_t *a;
+	set_t *s;
+	char tag[strlen(prpl->name)+10];
+	
+	if( bee->accounts )
+	{
+		for( a = bee->accounts; a->next; a = a->next );
+		a = a->next = g_new0( account_t, 1 );
+	}
+	else
+	{
+		bee->accounts = a = g_new0 ( account_t, 1 );
+	}
+	
+	a->prpl = prpl;
+	a->user = g_strdup( user );
+	a->pass = g_strdup( pass );
+	a->auto_connect = 1;
+	a->bee = bee;
+	
+	s = set_add( &a->set, "auto_connect", "true", set_eval_account, a );
+	s->flags |= ACC_SET_NOSAVE;
+	
+	s = set_add( &a->set, "auto_reconnect", "true", set_eval_bool, a );
+	
+	s = set_add( &a->set, "nick_format", NULL, NULL, a );
+	s->flags |= SET_NULL_OK;
+	
+	s = set_add( &a->set, "nick_source", "handle", set_eval_nick_source, a );
+	s->flags |= ACC_SET_NOSAVE; /* Just for bw compatibility! */
+	
+	s = set_add( &a->set, "password", NULL, set_eval_account, a );
+	s->flags |= ACC_SET_NOSAVE | SET_NULL_OK;
+	
+	s = set_add( &a->set, "tag", NULL, set_eval_account, a );
+	s->flags |= ACC_SET_NOSAVE;
+	
+	s = set_add( &a->set, "username", NULL, set_eval_account, a );
+	s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
+	set_setstr( &a->set, "username", user );
+	
+	/* Hardcode some more clever tag guesses. */
+	strcpy( tag, prpl->name );
+	if( strcmp( prpl->name, "oscar" ) == 0 )
+	{
+		if( isdigit( a->user[0] ) )
+			strcpy( tag, "icq" );
+		else
+			strcpy( tag, "aim" );
+	}
+	else if( strcmp( prpl->name, "jabber" ) == 0 )
+	{
+		if( strstr( a->user, "@gmail.com" ) ||
+		    strstr( a->user, "@googlemail.com" ) )
+			strcpy( tag, "gtalk" );
+		else if( strstr( a->user, "@chat.facebook.com" ) )
+			strcpy( tag, "fb" );
+	}
+	
+	if( account_by_tag( bee, tag ) )
+	{
+		char *numpos = tag + strlen( tag );
+		int i;
+
+		for( i = 2; i < 10000; i ++ )
+		{
+			sprintf( numpos, "%d", i );
+			if( !account_by_tag( bee, tag ) )
+				break;
+		}
+	}
+	set_setstr( &a->set, "tag", tag );
+	
+	a->nicks = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free );
+	
+	/* This function adds some more settings (and might want to do more
+	   things that have to be done now, although I can't think of anything. */
+	if( prpl->init )
+		prpl->init( a );
+	
+	s = set_add( &a->set, "away", NULL, set_eval_account, a );
+	s->flags |= SET_NULL_OK;
+	
+	if( a->flags & ACC_FLAG_STATUS_MESSAGE )
+	{
+		s = set_add( &a->set, "status", NULL, set_eval_account, a );
+		s->flags |= SET_NULL_OK;
+	}
+	
+	return a;
+}
+
+char *set_eval_account( set_t *set, char *value )
+{
+	account_t *acc = set->data;
+	
+	/* Double-check: We refuse to edit on-line accounts. */
+	if( set->flags & ACC_SET_OFFLINE_ONLY && acc->ic )
+		return SET_INVALID;
+	
+	if( strcmp( set->key, "server" ) == 0 )
+	{
+		g_free( acc->server );
+		if( value && *value )
+		{
+			acc->server = g_strdup( value );
+			return value;
+		}
+		else
+		{
+			acc->server = g_strdup( set->def );
+			return g_strdup( set->def );
+		}
+	}
+	else if( strcmp( set->key, "username" ) == 0 )
+	{
+		g_free( acc->user );
+		acc->user = g_strdup( value );
+		return value;
+	}
+	else if( strcmp( set->key, "password" ) == 0 )
+	{
+		if( value )
+		{
+			g_free( acc->pass );
+			acc->pass = g_strdup( value );
+			return NULL;	/* password shouldn't be visible in plaintext! */
+		}
+		else
+		{
+			/* NULL can (should) be stored in the set_t
+			   variable, but is otherwise not correct. */
+			return SET_INVALID;
+		}
+	}
+	else if( strcmp( set->key, "tag" ) == 0 )
+	{
+		account_t *oa;
+		
+		/* Enforce uniqueness. */
+		if( ( oa = account_by_tag( acc->bee, value ) ) && oa != acc )
+			return SET_INVALID;
+		
+		g_free( acc->tag );
+		acc->tag = g_strdup( value );
+		return value;
+	}
+	else if( strcmp( set->key, "auto_connect" ) == 0 )
+	{
+		if( !is_bool( value ) )
+			return SET_INVALID;
+		
+		acc->auto_connect = bool2int( value );
+		return value;
+	}
+	else if( strcmp( set->key, "away" ) == 0 ||
+	         strcmp( set->key, "status" ) == 0 )
+	{
+		if( acc->ic && acc->ic->flags & OPT_LOGGED_IN )
+		{
+			/* If we're currently on-line, set the var now already
+			   (bit of a hack) and send an update. */
+			g_free( set->value );
+			set->value = g_strdup( value );
+			
+			imc_away_send_update( acc->ic );
+		}
+		
+		return value;
+	}
+	
+	return SET_INVALID;
+}
+
+/* For bw compatibility, have this write-only setting. */
+static char *set_eval_nick_source( set_t *set, char *value )
+{
+	account_t *a = set->data;
+	
+	if( strcmp( value, "full_name" ) == 0 )
+		set_setstr( &a->set, "nick_format", "%full_name" );
+	else if( strcmp( value, "first_name" ) == 0 )
+		set_setstr( &a->set, "nick_format", "%first_name" );
+	else
+		set_setstr( &a->set, "nick_format", "%-@nick" );
+	
+	return value;
+}
+
+account_t *account_get( bee_t *bee, const char *id )
+{
+	account_t *a, *ret = NULL;
+	char *handle, *s;
+	int nr;
+	
+	/* Tags get priority above anything else. */
+	if( ( a = account_by_tag( bee, id ) ) )
+		return a;
+	
+	/* This checks if the id string ends with (...) */
+	if( ( handle = strchr( id, '(' ) ) && ( s = strchr( handle, ')' ) ) && s[1] == 0 )
+	{
+		struct prpl *proto;
+		
+		*s = *handle = 0;
+		handle ++;
+		
+		if( ( proto = find_protocol( id ) ) )
+		{
+			for( a = bee->accounts; a; a = a->next )
+				if( a->prpl == proto &&
+				    a->prpl->handle_cmp( handle, a->user ) == 0 )
+					ret = a;
+		}
+		
+		/* Restore the string. */
+		handle --;
+		*handle = '(';
+		*s = ')';
+		
+		if( ret )
+			return ret;
+	}
+	
+	if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 )
+	{
+		for( a = bee->accounts; a; a = a->next )
+			if( ( nr-- ) == 0 )
+				return( a );
+		
+		return( NULL );
+	}
+	
+	for( a = bee->accounts; a; a = a->next )
+	{
+		if( g_strcasecmp( id, a->prpl->name ) == 0 )
+		{
+			if( !ret )
+				ret = a;
+			else
+				return( NULL ); /* We don't want to match more than one... */
+		}
+		else if( strstr( a->user, id ) )
+		{
+			if( !ret )
+				ret = a;
+			else
+				return( NULL );
+		}
+	}
+	
+	return( ret );
+}
+
+account_t *account_by_tag( bee_t *bee, const char *tag )
+{
+	account_t *a;
+	
+	for( a = bee->accounts; a; a = a->next )
+		if( a->tag && g_strcasecmp( tag, a->tag ) == 0 )
+			return a;
+	
+	return NULL;
+}
+
+void account_del( bee_t *bee, account_t *acc )
+{
+	account_t *a, *l = NULL;
+	
+	if( acc->ic )
+		/* Caller should have checked, accounts still in use can't be deleted. */
+		return;
+	
+	for( a = bee->accounts; a; a = (l=a)->next )
+		if( a == acc )
+		{
+			if( l )
+				l->next = a->next;
+			else
+				bee->accounts = a->next;
+			
+			/** FIXME
+			for( c = bee->chatrooms; c; c = nc )
+			{
+				nc = c->next;
+				if( acc == c->acc )
+					chat_del( bee, c );
+			}
+			*/
+			
+			while( a->set )
+				set_del( &a->set, a->set->key );
+			
+			g_hash_table_destroy( a->nicks );
+			
+			g_free( a->tag );
+			g_free( a->user );
+			g_free( a->pass );
+			g_free( a->server );
+			if( a->reconnect )	/* This prevents any reconnect still queued to happen */
+				cancel_auto_reconnect( a );
+			g_free( a );
+			
+			break;
+		}
+}
+
+void account_on( bee_t *bee, account_t *a )
+{
+	if( a->ic )
+	{
+		/* Trying to enable an already-enabled account */
+		return;
+	}
+	
+	cancel_auto_reconnect( a );
+	
+	a->reconnect = 0;
+	a->prpl->login( a );
+}
+
+void account_off( bee_t *bee, account_t *a )
+{
+	imc_logout( a->ic, FALSE );
+	a->ic = NULL;
+	if( a->reconnect )
+	{
+		/* Shouldn't happen */
+		cancel_auto_reconnect( a );
+	}
+}
+
+struct account_reconnect_delay
+{
+	int start;
+	char op;
+	int step;
+	int max;
+};
+
+int account_reconnect_delay_parse( char *value, struct account_reconnect_delay *p )
+{
+	memset( p, 0, sizeof( *p ) );
+	/* A whole day seems like a sane "maximum maximum". */
+	p->max = 86400;
+	
+	/* Format: /[0-9]+([*+][0-9]+(<[0-9+])?)?/ */
+	while( *value && isdigit( *value ) )
+		p->start = p->start * 10 + *value++ - '0';
+	
+	/* Sure, call me evil for implementing my own fscanf here, but it's
+	   dead simple and I immediately know where to continue parsing. */
+	
+	if( *value == 0 )
+		/* If the string ends now, the delay is constant. */
+		return 1;
+	else if( *value != '+' && *value != '*' )
+		/* Otherwise allow either a + or a * */
+		return 0;
+	
+	p->op = *value++;
+	
+	/* + or * the delay by this number every time. */
+	while( *value && isdigit( *value ) )
+		p->step = p->step * 10 + *value++ - '0';
+	
+	if( *value == 0 )
+		/* Use the default maximum (one day). */
+		return 1;
+	else if( *value != '<' )
+		return 0;
+	
+	p->max = 0;
+	value ++;
+	while( *value && isdigit( *value ) )
+		p->max = p->max * 10 + *value++ - '0';
+	
+	return p->max > 0;
+}
+
+char *set_eval_account_reconnect_delay( set_t *set, char *value )
+{
+	struct account_reconnect_delay p;
+	
+	return account_reconnect_delay_parse( value, &p ) ? value : SET_INVALID;
+}
+
+int account_reconnect_delay( account_t *a )
+{
+	char *setting = set_getstr( &a->bee->set, "auto_reconnect_delay" );
+	struct account_reconnect_delay p;
+	
+	if( account_reconnect_delay_parse( setting, &p ) )
+	{
+		if( a->auto_reconnect_delay == 0 )
+			a->auto_reconnect_delay = p.start;
+		else if( p.op == '+' )
+			a->auto_reconnect_delay += p.step;
+		else if( p.op == '*' )
+			a->auto_reconnect_delay *= p.step;
+		
+		if( a->auto_reconnect_delay > p.max )
+			a->auto_reconnect_delay = p.max;
+	}
+	else
+	{
+		a->auto_reconnect_delay = 0;
+	}
+	
+	return a->auto_reconnect_delay;
+}
Index: protocols/account.h
===================================================================
--- protocols/account.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/account.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,74 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Account management functions                                         */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef _ACCOUNT_H
+#define _ACCOUNT_H
+
+typedef struct account
+{
+	struct prpl *prpl;
+	char *user;
+	char *pass;
+	char *server;
+	char *tag;
+	
+	int auto_connect;
+	int auto_reconnect_delay;
+	int reconnect;
+	int flags;
+	
+	set_t *set;
+	GHashTable *nicks;
+	
+	struct bee *bee;
+	struct im_connection *ic;
+	struct account *next;
+} account_t;
+
+account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass );
+account_t *account_get( bee_t *bee, const char *id );
+account_t *account_by_tag( bee_t *bee, const char *tag );
+void account_del( bee_t *bee, account_t *acc );
+void account_on( bee_t *bee, account_t *a );
+void account_off( bee_t *bee, account_t *a );
+
+char *set_eval_account( set_t *set, char *value );
+char *set_eval_account_reconnect_delay( set_t *set, char *value );
+int account_reconnect_delay( account_t *a );
+
+typedef enum
+{
+	ACC_SET_NOSAVE = 0x01,          /* Don't save this setting (i.e. stored elsewhere). */
+	ACC_SET_OFFLINE_ONLY = 0x02,    /* Allow changes only if the acct is offline. */
+	ACC_SET_ONLINE_ONLY = 0x04,     /* Allow changes only if the acct is online. */
+} account_set_flag_t;
+
+typedef enum
+{
+	ACC_FLAG_AWAY_MESSAGE = 0x01,   /* Supports away messages instead of just states. */
+	ACC_FLAG_STATUS_MESSAGE = 0x02, /* Supports status messages (without being away). */
+} account_flag_t;
+
+#endif
Index: protocols/bee.c
===================================================================
--- protocols/bee.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/bee.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,95 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Some IM-core stuff                                                   */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+
+static char *set_eval_away_status( set_t *set, char *value );
+
+bee_t *bee_new()
+{
+	bee_t *b = g_new0( bee_t, 1 );
+	set_t *s;
+	
+	s = set_add( &b->set, "away", NULL, set_eval_away_status, b );
+	s->flags |= SET_NULL_OK;
+	s = set_add( &b->set, "auto_connect", "true", set_eval_bool, b );
+	s = set_add( &b->set, "auto_reconnect", "true", set_eval_bool, b );
+	s = set_add( &b->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, b );
+	s = set_add( &b->set, "debug", "false", set_eval_bool, b );
+	s = set_add( &b->set, "save_on_quit", "true", set_eval_bool, b );
+	s = set_add( &b->set, "status", NULL, set_eval_away_status, b );
+	s->flags |= SET_NULL_OK;
+	s = set_add( &b->set, "strip_html", "true", NULL, b );
+	
+	b->user = g_malloc( 1 );
+	
+	return b;
+}
+
+void bee_free( bee_t *b )
+{
+	while( b->accounts )
+	{
+		if( b->accounts->ic )
+			imc_logout( b->accounts->ic, FALSE );
+		else if( b->accounts->reconnect )
+			cancel_auto_reconnect( b->accounts );
+		
+		if( b->accounts->ic == NULL )
+			account_del( b, b->accounts );
+		else
+			/* Nasty hack, but account_del() doesn't work in this
+			   case and we don't want infinite loops, do we? ;-) */
+			b->accounts = b->accounts->next;
+	}
+	
+	while( b->set )
+		set_del( &b->set, b->set->key );
+	
+	bee_group_free( b );
+	
+	g_free( b->user );
+	g_free( b );
+}
+
+static char *set_eval_away_status( set_t *set, char *value )
+{
+	bee_t *bee = set->data;
+	account_t *a;
+	
+	g_free( set->value );
+	set->value = g_strdup( value );
+	
+	for( a = bee->accounts; a; a = a->next )
+	{
+		struct im_connection *ic = a->ic;
+		
+		if( ic && ic->flags & OPT_LOGGED_IN )
+			imc_away_send_update( ic );
+	}
+	
+	return value;
+}
Index: protocols/bee.h
===================================================================
--- protocols/bee.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/bee.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,176 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Stuff to handle, save and search buddies                             */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef __BEE_H__
+#define __BEE_H__
+
+struct bee_ui_funcs;
+struct groupchat;
+
+typedef struct bee
+{
+	/* Settings. See set.h for how these work. The UI can add its
+	   own settings here. */
+	struct set *set;
+	
+	GSList *users;  /* struct bee_user */
+	GSList *groups; /* struct bee_group */
+	struct account *accounts; /* TODO(wilmer): Use GSList here too? */
+	
+	/* Symbolic, to refer to the local user (who has no real bee_user
+	   object). Not to be used by anything except so far imcb_chat_add/
+	   remove_buddy(). */
+	struct bee_user *user;
+	
+	/* Fill in the callbacks for events you care about. */
+	const struct bee_ui_funcs *ui;
+	
+	/* And this one will be passed to every callback for any state the
+	   UI may want to keep. */
+	void *ui_data;
+} bee_t;
+
+bee_t *bee_new();
+void bee_free( bee_t *b );
+
+typedef enum
+{
+	BEE_USER_ONLINE = 1,    /* Compatibility with old OPT_LOGGED_IN flag */
+	BEE_USER_AWAY = 4,      /* Compatibility with old OPT_AWAY flag */
+	BEE_USER_LOCAL = 256,   /* Locally-added contacts (not in real contact list) */
+} bee_user_flags_t;
+
+typedef struct bee_user
+{
+	struct im_connection *ic;
+	char *handle;
+	char *fullname;
+	char *nick;
+	struct bee_group *group;
+
+	bee_user_flags_t flags;
+	char *status;     /* NULL means available, anything else is an away state. */
+	char *status_msg; /* Status and/or away message. */
+	
+	/* Set using imcb_buddy_times(). */
+	time_t login_time, idle_time;
+	
+	bee_t *bee;
+	void *ui_data;
+	void *data; /* Can be used by the IM module. */
+} bee_user_t;
+
+/* This one's mostly used so save space and make it easier (cheaper) to
+   compare groups of contacts, etc. */
+typedef struct bee_group
+{
+	char *key;  /* Lower case version of the name. */
+	char *name;
+} bee_group_t;
+
+typedef struct bee_ui_funcs
+{
+	void (*imc_connected)( struct im_connection *ic );
+	void (*imc_disconnected)( struct im_connection *ic );
+	
+	gboolean (*user_new)( bee_t *bee, struct bee_user *bu );
+	gboolean (*user_free)( bee_t *bee, struct bee_user *bu );
+	/* Set the fullname first, then call this one to notify the UI. */
+	gboolean (*user_fullname)( bee_t *bee, bee_user_t *bu );
+	gboolean (*user_nick_hint)( bee_t *bee, bee_user_t *bu, const char *hint );
+	/* Notify the UI when an existing user is moved between groups. */
+	gboolean (*user_group)( bee_t *bee, bee_user_t *bu );
+	/* State info is already updated, old is provided in case the UI needs a diff. */
+	gboolean (*user_status)( bee_t *bee, struct bee_user *bu, struct bee_user *old );
+	/* On every incoming message. sent_at = 0 means unknown. */
+	gboolean (*user_msg)( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at );
+	/* Flags currently defined (OPT_TYPING/THINKING) in nogaim.h. */
+	gboolean (*user_typing)( bee_t *bee, bee_user_t *bu, guint32 flags );
+	
+	/* Called at creation time. Don't show to the user until s/he is
+	   added using chat_add_user().  UI state can be stored via c->data. */
+	gboolean (*chat_new)( bee_t *bee, struct groupchat *c );
+	gboolean (*chat_free)( bee_t *bee, struct groupchat *c );
+	/* System messages of any kind. */
+	gboolean (*chat_log)( bee_t *bee, struct groupchat *c, const char *text );
+	gboolean (*chat_msg)( bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at );
+	gboolean (*chat_add_user)( bee_t *bee, struct groupchat *c, bee_user_t *bu );
+	gboolean (*chat_remove_user)( bee_t *bee, struct groupchat *c, bee_user_t *bu );
+	gboolean (*chat_topic)( bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu );
+	gboolean (*chat_name_hint)( bee_t *bee, struct groupchat *c, const char *name );
+	
+	struct file_transfer* (*ft_in_start)( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size );
+	gboolean (*ft_out_start)( struct im_connection *ic, struct file_transfer *ft );
+	void (*ft_close)( struct im_connection *ic, struct file_transfer *ft );
+	void (*ft_finished)( struct im_connection *ic, struct file_transfer *ft );
+} bee_ui_funcs_t;
+
+
+/* bee.c */
+bee_t *bee_new();
+void bee_free( bee_t *b );
+
+/* bee_user.c */
+bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags );
+int bee_user_free( bee_t *bee, bee_user_t *bu );
+bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle );
+int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags );
+bee_group_t *bee_group_by_name( bee_t *bee, const char *name, gboolean creat );
+void bee_group_free( bee_t *bee );
+
+/* Callbacks from IM modules to core: */
+/* Buddy activity */
+/* To manipulate the status of a handle.
+ * - flags can be |='d with OPT_* constants. You will need at least:
+ *   OPT_LOGGED_IN and OPT_AWAY.
+ * - 'state' and 'message' can be NULL */
+G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message );
+G_MODULE_EXPORT void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle );
+/* Call when a handle says something. 'flags' and 'sent_at may be just 0. */
+G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, guint32 flags, time_t sent_at );
+
+/* bee_chat.c */
+/* These two functions are to create a group chat.
+ * - imcb_chat_new(): the 'handle' parameter identifies the chat, like the
+ *   channel name on IRC.
+ * - After you have a groupchat pointer, you should add the handles, finally  
+ *   the user her/himself. At that point the group chat will be visible to the
+ *   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_free( struct groupchat *c );
+/* To tell BitlBee 'who' said 'msg' in 'c'. 'flags' and 'sent_at' can be 0. */
+G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, guint32 flags, time_t sent_at );
+/* System messages specific to a groupchat, so they can be displayed in the right context. */
+G_MODULE_EXPORT void imcb_chat_log( struct groupchat *c, char *format, ... );
+/* To tell BitlBee 'who' changed the topic of 'c' to 'topic'. */
+G_MODULE_EXPORT void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at );
+G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *c, const char *handle );
+/* To remove a handle from a group chat. Reason can be NULL. */
+G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason );
+G_MODULE_EXPORT int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags );
+G_MODULE_EXPORT struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title );
+
+#endif /* __BEE_H__ */
Index: protocols/bee_chat.c
===================================================================
--- protocols/bee_chat.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/bee_chat.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,234 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Stuff to handle rooms                                                */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+
+struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle )
+{
+	struct groupchat *c = g_new0( struct groupchat, 1 );
+	bee_t *bee = ic->bee;
+	
+	/* This one just creates the conversation structure, user won't see
+	   anything yet until s/he is joined to the conversation. (This
+	   allows you to add other already present participants first.) */
+	
+	ic->groupchats = g_slist_prepend( ic->groupchats, c );
+	c->ic = ic;
+	c->title = g_strdup( handle );
+	c->topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title );
+	
+	if( set_getbool( &ic->bee->set, "debug" ) )
+		imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle );
+	
+	if( bee->ui->chat_new )
+		bee->ui->chat_new( bee, c );
+	
+	return c;
+}
+
+void imcb_chat_name_hint( struct groupchat *c, const char *name )
+{
+	bee_t *bee = c->ic->bee;
+	
+	if( bee->ui->chat_name_hint )
+		bee->ui->chat_name_hint( bee, c, name );
+}
+
+void imcb_chat_free( struct groupchat *c )
+{
+	struct im_connection *ic = c->ic;
+	bee_t *bee = ic->bee;
+	GList *ir;
+	
+	if( bee->ui->chat_free )
+		bee->ui->chat_free( bee, c );
+	
+	if( set_getbool( &ic->bee->set, "debug" ) )
+		imcb_log( ic, "You were removed from conversation %p", c );
+	
+	ic->groupchats = g_slist_remove( ic->groupchats, c );
+	
+	for( ir = c->in_room; ir; ir = ir->next )
+		g_free( ir->data );
+	g_list_free( c->in_room );
+	g_free( c->title );
+	g_free( c->topic );
+	g_free( c );
+}
+
+void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at )
+{
+	struct im_connection *ic = c->ic;
+	bee_t *bee = ic->bee;
+	bee_user_t *bu;
+	char *s;
+	
+	/* Gaim sends own messages through this too. IRC doesn't want this, so kill them */
+	if( g_strcasecmp( who, ic->acc->user ) == 0 )
+		return;
+	
+	bu = bee_user_by_handle( bee, ic, who );
+	
+	s = set_getstr( &ic->bee->set, "strip_html" );
+	if( ( g_strcasecmp( s, "always" ) == 0 ) ||
+	    ( ( ic->flags & OPT_DOES_HTML ) && s ) )
+		strip_html( msg );
+	
+	if( bu && bee->ui->chat_msg )
+		bee->ui->chat_msg( bee, c, bu, msg, sent_at );
+	else
+		imcb_chat_log( c, "Message from unknown participant %s: %s", who, msg );
+}
+
+void imcb_chat_log( struct groupchat *c, char *format, ... )
+{
+	struct im_connection *ic = c->ic;
+	bee_t *bee = ic->bee;
+	va_list params;
+	char *text;
+	
+	if( !bee->ui->chat_log )
+		return;
+	
+	va_start( params, format );
+	text = g_strdup_vprintf( format, params );
+	va_end( params );
+	
+	bee->ui->chat_log( bee, c, text );
+	g_free( text );
+}
+
+void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at )
+{
+	struct im_connection *ic = c->ic;
+	bee_t *bee = ic->bee;
+	bee_user_t *bu;
+	
+	if( !bee->ui->chat_topic )
+		return;
+	
+	if( who == NULL)
+		bu = NULL;
+	else if( g_strcasecmp( who, ic->acc->user ) == 0 )
+		bu = bee->user;
+	else
+		bu = bee_user_by_handle( bee, ic, who );
+	
+	if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) ||
+	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) )
+		strip_html( topic );
+	
+	bee->ui->chat_topic( bee, c, topic, bu );
+}
+
+void imcb_chat_add_buddy( struct groupchat *c, const char *handle )
+{
+	struct im_connection *ic = c->ic;
+	bee_t *bee = ic->bee;
+	bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
+	gboolean me;
+	
+	if( set_getbool( &c->ic->bee->set, "debug" ) )
+		imcb_log( c->ic, "User %s added to conversation %p", handle, c );
+	
+	me = ic->acc->prpl->handle_cmp( handle, ic->acc->user ) == 0;
+	
+	/* Most protocols allow people to join, even when they're not in
+	   your contact list. Try to handle that here */
+	if( !me && !bu )
+		bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL );
+	
+	/* Add the handle to the room userlist */
+	/* TODO: Use bu instead of a string */
+	c->in_room = g_list_append( c->in_room, g_strdup( handle ) );
+	
+	if( bee->ui->chat_add_user )
+		bee->ui->chat_add_user( bee, c, me ? bee->user : bu );
+	
+	if( me )
+		c->joined = 1;
+}
+
+void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason )
+{
+	struct im_connection *ic = c->ic;
+	bee_t *bee = ic->bee;
+	bee_user_t *bu = NULL;
+	
+	if( set_getbool( &bee->set, "debug" ) )
+		imcb_log( ic, "User %s removed from conversation %p (%s)", handle, c, reason ? reason : "" );
+	
+	/* It might be yourself! */
+	if( g_strcasecmp( handle, ic->acc->user ) == 0 )
+	{
+		if( c->joined == 0 )
+			return;
+		
+		bu = bee->user;
+		c->joined = 0;
+	}
+	else
+	{
+		bu = bee_user_by_handle( bee, ic, handle );
+	}
+	
+	if( bee->ui->chat_remove_user )
+		bee->ui->chat_remove_user( bee, c, bu );
+}
+
+int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags )
+{
+	struct im_connection *ic = c->ic;
+	char *buf = NULL;
+	
+	if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
+	{
+		buf = escape_html( msg );
+		msg = buf;
+	}
+	else
+		buf = g_strdup( msg );
+	
+	ic->acc->prpl->chat_msg( c, buf, flags );
+	g_free( buf );
+	
+	return 1;
+}
+
+struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title )
+{
+	struct groupchat *c;
+	GSList *l;
+	
+	for( l = ic->groupchats; l; l = l->next )
+	{
+		c = l->data;
+		if( strcmp( c->title, title ) == 0 )
+			return c;
+	}
+	
+	return NULL;
+}
Index: protocols/bee_ft.c
===================================================================
--- protocols/bee_ft.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/bee_ft.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,66 @@
+/********************************************************************\
+* BitlBee -- An IRC to other IM-networks gateway                     *
+*                                                                    *
+* 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 with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+#include "ft.h"
+
+file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size )
+{
+	bee_t *bee = ic->bee; 
+	bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
+	
+	if( bee->ui->ft_in_start )
+		return bee->ui->ft_in_start( bee, bu, file_name, file_size );
+	else
+		return NULL;
+}
+
+gboolean imcb_file_recv_start( struct im_connection *ic, file_transfer_t *ft )
+{
+	bee_t *bee = ic->bee;
+	
+	if( bee->ui->ft_out_start )
+		return bee->ui->ft_out_start( ic, ft );
+	else
+		return FALSE;
+}
+
+void imcb_file_canceled( struct im_connection *ic, file_transfer_t *file, char *reason )
+{
+	bee_t *bee = ic->bee;
+	
+	if( file->canceled )
+		file->canceled( file, reason );
+	
+	if( bee->ui->ft_close )
+		bee->ui->ft_close( ic, file );
+}
+
+void imcb_file_finished( struct im_connection *ic, file_transfer_t *file )
+{
+	bee_t *bee = ic->bee;
+	
+	if( bee->ui->ft_finished )
+		bee->ui->ft_finished( ic, file );
+}
Index: protocols/bee_user.c
===================================================================
--- protocols/bee_user.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/bee_user.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,252 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* Stuff to handle, save and search buddies                             */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+
+bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags )
+{
+	bee_user_t *bu;
+	
+	if( bee_user_by_handle( bee, ic, handle ) != NULL )
+		return NULL;
+	
+	bu = g_new0( bee_user_t, 1 );
+	bu->bee = bee;
+	bu->ic = ic;
+	bu->flags = flags;
+	bu->handle = g_strdup( handle );
+	bee->users = g_slist_prepend( bee->users, bu );
+	
+	if( bee->ui->user_new )
+		bee->ui->user_new( bee, bu );
+	if( ic->acc->prpl->buddy_data_add )
+		ic->acc->prpl->buddy_data_add( bu );
+	
+	/* Offline by default. This will set the right flags. */
+	imcb_buddy_status( ic, handle, 0, NULL, NULL );
+	
+	return bu;
+}
+
+int bee_user_free( bee_t *bee, bee_user_t *bu )
+{
+	if( !bu )
+		return 0;
+	
+	if( bee->ui->user_free )
+		bee->ui->user_free( bee, bu );
+	if( bu->ic->acc->prpl->buddy_data_free )
+		bu->ic->acc->prpl->buddy_data_free( bu );
+	
+	g_free( bu->handle );
+	g_free( bu->fullname );
+	g_free( bu->nick );
+	g_free( bu->status );
+	g_free( bu->status_msg );
+	g_free( bu );
+	
+	bee->users = g_slist_remove( bee->users, bu );
+	
+	return 1;
+}
+
+bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle )
+{
+	GSList *l;
+	
+	for( l = bee->users; l; l = l->next )
+	{
+		bee_user_t *bu = l->data;
+		
+		if( bu->ic == ic && ic->acc->prpl->handle_cmp( bu->handle, handle ) == 0 )
+			return bu;
+	}
+	
+	return NULL;
+}
+
+int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags )
+{
+	char *buf = NULL;
+	int st;
+	
+	if( ( bu->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
+	{
+		buf = escape_html( msg );
+		msg = buf;
+	}
+	else
+		buf = g_strdup( msg );
+	
+	st = bu->ic->acc->prpl->buddy_msg( bu->ic, bu->handle, buf, flags );
+	g_free( buf );
+	
+	return st;
+}
+
+
+/* Groups */
+static bee_group_t *bee_group_new( bee_t *bee, const char *name )
+{
+	bee_group_t *bg = g_new0( bee_group_t, 1 );
+	
+	bg->name = g_strdup( name );
+	bg->key = g_utf8_casefold( name, -1 );
+	bee->groups = g_slist_prepend( bee->groups, bg );
+	
+	return bg;
+}
+
+bee_group_t *bee_group_by_name( bee_t *bee, const char *name, gboolean creat )
+{
+	GSList *l;
+	char *key;
+	
+	if( name == NULL )
+		return NULL;
+	
+	key = g_utf8_casefold( name, -1 );
+	for( l = bee->groups; l; l = l->next )
+	{
+		bee_group_t *bg = l->data;
+		if( strcmp( bg->key, key ) == 0 )
+			break;
+	}
+	g_free( key );
+	
+	if( !l )
+		return creat ? bee_group_new( bee, name ) : NULL;
+	else
+		return l->data;
+}
+
+void bee_group_free( bee_t *bee )
+{
+	while( bee->groups )
+	{
+		bee_group_t *bg = bee->groups->data;
+		g_free( bg->name );
+		g_free( bg->key );
+		g_free( bg );
+		bee->groups = g_slist_remove( bee->groups, bee->groups->data );
+	}
+}
+
+
+/* IM->UI callbacks */
+void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message )
+{
+	bee_t *bee = ic->bee;
+	bee_user_t *bu, *old;
+	
+	if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
+	{
+		if( g_strcasecmp( set_getstr( &ic->bee->set, "handle_unknown" ), "add" ) == 0 )
+		{
+			bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL );
+		}
+		else
+		{
+			if( g_strcasecmp( set_getstr( &ic->bee->set, "handle_unknown" ), "ignore" ) != 0 )
+			{
+				imcb_log( ic, "imcb_buddy_status() for unknown handle %s:\n"
+				              "flags = %d, state = %s, message = %s", handle, flags,
+				              state ? state : "NULL", message ? message : "NULL" );
+			}
+			
+			return;
+		}
+	}
+	
+	/* May be nice to give the UI something to compare against. */
+	old = g_memdup( bu, sizeof( bee_user_t ) );
+	
+	/* TODO(wilmer): OPT_AWAY, or just state == NULL ? */
+	bu->flags = flags;
+	bu->status = g_strdup( ( flags & OPT_AWAY ) && state == NULL ? "Away" : state );
+	bu->status_msg = g_strdup( message );
+	
+	if( bee->ui->user_status )
+		bee->ui->user_status( bee, bu, old );
+	
+	g_free( old->status_msg );
+	g_free( old->status );
+	g_free( old );
+}
+
+void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle )
+{
+	bee_t *bee = ic->bee;
+	bee_user_t *bu;
+	
+	if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
+		return;
+	
+	bu->login_time = login;
+	bu->idle_time = idle;
+}
+
+void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at )
+{
+	bee_t *bee = ic->bee;
+	bee_user_t *bu;
+	
+	bu = bee_user_by_handle( bee, ic, handle );
+	
+	if( !bu )
+	{
+		char *h = set_getstr( &bee->set, "handle_unknown" );
+		
+		if( g_strcasecmp( h, "ignore" ) == 0 )
+		{
+			return;
+		}
+		else if( g_strncasecmp( h, "add", 3 ) == 0 )
+		{
+			bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL );
+		}
+	}
+	
+	if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) ||
+	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) )
+		strip_html( msg );
+	
+	if( bee->ui->user_msg && bu )
+		bee->ui->user_msg( bee, bu, msg, sent_at );
+	else
+		imcb_log( ic, "Message from unknown handle %s:\n%s", handle, msg );
+}
+
+void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags )
+{
+	bee_user_t *bu;
+	
+	if( ic->bee->ui->user_typing &&
+	    ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) )
+	{
+		ic->bee->ui->user_typing( ic->bee, bu, flags );
+	}
+}
Index: protocols/ft.h
===================================================================
--- protocols/ft.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/ft.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,176 @@
+/********************************************************************\
+* BitlBee -- An IRC to other IM-networks gateway                     *
+*                                                                    *
+* Copyright 2006 Marijn Kruisselbrink and others                     *
+\********************************************************************/
+
+/* Generic file transfer header                                     */
+
+/*
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef _FT_H
+#define _FT_H
+
+/*
+ * One buffer is needed for each transfer. The receiver stores a message
+ * in it and gives it to the sender. The sender will stall the receiver
+ * till the buffer has been sent out.
+ */
+#define FT_BUFFER_SIZE 2048
+
+typedef enum {
+	FT_STATUS_LISTENING	= 1,
+	FT_STATUS_TRANSFERRING	= 2,
+	FT_STATUS_FINISHED	= 4,
+	FT_STATUS_CANCELED	= 8,
+	FT_STATUS_CONNECTING	= 16
+} file_status_t;
+
+/*
+ * This structure holds all irc specific information regarding an incoming (from the point of view of
+ * the irc client) file transfer. New instances of this struct should only be created by calling the
+ * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various
+ * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed,
+ * canceled, or the connection to the irc client has been lost (note that also if only the irc connection
+ * and not the file transfer connection is lost, the file transfer will still be canceled and freed).
+ *
+ * The following (poor ascii-art) diagram illustrates what methods are called for which status-changes:
+ *
+ *	                        /-----------\                    /----------\
+ *	               -------> | LISTENING | -----------------> | CANCELED |
+ *	                        \-----------/  [canceled,]free   \----------/
+ *	                              |
+ *	                              | accept
+ *	                              V
+ *	               /------ /-------------\                    /------------------------\
+ *	   out_of_data |       | TRANSFERING | -----------------> | TRANSFERING | CANCELED |
+ *	               \-----> \-------------/  [canceled,]free   \------------------------/
+ *	                              |
+ *	                              | finished,free
+ *	                              V
+ *	                 /------------------------\
+ *	                 | TRANSFERING | FINISHED |
+ *	                 \------------------------/
+ */
+typedef struct file_transfer {
+
+	/* Are we sending something? */
+	int sending;
+
+	/*
+	 * The current status of this file transfer.
+	 */ 
+	file_status_t status;
+	
+	/*
+	 * file size
+	 */
+	size_t file_size;
+	
+	/*
+	 * Number of bytes that have been successfully transferred.
+	 */
+	size_t bytes_transferred;
+
+	/*
+	 * Time started. Used to calculate kb/s.
+	 */
+	time_t started;
+
+	/*
+	 * file name
+	 */
+	char *file_name;
+
+	/*
+	 * A unique local ID for this file transfer.
+	 */
+	unsigned int local_id;
+
+	/*
+	 * IM-protocol specific data associated with this file transfer.
+	 */
+	gpointer data;
+	struct im_connection *ic;
+	
+	/*
+	 * Private data.
+	 */
+	gpointer priv;
+	
+	/*
+	 * If set, called after succesful connection setup.
+	 */
+	void (*accept) ( struct file_transfer *file );
+	
+	/*
+	 * If set, called when the transfer is canceled or finished.
+	 * Subsequently, this structure will be freed.
+	 *
+	 */
+	void (*free) ( struct file_transfer *file );
+	
+	/*
+	 * If set, called when the transfer is finished and successful.
+	 */
+	void (*finished) ( struct file_transfer *file );
+	
+	/*
+	 * If set, called when the transfer is canceled.
+	 * ( canceled either by the transfer implementation or by
+	 *  a call to imcb_file_canceled )
+	 */
+	void (*canceled) ( struct file_transfer *file, char *reason );
+	
+	/*
+	 * called by the sending side to indicate that it is writable.
+	 * The callee should check if data is available and call the 
+	 * function(as seen below) if that is the case.
+	 */
+	gboolean (*write_request) ( struct file_transfer *file );
+
+	/*
+	 * When sending files, protocols register this function to receive data.
+	 * This should only be called once after write_request is called. The caller
+	 * should not read more data until write_request is called again. This technique
+	 * avoids buffering.
+	 */
+	gboolean (*write) (struct file_transfer *file, char *buffer, unsigned int len );
+
+	/* The send buffer associated with this transfer.
+	 * Since receivers always wait for a write_request call one is enough.
+	 */
+	char buffer[FT_BUFFER_SIZE];
+
+} file_transfer_t;
+
+/*
+ * This starts a file transfer from bitlbee to the user.
+ */
+file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size );
+
+/*
+ * This should be called by a protocol when the transfer is canceled. Note that
+ * the canceled() and free() callbacks given in file will be called by this function.
+ */
+void imcb_file_canceled( struct im_connection *ic, file_transfer_t *file, char *reason );
+
+gboolean imcb_file_recv_start( struct im_connection *ic, file_transfer_t *ft );
+
+void imcb_file_finished( struct im_connection *ic, file_transfer_t *file );
+#endif
Index: protocols/jabber/Makefile
===================================================================
--- protocols/jabber/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -8,9 +8,11 @@
 
 -include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/jabber/
+endif
 
 # [SH] Program variables
-objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o
+objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o
 
-CFLAGS += -Wall
 LFLAGS += -r
 
@@ -33,5 +35,5 @@
 $(objects): ../../Makefile.settings Makefile
 
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
 	@echo '*' Compiling $<
 	@$(CC) -c $(CFLAGS) $< -o $@
Index: protocols/jabber/conference.c
===================================================================
--- protocols/jabber/conference.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/conference.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -92,9 +92,11 @@
 {
 	char *normalized = jabber_normalize( name );
+	GSList *l;
 	struct groupchat *ret;
 	struct jabber_chat *jc;
 	
-	for( ret = ic->groupchats; ret; ret = ret->next )
-	{
+	for( l = ic->groupchats; l; l = l->next )
+	{
+		ret = l->data;
 		jc = ret->data;
 		if( strcmp( normalized, jc->name ) == 0 )
@@ -103,5 +105,5 @@
 	g_free( normalized );
 	
-	return ret;
+	return l ? ret : NULL;
 }
 
@@ -272,6 +274,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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/io.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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/iq.c
===================================================================
--- protocols/jabber/iq.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/iq.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -65,5 +65,5 @@
 		if( strcmp( s, XMLNS_VERSION ) == 0 )
 		{
-			xt_add_child( reply, xt_new_node( "name", "BitlBee", NULL ) );
+			xt_add_child( reply, xt_new_node( "name", set_getstr( &ic->acc->set, "user_agent" ), NULL ) );
 			xt_add_child( reply, xt_new_node( "version", BITLBEE_VERSION, NULL ) );
 			xt_add_child( reply, xt_new_node( "os", ARCH, NULL ) );
@@ -91,7 +91,7 @@
 			pack = 0;
 		}
-		else if( strcmp( s, XMLNS_DISCOVER ) == 0 )
-		{
-			const char *features[] = { XMLNS_DISCOVER,
+		else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 )
+		{
+			const char *features[] = { XMLNS_DISCO_INFO,
 			                           XMLNS_VERSION,
 			                           XMLNS_TIME,
@@ -99,4 +99,7 @@
 			                           XMLNS_MUC,
 			                           XMLNS_PING,
+			                           XMLNS_SI,
+			                           XMLNS_BYTESTREAMS,
+			                           XMLNS_FILETRANSFER,
 			                           NULL };
 			const char **f;
@@ -105,5 +108,5 @@
 			xt_add_attr( c, "category", "client" );
 			xt_add_attr( c, "type", "pc" );
-			xt_add_attr( c, "name", "BitlBee" );
+			xt_add_attr( c, "name", set_getstr( &ic->acc->set, "user_agent" ) );
 			xt_add_child( reply, c );
 			
@@ -118,5 +121,5 @@
 		{
 			xt_free_node( reply );
-			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" );
+			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL );
 			pack = 0;
 		}
@@ -124,16 +127,20 @@
 	else if( strcmp( type, "set" ) == 0 )
 	{
-		if( !( c = xt_find_node( node->children, "query" ) ) ||
-		    !( s = xt_find_attr( c, "xmlns" ) ) )
-		{
-			imcb_log( ic, "Warning: Received incomplete IQ-%s packet", type );
+		if( ( c = xt_find_node( node->children, "si" ) ) &&
+		    ( s = xt_find_attr( c, "xmlns" ) ) &&
+		    ( strcmp( s, XMLNS_SI ) == 0 ) )
+		{
+			return jabber_si_handle_request( ic, node, c );
+		}
+		else if( !( c = xt_find_node( node->children, "query" ) ) ||
+		         !( s = xt_find_attr( c, "xmlns" ) ) )
+		{
 			return XT_HANDLED;
 		}
-		
+		else if( strcmp( s, XMLNS_ROSTER ) == 0 )
+		{
 		/* This is a roster push. XMPP servers send this when someone
 		   was added to (or removed from) the buddy list. AFAIK they're
 		   sent even if we added this buddy in our own session. */
-		if( strcmp( s, XMLNS_ROSTER ) == 0 )
-		{
 			int bare_len = strlen( ic->acc->user );
 			
@@ -152,12 +159,17 @@
 				
 				xt_free_node( reply );
-				reply = jabber_make_error_packet( node, "not-allowed", "cancel" );
+				reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL );
 				pack = 0;
 			}
 		}
+		else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 )
+		{
+			/* Bytestream Request (stage 2 of file transfer) */
+			return jabber_bs_recv_request( ic, node, c );
+		}
 		else
 		{
 			xt_free_node( reply );
-			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" );
+			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL );
 			pack = 0;
 		}
@@ -370,5 +382,5 @@
 	while( ( c = xt_find_node( c, "item" ) ) )
 	{
-		struct xt_node *group = xt_find_node( node->children, "group" );
+		struct xt_node *group = xt_find_node( c->children, "group" );
 		char *jid = xt_find_attr( c, "jid" );
 		char *name = xt_find_attr( c, "name" );
@@ -379,7 +391,6 @@
 			if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) )
 			{
-				if( initial || imcb_find_buddy( ic, jid ) == NULL )
-					imcb_add_buddy( ic, jid, ( group && group->text_len ) ?
-					                           group->text : NULL );
+				imcb_add_buddy( ic, jid, ( group && group->text_len ) ?
+				                           group->text : NULL );
 				
 				if( name )
@@ -544,5 +555,5 @@
 static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
 
-int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name )
+int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group )
 {
 	struct xt_node *node;
@@ -554,4 +565,6 @@
 	if( name )
 		xt_add_attr( node, "name", name );
+	if( group )
+		xt_add_child( node, xt_new_node( "group", group, NULL ) );
 	
 	/* And pack it into a roster-add packet */
@@ -577,5 +590,5 @@
 	    strcmp( s, "result" ) == 0 )
 	{
-		if( imcb_find_buddy( ic, jid ) == NULL )
+		if( bee_user_by_handle( ic->bee, ic, jid ) == NULL )
 			imcb_add_buddy( ic, jid, NULL );
 	}
@@ -609,2 +622,174 @@
 	return st;
 }
+
+xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid )
+{
+	struct xt_node *node, *query;
+	struct jabber_buddy *bud;
+	
+	if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL )
+	{
+		/* Who cares about the unknown... */
+		imcb_log( ic, "Couldn't find buddy: %s", bare_jid);
+		return XT_HANDLED;
+	}
+	
+	if( bud->features ) /* been here already */
+		return XT_HANDLED;
+	
+	node = xt_new_node( "query", NULL, NULL );
+	xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO );
+	
+	if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) )
+	{
+		imcb_log( ic, "WARNING: Couldn't generate feature query" );
+		xt_free_node( node );
+		return XT_HANDLED;
+	}
+
+	jabber_cache_add( ic, query, jabber_iq_parse_features );
+
+	return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT;
+}
+
+xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+	struct xt_node *c;
+	struct jabber_buddy *bud;
+	char *feature, *xmlns, *from;
+
+	if( !( from = xt_find_attr( node, "from" ) ) ||
+	    !( c = xt_find_node( node->children, "query" ) ) ||
+	    !( xmlns = xt_find_attr( c, "xmlns" ) ) ||
+	    !( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" );
+		return XT_HANDLED;
+	}
+	if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL )
+	{
+		/* Who cares about the unknown... */
+		imcb_log( ic, "Couldn't find buddy: %s", from );
+		return XT_HANDLED;
+	}
+	
+	c = c->children;
+	while( ( c = xt_find_node( c, "feature" ) ) )
+	{
+		feature = xt_find_attr( c, "var" );
+		if( feature )
+			bud->features = g_slist_append( bud->features, g_strdup( feature ) );
+		c = c->next;
+	}
+
+	return XT_HANDLED;
+}
+
+xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns )
+{
+	struct xt_node *node, *query;
+	struct jabber_data *jd = ic->proto_data;
+	
+	node = xt_new_node( "query", NULL, NULL );
+	xt_add_attr( node, "xmlns", xmlns );
+	
+	if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) )
+	{
+		imcb_log( ic, "WARNING: Couldn't generate server query" );
+		xt_free_node( node );
+	}
+
+	jd->have_streamhosts--;
+	jabber_cache_add( ic, query, jabber_iq_parse_server_features );
+
+	return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT;
+}
+
+/*
+ * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info
+ */
+xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+	struct xt_node *c;
+	struct jabber_data *jd = ic->proto_data;
+	char *xmlns, *from;
+
+	if( !( c = xt_find_node( node->children, "query" ) ) ||
+	    !( from = xt_find_attr( node, "from" ) ) ||
+	    !( xmlns = xt_find_attr( c, "xmlns" ) ) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" );
+		return XT_HANDLED;
+	}
+
+	jd->have_streamhosts++;
+
+	if( strcmp( xmlns, XMLNS_DISCO_ITEMS ) == 0 )
+	{
+		char *itemjid;
+
+		/* answer from server */
+	
+		c = c->children;
+		while( ( c = xt_find_node( c, "item" ) ) )
+		{
+			itemjid = xt_find_attr( c, "jid" );
+			
+			if( itemjid )
+				jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO );
+
+			c = c->next;
+		}
+	}
+	else if( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 )
+	{
+		char *category, *type;
+
+		/* answer from potential proxy */
+
+		c = c->children;
+		while( ( c = xt_find_node( c, "identity" ) ) )
+		{
+			category = xt_find_attr( c, "category" );
+			type = xt_find_attr( c, "type" );
+
+			if( type && ( strcmp( type, "bytestreams" ) == 0 ) &&
+			    category && ( strcmp( category, "proxy" ) == 0 ) )
+				jabber_iq_query_server( ic, from, XMLNS_BYTESTREAMS );
+
+			c = c->next;
+		}
+	}
+	else if( strcmp( xmlns, XMLNS_BYTESTREAMS ) == 0 )
+	{
+		char *host, *jid, *port_s;
+		int port;
+
+		/* answer from proxy */
+
+		if( ( c = xt_find_node( c->children, "streamhost" ) ) &&
+		    ( host = xt_find_attr( c, "host" ) ) &&
+		    ( port_s = xt_find_attr( c, "port" ) ) &&
+		    ( sscanf( port_s, "%d", &port ) == 1 ) &&
+		    ( jid = xt_find_attr( c, "jid" ) ) )
+		{
+			jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 );
+			
+			sh->jid = g_strdup( jid );
+			sh->host = g_strdup( host );
+			g_snprintf( sh->port, sizeof( sh->port ), "%u", port );
+
+			imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port );
+			jd->streamhosts = g_slist_append( jd->streamhosts, sh );
+		}
+	}
+
+	if( jd->have_streamhosts == 0 )
+		jd->have_streamhosts++;
+
+	return XT_HANDLED;
+}
Index: protocols/jabber/jabber.c
===================================================================
--- protocols/jabber/jabber.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/jabber.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -65,4 +65,6 @@
 	
 	s = set_add( &acc->set, "priority", "0", set_eval_priority, acc );
+
+	s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc );
 	
 	s = set_add( &acc->set, "resource", "BitlBee", NULL, acc );
@@ -79,4 +81,6 @@
 	s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );
 	s->flags |= ACC_SET_OFFLINE_ONLY;
+	
+	s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc );
 	
 	s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
@@ -264,9 +268,21 @@
 	struct jabber_data *jd = ic->proto_data;
 	
+	while( jd->filetransfers )
+		imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" );
+
+	while( jd->streamhosts )
+	{
+		jabber_streamhost_t *sh = jd->streamhosts->data;
+		jd->streamhosts = g_slist_remove( jd->streamhosts, sh );
+		g_free( sh->jid );
+		g_free( sh->host );
+		g_free( sh );
+	}
+
 	if( jd->fd >= 0 )
 		jabber_end_stream( ic );
 	
 	while( ic->groupchats )
-		jabber_chat_free( ic->groupchats );
+		jabber_chat_free( ic->groupchats->data );
 	
 	if( jd->r_inpa >= 0 )
@@ -285,4 +301,6 @@
 	if( jd->node_cache )
 		g_hash_table_destroy( jd->node_cache );
+	
+	jabber_buddy_remove_all( ic );
 	
 	xt_free( jd->xt );
@@ -363,5 +381,5 @@
 		if( bud->away_state )
 			imcb_log( ic, "Away state: %s", bud->away_state->full_name );
-		imcb_log( ic, "Status message: %s", bud->away_message ? : "(none)" );
+		imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" );
 		
 		bud = bud->next;
@@ -377,6 +395,8 @@
 	/* state_txt == NULL -> Not away.
 	   Unknown state -> fall back to the first defined away state. */
-	jd->away_state = state_txt ? jabber_away_state_by_name( state_txt )
-	                 ? : jabber_away_state_list : NULL;
+	if( state_txt == NULL )
+		jd->away_state = NULL;
+	else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL )
+		jd->away_state = jabber_away_state_list;
 	
 	g_free( jd->away_message );
@@ -397,5 +417,5 @@
 	}
 	
-	if( jabber_add_to_roster( ic, who, NULL ) )
+	if( jabber_add_to_roster( ic, who, NULL, group ) )
 		presence_send_request( ic, who, "subscribe" );
 }
@@ -423,5 +443,5 @@
 }
 
-static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password )
+static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
 {
 	if( strchr( room, '@' ) == NULL )
@@ -470,5 +490,6 @@
 {
 	/* Just any whitespace character is enough as a keepalive for XMPP sessions. */
-	jabber_write( ic, "\n", 1 );
+	if( !jabber_write( ic, "\n", 1 ) )
+		return;
 	
 	/* This runs the garbage collection every minute, which means every packet
@@ -544,4 +565,5 @@
 	ret->send_typing = jabber_send_typing;
 	ret->handle_cmp = g_strcasecmp;
+	ret->transfer_request = jabber_si_transfer_request;
 
 	register_protocol( ret );
Index: protocols/jabber/jabber.h
===================================================================
--- protocols/jabber/jabber.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/jabber.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -61,4 +61,12 @@
 } jabber_buddy_flags_t;
 
+/* Stores a streamhost's (a.k.a. proxy) data */
+typedef struct
+{
+	char *jid;
+	char *host;
+	char port[6];
+} jabber_streamhost_t;
+
 typedef enum
 {
@@ -91,4 +99,8 @@
 	GHashTable *node_cache;
 	GHashTable *buddies;
+
+	GSList *filetransfers;
+	GSList *streamhosts;
+	int have_streamhosts;
 };
 
@@ -127,4 +139,5 @@
 	struct jabber_away_state *away_state;
 	char *away_message;
+	GSList *features;
 	
 	time_t last_msg;
@@ -140,4 +153,34 @@
 	char *my_full_jid; /* Separate copy because of case sensitivity. */
 	struct jabber_buddy *me;
+};
+
+struct jabber_transfer
+{
+	/* bitlbee's handle for this transfer */
+	file_transfer_t *ft;
+
+	/* the stream's private handle */
+	gpointer streamhandle;
+
+	/* timeout for discover queries */
+	gint disco_timeout;
+	gint disco_timeout_fired;
+
+	struct im_connection *ic;
+
+	struct jabber_buddy *bud;
+
+	int watch_in;
+	int watch_out;
+
+	char *ini_jid;
+	char *tgt_jid;
+	char *iq_id;
+	char *sid;
+	int accepted;
+
+	size_t bytesread, byteswritten;
+	int fd;
+	struct sockaddr_storage saddr;
 };
 
@@ -167,15 +210,22 @@
 
 /* Some supported extensions/legacy stuff */
-#define XMLNS_AUTH         "jabber:iq:auth"                     /* XEP-0078 */
-#define XMLNS_VERSION      "jabber:iq:version"                  /* XEP-0092 */
-#define XMLNS_TIME         "jabber:iq:time"                     /* XEP-0090 */
-#define XMLNS_PING         "urn:xmpp:ping"                      /* XEP-0199 */
-#define XMLNS_VCARD        "vcard-temp"                         /* XEP-0054 */
-#define XMLNS_DELAY        "jabber:x:delay"                     /* XEP-0091 */
-#define XMLNS_CHATSTATES   "http://jabber.org/protocol/chatstates"  /* 0085 */
-#define XMLNS_DISCOVER     "http://jabber.org/protocol/disco#info"  /* 0030 */
-#define XMLNS_MUC          "http://jabber.org/protocol/muc"     /* XEP-0045 */
-#define XMLNS_MUC_USER     "http://jabber.org/protocol/muc#user"/* XEP-0045 */
-#define XMLNS_CAPS         "http://jabber.org/protocol/caps"    /* XEP-0115 */
+#define XMLNS_AUTH         "jabber:iq:auth"                                      /* XEP-0078 */
+#define XMLNS_VERSION      "jabber:iq:version"                                   /* XEP-0092 */
+#define XMLNS_TIME         "jabber:iq:time"                                      /* XEP-0090 */
+#define XMLNS_PING         "urn:xmpp:ping"                                       /* XEP-0199 */
+#define XMLNS_VCARD        "vcard-temp"                                          /* XEP-0054 */
+#define XMLNS_DELAY        "jabber:x:delay"                                      /* XEP-0091 */
+#define XMLNS_XDATA        "jabber:x:data"                                       /* XEP-0004 */
+#define XMLNS_CHATSTATES   "http://jabber.org/protocol/chatstates"               /* XEP-0085 */
+#define XMLNS_DISCO_INFO   "http://jabber.org/protocol/disco#info"               /* XEP-0030 */
+#define XMLNS_DISCO_ITEMS  "http://jabber.org/protocol/disco#items"              /* XEP-0030 */
+#define XMLNS_MUC          "http://jabber.org/protocol/muc"                      /* XEP-0045 */
+#define XMLNS_MUC_USER     "http://jabber.org/protocol/muc#user"                 /* XEP-0045 */
+#define XMLNS_CAPS         "http://jabber.org/protocol/caps"                     /* XEP-0115 */
+#define XMLNS_FEATURE      "http://jabber.org/protocol/feature-neg"              /* XEP-0020 */
+#define XMLNS_SI           "http://jabber.org/protocol/si"                       /* XEP-0095 */
+#define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */
+#define XMLNS_BYTESTREAMS  "http://jabber.org/protocol/bytestreams"              /* XEP-0065 */
+#define XMLNS_IBB          "http://jabber.org/protocol/ibb"                      /* XEP-0047 */
 
 /* iq.c */
@@ -185,6 +235,18 @@
 int jabber_get_roster( struct im_connection *ic );
 int jabber_get_vcard( struct im_connection *ic, char *bare_jid );
-int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name );
+int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group );
 int jabber_remove_from_roster( struct im_connection *ic, char *handle );
+xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid );
+xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns );
+
+/* si.c */
+int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode );
+void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who );
+void jabber_si_free_transfer( file_transfer_t *ft);
+
+/* s5bytestream.c */
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode);
+gboolean jabber_bs_send_start( struct jabber_transfer *tf );
+gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len );
 
 /* message.c */
@@ -200,5 +262,5 @@
 char *set_eval_tls( set_t *set, char *value );
 struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children );
-struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type );
+struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code );
 void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func );
 struct xt_node *jabber_cache_get( struct im_connection *ic, char *id );
@@ -230,4 +292,5 @@
 int jabber_buddy_remove( struct im_connection *ic, char *full_jid );
 int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid );
+void jabber_buddy_remove_all( struct im_connection *ic );
 time_t jabber_get_timestamp( struct xt_node *xt );
 struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns );
Index: protocols/jabber/jabber_util.c
===================================================================
--- protocols/jabber/jabber_util.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/jabber_util.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -99,5 +99,5 @@
 }
 
-struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type )
+struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code )
 {
 	struct xt_node *node, *c;
@@ -111,4 +111,8 @@
 	c = xt_new_node( "error", NULL, c );
 	xt_add_attr( c, "type", err_type );
+	
+	/* Add the error code, if present */
+	if (err_code)
+		xt_add_attr( c, "code", err_code );
 	
 	/* To make the actual error packet, we copy the original packet and
@@ -275,6 +279,5 @@
 	presence_send_request( bla->ic, bla->handle, "subscribed" );
 	
-	if( imcb_find_buddy( bla->ic, bla->handle ) == NULL )
-		imcb_ask_add( bla->ic, bla->handle, NULL );
+	imcb_ask_add( bla->ic, bla->handle, NULL );
 	
 	g_free( bla->handle );
@@ -458,5 +461,5 @@
 		
 		if( bud == NULL && ( flags & GET_BUDDY_CREAT ) &&
-		    ( bare_exists || imcb_find_buddy( ic, jid ) ) )
+		    ( bare_exists || bee_user_by_handle( ic->bee, ic, jid ) ) )
 		{
 			*s = '/';
@@ -479,5 +482,6 @@
 		if( bud == NULL )
 			/* No match. Create it now? */
-			return ( ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid_ ) ) ?
+			return ( ( flags & GET_BUDDY_CREAT ) &&
+			         bee_user_by_handle( ic->bee, ic, jid_ ) ) ?
 			           jabber_buddy_add( ic, jid_ ) : NULL;
 		else if( bud->resource && ( flags & GET_BUDDY_EXACT ) )
@@ -665,10 +669,35 @@
 }
 
+static gboolean jabber_buddy_remove_all_cb( gpointer key, gpointer value, gpointer data )
+{
+	struct jabber_buddy *bud, *next;
+	
+	bud = value;
+	while( bud )
+	{
+		next = bud->next;
+		g_free( bud->ext_jid );
+		g_free( bud->full_jid );
+		g_free( bud->away_message );
+		g_free( bud );
+		bud = next;
+	}
+	
+	return TRUE;
+}
+
+void jabber_buddy_remove_all( struct im_connection *ic )
+{
+	struct jabber_data *jd = ic->proto_data;
+	
+	g_hash_table_foreach_remove( jd->buddies, jabber_buddy_remove_all_cb, NULL );
+	g_hash_table_destroy( jd->buddies );
+}
+
 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 )
@@ -688,28 +717,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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/message.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -55,9 +55,10 @@
 			struct xt_node *inv, *reason;
 			
-			if( strcmp( ns, XMLNS_MUC_USER ) == 0 &&
+			if( ns && strcmp( ns, XMLNS_MUC_USER ) == 0 &&
 			    ( inv = xt_find_node( c->children, "invite" ) ) )
 			{
 				room = from;
-				from = xt_find_attr( inv, "from" ) ? : from;
+				if( ( from = xt_find_attr( inv, "from" ) ) == NULL )
+					from = room;
 
 				g_string_append_printf( fullmsg, "<< \002BitlBee\002 - Invitation to chatroom %s >>\n", room );
@@ -72,5 +73,5 @@
 			{
 				bud->last_msg = time( NULL );
-				from = bud->ext_jid ? : bud->bare_jid;
+				from = bud->ext_jid ? bud->ext_jid : bud->bare_jid;
 			}
 			else
@@ -80,6 +81,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/presence.c
===================================================================
--- protocols/jabber/presence.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/jabber/presence.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -205,5 +205,5 @@
 	struct jabber_data *jd = ic->proto_data;
 	struct xt_node *node, *cap;
-	struct groupchat *c;
+	GSList *l;
 	int st;
 	
@@ -229,6 +229,7 @@
 	/* Have to send this update to all groupchats too, the server won't
 	   do this automatically. */
-	for( c = ic->groupchats; c && st; c = c->next )
-	{
+	for( l = ic->groupchats; l && st; l = l->next )
+	{
+		struct groupchat *c = l->data;
 		struct jabber_chat *jc = c->data;
 		
Index: protocols/jabber/s5bytestream.c
===================================================================
--- protocols/jabber/s5bytestream.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/jabber/s5bytestream.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,1153 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Jabber module - SOCKS5 Bytestreams ( XEP-0065 )                          *
+*                                                                           *
+*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#include "jabber.h"
+#include "sha1.h"
+#include "lib/ftutil.h"
+#include <poll.h>
+
+struct bs_transfer {
+
+	struct jabber_transfer *tf;
+
+	jabber_streamhost_t *sh;
+	GSList *streamhosts;
+
+	enum 
+	{ 
+		BS_PHASE_CONNECT, 
+		BS_PHASE_CONNECTED, 
+		BS_PHASE_REQUEST, 
+		BS_PHASE_REPLY
+	} phase;
+
+	/* SHA1( SID + Initiator JID + Target JID) */
+	char *pseudoadr;
+
+	gint connect_timeout;
+	
+	char peek_buf[64];
+	int peek_buf_len;
+};
+
+struct socks5_message
+{
+	unsigned char ver;
+	union
+	{
+		unsigned char cmd;
+		unsigned char rep;
+	} cmdrep;
+	unsigned char rsv;
+	unsigned char atyp;
+	unsigned char addrlen;
+	unsigned char address[40];
+	in_port_t port;
+} __attribute__ ((packed)); 
+
+char *socks5_reply_code[] = {
+	"succeeded",
+	"general SOCKS server failure",
+	"connection not allowed by ruleset",
+	"Network unreachable",
+	"Host unreachable",
+	"Connection refused",
+	"TTL expired",
+	"Command not supported",
+	"Address type not supported",
+	"unassigned"};
+
+/* connect() timeout in seconds. */
+#define JABBER_BS_CONTIMEOUT 15
+/* listen timeout */
+#define JABBER_BS_LISTEN_TIMEOUT  90
+
+/* very useful */
+#define ASSERTSOCKOP(op, msg) \
+	if( (op) == -1 ) \
+		return jabber_bs_abort( bt , msg ": %s", strerror( errno ) );
+
+gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... );
+void jabber_bs_canceled( file_transfer_t *ft , char *reason );
+void jabber_bs_free_transfer( file_transfer_t *ft );
+gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents );
+gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen );
+
+void jabber_bs_recv_answer_request( struct bs_transfer *bt );
+gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_recv_write_request( file_transfer_t *ft );
+gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error );
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode );
+
+gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error );
+gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts );
+gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond );
+static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+void jabber_bs_send_activate( struct bs_transfer *bt );
+
+/*
+ * Frees a bs_transfer struct and calls the SI free function
+ */
+void jabber_bs_free_transfer( file_transfer_t *ft) {
+	struct jabber_transfer *tf = ft->data;
+	struct bs_transfer *bt = tf->streamhandle;
+	jabber_streamhost_t *sh;
+
+	if ( bt->connect_timeout )
+	{
+		b_event_remove( bt->connect_timeout );
+		bt->connect_timeout = 0;
+	}
+
+	if ( tf->watch_in )
+		b_event_remove( tf->watch_in );
+	
+	if( tf->watch_out )
+		b_event_remove( tf->watch_out );
+	
+	g_free( bt->pseudoadr );
+
+	while( bt->streamhosts )
+	{
+		sh = bt->streamhosts->data;
+		bt->streamhosts = g_slist_remove( bt->streamhosts, sh );
+		g_free( sh->jid );
+		g_free( sh->host );
+		g_free( sh );
+	}
+	
+	g_free( bt );
+
+	jabber_si_free_transfer( ft );
+}
+
+/*
+ * Checks if buflen data is available on the socket and
+ * writes it to buffer if that's the case.
+ */
+gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen )
+{
+	int ret;
+	int fd = bt->tf->fd;
+
+	if( buflen > sizeof( bt->peek_buf ) )
+		return jabber_bs_abort( bt, "BUG: %d > sizeof(peek_buf)", buflen );
+
+	ASSERTSOCKOP( ret = recv( fd, bt->peek_buf + bt->peek_buf_len,
+		buflen - bt->peek_buf_len, 0 ), "recv() on SOCKS5 connection" );
+
+	if( ret == 0 )
+		return jabber_bs_abort( bt, "Remote end closed connection" );
+	
+	bt->peek_buf_len += ret;
+	memcpy( buffer, bt->peek_buf, bt->peek_buf_len );
+	
+	if( bt->peek_buf_len == buflen )
+	{
+		/* If we have everything the caller wanted, reset the peek buffer. */
+		bt->peek_buf_len = 0;
+		return buflen;
+	}
+	else
+		return bt->peek_buf_len;
+}
+
+
+/* 
+ * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect().
+ */
+gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond )
+{
+	struct bs_transfer *bt = data;
+
+	bt->connect_timeout = 0;
+
+	jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT );
+
+	return FALSE;
+}
+
+/* 
+ * Polls the socket, checks for errors and removes a connect timer
+ * if there is one.
+ */
+gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents )
+{
+	struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR };
+	
+	if ( bt->connect_timeout )
+	{
+		b_event_remove( bt->connect_timeout );
+		bt->connect_timeout = 0;
+	}
+
+	ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" )
+
+	if( pfd.revents & POLLERR )
+	{
+		int sockerror;
+		socklen_t errlen = sizeof( sockerror );
+
+		if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) )
+			return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" );
+
+		if ( bt->phase == BS_PHASE_CONNECTED )
+			return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) );
+
+		return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) );
+	}
+
+	if( pfd.revents & POLLHUP )
+		return jabber_bs_abort( bt, "Remote end closed connection" );
+	
+	*revents = pfd.revents;
+	
+	return TRUE;
+}
+
+/*
+ * Used for receive and send path.
+ */
+gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... )
+{
+	va_list params;
+	va_start( params, format );
+	char error[128];
+
+	if( vsnprintf( error, 128, format, params ) < 0 )
+		sprintf( error, "internal error parsing error string (BUG)" );
+	va_end( params );
+	if( bt->tf->ft->sending )
+		return jabber_bs_send_handshake_abort( bt, error );
+	else
+		return jabber_bs_recv_handshake_abort( bt, error );
+}
+
+/* Bad luck */
+void jabber_bs_canceled( file_transfer_t *ft , char *reason )
+{
+	struct jabber_transfer *tf = ft->data;
+
+	imcb_log( tf->ic, "File transfer aborted: %s", reason );
+}
+
+/*
+ * Parses an incoming bytestream request and calls jabber_bs_handshake on success.
+ */
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode)
+{
+	char *sid, *ini_jid, *tgt_jid, *mode, *iq_id;
+	struct jabber_data *jd = ic->proto_data;
+	struct jabber_transfer *tf = NULL;
+	GSList *tflist;
+	struct bs_transfer *bt;
+	GSList *shlist=NULL;
+	struct xt_node *shnode;
+
+	sha1_state_t sha;
+	char hash_hex[41];
+	unsigned char hash[20];
+	int i;
+	
+	if( !(iq_id   = xt_find_attr( node, "id" ) ) ||
+	    !(ini_jid = xt_find_attr( node, "from" ) ) ||
+	    !(tgt_jid = xt_find_attr( node, "to" ) ) ||
+	    !(sid     = xt_find_attr( qnode, "sid" ) ) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete SI bytestream request");
+		return XT_HANDLED;
+	}
+
+	if( ( mode = xt_find_attr( qnode, "mode" ) ) &&
+	      ( strcmp( mode, "tcp" ) != 0 ) ) 
+	{
+		imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) );
+		return XT_HANDLED;
+	}
+
+	shnode = qnode->children;
+	while( ( shnode = xt_find_node( shnode, "streamhost" ) ) )
+	{
+		char *jid, *host, *port_s;
+		int port;
+		if( ( jid = xt_find_attr( shnode, "jid" ) ) &&
+		    ( host = xt_find_attr( shnode, "host" ) ) &&
+		    ( port_s = xt_find_attr( shnode, "port" ) ) &&
+		    ( sscanf( port_s, "%d", &port ) == 1 ) )
+		{
+			jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 );
+			sh->jid = g_strdup(jid);
+			sh->host = g_strdup(host);
+			sprintf( sh->port, "%u", port );
+			shlist = g_slist_append( shlist, sh );
+		}
+		shnode = shnode->next;
+	}
+	
+	if( !shlist )
+	{
+		imcb_log( ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries");
+		return XT_HANDLED;
+	}
+
+	/* Let's see if we can find out what this bytestream should be for... */
+
+	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+	{
+		struct jabber_transfer *tft = tflist->data;
+		if( ( strcmp( tft->sid, sid ) == 0 ) &&
+		    ( strcmp( tft->ini_jid, ini_jid ) == 0 ) &&
+		    ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) )
+		{
+		    	tf = tft;
+			break;
+		}
+	}
+
+	if (!tf) 
+	{
+		imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid );
+		return XT_HANDLED;
+	}
+
+	/* iq_id and canceled can be reused since SI is done */
+	g_free( tf->iq_id );
+	tf->iq_id = g_strdup( iq_id );
+
+	tf->ft->canceled = jabber_bs_canceled;
+
+	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
+	sha1_init( &sha );
+	sha1_append( &sha, (unsigned char*) sid, strlen( sid ) );
+	sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) );
+	sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) );
+	sha1_finish( &sha, hash );
+	
+	for( i = 0; i < 20; i ++ )
+		sprintf( hash_hex + i * 2, "%02x", hash[i] );
+		
+	bt = g_new0( struct bs_transfer, 1 );
+	bt->tf = tf;
+	bt->streamhosts = shlist;
+	bt->sh = shlist->data;
+	bt->phase = BS_PHASE_CONNECT;
+	bt->pseudoadr = g_strdup( hash_hex );
+	tf->streamhandle = bt;
+	tf->ft->free = jabber_bs_free_transfer;
+
+	jabber_bs_recv_handshake( bt, -1, 0 ); 
+
+	return XT_HANDLED;
+}
+
+/*
+ * This is what a protocol handshake can look like in cooperative multitasking :)
+ * Might be confusing at first because it's called from different places and is recursing.
+ * (places being the event thread, bs_request, bs_handshake_abort, and itself)
+ *
+ * All in all, it turned out quite nice :)
+ */
+gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond )
+{
+
+	struct bs_transfer *bt = data;
+	short revents;
+	int gret;
+
+	if ( ( fd != -1 ) && !jabber_bs_poll( bt, fd, &revents ) )
+		return FALSE;
+	
+	switch( bt->phase ) 
+	{
+	case BS_PHASE_CONNECT:
+		{
+			struct addrinfo hints, *rp;
+
+			memset( &hints, 0, sizeof( struct addrinfo ) );
+			hints.ai_socktype = SOCK_STREAM;
+
+			if ( ( gret = getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) ) != 0 )
+				return jabber_bs_abort( bt, "getaddrinfo() failed: %s", gai_strerror( gret ) );
+
+			ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" );
+
+			sock_make_nonblocking( fd );
+
+			imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, bt->sh->host, bt->sh->port );
+
+			if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) &&
+			    ( errno != EINPROGRESS ) )
+				return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) );
+
+			freeaddrinfo( rp );
+
+			bt->phase = BS_PHASE_CONNECTED;
+			
+			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 */
+			bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt );
+
+			bt->tf->watch_in = 0;
+			return FALSE;
+		}
+	case BS_PHASE_CONNECTED:
+		{
+			struct {
+				unsigned char ver;
+				unsigned char nmethods;
+				unsigned char method;
+			} socks5_hello = {
+				.ver = 5,
+				.nmethods = 1,
+				.method = 0x00 /* no auth */
+				/* one could also implement username/password. If you know
+				 * a jabber client or proxy that actually does it, tell me.
+				 */
+			};
+			
+			ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" );
+
+			bt->phase = BS_PHASE_REQUEST;
+
+			bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_recv_handshake, bt );
+
+			bt->tf->watch_out = 0;
+			return FALSE;
+		}
+	case BS_PHASE_REQUEST:
+		{
+			struct socks5_message socks5_connect = 
+			{
+				.ver = 5,
+				.cmdrep.cmd = 0x01,
+				.rsv = 0,
+				.atyp = 0x03,
+				.addrlen = strlen( bt->pseudoadr ),
+				.port = 0
+			};
+			int ret;
+			char buf[2];
+
+			/* If someone's trying to be funny and sends only one byte at a time we'll fail :) */
+			ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" );
+
+			if( !( ret == 2 ) ||
+			    !( buf[0] == 5 ) ||
+			    !( buf[1] == 0 ) )
+				return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)",
+									ret, buf[0], buf[1] );
+
+			/* copy hash into connect message */
+			memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen );
+
+			ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" );
+
+			bt->phase = BS_PHASE_REPLY;
+
+			return TRUE;
+		}
+	case BS_PHASE_REPLY:
+		{
+			struct socks5_message socks5_reply;
+			int ret;
+
+			if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) )
+				return FALSE;
+
+			if ( ret < 5 ) /* header up to address length */
+				return TRUE;
+			else if( ret < sizeof( struct socks5_message ) )
+			{
+				/* Either a buggy proxy or just one that doesnt regard
+				 * the SHOULD in XEP-0065 saying the reply SHOULD
+				 * contain the address. We'll take it, so make sure the
+				 * next jabber_bs_peek starts with an empty buffer. */
+				bt->peek_buf_len = 0;
+			}
+
+			if( !( socks5_reply.ver == 5 ) ||
+			    !( socks5_reply.cmdrep.rep == 0 ) ) {
+			    	char errstr[128] = "";
+				if( ( socks5_reply.ver == 5 ) && ( socks5_reply.cmdrep.rep < 
+				    ( sizeof( socks5_reply_code ) / sizeof( socks5_reply_code[0] ) ) ) ) {
+					sprintf( errstr, "with \"%s\" ", socks5_reply_code[ socks5_reply.cmdrep.rep ] );
+				}
+				return jabber_bs_abort( bt, "SOCKS5 CONNECT failed %s(reply: ver=%d, rep=%d, atyp=%d, addrlen=%d)", 
+					errstr,
+					socks5_reply.ver,
+					socks5_reply.cmdrep.rep,
+					socks5_reply.atyp,
+					socks5_reply.addrlen);
+			}
+			
+			/* usually a proxy sends back the 40 bytes address but I encountered at least one (of jabber.cz) 
+			 * that sends atyp=0 addrlen=0 and only 6 bytes (one less than one would expect).
+			 * Therefore I removed the wait for more bytes. Since we don't care about what else the proxy
+			 * is sending, it shouldnt matter */
+
+			if( bt->tf->ft->sending )
+				jabber_bs_send_activate( bt );
+			else
+				jabber_bs_recv_answer_request( bt );
+
+			return FALSE;
+		}
+	default:
+		/* BUG */
+		imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" );
+
+		bt->tf->watch_in = 0;
+		return FALSE;
+	}
+}
+
+/*
+ * If the handshake failed we can try the next streamhost, if there is one.
+ * An intelligent sender would probably specify himself as the first streamhost and
+ * a proxy as the second (Kopete and PSI are examples here). That way, a (potentially) 
+ * slow proxy is only used if neccessary. This of course also means, that the timeout
+ * per streamhost should be kept short. If one or two firewalled adresses are specified,
+ * they have to timeout first before a proxy is tried.
+ */
+gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error )
+{
+	struct jabber_transfer *tf = bt->tf;
+	struct xt_node *reply, *iqnode;
+	GSList *shlist;
+
+	imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", 
+		  tf->ft->file_name, 
+		  bt->sh->host,
+		  bt->sh->port,
+		  error );
+
+	/* Alright, this streamhost failed, let's try the next... */
+	bt->phase = BS_PHASE_CONNECT;
+	shlist = g_slist_find( bt->streamhosts, bt->sh );
+	if( shlist && shlist->next )
+	{
+		bt->sh = shlist->next->data;
+		return jabber_bs_recv_handshake( bt, -1, 0 );
+	}
+
+
+	/* out of stream hosts */
+
+	iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL );
+	reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" );
+	xt_free_node( iqnode );
+
+	xt_add_attr( reply, "id", tf->iq_id );
+		
+	if( !jabber_write_packet( tf->ic, reply ) )
+		imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" );
+	xt_free_node( reply );
+
+	imcb_file_canceled( tf->ic, tf->ft, "couldn't connect to any streamhosts" );
+
+	/* MUST always return FALSE! */
+	return FALSE;
+}
+
+/* 
+ * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose.
+ * If he is the streamhost himself, he might already know that. However, if it's a proxy,
+ * the initiator will have to make a connection himself.
+ */
+void jabber_bs_recv_answer_request( struct bs_transfer *bt )
+{
+	struct jabber_transfer *tf = bt->tf;
+	struct xt_node *reply;
+
+	imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s", 
+		  tf->ft->file_name, 
+		  bt->sh->host,
+		  bt->sh->port );
+
+	tf->ft->data = tf;
+	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;
+
+	reply = xt_new_node( "streamhost-used", NULL, NULL );
+	xt_add_attr( reply, "jid", bt->sh->jid );
+
+	reply = xt_new_node( "query", NULL, reply );
+	xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS );
+
+	reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply );
+
+	xt_add_attr( reply, "id", tf->iq_id );
+		
+	if( !jabber_write_packet( tf->ic, reply ) )
+		imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream response" );
+	xt_free_node( reply );
+}
+
+/* 
+ * This function is called from write_request directly. If no data is available, it will install itself
+ * as a watcher for input on fd and once that happens, deliver the data and unschedule itself again.
+ */
+gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond )
+{
+	int ret;
+	struct bs_transfer *bt = data;
+	struct jabber_transfer *tf = bt->tf;
+
+	if( fd != -1 ) /* called via event thread */
+	{
+		tf->watch_in = 0;
+		ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" );
+	}
+	else
+	{
+		/* called directly. There might not be any data available. */
+		if( ( ( ret = recv( tf->fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) ) == -1 ) &&
+		    ( errno != EAGAIN ) )
+		    return jabber_bs_abort( bt, "Receiving: %s", strerror( errno ) );
+
+		if( ( ret == -1 ) && ( errno == EAGAIN ) )
+		{
+			tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
+			return FALSE;
+		}
+	}
+
+	/* shouldn't happen since we know the file size */
+	if( ret == 0 )
+		return jabber_bs_abort( bt, "Remote end closed connection" );
+	
+	tf->bytesread += ret;
+
+	if( tf->bytesread >= tf->ft->file_size )
+		imcb_file_finished( tf->ic, tf->ft );
+
+	tf->ft->write( tf->ft, tf->ft->buffer, ret );	
+
+	return FALSE;
+}
+
+/* 
+ * imc callback that is invoked when it is ready to receive some data.
+ */
+gboolean jabber_bs_recv_write_request( file_transfer_t *ft )
+{
+	struct jabber_transfer *tf = ft->data;
+
+	if( tf->watch_in )
+	{
+		imcb_file_canceled( tf->ic, ft, "BUG in jabber file transfer: write_request called when already watching for input" );
+		return FALSE;
+	}
+	
+	jabber_bs_recv_read( tf->streamhandle, -1 , 0 );
+
+	return TRUE;
+}
+
+/* 
+ * Issues a write_request to imc.
+ * */
+gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond )
+{
+	struct bs_transfer *bt = data;
+
+	bt->tf->watch_out = 0;
+
+	bt->tf->ft->write_request( bt->tf->ft );
+
+	return FALSE;
+}
+
+/*
+ * This should only be called if we can write, so just do it.
+ * Add a write watch so we can write more during the next cycle (if possible).
+ */
+gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len )
+{
+	struct jabber_transfer *tf = ft->data;
+	struct bs_transfer *bt = tf->streamhandle;
+	int ret;
+
+	if( tf->watch_out )
+		return jabber_bs_abort( bt, "BUG: write() called while watching " );
+	
+	/* TODO: catch broken pipe */
+	ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" );
+
+	tf->byteswritten += ret;
+	
+	/* TODO: this should really not be fatal */
+	if( ret < len )
+		return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len );
+
+	if( tf->byteswritten >= ft->file_size )
+		imcb_file_finished( tf->ic, ft );
+	else
+		bt->tf->watch_out = b_input_add( tf->fd, B_EV_IO_WRITE, jabber_bs_send_can_write, bt );
+		
+	return TRUE;
+}
+
+/*
+ * Handles the reply by the receiver containing the used streamhost.
+ */
+static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) {
+	struct jabber_transfer *tf = NULL;
+	struct jabber_data *jd = ic->proto_data;
+	struct bs_transfer *bt;
+	GSList *tflist;
+	struct xt_node *c;
+	char *sid, *jid;
+
+	if( !( c = xt_find_node( node->children, "query" ) ) ||
+	    !( c = xt_find_node( c->children, "streamhost-used" ) ) ||
+	    !( jid = xt_find_attr( c, "jid" ) ) )
+
+	{
+		imcb_log( ic, "WARNING: Received incomplete bytestream reply" );
+		return XT_HANDLED;
+	}
+	
+	if( !( c = xt_find_node( orig->children, "query" ) ) ||
+	    !( sid = xt_find_attr( c, "sid" ) ) )
+	{
+		imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" );
+		return XT_HANDLED;
+	}
+
+	/* Let's see if we can find out what this bytestream should be for... */
+
+	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+	{
+		struct jabber_transfer *tft = tflist->data;
+		if( ( strcmp( tft->sid, sid ) == 0 ) )
+		{
+		    	tf = tft;
+			break;
+		}
+	}
+
+	if( !tf )
+	{
+		imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" );
+		return XT_HANDLED;
+	}
+
+	bt = tf->streamhandle;
+
+	tf->accepted = TRUE;
+
+	if( strcmp( jid, tf->ini_jid ) == 0 )
+	{
+		/* we're streamhost and target */
+		if( bt->phase == BS_PHASE_REPLY )
+		{
+			/* handshake went through, let's start transferring */
+			tf->ft->write_request( tf->ft );
+		}
+	} else
+	{
+		/* using a proxy, abort listen */
+
+		if( tf->watch_in )
+		{
+			b_event_remove( tf->watch_in );
+			tf->watch_in = 0;
+		}
+		
+		if( tf->fd != -1 ) {
+			closesocket( tf->fd );
+			tf->fd = -1;
+		}
+
+		if ( bt->connect_timeout )
+		{
+			b_event_remove( bt->connect_timeout );
+			bt->connect_timeout = 0;
+		}
+
+		GSList *shlist;
+		for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) )
+		{
+			jabber_streamhost_t *sh = shlist->data;
+			if( strcmp( sh->jid, jid ) == 0 )
+			{
+				bt->sh = sh;
+				jabber_bs_recv_handshake( bt, -1, 0 );
+				return XT_HANDLED;
+			}
+		}
+
+		imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid );
+	}
+
+	return XT_HANDLED;
+}
+
+/* 
+ * Tell the proxy to activate the stream. Looks like this:
+ *
+ * <iq type=set>
+ * 	<query xmlns=bs sid=sid>
+ * 		<activate>tgt_jid</activate>
+ * 	</query>
+ * </iq>
+ */
+void jabber_bs_send_activate( struct bs_transfer *bt )
+{
+	struct xt_node *node;
+
+	node = xt_new_node( "activate", bt->tf->tgt_jid, NULL );
+	node = xt_new_node( "query", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS );
+	xt_add_attr( node, "sid", bt->tf->sid );
+	node = jabber_make_packet( "iq", "set", bt->sh->jid, node );
+
+	jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate );
+
+	jabber_write_packet( bt->tf->ic, node );
+}
+
+/*
+ * The proxy has activated the bytestream.
+ * We can finally start pushing some data out.
+ */
+static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+	char *sid;
+	GSList *tflist;
+	struct jabber_transfer *tf = NULL;
+	struct xt_node *query;
+	struct jabber_data *jd = ic->proto_data;
+
+	query = xt_find_node( orig->children, "query" );
+	sid = xt_find_attr( query, "sid" );
+
+	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+	{
+		struct jabber_transfer *tft = tflist->data;
+		if( ( strcmp( tft->sid, sid ) == 0 ) )
+		{
+		    	tf = tft;
+			break;
+		}
+	}
+
+	if( !tf )
+	{
+		imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" );
+		return XT_HANDLED;
+	}
+
+	imcb_log( tf->ic, "File %s: SOCKS5 handshake and activation successful! Transfer about to start...", tf->ft->file_name );
+
+	/* handshake went through, let's start transferring */
+	tf->ft->write_request( tf->ft );
+
+	return XT_HANDLED;
+}
+
+jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *proxy )
+{
+	char *host, *port, *jid;
+	jabber_streamhost_t *sh;
+
+	if( ( ( host = strchr( proxy, ',' ) ) == 0 ) ||
+	     ( ( port = strchr( host+1, ',' ) ) == 0 ) ) {
+		imcb_log( ic, "Error parsing proxy setting: \"%s\" (ignored)", proxy );
+		return NULL;
+	}
+	
+	jid = proxy;
+	*host++ = '\0';
+	*port++ = '\0';
+
+	sh = g_new0( jabber_streamhost_t, 1 );
+	sh->jid = g_strdup( jid );
+	sh->host = g_strdup( host );
+	strcpy( sh->port, port );
+
+	return sh;
+}
+
+void jabber_si_set_proxies( struct bs_transfer *bt )
+{
+	struct jabber_transfer *tf = bt->tf;
+	struct jabber_data *jd = tf->ic->proto_data;
+	char *proxysetting = g_strdup ( set_getstr( &tf->ic->acc->set, "proxy" ) );
+	char *proxy, *next, *errmsg = NULL;
+	char port[6];
+	char host[HOST_NAME_MAX+1];
+	jabber_streamhost_t *sh, *sh2;
+	GSList *streamhosts = jd->streamhosts;
+
+	proxy = proxysetting;
+	while ( proxy && ( *proxy!='\0' ) ) {
+		if( ( next = strchr( proxy, ';' ) ) )
+			*next++ = '\0';	
+		
+		if( strcmp( proxy, "<local>" ) == 0 ) {
+			if( ( tf->fd = ft_listen( &tf->saddr, host, port, jd->fd, FALSE, &errmsg ) ) != -1 ) {
+				sh = g_new0( jabber_streamhost_t, 1 );
+				sh->jid = g_strdup( tf->ini_jid );
+				sh->host = g_strdup( host );
+				strcpy( sh->port, port );
+				bt->streamhosts = g_slist_append( bt->streamhosts, sh );
+
+				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 {
+				imcb_log( tf->ic, "Transferring file %s: couldn't listen locally(non fatal, check your ft_listen setting in bitlbee.conf): %s",
+					  tf->ft->file_name,
+					  errmsg );
+			}
+		} else if( strcmp( proxy, "<auto>" ) == 0 ) {
+			while ( streamhosts ) {
+				sh = g_new0( jabber_streamhost_t, 1 );
+				sh2 = streamhosts->data;
+				sh->jid = g_strdup( sh2->jid );
+				sh->host = g_strdup( sh2->host );
+				strcpy( sh->port, sh2->port );
+				bt->streamhosts = g_slist_append( bt->streamhosts, sh );
+				streamhosts = g_slist_next( streamhosts );
+			}
+		} else if( ( sh = jabber_si_parse_proxy( tf->ic, proxy ) ) )
+			bt->streamhosts = g_slist_append( bt->streamhosts, sh );
+		proxy = next;
+	}
+}
+
+/*
+ * Starts a bytestream.
+ */
+gboolean jabber_bs_send_start( struct jabber_transfer *tf )
+{
+	struct bs_transfer *bt;
+	sha1_state_t sha;
+	char hash_hex[41];
+	unsigned char hash[20];
+	int i,ret;
+
+	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
+	sha1_init( &sha );
+	sha1_append( &sha, (unsigned char*) tf->sid, strlen( tf->sid ) );
+	sha1_append( &sha, (unsigned char*) tf->ini_jid, strlen( tf->ini_jid ) );
+	sha1_append( &sha, (unsigned char*) tf->tgt_jid, strlen( tf->tgt_jid ) );
+	sha1_finish( &sha, hash );
+	
+	for( i = 0; i < 20; i ++ )
+		sprintf( hash_hex + i * 2, "%02x", hash[i] );
+		
+	bt = g_new0( struct bs_transfer, 1 );
+	bt->tf = tf;
+	bt->phase = BS_PHASE_CONNECT;
+	bt->pseudoadr = g_strdup( hash_hex );
+	tf->streamhandle = bt;
+	tf->ft->free = jabber_bs_free_transfer;
+	tf->ft->canceled = jabber_bs_canceled;
+
+	jabber_si_set_proxies( bt );
+
+	ret = jabber_bs_send_request( tf, bt->streamhosts);
+
+	return ret;
+}
+
+gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts )
+{
+	struct xt_node *shnode, *query, *iq;
+
+	query = xt_new_node( "query", NULL, NULL );
+	xt_add_attr( query, "xmlns", XMLNS_BYTESTREAMS );
+	xt_add_attr( query, "sid", tf->sid );
+	xt_add_attr( query, "mode", "tcp" );
+
+	while( streamhosts ) {
+		jabber_streamhost_t *sh = streamhosts->data;
+		shnode = xt_new_node( "streamhost", NULL, NULL );
+		xt_add_attr( shnode, "jid", sh->jid );
+		xt_add_attr( shnode, "host", sh->host );
+		xt_add_attr( shnode, "port", sh->port );
+
+		xt_add_child( query, shnode );
+
+		streamhosts = g_slist_next( streamhosts );
+	}
+
+
+	iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query );
+	xt_add_attr( iq, "from", tf->ini_jid );
+
+	jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply );
+
+	if( !jabber_write_packet( tf->ic, iq ) )
+		imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream request" );
+	return TRUE;
+}
+
+gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error )
+{
+	struct jabber_transfer *tf = bt->tf;
+	struct jabber_data *jd = tf->ic->proto_data;
+
+	/* TODO: did the receiver get here somehow??? */
+	imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s", 
+		  tf->ft->file_name, 
+		  error );
+
+	if( jd->streamhosts==NULL ) /* we're done here unless we have a proxy to try */
+		imcb_file_canceled( tf->ic, tf->ft, error );
+
+	/* MUST always return FALSE! */
+	return FALSE;
+}
+
+/*
+ * SOCKS5BYTESTREAM protocol for the sender
+ */
+gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond )
+{
+	struct bs_transfer *bt = data;
+	struct jabber_transfer *tf = bt->tf;
+	short revents;
+
+	if ( !jabber_bs_poll( bt, fd, &revents ) )
+		return FALSE;
+	
+	switch( bt->phase ) 
+	{
+	case BS_PHASE_CONNECT:
+		{
+			struct sockaddr_storage clt_addr;
+			socklen_t ssize = sizeof( clt_addr );
+			
+			/* Connect */
+
+			ASSERTSOCKOP( tf->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" );
+
+			closesocket( fd );
+			fd = tf->fd;
+			sock_make_nonblocking( fd );
+			
+			bt->phase = BS_PHASE_CONNECTED;
+
+			bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_send_handshake, bt );
+			return FALSE;
+		}
+	case BS_PHASE_CONNECTED:
+		{
+			int ret, have_noauth=FALSE;
+			struct {
+				unsigned char ver;
+				unsigned char method;
+			} socks5_auth_reply = { .ver = 5, .method = 0 };
+			struct {
+				unsigned char ver;
+				unsigned char nmethods;
+				unsigned char method;
+			} socks5_hello;
+
+			if( !( ret = jabber_bs_peek( bt, &socks5_hello, sizeof( socks5_hello ) ) ) )
+				return FALSE;
+
+			if( ret < sizeof( socks5_hello ) )
+				return TRUE;
+
+			if( !( socks5_hello.ver == 5 ) ||
+			    !( socks5_hello.nmethods >= 1 ) ||
+			    !( socks5_hello.nmethods < 32 ) )
+				return jabber_bs_abort( bt, "Invalid auth request ver=%d nmethods=%d method=%d", socks5_hello.ver, socks5_hello.nmethods, socks5_hello.method );
+
+			have_noauth = socks5_hello.method == 0;
+
+			if( socks5_hello.nmethods > 1 )
+			{
+				char mbuf[32];
+				int i;
+				ASSERTSOCKOP( ret = recv( fd, mbuf, socks5_hello.nmethods - 1, 0 ) , "Receiving auth methods" );
+				if( ret < ( socks5_hello.nmethods - 1 ) )
+					return jabber_bs_abort( bt, "Partial auth request");
+				for( i = 0 ; !have_noauth && ( i < socks5_hello.nmethods - 1 ) ; i ++ )
+					if( mbuf[i] == 0 )
+						have_noauth = TRUE;
+			}
+			
+			if( !have_noauth )
+				return jabber_bs_abort( bt, "Auth request didn't include no authentication" );
+
+			ASSERTSOCKOP( send( fd, &socks5_auth_reply, sizeof( socks5_auth_reply ) , 0 ), "Sending auth reply" );
+
+			bt->phase = BS_PHASE_REQUEST;
+
+			return TRUE;
+		}
+	case BS_PHASE_REQUEST:
+		{
+			struct socks5_message socks5_connect;
+			int msgsize = sizeof( struct socks5_message );
+			int ret;
+
+			if( !( ret = jabber_bs_peek( bt, &socks5_connect, msgsize ) ) )
+				return FALSE;
+
+			if( ret < msgsize )
+				return TRUE;
+
+			if( !( socks5_connect.ver == 5) ||
+			    !( socks5_connect.cmdrep.cmd == 1 ) ||
+			    !( socks5_connect.atyp == 3 ) ||
+			    !(socks5_connect.addrlen == 40 ) )
+				return jabber_bs_abort( bt, "Invalid SOCKS5 Connect message (addrlen=%d, ver=%d, cmd=%d, atyp=%d)", socks5_connect.addrlen, socks5_connect.ver, socks5_connect.cmdrep.cmd, socks5_connect.atyp );
+			if( !( memcmp( socks5_connect.address, bt->pseudoadr, 40 ) == 0 ) )
+				return jabber_bs_abort( bt, "SOCKS5 Connect message contained wrong digest");
+
+			socks5_connect.cmdrep.rep = 0;
+
+			ASSERTSOCKOP( send( fd, &socks5_connect, msgsize, 0 ), "Sending connect reply" );
+
+			bt->phase = BS_PHASE_REPLY;
+
+			imcb_log( tf->ic, "File %s: SOCKS5 handshake successful! Transfer about to start...", tf->ft->file_name );
+
+			if( tf->accepted )
+			{
+				/* streamhost-used message came already in(possible?), let's start sending */
+				tf->ft->write_request( tf->ft );
+			}
+
+			tf->watch_in = 0;
+			return FALSE;
+
+		}
+	default:
+		/* BUG */
+		imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" );
+
+		bt->tf->watch_in = 0;
+		return FALSE;
+	}
+}
+#undef ASSERTSOCKOP
Index: protocols/jabber/si.c
===================================================================
--- protocols/jabber/si.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/jabber/si.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,529 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Jabber module - SI packets                                               *
+*                                                                           *
+*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#include "jabber.h"
+#include "sha1.h"
+
+void jabber_si_answer_request( file_transfer_t *ft );
+int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf );
+
+/* file_transfer free() callback */
+void jabber_si_free_transfer( file_transfer_t *ft)
+{
+	struct jabber_transfer *tf = ft->data;
+	struct jabber_data *jd = tf->ic->proto_data;
+
+	if ( tf->watch_in )
+		b_event_remove( tf->watch_in );
+
+	jd->filetransfers = g_slist_remove( jd->filetransfers, tf );
+
+	if( tf->fd != -1 )
+	{
+		closesocket( tf->fd );
+		tf->fd = -1;
+	}
+
+	if( tf->disco_timeout )
+		b_event_remove( tf->disco_timeout );
+	
+	g_free( tf->ini_jid );
+	g_free( tf->tgt_jid );
+	g_free( tf->iq_id );
+	g_free( tf->sid );
+	g_free( tf );
+}
+
+/* file_transfer canceled() callback */
+void jabber_si_canceled( file_transfer_t *ft, char *reason )
+{
+	struct jabber_transfer *tf = ft->data;
+	struct xt_node *reply, *iqnode;
+
+	if( tf->accepted )
+		return;
+	
+	iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL );
+	xt_add_attr( iqnode, "id", tf->iq_id );
+	reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" );
+	xt_free_node( iqnode );
+	
+	if( !jabber_write_packet( tf->ic, reply ) )
+		imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" );
+	xt_free_node( reply );
+
+}
+
+int jabber_si_check_features( struct jabber_transfer *tf, GSList *features ) {
+	int foundft = FALSE, foundbt = FALSE, foundsi = FALSE;
+
+	while ( features )
+	{
+		if( !strcmp( features->data, XMLNS_FILETRANSFER ) )
+			foundft = TRUE;
+		if( !strcmp( features->data, XMLNS_BYTESTREAMS ) )
+			foundbt = TRUE;
+		if( !strcmp( features->data, XMLNS_SI ) )
+			foundsi = TRUE;
+
+		features = g_slist_next(features);
+	}
+
+	if( !foundft )
+		imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature file transfers" );
+	else if( !foundbt )
+		imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature byte streams (required)" );
+	else if( !foundsi )
+		imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature stream initiation (required)" );
+		
+	return foundft && foundbt && foundsi;
+}
+
+void jabber_si_transfer_start( struct jabber_transfer *tf ) {
+
+	if( !jabber_si_check_features( tf, tf->bud->features ) )
+		return;
+		
+	/* send the request to our buddy */
+	jabber_si_send_request( tf->ic, tf->bud->full_jid, tf );
+
+	/* and start the receive logic */
+	imcb_file_recv_start( tf->ic, tf->ft );
+
+}
+
+gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond )
+{
+	struct jabber_transfer *tf = data;
+	struct jabber_data *jd = tf->ic->proto_data;
+
+	tf->disco_timeout_fired++;
+
+	if( tf->bud->features && jd->have_streamhosts==1 ) {
+		tf->disco_timeout = 0;
+		jabber_si_transfer_start( tf );
+		return FALSE;
+	}
+
+	/* 8 seconds should be enough for server and buddy to respond */
+	if ( tf->disco_timeout_fired < 16 )
+		return TRUE;
+	
+	if( !tf->bud->features && jd->have_streamhosts!=1 )
+		imcb_log( tf->ic, "Couldn't get buddy's features nor discover all services of the server" );
+	else if( !tf->bud->features )
+		imcb_log( tf->ic, "Couldn't get buddy's features" );
+	else
+		imcb_log( tf->ic, "Couldn't discover some of the server's services" );
+	
+	tf->disco_timeout = 0;
+	jabber_si_transfer_start( tf );
+	return FALSE;
+}
+
+void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ) 
+{
+	struct jabber_transfer *tf;
+	struct jabber_data *jd = ic->proto_data;
+	struct jabber_buddy *bud;
+	char *server = jd->server, *s;
+
+	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
+		bud = jabber_buddy_by_ext_jid( ic, who, 0 );
+	else
+		bud = jabber_buddy_by_jid( ic, who, 0 );
+
+	if( bud == NULL )
+	{
+		imcb_file_canceled( ic, ft, "Couldn't find buddy (BUG?)" );
+		return;
+	}
+	
+	imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who );
+
+	tf = g_new0( struct jabber_transfer, 1 );
+
+	tf->ic = ic;
+	tf->ft = ft;
+	tf->fd = -1;
+	tf->ft->data = tf;
+	tf->ft->free = jabber_si_free_transfer;
+	tf->bud = bud;
+	ft->write = jabber_bs_send_write;
+
+	jd->filetransfers = g_slist_prepend( jd->filetransfers, tf );
+
+	/* query buddy's features and server's streaming proxies if neccessary */
+
+	if( !tf->bud->features )
+		jabber_iq_query_features( ic, bud->full_jid );
+
+	/* If <auto> is not set don't check for proxies */
+	if( ( jd->have_streamhosts!=1 ) && ( jd->streamhosts==NULL ) &&
+	    ( strstr( set_getstr( &ic->acc->set, "proxy" ), "<auto>" ) != NULL ) ) {
+		jd->have_streamhosts = 0;
+		jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS );
+	} else if ( jd->streamhosts!=NULL )
+		jd->have_streamhosts = 1;
+
+	/* if we had to do a query, wait for the result. 
+	 * Otherwise fire away. */
+	if( !tf->bud->features || jd->have_streamhosts!=1 )
+		tf->disco_timeout = b_timeout_add( 500, jabber_si_waitfor_disco, tf );
+	else
+		jabber_si_transfer_start( tf );
+}
+
+/*
+ * First function that gets called when a file transfer request comes in.
+ * A lot to parse.
+ *
+ * We choose a stream type from the options given by the initiator.
+ * Then we wait for imcb to call the accept or cancel callbacks.
+ */
+int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode)
+{
+	struct xt_node *c, *d, *reply;
+	char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s;
+	struct jabber_buddy *bud;
+	int requestok = FALSE;
+	char *name, *cmp;
+	size_t size;
+	struct jabber_transfer *tf;
+	struct jabber_data *jd = ic->proto_data;
+	file_transfer_t *ft;
+	
+	/* All this means we expect something like this: ( I think )
+	 * <iq from=... to=... id=...>
+	 * 	<si id=id xmlns=si profile=ft>
+	 * 		<file xmlns=ft/>
+	 * 		<feature xmlns=feature>
+	 * 			<x xmlns=xdata type=submit>
+	 * 				<field var=stream-method>
+	 *
+	 */
+	if( !( ini_jid 		= xt_find_attr(   node, "from" ) 			) ||
+	    !( tgt_jid 		= xt_find_attr(   node, "to" ) 				) ||
+	    !( iq_id 		= xt_find_attr(   node, "id" ) 				) ||
+	    !( sid 		= xt_find_attr( sinode, "id" ) 				) ||
+	    !( cmp              = xt_find_attr( sinode, "profile" )                     ) ||
+	    !( 0               == strcmp( cmp, XMLNS_FILETRANSFER )			) ||
+	    !( d 		= xt_find_node( sinode->children, "file" ) 		) ||
+	    !( cmp = xt_find_attr( d, "xmlns" )						) ||
+	    !( 0               == strcmp( cmp, XMLNS_FILETRANSFER )			) ||
+	    !( name 		= xt_find_attr( d, "name" ) 				) ||
+	    !( size_s           = xt_find_attr( d, "size" )                             ) ||
+	    !( 1               == sscanf( size_s, "%zd", &size )                        ) ||
+	    !( d 		= xt_find_node( sinode->children, "feature" ) 		) ||
+	    !( cmp              = xt_find_attr( d, "xmlns" )				) ||
+	    !( 0               == strcmp( cmp, XMLNS_FEATURE )				) ||
+	    !( d 		= xt_find_node( d->children, "x" ) 			) ||
+	    !( cmp              = xt_find_attr( d, "xmlns" )				) ||
+	    !( 0               == strcmp( cmp, XMLNS_XDATA )				) ||
+	    !( cmp              = xt_find_attr( d, "type" )				) ||
+	    !( 0               == strcmp( cmp, "form" )					) ||
+	    !( d 		= xt_find_node( d->children, "field" ) 			) ||
+	    !( cmp              = xt_find_attr( d, "var" )				) ||
+	    !( 0               == strcmp( cmp, "stream-method" )			) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" );
+	}
+	else
+	{
+		/* Check if we support one of the options */
+
+		c = d->children;
+		while( ( c = xt_find_node( c, "option" ) ) )
+			if( ( d = xt_find_node( c->children, "value" ) ) &&
+			    ( d->text != NULL ) &&
+			    ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) )
+			{
+				requestok = TRUE;
+				break;
+			}
+
+		if ( !requestok )
+			imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid);
+	}
+	
+	if( requestok )
+	{
+		/* Figure out who the transfer should come frome... */
+
+		ext_jid = ini_jid;
+		if( ( s = strchr( ini_jid, '/' ) ) )
+		{
+			if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) )
+			{
+				bud->last_msg = time( NULL );
+				ext_jid = bud->ext_jid ? : bud->bare_jid;
+			}
+			else
+				*s = 0; /* We need to generate a bare JID now. */
+		}
+
+		if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) )
+		{ 
+			imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid);
+			requestok = FALSE;
+		}
+
+		*s = '/';
+	}
+	else
+	{ 
+		reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL );
+		if (!jabber_write_packet( ic, reply ))
+			imcb_log( ic, "WARNING: Error generating reply to file transfer request" );
+		xt_free_node( reply );
+		return XT_HANDLED;
+	}
+
+	/* Request is fine. */
+
+	tf = g_new0( struct jabber_transfer, 1 );
+
+	tf->ini_jid = g_strdup( ini_jid );
+	tf->tgt_jid = g_strdup( tgt_jid );
+	tf->iq_id = g_strdup( iq_id );
+	tf->sid = g_strdup( sid );
+	tf->ic = ic;
+	tf->ft = ft;
+	tf->fd = -1;
+	tf->ft->data = tf;
+	tf->ft->accept = jabber_si_answer_request;
+	tf->ft->free = jabber_si_free_transfer;
+	tf->ft->canceled = jabber_si_canceled;
+
+	jd->filetransfers = g_slist_prepend( jd->filetransfers, tf );
+
+	return XT_HANDLED;
+}
+
+/*
+ * imc called the accept callback which probably means that the user accepted this file transfer.
+ * We send our response to the initiator.
+ * In the next step, the initiator will send us a request for the given stream type.
+ * (currently that can only be a SOCKS5 bytestream)
+ */
+void jabber_si_answer_request( file_transfer_t *ft ) {
+	struct jabber_transfer *tf = ft->data;
+	struct xt_node *node, *sinode, *reply;
+
+	/* generate response, start with the SI tag */
+	sinode = xt_new_node( "si", NULL, NULL );
+	xt_add_attr( sinode, "xmlns", XMLNS_SI );
+	xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER );
+	xt_add_attr( sinode, "id", tf->sid );
+
+	/* now the file tag */
+	node = xt_new_node( "file", NULL, NULL );
+	xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER );
+
+	xt_add_child( sinode, node );
+
+	/* and finally the feature tag */
+	node = xt_new_node( "field", NULL, NULL );
+	xt_add_attr( node, "var", "stream-method" );
+	xt_add_attr( node, "type", "list-single" );
+
+	/* Currently all we can do. One could also implement in-band (IBB) */
+	xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) );
+
+	node = xt_new_node( "x", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_XDATA );
+	xt_add_attr( node, "type", "submit" );
+
+	node = xt_new_node( "feature", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_FEATURE );
+
+	xt_add_child( sinode, node );
+
+	reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode );
+	xt_add_attr( reply, "id", tf->iq_id );
+	
+	if( !jabber_write_packet( tf->ic, reply ) )
+		imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" );
+	else
+		tf->accepted = TRUE;
+	xt_free_node( reply );
+}
+
+static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+	struct xt_node *c, *d;
+	char *ini_jid, *tgt_jid, *iq_id, *cmp;
+	GSList *tflist;
+	struct jabber_transfer *tf=NULL;
+	struct jabber_data *jd = ic->proto_data;
+
+	if( !( tgt_jid = xt_find_attr( node, "from" ) ) ||
+	    !( ini_jid = xt_find_attr( node, "to" ) ) )
+	{
+		imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid );
+		return XT_HANDLED;
+	}
+	
+	/* All this means we expect something like this: ( I think )
+	 * <iq from=... to=... id=...>
+	 * 	<si xmlns=si>
+	 * 	[	<file xmlns=ft/>    ] <-- not neccessary
+	 * 		<feature xmlns=feature>
+	 * 			<x xmlns=xdata type=submit>
+	 * 				<field var=stream-method>
+	 * 					<value>
+	 */
+	if( !( tgt_jid = xt_find_attr( node, "from" ) ) ||
+	    !( ini_jid = xt_find_attr( node, "to" ) ) ||
+	    !( iq_id   = xt_find_attr( node, "id" ) ) ||
+	    !( c = xt_find_node( node->children, "si" ) ) ||
+	    !( cmp = xt_find_attr( c, "xmlns" ) ) ||
+	    !( strcmp( cmp, XMLNS_SI ) == 0 ) ||
+	    !( d = xt_find_node( c->children, "feature" ) ) ||
+	    !( cmp = xt_find_attr( d, "xmlns" ) ) ||
+	    !( strcmp( cmp, XMLNS_FEATURE ) == 0 ) ||
+	    !( d = xt_find_node( d->children, "x" ) ) ||
+	    !( cmp = xt_find_attr( d, "xmlns" ) ) ||
+	    !( strcmp( cmp, XMLNS_XDATA ) == 0 ) ||
+	    !( cmp = xt_find_attr( d, "type" ) ) ||
+	    !( strcmp( cmp, "submit" ) == 0 ) ||
+	    !( d = xt_find_node( d->children, "field" ) ) ||
+	    !( cmp = xt_find_attr( d, "var" ) ) ||
+	    !( strcmp( cmp, "stream-method" ) == 0 ) ||
+	    !( d = xt_find_node( d->children, "value" ) ) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" );
+		return XT_HANDLED;
+	}
+
+	if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { 
+		/* since we should only have advertised what we can do and the peer should
+		 * only have chosen what we offered, this should never happen */
+		imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text );
+			
+		return XT_HANDLED;
+	}
+	
+	/* Let's see if we can find out what this bytestream should be for... */
+
+	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+	{
+		struct jabber_transfer *tft = tflist->data;
+		if( ( strcmp( tft->iq_id, iq_id ) == 0 ) )
+		{
+		    	tf = tft;
+			break;
+		}
+	}
+
+	if (!tf) 
+	{
+		imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid );
+		return XT_HANDLED;
+	}
+
+	tf->ini_jid = g_strdup( ini_jid );
+	tf->tgt_jid = g_strdup( tgt_jid );
+
+	imcb_log( ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid );
+
+	jabber_bs_send_start( tf );
+
+	return XT_HANDLED;
+}
+
+int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf )
+{
+	struct xt_node *node, *sinode;
+	struct jabber_buddy *bud;
+
+	/* who knows how many bits the future holds :) */
+	char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ];
+
+	const char *methods[] = 
+	{  	
+		XMLNS_BYTESTREAMS,
+		//XMLNS_IBB,
+		NULL 
+	};
+	const char **m;
+	char *s;
+
+	/* Maybe we should hash this? */
+	tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id );
+	
+	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
+		bud = jabber_buddy_by_ext_jid( ic, who, 0 );
+	else
+		bud = jabber_buddy_by_jid( ic, who, 0 );
+
+	/* start with the SI tag */
+	sinode = xt_new_node( "si", NULL, NULL );
+	xt_add_attr( sinode, "xmlns", XMLNS_SI );
+	xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER );
+	xt_add_attr( sinode, "id", tf->sid );
+
+/*	if( mimetype ) 
+		xt_add_attr( node, "mime-type", mimetype ); */
+
+	/* now the file tag */
+/*	if( desc )
+ 		node = xt_new_node( "desc", descr, NULL ); */
+	node = xt_new_node( "range", NULL, NULL );
+
+	sprintf( filesizestr, "%zd", tf->ft->file_size );
+	node = xt_new_node( "file", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER );
+	xt_add_attr( node, "name", tf->ft->file_name );
+	xt_add_attr( node, "size", filesizestr );
+/*	if (hash)
+		xt_add_attr( node, "hash", hash );
+	if (date)
+		xt_add_attr( node, "date", date ); */
+
+	xt_add_child( sinode, node );
+
+	/* and finally the feature tag */
+	node = xt_new_node( "field", NULL, NULL );
+	xt_add_attr( node, "var", "stream-method" );
+	xt_add_attr( node, "type", "list-single" );
+
+	for ( m = methods ; *m ; m ++ )
+		xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) );
+
+	node = xt_new_node( "x", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_XDATA );
+	xt_add_attr( node, "type", "form" );
+
+	node = xt_new_node( "feature", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_FEATURE );
+
+	xt_add_child( sinode, node );
+
+	/* and we are there... */
+	node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode );
+	jabber_cache_add( ic, node, jabber_si_handle_response );
+	tf->iq_id = g_strdup( xt_find_attr( node, "id" ) );
+	
+	return jabber_write_packet( ic, node );
+}
Index: protocols/msn/Makefile
===================================================================
--- protocols/msn/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/msn/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -8,9 +8,11 @@
 
 -include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/msn/
+endif
 
 # [SH] Program variables
 objects = msn.o msn_util.o ns.o passport.o sb.o soap.o tables.o
 
-CFLAGS += -Wall
 LFLAGS += -r
 
@@ -33,5 +35,5 @@
 $(objects): ../../Makefile.settings Makefile
 
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
 	@echo '*' Compiling $<
 	@$(CC) -c $(CFLAGS) $< -o $@
Index: protocols/msn/invitation.c
===================================================================
--- protocols/msn/invitation.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/msn/invitation.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,622 @@
+/********************************************************************\
+* BitlBee -- An IRC to other IM-networks gateway                     *
+*                                                                    *
+* Copyright 2008 Uli Meis					     *
+* Copyright 2006 Marijn Kruisselbrink and others                     *
+\********************************************************************/
+
+/* MSN module - File transfer support             */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ 
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ 
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "bitlbee.h"
+#include "invitation.h"
+#include "msn.h"
+#include "lib/ftutil.h"
+
+#ifdef debug
+#undef debug
+#endif
+#define debug(msg...) log_message( LOGLVL_INFO, msg )
+
+static void msn_ftp_free( file_transfer_t *file );
+static void msn_ftpr_accept( file_transfer_t *file );
+static void msn_ftp_finished( file_transfer_t *file );
+static void msn_ftp_canceled( file_transfer_t *file, char *reason );
+static gboolean msn_ftpr_write_request( file_transfer_t *file );
+
+static gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond );
+static gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond );
+gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len );
+
+/*
+ * Vararg wrapper for imcb_file_canceled().
+ */
+gboolean msn_ftp_abort( file_transfer_t *file, char *format, ... )
+{
+        va_list params;
+        va_start( params, format );
+        char error[128];
+
+        if( vsnprintf( error, 128, format, params ) < 0 )
+                sprintf( error, "internal error parsing error string (BUG)" );
+        va_end( params );
+	imcb_file_canceled( file, error );
+	return FALSE;
+}
+
+/* very useful */
+#define ASSERTSOCKOP(op, msg) \
+	if( (op) == -1 ) \
+		return msn_ftp_abort( file , msg ": %s", strerror( errno ) );
+
+void msn_ftp_invitation_cmd( struct im_connection *ic, char *who, int cookie, char *icmd,
+			     char *trailer )
+{
+	struct msn_message *m = g_new0( struct msn_message, 1 );
+	
+	m->text = g_strdup_printf( "%s"
+		    "Invitation-Command: %s\r\n"
+		    "Invitation-Cookie: %u\r\n"
+		    "%s",
+		    MSN_INVITE_HEADERS,
+		    icmd,
+		    cookie,
+		    trailer);
+	
+	m->who = g_strdup( who );
+
+	msn_sb_write_msg( ic, m );
+}
+
+void msn_ftp_cancel_invite( struct im_connection *ic, char *who,  int cookie, char *code )
+{
+	char buf[64];
+
+	g_snprintf( buf, sizeof( buf ), "Cancel-Code: %s\r\n", code );
+	msn_ftp_invitation_cmd( ic, who, cookie, "CANCEL", buf );
+}
+
+void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *file, char *who )
+{
+	unsigned int cookie = time( NULL ); /* TODO: randomize */
+	char buf[2048];
+
+	msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 );
+	file->data = msn_file;
+	file->free = msn_ftp_free;
+	file->canceled = msn_ftp_canceled;
+	file->write = msn_ftps_write;
+	msn_file->md = ic->proto_data;
+	msn_file->invite_cookie = cookie;
+	msn_file->handle = g_strdup( who );
+	msn_file->dcc = file;
+	msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc );
+	msn_file->fd = -1;
+	msn_file->sbufpos = 3;
+
+	g_snprintf( buf, sizeof( buf ), 
+		"Application-Name: File Transfer\r\n"
+		"Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
+		"Application-File: %s\r\n"
+		"Application-FileSize: %zd\r\n",
+		file->file_name,
+		file->file_size);
+
+	msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, cookie, "INVITE", buf );
+
+	imcb_file_recv_start( file );
+}
+
+void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen )
+{
+	char *itype = msn_findheader( body, "Application-GUID:", blen );
+	char *name, *size, *invitecookie, *reject = NULL;
+	user_t *u;
+	size_t isize;
+	file_transfer_t *file;
+	
+	if( !itype || strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) != 0 ) {
+		/* Don't know what that is - don't care */
+		char *iname = msn_findheader( body, "Application-Name:", blen );
+		imcb_log( sb->ic, "Received unknown MSN invitation %s (%s) from %s",
+			  itype ? : "with no GUID", iname ? iname : "no application name", handle );
+		g_free( iname );
+		reject = "REJECT_NOT_INSTALLED";
+	} else if ( 
+		!( name = msn_findheader( body, "Application-File:", blen )) || 
+		!( size = msn_findheader( body, "Application-FileSize:", blen )) || 
+		!( invitecookie = msn_findheader( body, "Invitation-Cookie:", blen)) ||
+		!( isize = atoll( size ) ) ) { 
+		imcb_log( sb->ic, "Received corrupted transfer request from %s"
+			  "(name=%s, size=%s, invitecookie=%s)",
+			  handle, name, size, invitecookie );
+		reject = "REJECT";
+	} else if ( !( u = user_findhandle( sb->ic, handle ) ) ) {
+		imcb_log( sb->ic, "Error in parsing transfer request, User '%s'"
+			  "is not in contact list", handle );
+		reject = "REJECT";
+	} else if ( !( file = imcb_file_send_start( sb->ic, handle, name, isize ) ) ) {
+		imcb_log( sb->ic, "Error initiating transfer for request from %s for %s",
+			  handle, name );
+		reject = "REJECT";
+	} else {
+		msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 );
+		file->data = msn_file;
+		file->accept = msn_ftpr_accept;
+		file->free = msn_ftp_free;
+		file->finished = msn_ftp_finished;
+		file->canceled = msn_ftp_canceled;
+		file->write_request = msn_ftpr_write_request;
+		msn_file->md = sb->ic->proto_data;
+		msn_file->invite_cookie = cookie;
+		msn_file->handle = g_strdup( handle );
+		msn_file->dcc = file;
+		msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc );
+		msn_file->fd = -1;
+	}
+
+	if( reject )
+		msn_ftp_cancel_invite( sb->ic, sb->who, cookie, reject );
+
+	g_free( name );
+	g_free( size );
+	g_free( invitecookie );
+	g_free( itype );
+}
+
+msn_filetransfer_t* msn_find_filetransfer( struct msn_data *md, unsigned int cookie, char *handle )
+{
+	GSList *l;
+	
+	for( l = md->filetransfers; l; l = l->next ) {
+		msn_filetransfer_t *file = ( (file_transfer_t*) l->data )->data;
+		if( file->invite_cookie == cookie && strcmp( handle, file->handle ) == 0 ) {
+			return file;
+		}
+	}
+	return NULL;
+}
+
+gboolean msn_ftps_connected( gpointer data, gint fd, b_input_condition cond )
+{
+	file_transfer_t *file = data;
+	msn_filetransfer_t *msn_file = file->data;
+	struct sockaddr_storage clt_addr;
+	socklen_t ssize = sizeof( clt_addr );
+	
+	debug( "Connected to MSNFTP client" );
+	
+	ASSERTSOCKOP( msn_file->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" );
+
+	closesocket( fd );
+	fd = msn_file->fd;
+	sock_make_nonblocking( fd );
+
+	msn_file->r_event_id = b_input_add( fd, B_EV_IO_READ, msn_ftp_read, file );
+
+	return FALSE;
+}
+
+void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen )
+{
+	file_transfer_t *file = msn_file->dcc;
+	char buf[1024];
+	unsigned int acookie = time ( NULL );
+	char host[HOST_NAME_MAX+1];
+	char port[6];
+	char *errmsg;
+
+	msn_file->auth_cookie = acookie;
+
+	if( ( msn_file->fd = ft_listen( NULL, host, port, FALSE, &errmsg ) ) == -1 ) {
+		msn_ftp_abort( file, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg );
+		return;
+	}
+
+	msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file );
+
+	g_snprintf( buf, sizeof( buf ),
+		    "IP-Address: %s\r\n"
+		    "Port: %s\r\n"
+		    "AuthCookie: %d\r\n"
+		    "Launch-Application: FALSE\r\n"
+		    "Request-Data: IP-Address:\r\n\r\n",
+		    host,
+		    port,
+		    msn_file->auth_cookie );
+
+	msn_ftp_invitation_cmd( msn_file->md->ic, handle, msn_file->invite_cookie, "ACCEPT", buf );
+}
+
+void msn_invitationr_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) {
+	file_transfer_t *file = msn_file->dcc;
+	char *authcookie, *ip, *port;
+
+	if( !( authcookie = msn_findheader( body, "AuthCookie:", blen ) ) ||
+	    !( ip = msn_findheader( body, "IP-Address:", blen ) ) ||
+	    !( port = msn_findheader( body, "Port:", blen ) ) ) {
+		msn_ftp_abort( file, "Received invalid accept reply" );
+	} else if( 
+		( msn_file->fd = proxy_connect( ip, atoi( port ), msn_ftp_connected, file ) )
+		< 0 ) {
+			msn_ftp_abort( file, "Error connecting to MSN client" );
+	} else
+		msn_file->auth_cookie = strtoul( authcookie, NULL, 10 );
+
+	g_free( authcookie );
+	g_free( ip );
+	g_free( port );
+}
+
+void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen )
+{
+	msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle );
+	file_transfer_t *file = msn_file ? msn_file->dcc : NULL;
+	
+	if( !msn_file  )
+		imcb_log( sb->ic, "Received invitation ACCEPT message for unknown invitation (already aborted?)" );
+	else if( file->sending ) 
+		msn_invitations_accept( msn_file, sb, handle, cookie, body, blen );
+	else
+		msn_invitationr_accept( msn_file, sb, handle, cookie, body, blen );
+}
+
+void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen )
+{
+	msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle );
+	
+	if( !msn_file )
+		imcb_log( sb->ic, "Received invitation CANCEL message for unknown invitation (already aborted?)" );
+	else
+		msn_ftp_abort( msn_file->dcc, msn_findheader( body, "Cancel-Code:", blen ) );
+}
+
+int msn_ftp_write( file_transfer_t *file, char *format, ... )
+{
+	msn_filetransfer_t *msn_file = file->data;
+	va_list params;
+	int st;
+	char *s;
+	
+	va_start( params, format );
+	s = g_strdup_vprintf( format, params );
+	va_end( params );
+	
+	st = write( msn_file->fd, s, strlen( s ) );
+	if( st != strlen( s ) )
+		return msn_ftp_abort( file, "Error sending data over MSNFTP connection: %s",
+				strerror( errno ) );
+	
+	g_free( s );
+	return 1;
+}
+
+gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond )
+{
+	file_transfer_t *file = data;
+	msn_filetransfer_t *msn_file = file->data;
+	
+	debug( "Connected to MSNFTP server, starting authentication" );
+	if( !msn_ftp_write( file, "VER MSNFTP\r\n" ) ) 
+		return FALSE;
+	
+	sock_make_nonblocking( msn_file->fd );
+	msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file );
+	
+	return FALSE;
+}
+
+gboolean msn_ftp_handle_command( file_transfer_t *file, char* line )
+{
+	msn_filetransfer_t *msn_file = file->data;
+	char **cmd = msn_linesplit( line );
+	int count = 0;
+	if( cmd[0] ) while( cmd[++count] );
+	
+	if( count < 1 )
+		return msn_ftp_abort( file, "Missing command in MSNFTP communication" );
+	
+	if( strcmp( cmd[0], "VER" ) == 0 ) {
+		if( strcmp( cmd[1], "MSNFTP" ) != 0 )
+			return msn_ftp_abort( file, "Unsupported filetransfer protocol: %s", cmd[1] );
+		if( file->sending )
+			msn_ftp_write( file, "VER MSNFTP\r\n" );
+		else 
+			msn_ftp_write( file, "USR %s %u\r\n", msn_file->md->ic->acc->user, msn_file->auth_cookie );
+	} else if( strcmp( cmd[0], "FIL" ) == 0 ) {
+		if( strtoul( cmd[1], NULL, 10 ) != file->file_size )
+			return msn_ftp_abort( file, "FIL reply contains a different file size than the size in the invitation" );
+		msn_ftp_write( file, "TFR\r\n" );
+		msn_file->status |= MSN_TRANSFER_RECEIVING;
+	} else if( strcmp( cmd[0], "USR" ) == 0 ) {
+		if( ( strcmp( cmd[1], msn_file->handle ) != 0 ) ||
+		    ( strtoul( cmd[2], NULL, 10 ) != msn_file->auth_cookie ) )
+			msn_ftp_abort( file, "Authentication failed. "
+				"Expected handle: %s (got %s), cookie: %u (got %s)",
+				msn_file->handle, cmd[1],
+				msn_file->auth_cookie, cmd[2] );
+		msn_ftp_write( file, "FIL %zu\r\n", file->file_size);
+	} else if( strcmp( cmd[0], "TFR" ) == 0 ) {
+		file->write_request( file );
+	} else if( strcmp( cmd[0], "BYE" ) == 0 ) {
+		unsigned int retcode = count > 1 ? atoi(cmd[1]) : 1;
+
+		if( ( retcode==16777989 ) || ( retcode==16777987 ) )
+			imcb_file_finished( file );
+		else if( retcode==2147942405 )
+			imcb_file_canceled( file, "Failure: receiver is out of disk space" );
+		else if( retcode==2164261682 )
+			imcb_file_canceled( file, "Failure: receiver cancelled the transfer" );
+		else if( retcode==2164261683 )
+			imcb_file_canceled( file, "Failure: sender has cancelled the transfer" );
+		else if( retcode==2164261694 )
+			imcb_file_canceled( file, "Failure: connection is blocked" );
+		else {
+			char buf[128];
+
+			sprintf( buf, "Failure: unknown BYE code: %d", retcode);
+			imcb_file_canceled( file, buf );
+		}
+	} else if( strcmp( cmd[0], "CCL" ) == 0 ) {
+		imcb_file_canceled( file, "Failure: receiver cancelled the transfer" );
+	} else {
+		msn_ftp_abort( file, "Received invalid command %s from msn client", cmd[0] );
+	}
+	return TRUE;
+}
+
+gboolean msn_ftp_send( gpointer data, gint fd, b_input_condition cond )
+{
+	file_transfer_t *file = data;
+	msn_filetransfer_t *msn_file = file->data;
+
+	msn_file->w_event_id = 0;
+
+	file->write_request( file );
+
+	return FALSE;
+}
+
+/*
+ * This should only be called if we can write, so just do it.
+ * Add a write watch so we can write more during the next cycle (if possible).
+ * This got a bit complicated because (at least) amsn expects packets of size 2045.
+ */
+gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len )
+{
+	msn_filetransfer_t *msn_file = file->data;
+        int ret, overflow;
+
+	/* what we can't send now */
+	overflow = msn_file->sbufpos + len - MSNFTP_PSIZE;
+
+	/* append what we can do the send buffer */
+	memcpy( msn_file->sbuf + msn_file->sbufpos, buffer, MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ) );
+	msn_file->sbufpos += MIN( len, MSNFTP_PSIZE - msn_file->sbufpos );
+
+	/* if we don't have enough for a full packet and there's more wait for it */
+	if( ( msn_file->sbufpos < MSNFTP_PSIZE ) && 
+	    ( 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, B_EV_IO_WRITE, msn_ftp_send, file );
+		return TRUE;
+	}
+
+	/* Accumulated enough data, lets send something out */
+
+	msn_file->sbuf[0] = 0;
+	msn_file->sbuf[1] = ( msn_file->sbufpos - 3 ) & 0xff;
+	msn_file->sbuf[2] = ( ( msn_file->sbufpos - 3 ) >> 8 ) & 0xff;
+
+        ASSERTSOCKOP( ret = send( msn_file->fd, msn_file->sbuf, msn_file->sbufpos, 0 ), "Sending" );
+
+        msn_file->data_sent += ret - 3;
+
+        /* TODO: this should really not be fatal */
+        if( ret < msn_file->sbufpos )
+                return msn_ftp_abort( file, "send() sent %d instead of %d (send buffer full!)", ret, msn_file->sbufpos );
+
+	msn_file->sbufpos = 3;
+
+	if( overflow > 0 ) {
+		while( overflow > ( MSNFTP_PSIZE - 3 ) ) {
+			if( !msn_ftps_write( file, buffer + len - overflow, MSNFTP_PSIZE - 3 ) )
+				return FALSE;
+			overflow -= MSNFTP_PSIZE - 3;
+		}
+		return msn_ftps_write( file, buffer + len - overflow, overflow );
+	}
+
+	if( msn_file->data_sent == file->file_size ) {
+		if( msn_file->w_event_id ) {
+			b_event_remove( msn_file->w_event_id );
+			msn_file->w_event_id = 0;
+		}
+	} else {
+		/* 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, B_EV_IO_WRITE, msn_ftp_send, file );
+	}
+
+        return TRUE;
+}
+
+/* Binary part of the file transfer protocol */
+gboolean msn_ftpr_read( file_transfer_t *file ) 
+{
+	msn_filetransfer_t *msn_file = file->data;
+	int st;
+	unsigned char buf[3];
+
+	if( msn_file->data_remaining ) {
+		msn_file->r_event_id = 0;
+
+		ASSERTSOCKOP( st = read( msn_file->fd, file->buffer, MIN( sizeof( file->buffer ), msn_file->data_remaining ) ), "Receiving" );
+
+		if( st == 0 )
+			return msn_ftp_abort( file, "Remote end closed connection");
+
+		msn_file->data_sent += st;
+
+		msn_file->data_remaining -= st;
+
+		file->write( file, file->buffer, st );
+
+		if( msn_file->data_sent >= file->file_size )
+			imcb_file_finished( file );
+
+		return FALSE;
+	} else {
+		ASSERTSOCKOP( st = read( msn_file->fd, buf, 1 ), "Receiving" );
+		if( st == 0 ) {
+			return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" );
+		} else if( buf[0] == '\r' || buf[0] == '\n' ) {
+			debug( "Discarding extraneous newline" );
+		} else if( buf[0] != 0 ) {
+			msn_ftp_abort( file, "Remote end canceled the transfer");
+			/* don't really care about these last 2 (should be 0,0) */
+			read( msn_file->fd, buf, 2 );
+			return FALSE;
+		} else {
+			unsigned int size;
+			ASSERTSOCKOP( st = read( msn_file->fd, buf, 2 ), "Receiving" );
+			if( st < 2 )
+				return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" );
+
+			size = buf[0] + ((unsigned int) buf[1] << 8);
+			msn_file->data_remaining = size;
+		}
+	}
+	return TRUE;
+}
+
+/* Text mode part of the file transfer protocol */
+gboolean msn_ftp_txtproto( file_transfer_t *file )
+{
+	msn_filetransfer_t *msn_file = file->data;
+	int i = msn_file->tbufpos, st;
+	char *tbuf = msn_file->tbuf;
+
+	ASSERTSOCKOP( st = read( msn_file->fd, 
+				 tbuf + msn_file->tbufpos, 
+				 sizeof( msn_file->tbuf ) - msn_file->tbufpos ),
+				 "Receiving" );
+
+	if( st == 0 )
+		return msn_ftp_abort( file, "read returned EOF while reading text from msn client" );
+
+	msn_file->tbufpos += st;
+
+	do {
+		for( ;i < msn_file->tbufpos; i++ ) {
+			if( tbuf[i] == '\n' || tbuf[i] == '\r' ) {
+				tbuf[i] = '\0';
+				if( i > 0 )
+					msn_ftp_handle_command( file, tbuf );
+				else
+					while( tbuf[i] == '\n' || tbuf[i] == '\r' ) i++;
+				memmove( tbuf, tbuf + i + 1, msn_file->tbufpos - i - 1 );
+				msn_file->tbufpos -= i + 1;
+				i = 0;
+				break;
+			}
+		}
+	} while ( i < msn_file->tbufpos );
+
+	if( msn_file->tbufpos == sizeof( msn_file->tbuf ) )
+		return msn_ftp_abort( file, 
+				      "Line exceeded %d bytes in text protocol", 
+				      sizeof( msn_file->tbuf ) );
+	return TRUE;
+}
+
+gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond )
+{
+	file_transfer_t *file = data;
+	msn_filetransfer_t *msn_file = file->data;
+	
+	if( msn_file->status & MSN_TRANSFER_RECEIVING )
+		return msn_ftpr_read( file );
+	else
+		return msn_ftp_txtproto( file );
+}
+
+void msn_ftp_free( file_transfer_t *file )
+{
+	msn_filetransfer_t *msn_file = file->data;
+	
+	if( msn_file->r_event_id )
+		b_event_remove( msn_file->r_event_id );
+
+	if( msn_file->w_event_id )
+		b_event_remove( msn_file->w_event_id );
+
+	if( msn_file->fd != -1 )
+		closesocket( msn_file->fd );
+
+	msn_file->md->filetransfers = g_slist_remove( msn_file->md->filetransfers, msn_file->dcc );
+	
+	g_free( msn_file->handle );
+	
+	g_free( msn_file );
+}
+
+void msn_ftpr_accept( file_transfer_t *file )
+{
+	msn_filetransfer_t *msn_file = file->data;
+
+	msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, "ACCEPT", 
+				"Launch-Application: FALSE\r\n" 
+				"Request-Data: IP-Address:\r\n");
+}
+
+void msn_ftp_finished( file_transfer_t *file )
+{
+	msn_ftp_write( file, "BYE 16777989\r\n" );
+}
+
+void msn_ftp_canceled( file_transfer_t *file, char *reason )
+{
+	msn_filetransfer_t *msn_file = file->data;
+
+	msn_ftp_cancel_invite( msn_file->md->ic, msn_file->handle, 
+			       msn_file->invite_cookie, 
+			       file->status & FT_STATUS_TRANSFERRING ? 
+					"FTTIMEOUT" : 
+					"FAIL" );
+
+	imcb_log( msn_file->md->ic, "File transfer aborted: %s", reason );
+}
+
+gboolean msn_ftpr_write_request( file_transfer_t *file )
+{
+	msn_filetransfer_t *msn_file = file->data;
+	if( msn_file->r_event_id != 0 ) {
+		msn_ftp_abort( file, 
+					"BUG in MSN file transfer:"
+					"write_request called when"
+					"already watching for input" );
+		return FALSE;
+	}
+
+	msn_file->r_event_id = 
+		b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file );
+
+	return TRUE;
+}
Index: protocols/msn/invitation.h
===================================================================
--- protocols/msn/invitation.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/msn/invitation.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,82 @@
+/********************************************************************\
+* BitlBee -- An IRC to other IM-networks gateway                     *
+*                                                                    *
+* Copyright 2006 Marijn Kruisselbrink and others                     *
+\********************************************************************/
+
+/* MSN module - File transfer support             */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ 
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ 
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MSN_INVITATION_H
+#define _MSN_INVITATION_H
+
+#include "msn.h"
+
+#define MSN_INVITE_HEADERS	"MIME-Version: 1.0\r\n" \
+				"Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" \
+				"\r\n"
+
+#define MSNFTP_PSIZE 2048
+
+typedef enum {
+	MSN_TRANSFER_RECEIVING	= 1,
+	MSN_TRANSFER_SENDING	= 2
+} msn_filetransfer_status_t;
+
+typedef struct msn_filetransfer
+{
+/* Generic invitation data */	
+	/* msn_data instance this invitation was received with. */
+	struct msn_data *md;
+	/* Cookie specifying this invitation. */
+	unsigned int invite_cookie;
+	/* Handle of user that started this invitation. */
+	char *handle;
+
+/* File transfer specific data */
+	/* Current status of the file transfer. */
+	msn_filetransfer_status_t status;
+	/* Pointer to the dcc structure for this transfer. */
+	file_transfer_t *dcc;
+	/* Socket the transfer is taking place over. */
+	int fd;
+	/* Cookie received in the original invitation, this must be sent as soon as
+	   a connection has been established. */
+	unsigned int auth_cookie;
+	/* Data remaining to be received in the current packet. */
+	unsigned int data_remaining;
+	/* Buffer containing received, but unprocessed text. */
+	char tbuf[256];
+	unsigned int tbufpos;
+	
+	unsigned int data_sent;
+
+	gint r_event_id;
+	gint w_event_id;
+	
+	unsigned char sbuf[2048];
+	int sbufpos;
+
+} msn_filetransfer_t;
+
+void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen );
+void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen );
+void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen );
+
+#endif
Index: protocols/msn/msn.c
===================================================================
--- protocols/msn/msn.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/msn/msn.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 );
 }
 
@@ -81,4 +79,10 @@
 	if( md )
 	{
+		/** Disabling MSN ft support for now.
+		while( md->filetransfers ) {
+			imcb_file_canceled( md->filetransfers->data, "Closing connection" );
+		}
+		*/
+		
 		if( md->fd >= 0 )
 			closesocket( md->fd );
@@ -99,7 +103,15 @@
 			g_free( md->grouplist[--md->groupcount] );
 		g_free( md->grouplist );
-		
 		g_free( md->passport_token );
 		g_free( md->lock_key );
+		
+		while( md->grpq )
+		{
+			struct msn_groupadd *ga = md->grpq->data;
+			g_free( ga->group );
+			g_free( ga->who );
+			g_free( ga );
+			md->grpq = g_slist_remove( md->grpq, ga );
+		}
 		
 		g_free( md );
@@ -121,4 +133,12 @@
 	struct msn_switchboard *sb;
 	
+#ifdef DEBUG
+	if( strcmp( who, "raw" ) == 0 )
+	{
+		msn_write( ic, message, strlen( message ) );
+		msn_write( ic, "\r\n", 2 );
+	}
+	else
+#endif
 	if( ( sb = msn_sb_by_handle( ic, who ) ) )
 	{
@@ -158,9 +178,8 @@
 	struct msn_data *md = ic->proto_data;
 	
-	if( state )
-		md->away_state = msn_away_state_by_name( state ) ? :
-		                 msn_away_state_list + 1;
-	else
+	if( state == NULL )
 		md->away_state = msn_away_state_list;
+	else if( ( md->away_state = msn_away_state_by_name( state ) ) == NULL )
+		md->away_state = msn_away_state_list + 1;
 	
 	g_snprintf( buf, sizeof( buf ), "CHG %d %s\r\n", ++md->trId, md->away_state->code );
@@ -170,5 +189,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 );
 }
 
@@ -181,10 +200,14 @@
 static void msn_add_buddy( struct im_connection *ic, char *who, char *group )
 {
-	msn_buddy_list_add( ic, "FL", who, who );
+	struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who );
+	
+	msn_buddy_list_add( ic, "FL", who, who, group );
+	if( bu && bu->group )
+		msn_buddy_list_remove( ic, "FL", who, bu->group->name );
 }
 
 static void msn_remove_buddy( struct im_connection *ic, char *who, char *group )
 {
-	msn_buddy_list_remove( ic, "FL", who );
+	msn_buddy_list_remove( ic, "FL", who, NULL );
 }
 
@@ -222,4 +245,5 @@
 {
 	struct msn_switchboard *sb;
+	struct groupchat *c = imcb_chat_new( ic, who );
 	
 	if( ( sb = msn_sb_by_handle( ic, who ) ) )
@@ -239,8 +263,6 @@
 		msn_sb_write_msg( ic, m );
 
-		return NULL;
-	}
-	
-	return NULL;
+		return c;
+	}
 }
 
@@ -252,10 +274,10 @@
 static void msn_add_permit( struct im_connection *ic, char *who )
 {
-	msn_buddy_list_add( ic, "AL", who, who );
+	msn_buddy_list_add( ic, "AL", who, who, NULL );
 }
 
 static void msn_rem_permit( struct im_connection *ic, char *who )
 {
-	msn_buddy_list_remove( ic, "AL", who );
+	msn_buddy_list_remove( ic, "AL", who, NULL );
 }
 
@@ -264,5 +286,5 @@
 	struct msn_switchboard *sb;
 	
-	msn_buddy_list_add( ic, "BL", who, who );
+	msn_buddy_list_add( ic, "BL", who, who, NULL );
 	
 	/* If there's still a conversation with this person, close it. */
@@ -275,27 +297,27 @@
 static void msn_rem_deny( struct im_connection *ic, char *who )
 {
-	msn_buddy_list_remove( ic, "BL", who );
+	msn_buddy_list_remove( ic, "BL", who, NULL );
 }
 
 static int msn_send_typing( struct im_connection *ic, char *who, int typing )
 {
-	if( typing & OPT_TYPING )
+	struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who );
+	
+	if( !( bu->flags & BEE_USER_ONLINE ) )
+		return 0;
+	else if( typing & OPT_TYPING )
 		return( msn_buddy_msg( ic, who, TYPING_NOTIFICATION_MESSAGE, 0 ) );
 	else
-		return( 1 );
-}
-
-static char *msn_set_display_name( set_t *set, char *value )
+		return 1;
+}
+
+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 )
@@ -304,15 +326,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;
 }
 
@@ -343,4 +359,5 @@
 	ret->send_typing = msn_send_typing;
 	ret->handle_cmp = g_strcasecmp;
+	//ret->transfer_request = msn_ftp_transfer_request;
 
 	register_protocol(ret);
Index: protocols/msn/msn.h
===================================================================
--- protocols/msn/msn.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/msn/msn.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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
@@ -61,4 +62,8 @@
                            "\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/"
 
@@ -74,8 +79,9 @@
 	char *lock_key;
 	
-	GSList *msgq;
+	GSList *msgq, *grpq;
 	GSList *switchboards;
 	int sb_failures;
 	time_t first_sb_failure;
+	GSList *filetransfers;
 	
 	const struct msn_away_state *away_state;
@@ -92,4 +98,5 @@
 	gint inp;
 	struct msn_handler_data *handler;
+	gint keepalive;
 	
 	int trId;
@@ -121,4 +128,10 @@
 	char *who;
 	char *text;
+};
+
+struct msn_groupadd
+{
+	char *who;
+	char *group;
 };
 
@@ -162,6 +175,6 @@
 int msn_write( struct im_connection *ic, char *s, int len );
 int msn_logged_in( struct im_connection *ic );
-int msn_buddy_list_add( struct im_connection *ic, char *list, char *who, char *realname );
-int msn_buddy_list_remove( struct im_connection *ic, char *list, char *who );
+int msn_buddy_list_add( struct im_connection *ic, const char *list, const char *who, const char *realname_, const char *group );
+int msn_buddy_list_remove( struct im_connection *ic, char *list, const char *who, const char *group );
 void msn_buddy_ask( struct im_connection *ic, char *handle, char *realname );
 char *msn_findheader( char *text, char *header, int len );
@@ -170,4 +183,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 );
 char *msn_p11_challenge( char *challenge );
 
@@ -189,4 +203,9 @@
 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 */
+void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who );
 
 #endif //_MSN_H
Index: protocols/msn/msn_util.c
===================================================================
--- protocols/msn/msn_util.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/msn/msn_util.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -39,8 +39,8 @@
 		imcb_error( ic, "Short write() to main server" );
 		imc_logout( ic, TRUE );
-		return( 0 );
-	}
-	
-	return( 1 );
+		return 0;
+	}
+	
+	return 1;
 }
 
@@ -52,30 +52,80 @@
 }
 
-int msn_buddy_list_add( struct im_connection *ic, char *list, char *who, char *realname_ )
+int msn_buddy_list_add( struct im_connection *ic, const char *list, const char *who, const char *realname_, const char *group )
 {
 	struct msn_data *md = ic->proto_data;
-	char buf[1024], *realname;
+	char buf[1024], *realname, groupid[8];
+	
+	*groupid = '\0';
+	if( group )
+	{
+		int i;
+		for( i = 0; i < md->groupcount; i ++ )
+			if( g_strcasecmp( md->grouplist[i], group ) == 0 )
+			{
+				g_snprintf( groupid, sizeof( groupid ), " %d", i );
+				break;
+			}
+		
+		if( *groupid == '\0' )
+		{
+			/* Have to create this group, it doesn't exist yet. */
+			struct msn_groupadd *ga;
+			GSList *l;
+			
+			for( l = md->grpq; l; l = l->next )
+			{
+				ga = l->data;
+				if( g_strcasecmp( ga->group, group ) == 0 )
+					break;
+			}
+			
+			ga = g_new0( struct msn_groupadd, 1 );
+			ga->who = g_strdup( who );
+			ga->group = g_strdup( group );
+			md->grpq = g_slist_prepend( md->grpq, ga );
+			
+			if( l == NULL )
+			{
+				char *groupname = msn_http_encode( group );
+				g_snprintf( buf, sizeof( buf ), "ADG %d %s %d\r\n", ++md->trId, groupname, 0 );
+				g_free( groupname );
+				return msn_write( ic, buf, strlen( buf ) );
+			}
+			else
+			{
+				/* This can happen if the user's doing lots of adds to a
+				   new group at once; we're still waiting for the server
+				   to confirm group creation. */
+				return 1;
+			}
+		}
+	}
 	
 	realname = msn_http_encode( realname_ );
-	
-	g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s\r\n", ++md->trId, list, who, realname );
-	if( msn_write( ic, buf, strlen( buf ) ) )
-	{
-		g_free( realname );
-		
-		return( 1 );
-	}
-	
+	g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s%s\r\n", ++md->trId, list, who, realname, groupid );
 	g_free( realname );
 	
-	return( 0 );
-}
-
-int msn_buddy_list_remove( struct im_connection *ic, char *list, char *who )
+	return msn_write( ic, buf, strlen( buf ) );
+}
+
+int msn_buddy_list_remove( struct im_connection *ic, char *list, const char *who, const char *group )
 {
 	struct msn_data *md = ic->proto_data;
-	char buf[1024];
-	
-	g_snprintf( buf, sizeof( buf ), "REM %d %s %s\r\n", ++md->trId, list, who );
+	char buf[1024], groupid[8];
+	
+	*groupid = '\0';
+	if( group )
+	{
+		int i;
+		for( i = 0; i < md->groupcount; i ++ )
+			if( g_strcasecmp( md->grouplist[i], group ) == 0 )
+			{
+				g_snprintf( groupid, sizeof( groupid ), " %d", i );
+				break;
+			}
+	}
+	
+	g_snprintf( buf, sizeof( buf ), "REM %d %s %s%s\r\n", ++md->trId, list, who, groupid );
 	if( msn_write( ic, buf, strlen( buf ) ) )
 		return( 1 );
@@ -95,8 +145,7 @@
 	struct msn_buddy_ask_data *bla = data;
 	
-	msn_buddy_list_add( bla->ic, "AL", bla->handle, bla->realname );
-	
-	if( imcb_find_buddy( bla->ic, bla->handle ) == NULL )
-		imcb_ask_add( bla->ic, bla->handle, NULL );
+	msn_buddy_list_add( bla->ic, "AL", bla->handle, bla->realname, NULL );
+	
+	imcb_ask_add( bla->ic, bla->handle, NULL );
 	
 	g_free( bla->handle );
@@ -109,5 +158,5 @@
 	struct msn_buddy_ask_data *bla = data;
 	
-	msn_buddy_list_add( bla->ic, "BL", bla->handle, bla->realname );
+	msn_buddy_list_add( bla->ic, "BL", bla->handle, bla->realname, NULL );
 	
 	g_free( bla->handle );
@@ -350,4 +399,5 @@
 	GString *ret;
 	GSList *l;
+	int n = 0;
 	
 	l = *list;
@@ -364,5 +414,9 @@
 		m = l->data;
 		
-		g_string_append_printf( ret, "\n%s", m->text );
+		if( strncmp( m->text, "\r\r\r", 3 ) != 0 )
+		{
+			g_string_append_printf( ret, "\n%s", m->text );
+			n ++;
+		}
 		
 		g_free( m->who );
@@ -375,6 +429,19 @@
 	*list = NULL;
 	
-	imcb_log( ic, "%s", ret->str );
+	if( n > 0 )
+		imcb_log( ic, "%s", ret->str );
 	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;
 }
 
@@ -401,5 +468,4 @@
 
 /* Copied and heavily modified from http://tmsnc.sourceforge.net/chl.c */
-
 char *msn_p11_challenge( char *challenge )
 {
Index: protocols/msn/ns.c
===================================================================
--- protocols/msn/ns.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/msn/ns.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 )
@@ -541,6 +533,12 @@
 		else if( num_parts >= 6 && strcmp( cmd[2], "FL" ) == 0 )
 		{
+			const char *group = NULL;
+			int num;
+			
+			if( cmd[6] != NULL && sscanf( cmd[6], "%d", &num ) == 1 && num < md->groupcount )
+				group = md->grouplist[num];
+			
 			http_decode( cmd[5] );
-			imcb_add_buddy( ic, cmd[4], NULL );
+			imcb_add_buddy( ic, cmd[4], group );
 			imcb_rename_buddy( ic, cmd[4], cmd[5] );
 		}
@@ -567,4 +565,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 +598,5 @@
 		}
 	}
+#endif
 	else if( strcmp( cmd[0], "IPG" ) == 0 )
 	{
@@ -608,4 +610,48 @@
 			imc_logout( ic, TRUE );
 			return( 0 );
+		}
+	}
+	else if( strcmp( cmd[0], "ADG" ) == 0 )
+	{
+		char *group = g_strdup( cmd[3] );
+		int groupnum, i;
+		GSList *l, *next;
+		
+		http_decode( group );
+		if( sscanf( cmd[4], "%d", &groupnum ) == 1 )
+		{
+			if( groupnum >= md->groupcount )
+			{
+				md->grouplist = g_renew( char *, md->grouplist, groupnum + 1 );
+				for( i = md->groupcount; i <= groupnum; i ++ )
+					md->grouplist[i] = NULL;
+				md->groupcount = groupnum + 1;
+			}
+			g_free( md->grouplist[groupnum] );
+			md->grouplist[groupnum] = group;
+		}
+		else
+		{
+			/* Shouldn't happen, but if it does, give up on the group. */
+			g_free( group );
+			imcb_error( ic, "Syntax error" );
+			imc_logout( ic, TRUE );
+			return 0;
+		}
+		
+		for( l = md->grpq; l; l = next )
+		{
+			struct msn_groupadd *ga = l->data;
+			next = l->next;
+			if( g_strcasecmp( ga->group, group ) == 0 )
+			{
+				if( !msn_buddy_list_add( ic, "FL", ga->who, ga->who, group ) )
+					return 0;
+				
+				g_free( ga->group );
+				g_free( ga->who );
+				g_free( ga );
+				md->grpq = g_slist_remove( md->grpq, ga );
+			}
 		}
 	}
@@ -748,2 +794,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/passport.c
===================================================================
--- protocols/msn/passport.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/msn/passport.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -145,5 +145,7 @@
 	char *s;
 	
-	if( ( s = xt_find_attr( node, "Id" ) ) && strcmp( s, "PPToken1" ) == 0 )
+	if( ( s = xt_find_attr( node, "Id" ) ) &&
+	    ( strncmp( s, "Compact", 7 ) == 0 ||
+	      strncmp( s, "PPToken", 7 ) == 0 ) )
 		mad->token = g_memdup( node->text, node->text_len + 1 );
 	
Index: protocols/msn/sb.c
===================================================================
--- protocols/msn/sb.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/msn/sb.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -169,5 +169,21 @@
 		
 		/* Build the message. Convert LF to CR-LF for normal messages. */
-		if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) != 0 )
+		if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) == 0 )
+		{
+			i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user );
+			buf = g_new0( char, i );
+			i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user );
+		}
+		else if( strcmp( text, SB_KEEPALIVE_MESSAGE ) == 0 )
+		{
+			buf = g_strdup( SB_KEEPALIVE_HEADERS );
+			i = strlen( buf );
+		}
+		else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) 
+		{
+			buf = g_strdup( text );
+			i = strlen( buf );
+		}
+		else
 		{
 			buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 );
@@ -183,10 +199,4 @@
 			}
 		}
-		else
-		{
-			i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user );
-			buf = g_new0( char, i );
-			i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user );
-		}
 		
 		/* Build the final packet (MSG command + the message). */
@@ -223,9 +233,15 @@
 {
 	struct im_connection *ic = sb->ic;
+	struct groupchat *c = NULL;
 	char buf[1024];
 	
 	/* Create the groupchat structure. */
 	g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session );
-	sb->chat = imcb_chat_new( ic, buf );
+	if( sb->who )
+		c = bee_chat_by_title( ic->bee, ic, sb->who );
+	if( c && !msn_sb_by_chat( c ) )
+		sb->chat = c;
+	else
+		sb->chat = imcb_chat_new( ic, buf );
 	
 	/* Populate the channel. */
@@ -251,4 +267,5 @@
 	
 	msn_msgq_purge( ic, &sb->msgq );
+	msn_sb_stop_keepalives( sb );
 	
 	if( sb->key ) g_free( sb->key );
@@ -310,5 +327,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 );
@@ -323,7 +340,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 )
@@ -342,35 +363,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;
 }
 
@@ -472,4 +484,6 @@
 		
 		sb->ready = 1;
+		
+		msn_sb_start_keepalives( sb, FALSE );
 	}
 	else if( strcmp( cmd[0], "CAL" ) == 0 )
@@ -521,4 +535,6 @@
 			}
 			
+			msn_sb_start_keepalives( sb, FALSE );
+			
 			return( st );
 		}
@@ -582,4 +598,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 );
@@ -691,62 +709,44 @@
 			}
 		}
+#if 0
+		// Disable MSN ft support for now.
 		else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 )
 		{
-			char *itype = msn_findheader( body, "Application-GUID:", blen );
-			char buf[1024];
+			char *command = msn_findheader( body, "Invitation-Command:", blen );
+			char *cookie = msn_findheader( body, "Invitation-Cookie:", blen );
+			unsigned int icookie;
 			
 			g_free( ct );
 			
-			*buf = 0;
-			
-			if( !itype )
-				return( 1 );
-			
-			/* File transfer. */
-			if( strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) == 0 )
-			{
-				char *name = msn_findheader( body, "Application-File:", blen );
-				char *size = msn_findheader( body, "Application-FileSize:", blen );
-				
-				if( name && size )
-				{
-					g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Filetransfer: `%s', %s bytes >>\n"
-					            "Filetransfers are not supported by BitlBee for now...", name, size );
-				}
-				else
-				{
-					strcpy( buf, "<< \x02""BitlBee\x02"" - Corrupted MSN filetransfer invitation message >>" );
-				}
-				
-				if( name ) g_free( name );
-				if( size ) g_free( size );
-			}
-			else
-			{
-				char *iname = msn_findheader( body, "Application-Name:", blen );
-				
-				g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Unknown MSN invitation - %s (%s) >>",
-				                                itype, iname ? iname : "no name" );
-				
-				if( iname ) g_free( iname );
-			}
-			
-			g_free( itype );
-			
-			if( !*buf )
-				return( 1 );
-			
-			if( sb->who )
-			{
-				imcb_buddy_msg( ic, cmd[1], buf, 0, 0 );
-			}
-			else if( sb->chat )
-			{
-				imcb_chat_msg( sb->chat, cmd[1], buf, 0, 0 );
-			}
-			else
-			{
-				/* PANIC! */
-			}
+			/* Every invite should have both a Command and Cookie header */
+			if( !command || !cookie ) {
+				g_free( command );
+				g_free( cookie );
+				imcb_log( ic, "Warning: No command or cookie from %s", sb->who );
+				return 1;
+			}
+			
+			icookie = strtoul( cookie, NULL, 10 );
+			g_free( cookie );
+			
+			if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) {
+				msn_invitation_invite( sb, cmd[1], icookie, body, blen );
+			} else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) {
+				msn_invitation_accept( sb, cmd[1], icookie, body, blen );
+			} else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) {
+				msn_invitation_cancel( sb, cmd[1], icookie, body, blen );
+			} else {
+				imcb_log( ic, "Warning: Received invalid invitation with "
+						"command %s from %s", command, sb->who );
+			}
+			
+			g_free( command );
+		}
+#endif
+		else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 ) 
+		{
+			imcb_error( sb->ic, "Cannot receive file from %s: BitlBee does not "
+					"support msnmsgrp2p yet.", sb->who );
+			g_free( ct );
 		}
 		else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 )
@@ -770,2 +770,33 @@
 	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 )
+{
+	bee_user_t *bu;
+	
+	if( sb && sb->who && sb->keepalive == 0 &&
+	    ( bu = bee_user_by_handle( sb->ic->bee, sb->ic, sb->who ) ) &&
+	    !( bu->flags & BEE_USER_ONLINE ) &&
+	    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 ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/nogaim.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -36,7 +36,4 @@
 
 #include "nogaim.h"
-#include "chat.h"
-
-static int remove_chat_buddy_silent( struct groupchat *b, const char *handle );
 
 GSList *connections;
@@ -91,6 +88,4 @@
 }
 #endif
-
-/* nogaim.c */
 
 GList *protocols = NULL;
@@ -116,14 +111,16 @@
 {
 	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;
 }
 
-/* nogaim.c */
 void nogaim_init()
 {
@@ -132,4 +129,6 @@
 	extern void byahoo_initmodule();
 	extern void jabber_initmodule();
+	extern void twitter_initmodule();
+	extern void purple_initmodule();
 
 #ifdef WITH_MSN
@@ -149,4 +148,12 @@
 #endif
 
+#ifdef WITH_TWITTER
+	twitter_initmodule();
+#endif
+
+#ifdef WITH_PURPLE
+	purple_initmodule();
+#endif
+
 #ifdef WITH_PLUGINS
 	load_plugins();
@@ -156,6 +163,4 @@
 GSList *get_connections() { return connections; }
 
-/* multi.c */
-
 struct im_connection *imcb_new( account_t *acc )
 {
@@ -164,5 +169,5 @@
 	ic = g_new0( struct im_connection, 1 );
 	
-	ic->irc = acc->irc;
+	ic->bee = acc->bee;
 	ic->acc = acc;
 	acc->ic = ic;
@@ -178,5 +183,5 @@
 	
 	/* Destroy the pointer to this connection from the account list */
-	for( a = ic->irc->accounts; a; a = a->next )
+	for( a = ic->bee->accounts; a; a = a->next )
 		if( a->ic == ic )
 		{
@@ -199,10 +204,10 @@
 	va_end( params );
 
-	if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) ||
-	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) )
+	if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) ||
+	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) )
 		strip_html( text );
 	
 	/* Try to find a different connection on the same protocol. */
-	for( a = ic->irc->accounts; a; a = a->next )
+	for( a = ic->bee->accounts; a; a = a->next )
 		if( a->prpl == ic->acc->prpl && a->ic != ic )
 			break;
@@ -210,7 +215,8 @@
 	/* If we found one, include the screenname in the message. */
 	if( a )
-		irc_usermsg( ic->irc, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text );
+		/* FIXME(wilmer): ui_log callback or so */
+		irc_usermsg( ic->bee->ui_data, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text );
 	else
-		irc_usermsg( ic->irc, "%s - %s", ic->acc->prpl->name, text );
+		irc_usermsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text );
 	
 	g_free( text );
@@ -263,8 +269,4 @@
 void imcb_connected( struct im_connection *ic )
 {
-	irc_t *irc = ic->irc;
-	struct chat *c;
-	user_t *u;
-	
 	/* MSN servers sometimes redirect you to a different server and do
 	   the whole login sequence again, so these "late" calls to this
@@ -273,6 +275,4 @@
 		return;
 	
-	u = user_find( ic->irc, ic->irc->nick );
-	
 	imcb_log( ic, "Logged in" );
 	
@@ -287,12 +287,6 @@
 	ic->acc->auto_reconnect_delay = 0;
 	
-	for( c = irc->chatrooms; c; c = c->next )
-	{
-		if( c->acc != ic->acc )
-			continue;
-		
-		if( set_getbool( &c->set, "auto_join" ) )
-			chat_join( irc, c, NULL );
-	}
+	if( ic->bee->ui->imc_connected )
+		ic->bee->ui->imc_connected( ic );
 }
 
@@ -302,5 +296,5 @@
 	
 	a->reconnect = 0;
-	account_on( a->irc, a );
+	account_on( a->bee, a );
 	
 	return( FALSE );	/* Only have to run the timeout once */
@@ -315,7 +309,7 @@
 void imc_logout( struct im_connection *ic, int allow_reconnect )
 {
-	irc_t *irc = ic->irc;
-	user_t *t, *u;
+	bee_t *bee = ic->bee;
 	account_t *a;
+	GSList *l;
 	int delay;
 	
@@ -327,4 +321,7 @@
 		ic->flags |= OPT_LOGGING_OUT;
 	
+	if( ic->bee->ui->imc_disconnected )
+		ic->bee->ui->imc_disconnected( ic );
+	
 	imcb_log( ic, "Signing off.." );
 	
@@ -337,20 +334,18 @@
 	ic->away = NULL;
 	
-	u = irc->users;
-	while( u )
-	{
-		if( u->ic == ic )
-		{
-			t = u->next;
-			user_del( irc, u->nick );
-			u = t;
-		}
-		else
-			u = u->next;
-	}
-	
-	query_del_by_conn( ic->irc, ic );
-	
-	for( a = irc->accounts; a; a = a->next )
+	for( l = bee->users; l; )
+	{
+		bee_user_t *bu = l->data;
+		GSList *next = l->next;
+		
+		if( bu->ic == ic )
+			bee_user_free( bee, bu );
+		
+		l = next;
+	}
+	
+	query_del_by_conn( (irc_t*) ic->bee->ui_data, ic );
+	
+	for( a = bee->accounts; a; a = a->next )
 		if( a->ic == ic )
 			break;
@@ -360,5 +355,5 @@
 		/* Uhm... This is very sick. */
 	}
-	else if( allow_reconnect && set_getbool( &irc->set, "auto_reconnect" ) &&
+	else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) &&
 	         set_getbool( &a->set, "auto_reconnect" ) &&
 	         ( delay = account_reconnect_delay( a ) ) > 0 )
@@ -371,122 +366,46 @@
 }
 
-
-/* dialogs.c */
-
 void imcb_ask( struct im_connection *ic, char *msg, void *data,
                query_callback doit, query_callback dont )
 {
-	query_add( ic->irc, ic, msg, doit, dont, data );
-}
-
-
-/* list.c */
+	query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data );
+}
+
+void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data,
+                         query_callback doit, query_callback dont, query_callback myfree )
+{
+	query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data );
+}
 
 void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group )
 {
-	user_t *u;
-	char nick[MAX_NICK_LENGTH+1], *s;
-	irc_t *irc = ic->irc;
-	
-	if( user_findhandle( ic, handle ) )
-	{
-		if( set_getbool( &irc->set, "debug" ) )
-			imcb_log( ic, "User already exists, ignoring add request: %s", handle );
+	bee_user_t *bu;
+	bee_t *bee = ic->bee;
+	bee_group_t *oldg;
+	
+	if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
+		bu = bee_user_new( bee, ic, handle, 0 );
+	
+	oldg = bu->group;
+	bu->group = bee_group_by_name( bee, group, TRUE );
+	
+	if( bee->ui->user_group && bu->group != oldg )
+		bee->ui->user_group( bee, bu );
+}
+
+void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname )
+{
+	bee_t *bee = ic->bee;
+	bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
+	
+	if( !bu || !fullname ) return;
+	
+	if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 )
+	{
+		g_free( bu->fullname );
+		bu->fullname = g_strdup( fullname );
 		
-		return;
-		
-		/* Buddy seems to exist already. Let's ignore this request then...
-		   Eventually subsequent calls to this function *should* be possible
-		   when a buddy is in multiple groups. But for now BitlBee doesn't
-		   even support groups so let's silently ignore this for now. */
-	}
-	
-	memset( nick, 0, MAX_NICK_LENGTH + 1 );
-	strcpy( nick, nick_get( ic->acc, handle ) );
-	
-	u = user_add( ic->irc, nick );
-	
-//	if( !realname || !*realname ) realname = nick;
-//	u->realname = g_strdup( realname );
-	
-	if( ( s = strchr( handle, '@' ) ) )
-	{
-		u->host = g_strdup( s + 1 );
-		u->user = g_strndup( handle, s - handle );
-	}
-	else if( ic->acc->server )
-	{
-		u->host = g_strdup( ic->acc->server );
-		u->user = g_strdup( handle );
-		
-		/* s/ /_/ ... important for AOL screennames */
-		for( s = u->user; *s; s ++ )
-			if( *s == ' ' )
-				*s = '_';
-	}
-	else
-	{
-		u->host = g_strdup( ic->acc->prpl->name );
-		u->user = g_strdup( handle );
-	}
-	
-	u->ic = ic;
-	u->handle = g_strdup( handle );
-	if( group ) u->group = g_strdup( group );
-	u->send_handler = buddy_send_handler;
-	u->last_typing_notice = 0;
-}
-
-struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle )
-{
-	static struct buddy b[1];
-	user_t *u;
-	
-	u = user_findhandle( ic, handle );
-	
-	if( !u )
-		return( NULL );
-	
-	memset( b, 0, sizeof( b ) );
-	strncpy( b->name, handle, 80 );
-	strncpy( b->show, u->realname, BUDDY_ALIAS_MAXLEN );
-	b->present = u->online;
-	b->ic = u->ic;
-	
-	return( b );
-}
-
-void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *realname )
-{
-	user_t *u = user_findhandle( ic, handle );
-	char *set;
-	
-	if( !u || !realname ) return;
-	
-	if( g_strcasecmp( u->realname, realname ) != 0 )
-	{
-		if( u->realname != u->nick ) g_free( u->realname );
-		
-		u->realname = g_strdup( realname );
-		
-		if( ( ic->flags & OPT_LOGGED_IN ) && set_getbool( &ic->irc->set, "display_namechanges" ) )
-			imcb_log( ic, "User `%s' changed name to `%s'", u->nick, u->realname );
-	}
-	
-	set = set_getstr( &ic->acc->set, "nick_source" );
-	if( strcmp( set, "handle" ) != 0 )
-	{
-		char *name = g_strdup( realname );
-		
-		if( strcmp( set, "first_name" ) == 0 )
-		{
-			int i;
-			for( i = 0; name[i] && !isspace( name[i] ); i ++ ) {}
-			name[i] = '\0';
-		}
-		
-		imcb_buddy_nick_hint( ic, handle, name );
-		
-		g_free( name );
+		if( bee->ui->user_fullname )
+			bee->ui->user_fullname( bee, bu );
 	}
 }
@@ -494,8 +413,5 @@
 void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group )
 {
-	user_t *u;
-	
-	if( ( u = user_findhandle( ic, handle ) ) )
-		user_del( ic->irc, u->nick );
+	bee_user_free( ic->bee, bee_user_by_handle( ic->bee, ic, handle ) );
 }
 
@@ -504,35 +420,14 @@
 void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick )
 {
-	user_t *u = user_findhandle( ic, handle );
-	char newnick[MAX_NICK_LENGTH+1], *orig_nick;
-	
-	if( u && !u->online && !nick_saved( ic->acc, handle ) )
-	{
-		/* Only do this if the person isn't online yet (which should
-		   be the case if we just added it) and if the user hasn't
-		   assigned a nickname to this buddy already. */
-		
-		strncpy( newnick, nick, MAX_NICK_LENGTH );
-		newnick[MAX_NICK_LENGTH] = 0;
-		
-		/* Some processing to make sure this string is a valid IRC nickname. */
-		nick_strip( newnick );
-		if( set_getbool( &ic->irc->set, "lcnicks" ) )
-			nick_lc( newnick );
-		
-		if( strcmp( u->nick, newnick ) != 0 )
-		{
-			/* Only do this if newnick is different from the current one.
-			   If rejoining a channel, maybe we got this nick already
-			   (and dedupe would only add an underscore. */
-			nick_dedupe( ic->acc, handle, newnick );
-			
-			/* u->nick will be freed halfway the process, so it can't be
-			   passed as an argument. */
-			orig_nick = g_strdup( u->nick );
-			user_rename( ic->irc, orig_nick, newnick );
-			g_free( orig_nick );
-		}
-	}
+	bee_t *bee = ic->bee;
+	bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
+	
+	if( !bu || !nick ) return;
+	
+	g_free( bu->nick );
+	bu->nick = g_strdup( nick );
+	
+	if( bee->ui->user_nick_hint )
+		bee->ui->user_nick_hint( bee, bu, nick );
 }
 
@@ -573,5 +468,5 @@
 	
 	s = g_strdup_printf( "The user %s%s wants to add you to his/her buddy list.",
-	                     handle, realname_ ?: "" );
+	                     handle, realname_ ? realname_ : "" );
 	
 	g_free( realname_ );
@@ -579,5 +474,6 @@
 	data->ic = ic;
 	data->handle = g_strdup( handle );
-	query_add( ic->irc, ic, s, imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, data );
+	query_add( (irc_t *) ic->bee->ui_data, ic, s,
+	           imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data );
 }
 
@@ -595,5 +491,5 @@
 	cbd->ic->acc->prpl->add_buddy( cbd->ic, cbd->handle, NULL );
 	
-	return imcb_ask_add_cb_no( data );
+	imcb_ask_add_cb_no( data );
 }
 
@@ -604,5 +500,5 @@
 	
 	/* TODO: Make a setting for this! */
-	if( user_findhandle( ic, handle ) != NULL )
+	if( bee_user_by_handle( ic->bee, ic, handle ) != NULL )
 		return;
 	
@@ -611,466 +507,21 @@
 	data->ic = ic;
 	data->handle = g_strdup( handle );
-	query_add( ic->irc, ic, s, imcb_ask_add_cb_yes, imcb_ask_add_cb_no, data );
-}
-
-
-/* server.c */                    
-
-void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message )
-{
-	user_t *u;
-	int oa, oo;
-	
-	u = user_findhandle( ic, (char*) handle );
-	
-	if( !u )
-	{
-		if( g_strcasecmp( set_getstr( &ic->irc->set, "handle_unknown" ), "add" ) == 0 )
-		{
-			imcb_add_buddy( ic, (char*) handle, NULL );
-			u = user_findhandle( ic, (char*) handle );
-		}
-		else
-		{
-			if( set_getbool( &ic->irc->set, "debug" ) || g_strcasecmp( set_getstr( &ic->irc->set, "handle_unknown" ), "ignore" ) != 0 )
-			{
-				imcb_log( ic, "imcb_buddy_status() for unknown handle %s:", handle );
-				imcb_log( ic, "flags = %d, state = %s, message = %s", flags,
-				          state ? state : "NULL", message ? message : "NULL" );
-			}
-			
-			return;
-		}
-	}
-	
-	oa = u->away != NULL;
-	oo = u->online;
-	
-	g_free( u->away );
-	g_free( u->status_msg );
-	u->away = u->status_msg = NULL;
-	
-	if( ( flags & OPT_LOGGED_IN ) && !u->online )
-	{
-		irc_spawn( ic->irc, u );
-		u->online = 1;
-	}
-	else if( !( flags & OPT_LOGGED_IN ) && u->online )
-	{
-		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( flags & OPT_AWAY )
-	{
-		if( state && message )
-		{
-			u->away = g_strdup_printf( "%s (%s)", state, message );
-		}
-		else if( state )
-		{
-			u->away = g_strdup( state );
-		}
-		else if( message )
-		{
-			u->away = g_strdup( message );
-		}
-		else
-		{
-			u->away = g_strdup( "Away" );
-		}
-	}
-	else
-	{
-		u->status_msg = g_strdup( message );
-	}
-	
-	/* 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 );
-	}
-}
-
-void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at )
-{
-	irc_t *irc = ic->irc;
-	char *wrapped;
-	user_t *u;
-	
-	u = user_findhandle( ic, handle );
-	
-	if( !u )
-	{
-		char *h = set_getstr( &irc->set, "handle_unknown" );
-		
-		if( g_strcasecmp( h, "ignore" ) == 0 )
-		{
-			if( set_getbool( &irc->set, "debug" ) )
-				imcb_log( ic, "Ignoring message from unknown handle %s", handle );
-			
-			return;
-		}
-		else if( g_strncasecmp( h, "add", 3 ) == 0 )
-		{
-			int private = set_getbool( &irc->set, "private" );
-			
-			if( h[3] )
-			{
-				if( g_strcasecmp( h + 3, "_private" ) == 0 )
-					private = 1;
-				else if( g_strcasecmp( h + 3, "_channel" ) == 0 )
-					private = 0;
-			}
-			
-			imcb_add_buddy( ic, handle, NULL );
-			u = user_findhandle( ic, handle );
-			u->is_private = private;
-		}
-		else
-		{
-			imcb_log( ic, "Message from unknown handle %s:", handle );
-			u = user_find( irc, irc->mynick );
-		}
-	}
-	
-	if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) ||
-	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) )
-		strip_html( msg );
-
-	wrapped = word_wrap( msg, 425 );
-	irc_msgfrom( irc, u->nick, wrapped );
-	g_free( wrapped );
-}
-
-void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags )
-{
-	user_t *u;
-	
-	if( !set_getbool( &ic->irc->set, "typing_notice" ) )
-		return;
-	
-	if( ( u = user_findhandle( ic, handle ) ) )
-	{
-		char buf[256]; 
-		
-		g_snprintf( buf, 256, "\1TYPING %d\1", ( flags >> 8 ) & 3 );
-		irc_privmsg( ic->irc, u, "PRIVMSG", ic->irc->nick, NULL, buf );
-	}
-}
-
-struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle )
-{
-	struct groupchat *c;
-	
-	/* This one just creates the conversation structure, user won't see anything yet */
-	
-	if( ic->groupchats )
-	{
-		for( c = ic->groupchats; c->next; c = c->next );
-		c = c->next = g_new0( struct groupchat, 1 );
-	}
-	else
-		ic->groupchats = c = g_new0( struct groupchat, 1 );
-	
-	c->ic = ic;
-	c->title = g_strdup( handle );
-	c->channel = g_strdup_printf( "&chat_%03d", ic->irc->c_id++ );
-	c->topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title );
-	
-	if( set_getbool( &ic->irc->set, "debug" ) )
-		imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle );
-	
-	return c;
-}
-
-void imcb_chat_free( struct groupchat *c )
-{
-	struct im_connection *ic = c->ic;
-	struct groupchat *l;
-	GList *ir;
-	
-	if( set_getbool( &ic->irc->set, "debug" ) )
-		imcb_log( ic, "You were removed from conversation %p", c );
-	
-	if( c )
-	{
-		if( c->joined )
-		{
-			user_t *u, *r;
-			
-			r = user_find( ic->irc, ic->irc->mynick );
-			irc_privmsg( ic->irc, r, "PRIVMSG", c->channel, "", "Cleaning up channel, bye!" );
-			
-			u = user_find( ic->irc, ic->irc->nick );
-			irc_kick( ic->irc, u, c->channel, r );
-			/* irc_part( ic->irc, u, c->channel ); */
-		}
-		
-		/* Find the previous chat in the linked list. */
-		for( l = ic->groupchats; l && l->next != c; l = l->next );
-		
-		if( l )
-			l->next = c->next;
-		else
-			ic->groupchats = c->next;
-		
-		for( ir = c->in_room; ir; ir = ir->next )
-			g_free( ir->data );
-		g_list_free( c->in_room );
-		g_free( c->channel );
-		g_free( c->title );
-		g_free( c->topic );
-		g_free( c );
-	}
-}
-
-void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at )
-{
-	struct im_connection *ic = c->ic;
-	char *wrapped;
-	user_t *u;
-	
-	/* Gaim sends own messages through this too. IRC doesn't want this, so kill them */
-	if( g_strcasecmp( who, ic->acc->user ) == 0 )
-		return;
-	
-	u = user_findhandle( ic, who );
-	
-	if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) ||
-	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) )
-		strip_html( msg );
-	
-	wrapped = word_wrap( msg, 425 );
-	if( c && u )
-	{
-		irc_privmsg( ic->irc, u, "PRIVMSG", c->channel, "", wrapped );
-	}
-	else
-	{
-		imcb_log( ic, "Message from/to conversation %s@%p (unknown conv/user): %s", who, c, wrapped );
-	}
-	g_free( wrapped );
-}
-
-void imcb_chat_log( struct groupchat *c, char *format, ... )
-{
-	irc_t *irc = c->ic->irc;
-	va_list params;
-	char *text;
-	user_t *u;
-	
-	va_start( params, format );
-	text = g_strdup_vprintf( format, params );
-	va_end( params );
-	
-	u = user_find( irc, irc->mynick );
-	
-	irc_privmsg( irc, u, "PRIVMSG", c->channel, "System message: ", text );
-	
-	g_free( text );
-}
-
-void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at )
-{
-	struct im_connection *ic = c->ic;
-	user_t *u = NULL;
-	
-	if( who == NULL)
-		u = user_find( ic->irc, ic->irc->mynick );
-	else if( g_strcasecmp( who, ic->acc->user ) == 0 )
-		u = user_find( ic->irc, ic->irc->nick );
-	else
-		u = user_findhandle( ic, who );
-	
-	if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) ||
-	    ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) )
-		strip_html( topic );
-	
-	g_free( c->topic );
-	c->topic = g_strdup( topic );
-	
-	if( c->joined && u )
-		irc_write( ic->irc, ":%s!%s@%s TOPIC %s :%s", u->nick, u->user, u->host, c->channel, topic );
-}
-
-
-/* buddy_chat.c */
-
-void imcb_chat_add_buddy( struct groupchat *b, const char *handle )
-{
-	user_t *u = user_findhandle( b->ic, handle );
-	int me = 0;
-	
-	if( set_getbool( &b->ic->irc->set, "debug" ) )
-		imcb_log( b->ic, "User %s added to conversation %p", handle, b );
-	
-	/* It might be yourself! */
-	if( b->ic->acc->prpl->handle_cmp( handle, b->ic->acc->user ) == 0 )
-	{
-		u = user_find( b->ic->irc, b->ic->irc->nick );
-		if( !b->joined )
-			irc_join( b->ic->irc, u, b->channel );
-		b->joined = me = 1;
-	}
-	
-	/* Most protocols allow people to join, even when they're not in
-	   your contact list. Try to handle that here */
-	if( !u )
-	{
-		imcb_add_buddy( b->ic, handle, NULL );
-		u = user_findhandle( b->ic, handle );
-	}
-	
-	/* Add the handle to the room userlist, if it's not 'me' */
-	if( !me )
-	{
-		if( b->joined )
-			irc_join( b->ic->irc, u, b->channel );
-		b->in_room = g_list_append( b->in_room, g_strdup( handle ) );
-	}
-}
-
-/* This function is one BIG hack... :-( EREWRITE */
-void imcb_chat_remove_buddy( struct groupchat *b, const char *handle, const char *reason )
-{
-	user_t *u;
-	int me = 0;
-	
-	if( set_getbool( &b->ic->irc->set, "debug" ) )
-		imcb_log( b->ic, "User %s removed from conversation %p (%s)", handle, b, reason ? reason : "" );
-	
-	/* It might be yourself! */
-	if( g_strcasecmp( handle, b->ic->acc->user ) == 0 )
-	{
-		if( b->joined == 0 )
-			return;
-		
-		u = user_find( b->ic->irc, b->ic->irc->nick );
-		b->joined = 0;
-		me = 1;
-	}
-	else
-	{
-		u = user_findhandle( b->ic, handle );
-	}
-	
-	if( me || ( remove_chat_buddy_silent( b, handle ) && b->joined && u ) )
-		irc_part( b->ic->irc, u, b->channel );
-}
-
-static int remove_chat_buddy_silent( struct groupchat *b, const char *handle )
-{
-	GList *i;
-	
-	/* Find the handle in the room userlist and shoot it */
-	i = b->in_room;
-	while( i )
-	{
-		if( g_strcasecmp( handle, i->data ) == 0 )
-		{
-			g_free( i->data );
-			b->in_room = g_list_remove( b->in_room, i->data );
-			return( 1 );
-		}
-		
-		i = i->next;
-	}
-	
-	return( 0 );
-}
-
-
-/* Misc. BitlBee stuff which shouldn't really be here */
-
-char *set_eval_away_devoice( set_t *set, char *value )
-{
-	irc_t *irc = set->data;
-	int st;
-	
-	if( !is_bool( value ) )
-		return SET_INVALID;
-	
-	st = bool2int( value );
-	
-	/* Horror.... */
-	
-	if( st != set_getbool( &irc->set, "away_devoice" ) )
-	{
-		char list[80] = "";
-		user_t *u = irc->users;
-		int i = 0, count = 0;
-		char pm;
-		char v[80];
-		
-		if( st )
-			pm = '+';
-		else
-			pm = '-';
-		
-		while( u )
-		{
-			if( u->ic && u->online && !u->away )
-			{
-				if( ( strlen( list ) + strlen( u->nick ) ) >= 79 )
-				{
-					for( i = 0; i < count; v[i++] = 'v' ); v[i] = 0;
-					irc_write( irc, ":%s MODE %s %c%s%s",
-					           irc->myhost,
-		        			   irc->channel, pm, v, list );
-					
-					*list = 0;
-					count = 0;
-				}
-				
-				sprintf( list + strlen( list ), " %s", u->nick );
-				count ++;
-			}
-			u = u->next;
-		}
-		
-		/* $v = 'v' x $i */
-		for( i = 0; i < count; v[i++] = 'v' ); v[i] = 0;
-		irc_write( irc, ":%s MODE %s %c%s%s", irc->myhost,
-		                                            irc->channel, pm, v, list );
-	}
-	
-	return value;
-}
-
-
-
+	query_add( (irc_t *) ic->bee->ui_data, ic, s,
+	           imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data );
+}
+
+struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle )
+{
+	return bee_user_by_handle( ic->bee, ic, handle );
+}
 
 /* The plan is to not allow straight calls to prpl functions anymore, but do
    them all from some wrappers. We'll start to define some down here: */
 
-int imc_buddy_msg( struct im_connection *ic, char *handle, char *msg, int flags )
+int imc_chat_msg( struct groupchat *c, char *msg, int flags )
 {
 	char *buf = NULL;
-	int st;
-	
-	if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
+	
+	if( ( c->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
 	{
 		buf = escape_html( msg );
@@ -1078,20 +529,4 @@
 	}
 	
-	st = ic->acc->prpl->buddy_msg( ic, handle, msg, flags );
-	g_free( buf );
-	
-	return st;
-}
-
-int imc_chat_msg( struct groupchat *c, char *msg, int flags )
-{
-	char *buf = NULL;
-	
-	if( ( c->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
-	{
-		buf = escape_html( msg );
-		msg = buf;
-	}
-	
 	c->ic->acc->prpl->chat_msg( c, msg, flags );
 	g_free( buf );
@@ -1106,6 +541,10 @@
 	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" ) ?
-	     : set_getstr( &ic->irc->set, "away" );
+	     : set_getstr( &ic->bee->set, "away" );
 	if( away && *away )
 	{
@@ -1118,5 +557,5 @@
 		away = NULL;
 		msg = set_getstr( &ic->acc->set, "status" ) ?
-		    : set_getstr( &ic->irc->set, "status" );
+		    : set_getstr( &ic->bee->set, "status" );
 	}
 	
Index: protocols/nogaim.h
===================================================================
--- protocols/nogaim.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/nogaim.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -2,5 +2,5 @@
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  * Copyright 2002-2010 Wilmer van der Gaast and others                *
   \********************************************************************/
 
@@ -39,5 +39,11 @@
 #define _NOGAIM_H
 
+#if(__sun)
+#include <inttypes.h>
+#else
 #include <stdint.h>
+#endif
+
+#include "lib/events.h"
 
 #include "bitlbee.h"
@@ -45,4 +51,6 @@
 #include "proxy.h"
 #include "query.h"
+#include "md5.h"
+#include "ft.h"
 
 #define BUDDY_ALIAS_MAXLEN 388   /* because MSN names can be 387 characters */
@@ -85,7 +93,7 @@
 	
 	/* BitlBee */
-	irc_t *irc;
-	
-	struct groupchat *groupchats;
+	bee_t *bee;
+	
+	GSList *groupchats;
 };
 
@@ -98,8 +106,7 @@
 	 * already, for example to avoid adding somebody two times. */
 	GList *in_room;
-	GList *ignored;
-	
-	struct groupchat *next;
-	char *channel;
+	//GList *ignored;
+	
+	//struct groupchat *next;
 	/* The title variable contains the ID you gave when you created the
 	 * chat using imcb_chat_new(). */
@@ -112,4 +119,5 @@
 	 * structure for your protocol's needs. */
 	void *data;
+	void *ui_data;
 };
 
@@ -132,4 +140,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
@@ -209,5 +218,6 @@
 	 * not implement this. */
 	struct groupchat *
-	     (* chat_join)	(struct im_connection *, const char *room, const char *nick, const char *password);
+	     (* chat_join)	(struct im_connection *, const char *room,
+	                         const char *nick, const char *password, set_t **sets);
 	/* Change the topic, if supported. Note that BitlBee expects the IM
 	   server to confirm the topic change with a regular topic change
@@ -216,4 +226,9 @@
 	void (* chat_topic)	(struct groupchat *, char *topic);
 	
+	/* If your protocol module needs any special info for joining chatrooms
+	   other than a roomname + nickname, add them here. */
+	void (* chat_add_settings)	(account_t *acc, set_t **head);
+	void (* chat_free_settings)	(account_t *acc, set_t **head);
+	
 	/* You can tell what away states your protocol supports, so that
 	 * BitlBee will try to map the IRC away reasons to them. If your
@@ -228,4 +243,17 @@
 	void (* auth_allow)	(struct im_connection *, const char *who);
 	void (* auth_deny)	(struct im_connection *, const char *who);
+
+	/* Incoming transfer request */
+	void (* transfer_request) (struct im_connection *, file_transfer_t *ft, char *handle );
+	
+	void (* buddy_data_add) (struct bee_user *bu);
+	void (* buddy_data_free) (struct bee_user *bu);
+	
+	/* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */
+	void *resv1;
+	void *resv2;
+	void *resv3;
+	void *resv4;
+	void *resv5;
 };
 
@@ -263,4 +291,5 @@
  */
 G_MODULE_EXPORT void imcb_ask( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont );
+G_MODULE_EXPORT void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont, query_callback myfree );
 
 /* Two common questions you may want to ask:
@@ -281,39 +310,10 @@
 G_MODULE_EXPORT void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick );
 
-/* Buddy activity */
-/* To manipulate the status of a handle.
- * - flags can be |='d with OPT_* constants. You will need at least:
- *   OPT_LOGGED_IN and OPT_AWAY.
- * - 'state' and 'message' can be NULL */
-G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message );
-/* Not implemented yet! */ G_MODULE_EXPORT void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle );
-/* Call when a handle says something. 'flags' and 'sent_at may be just 0. */
-G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at );
 G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags );
+G_MODULE_EXPORT struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle );
 G_MODULE_EXPORT void imcb_clean_handle( struct im_connection *ic, char *handle );
-
-/* Groupchats */
-G_MODULE_EXPORT void imcb_chat_invited( struct im_connection *ic, char *handle, char *who, char *msg, GList *data );
-/* These two functions are to create a group chat.
- * - imcb_chat_new(): the 'handle' parameter identifies the chat, like the
- *   channel name on IRC.
- * - After you have a groupchat pointer, you should add the handles, finally
- *   the user her/himself. At that point the group chat will be visible to the
- *   user, too. */
-G_MODULE_EXPORT struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle );
-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. */
-G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *b, const char *handle, const char *reason );
-/* To tell BitlBee 'who' said 'msg' in 'c'. 'flags' and 'sent_at' can be 0. */
-G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at );
-/* System messages specific to a groupchat, so they can be displayed in the right context. */
-G_MODULE_EXPORT void imcb_chat_log( struct groupchat *c, char *format, ... ) G_GNUC_PRINTF( 2, 3 );
-/* To tell BitlBee 'who' changed the topic of 'c' to 'topic'. */
-G_MODULE_EXPORT void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at );
-G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c );
 
 /* Actions, or whatever. */
 int imc_away_send_update( struct im_connection *ic );
-int imc_buddy_msg( struct im_connection *ic, char *handle, char *msg, int flags );
 int imc_chat_msg( struct groupchat *c, char *msg, int flags );
 
@@ -324,5 +324,5 @@
 
 /* Misc. stuff */
-char *set_eval_away_devoice( set_t *set, char *value );
+char *set_eval_timezone( set_t *set, char *value );
 gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond );
 void cancel_auto_reconnect( struct account *a );
Index: protocols/oscar/Makefile
===================================================================
--- protocols/oscar/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/oscar/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -8,9 +8,12 @@
 
 -include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/oscar/
+CFLAGS += -I$(SRCDIR)
+endif
 
 # [SH] Program variables
 objects = admin.o auth.o bos.o buddylist.o chat.o chatnav.o conn.o icq.o im.o info.o misc.o msgcookie.o rxhandlers.o rxqueue.o search.o service.o snac.o ssi.o stats.o tlv.o txqueue.o oscar_util.o oscar.o
 
-CFLAGS += -Wall
 LFLAGS += -r
 
@@ -33,5 +36,5 @@
 $(objects): ../../Makefile.settings Makefile
 
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
 	@echo '*' Compiling $<
 	@$(CC) -c $(CFLAGS) $< -o $@
Index: protocols/oscar/auth.c
===================================================================
--- protocols/oscar/auth.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/oscar/auth.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -120,9 +120,10 @@
 	aim_snacid_t snacid;
 	aim_tlvlist_t *tl = NULL;
+	struct im_connection *ic = sess->aux_data;
 	
 	if (!sess || !conn || !sn)
 		return -EINVAL;
 
-	if ((sn[0] >= '0') && (sn[0] <= '9'))
+	if (isdigit(sn[0]) && set_getbool(&ic->acc->set, "old_icq_auth"))
 		return goddamnicq(sess, conn, sn);
 
Index: protocols/oscar/oscar.c
===================================================================
--- protocols/oscar/oscar.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/oscar/oscar.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 *, ...);
@@ -255,6 +254,4 @@
 
 	u = t = g_strdup(s);
-
-	strcpy(t, s);
 	g_strdown(t);
 
@@ -291,5 +288,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 +360,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);
 	
@@ -373,9 +370,14 @@
 	set_t *s;
 	
-	s = set_add( &acc->set, "server", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc );
+	if (isdigit(acc->user[0])) {
+		set_add(&acc->set, "ignore_auth_requests", "false", set_eval_bool, acc);
+		set_add(&acc->set, "old_icq_auth", "false", set_eval_bool, acc);
+	}
+	
+	s = set_add(&acc->set, "server", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc);
 	s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
 	
-	if (isdigit(acc->user[0])) {
-		s = set_add( &acc->set, "web_aware", "false", set_eval_bool, acc );
+	if(isdigit(acc->user[0])) {
+		s = set_add(&acc->set, "web_aware", "false", set_eval_bool, acc);
 		s->flags |= ACC_SET_OFFLINE_ONLY;
 	}
@@ -489,5 +491,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"));
@@ -566,5 +568,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);
@@ -600,140 +601,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, ...) {
@@ -783,4 +650,5 @@
 	struct im_connection *ic = sess->aux_data;
 	struct chat_connection *chatcon;
+	struct groupchat *c = NULL;
 	static int id = 1;
 
@@ -795,5 +663,10 @@
 	chatcon = find_oscar_chat_by_conn(ic, fr->conn);
 	chatcon->id = id;
-	chatcon->cnv = imcb_chat_new(ic, chatcon->show);
+	
+	c = bee_chat_by_title(ic->bee, ic, chatcon->show);
+	if (c && !c->data)
+		chatcon->cnv = c;
+	else
+		chatcon->cnv = imcb_chat_new(ic, chatcon->show);
 	chatcon->cnv->data = chatcon;
 
@@ -834,5 +707,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);
 	
@@ -862,5 +735,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);
 	
@@ -898,5 +771,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);
@@ -1065,5 +938,5 @@
 	tmp = normalize(info->sn);
 	imcb_buddy_status(ic, tmp, flags, state_string, NULL);
-	/* imcb_buddy_times(ic, tmp, signon, time_idle); */
+	imcb_buddy_times(ic, tmp, signon, time_idle);
 
 
@@ -1162,5 +1035,5 @@
 		m = g_list_append(m, exch);
 
-		g_snprintf( txt, 1024, "Got an invitation to chatroom %s from %s: %s", name, userinfo->sn, args->msg );
+		g_snprintf(txt, 1024, "Got an invitation to chatroom %s from %s: %s", name, userinfo->sn, args->msg);
 
 		inv->ic = ic;
@@ -1168,8 +1041,13 @@
 		inv->name = g_strdup(name);
 		
-		imcb_ask( ic, txt, inv, oscar_accept_chat, oscar_reject_chat);
+		imcb_ask(ic, txt, inv, oscar_accept_chat, oscar_reject_chat);
 	
 		if (name)
 			g_free(name);
+	} else if (args->reqclass & AIM_CAPS_ICQRTF) {
+		// TODO: constify
+		char text[strlen(args->info.rtfmsg.rtfmsg)+1];
+		strncpy(text, args->info.rtfmsg.rtfmsg, sizeof(text));
+		imcb_buddy_msg(ic, normalize(userinfo->sn), text, 0, 0);
 	}
 
@@ -1186,6 +1064,5 @@
 	aim_ssi_auth_reply(od->sess, od->conn, uin, 1, "");
 	// aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHGRANTED, &message);
-	if(imcb_find_buddy(data->ic, uin) == NULL)
-		imcb_ask_add(data->ic, uin, NULL);
+	imcb_ask_add(data->ic, uin, NULL);
 	
 	g_free(uin);
@@ -1212,8 +1089,13 @@
  */
 static void gaim_icq_authask(struct im_connection *ic, guint32 uin, char *msg) {
-	struct icq_auth *data = g_new(struct icq_auth, 1);
+	struct icq_auth *data;
 	char *reason = NULL;
 	char *dialog_msg;
-	
+
+	if (set_getbool(&ic->acc->set, "ignore_auth_requests"))
+		return;
+	
+	data = g_new(struct icq_auth, 1);
+
 	if (strlen(msg) > 6)
 		reason = msg + 6;
@@ -1943,4 +1825,5 @@
 	struct oscar_data *odata = (struct oscar_data *)g->proto_data;
 	if (odata->icq) {
+		/** FIXME(wilmer): Hmm, lost the ability to get away msgs here, do we care to get that back?
 		struct buddy *budlight = imcb_find_buddy(g, who);
 		if (budlight)
@@ -1948,4 +1831,5 @@
 				if (budlight->caps & AIM_CAPS_ICQSERVERRELAY)
 					aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xff80) >> 7);
+		*/
 	} else
 		aim_getinfo(odata->sess, odata->conn, who, AIM_GETINFO_AWAYMESSAGE);
@@ -2059,5 +1943,10 @@
 static void oscar_add_buddy(struct im_connection *g, char *name, char *group) {
 	struct oscar_data *odata = (struct oscar_data *)g->proto_data;
-	aim_ssi_addbuddies(odata->sess, odata->conn, OSCAR_GROUP, &name, 1, 0);
+	bee_user_t *bu;
+	
+	if (group && (bu = bee_user_by_handle(g->bee, g, name)) && bu->group)
+		aim_ssi_movebuddy(odata->sess, odata->conn, bu->group->name, group, name);
+	else
+		aim_ssi_addbuddies(odata->sess, odata->conn, group ? : OSCAR_GROUP, &name, 1, 0);
 }
 
@@ -2074,5 +1963,5 @@
 static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) {
 	struct im_connection *ic = sess->aux_data;
-	struct aim_ssi_item *curitem;
+	struct aim_ssi_item *curitem, *curgroup = NULL;
 	int tmp;
 	char *nrm;
@@ -2085,11 +1974,11 @@
 		switch (curitem->type) {
 			case 0x0000: /* Buddy */
-				if ((curitem->name) && (!imcb_find_buddy(ic, nrm))) {
+				if ((curitem->name) && (!imcb_buddy_by_handle(ic, nrm))) {
 					char *realname = NULL;
 
 					if (curitem->data && aim_gettlv(curitem->data, 0x0131, 1))
 						    realname = aim_gettlv_str(curitem->data, 0x0131, 1);
-						
-					imcb_add_buddy(ic, nrm, NULL);
+					
+					imcb_add_buddy(ic, nrm, curgroup ? (curgroup->gid == curitem->gid ? curgroup->name : NULL) : NULL);
 					
 					if (realname) {
@@ -2099,4 +1988,8 @@
 					}
 				}
+				break;
+
+			case 0x0001: /* Group */
+				curgroup = curitem;
 				break;
 
@@ -2179,8 +2072,11 @@
 		for( i = 0; i < count; i ++ )
 		{
+			struct aim_ssi_item *ssigroup = aim_ssi_itemlist_findparent( sess->ssi.items, list );
+			char *group = ssigroup ? ssigroup->name : NULL;
+			
 			st = aimbs_get16( &fr->data );
 			if( st == 0x00 )
 			{
-				imcb_add_buddy( sess->aux_data, normalize(list), NULL );
+				imcb_add_buddy( sess->aux_data, normalize(list), group );
 			}
 			else if( st == 0x0E )
@@ -2330,5 +2226,5 @@
 		}
 	}
-	if ((ip = (long) g_hash_table_lookup(od->ips, &info->uin)) != 0) {
+	if (od->ips && (ip = (long) g_hash_table_lookup(od->ips, &info->uin)) != 0) {
 		g_string_append_printf(str, "\n%s: %d.%d.%d.%d", _("Last used IP address"),
 		                       (ip >> 24), (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
@@ -2543,5 +2439,6 @@
 	char *s;
 	
-	ccon = c->data;
+	if (!(ccon = c->data))
+		return;
 	  	
 	for (s = message; *s; s++)
@@ -2584,5 +2481,8 @@
 	struct im_connection *ic = c->ic;
 	struct oscar_data * od = (struct oscar_data *)ic->proto_data;
-	struct chat_connection *ccon = c->data;
+	struct chat_connection *ccon;
+	
+	if (!(ccon = c->data))
+		return;
 	
 	aim_chat_invite(od->sess, od->conn, who, message ? message : "",
@@ -2609,10 +2509,14 @@
 void oscar_chat_leave(struct groupchat *c)
 {
+	if (!c->data)
+		return;
 	oscar_chat_kill(c->ic, c->data);
 }
 
-struct groupchat *oscar_chat_join(struct im_connection * ic, const char * room, const char * nick, const char * password )
+struct groupchat *oscar_chat_join_internal(struct im_connection *ic, const char *room,
+	const char *nick, const char *password, int exchange_number)
 {
 	struct oscar_data * od = (struct oscar_data *)ic->proto_data;
+	struct groupchat *ret = imcb_chat_new(ic, room);
 	aim_conn_t * cur;
 
@@ -2620,17 +2524,23 @@
 		int st;
 		
-		st = aim_chatnav_createroom(od->sess, cur, room, 4);
+		st = aim_chatnav_createroom(od->sess, cur, room, exchange_number);
 		
-		return NULL;
+		return ret;
 	} else {
 		struct create_room * cr = g_new0(struct create_room, 1);
 		
-		cr->exchange = 4;
+		cr->exchange = exchange_number;
 		cr->name = g_strdup(room);
 		od->create_rooms = g_slist_append(od->create_rooms, cr);
 		aim_reqservice(od->sess, od->conn, AIM_CONN_TYPE_CHATNAV);
 		
-		return NULL;
-	}
+		return ret;
+	}
+}
+
+struct groupchat *oscar_chat_join(struct im_connection *ic, const char *room,
+	const char *nick, const char *password, set_t **sets)
+{
+	return oscar_chat_join_internal(ic, room, nick, password, set_getint(sets, "exchange_number"));
 }
 
@@ -2640,10 +2550,16 @@
 	struct groupchat *ret;
 	static int chat_id = 0;
-	char * chatname;
-	
-	chatname = g_strdup_printf("%s%d", ic->acc->user, chat_id++);
-  
-	ret = oscar_chat_join(ic, chatname, NULL, NULL);
-
+	char * chatname, *s;
+	struct groupchat *c;
+	
+	chatname = g_strdup_printf("%s%s%d", isdigit(*ic->acc->user) ? "icq" : "",
+	                           ic->acc->user, chat_id++);
+	
+	for (s = chatname; *s; s ++)
+		if (!isalnum(*s))
+			*s = '0';
+	
+	c = imcb_chat_new(ic, chatname);
+	ret = oscar_chat_join_internal(ic, chatname, NULL, NULL, 4);
 	aim_chat_invite(od->sess, od->conn, who, "", 4, chatname, 0x0);
 
@@ -2657,5 +2573,5 @@
 	struct aim_chat_invitation * inv = data;
 	
-	oscar_chat_join(inv->ic, inv->name, NULL, NULL);
+	oscar_chat_join_internal(inv->ic, inv->name, NULL, NULL, 4);
 	g_free(inv->name);
 	g_free(inv);
@@ -2668,4 +2584,14 @@
 	g_free(inv->name);
 	g_free(inv);
+}
+
+void oscar_chat_add_settings(account_t *acc, set_t **head)
+{
+	set_add(head, "exchange_number", "4", set_eval_int, NULL);
+}
+
+void oscar_chat_free_settings(account_t *acc, set_t **head)
+{
+	set_del(head, "exchange_number");
 }
 
@@ -2690,4 +2616,6 @@
 	ret->chat_with = oscar_chat_with;
 	ret->chat_join = oscar_chat_join;
+	ret->chat_add_settings = oscar_chat_add_settings;
+	ret->chat_free_settings = oscar_chat_free_settings;
 	ret->add_permit = oscar_add_permit;
 	ret->add_deny = oscar_add_deny;
Index: protocols/purple/Makefile
===================================================================
--- protocols/purple/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/purple/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,44 @@
+###########################
+## Makefile for BitlBee  ##
+##                       ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/purple/
+endif
+
+# [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: $(SRCDIR)%.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 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/purple/ft-direct.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/purple/ft.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,355 @@
+/***************************************************************************\
+*                                                                           *
+*  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;
+		px->ic = purple_ic_by_pa( xfer->account );
+		
+		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( px->ic, ft, "Read error" );
+		}
+	}
+	
+	if( lseek( px->fd, 0, SEEK_CUR ) == px->xfer->size )
+	{
+		/*purple_xfer_end( px->xfer );*/
+		imcb_file_finished( px->ic, 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->ic, 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;
+	struct prpl_xfer_data *px = ft->data;
+	
+	if( ft->write == NULL )
+	{
+		ft->write = prpl_xfer_write;
+		imcb_file_recv_start( px->ic, 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( px->ic, 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( px->ic, 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 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/purple/purple.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,1201 @@
+/***************************************************************************\
+*                                                                           *
+*  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 bee_t *local_bee;
+
+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:
+			/** No way to talk to the user right now, invent one when
+			this becomes important.
+			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_bee != NULL && local_bee != acc->bee )
+	{
+		imcb_error( ic,  "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
+		                 "Please use inetd or ForkDaemon mode instead." );
+		imc_logout( ic, FALSE );
+		return;
+	}
+	local_bee = acc->bee;
+	
+	/* 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;
+	PurpleGroup *pg = NULL;
+	
+	if( group && !( pg = purple_find_group( group ) ) )
+	{
+		pg = purple_group_new( group );
+		purple_blist_add_group( pg, NULL );
+	}
+	
+	pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
+	purple_blist_add_buddy( pb, NULL, pg, 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 )
+	{
+		PurpleGroup *group;
+		
+		group = purple_buddy_get_group( pb );
+		purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, group );
+		
+		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_update( PurpleBuddyList *list, PurpleBlistNode *node )
+{
+	if( node->type == PURPLE_BLIST_BUDDY_NODE )
+	{
+		PurpleBuddy *bud = (PurpleBuddy*) node;
+		PurpleGroup *group = purple_buddy_get_group( bud );
+		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 );
+		
+		if( group )
+			imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) );
+		
+		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" ) );
+		
+		imcb_buddy_times( ic, bud->name,
+		                  purple_presence_get_login_time( bud->presence ),
+		                  purple_presence_get_idle_time( bud->presence ) );
+	}
+}
+
+static void prplcb_blist_new( PurpleBlistNode *node )
+{
+	if( node->type == PURPLE_BLIST_BUDDY_NODE )
+	{
+		PurpleBuddy *bud = (PurpleBuddy*) node;
+		struct im_connection *ic = purple_ic_by_pa( bud->account );
+		
+		if( ic == NULL )
+			return;
+		
+		imcb_add_buddy( ic, bud->name, NULL );
+		
+		prplcb_blist_update( NULL, node );
+	}
+}
+
+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;
+	
+	if( pqad->yes )
+		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;
+	
+	if( pqad->no )
+		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" ) || strstr( caption, "OK" ) )
+		{
+			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;
+	
+	/* TODO: IRC stuff here :-( */
+	q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
+	pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q,
+		prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad );
+	
+	g_free( q );
+	
+	return pqad;
+}
+
+/*
+static void prplcb_request_test()
+{
+	fprintf( stderr, "bla\n" );
+}
+*/
+
+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,
+};
+
+static void *prplcb_account_request_authorize( PurpleAccount *account, const char *remote_user,
+	const char *id, const char *alias, const char *message, gboolean on_list,
+	PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data )
+{
+	struct im_connection *ic = purple_ic_by_pa( account );
+	char *q;
+	
+	if( alias )
+		q = g_strdup_printf( "%s (%s) wants to add you to his/her contact "
+		                     "list. (%s)", alias, remote_user, message );
+	else
+		q = g_strdup_printf( "%s wants to add you to his/her contact "
+		                     "list. (%s)", remote_user, message );
+	
+	imcb_ask_with_free( ic, q, user_data, authorize_cb, deny_cb, NULL );
+	g_free( q );
+	
+	return NULL;
+}
+
+static PurpleAccountUiOps bee_account_uiops =
+{
+	NULL,
+	NULL,
+	NULL,
+	prplcb_account_request_authorize,
+	NULL,
+};
+
+extern PurpleXferUiOps bee_xfer_uiops;
+
+static void purple_ui_init()
+{
+	purple_connections_set_ui_ops( &bee_conn_uiops );
+	purple_blist_set_ui_ops( &bee_blist_uiops );
+	purple_conversations_set_ui_ops( &bee_conv_uiops );
+	purple_request_set_ui_ops( &bee_request_uiops );
+	purple_privacy_set_ui_ops( &bee_privacy_uiops );
+	purple_notify_set_ui_ops( &bee_notify_uiops );
+	purple_accounts_set_ui_ops( &bee_account_uiops );
+	purple_xfers_set_ui_ops( &bee_xfer_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 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/twitter/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,45 @@
+###########################
+## Makefile for BitlBee  ##
+##                       ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/twitter/
+endif
+
+# [SH] Program variables
+objects = twitter.o twitter_http.o twitter_lib.o
+
+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: $(SRCDIR)%.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 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/twitter/twitter.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,553 @@
+/***************************************************************************\
+*                                                                           *
+*  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"
+#include "url.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;
+
+	// 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, "Getting initial statuses" );
+
+	// 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 void twitter_oauth_start( struct im_connection *ic );
+
+void twitter_login_finish( struct im_connection *ic )
+{
+	struct twitter_data *td = ic->proto_data;
+	
+	if( set_getbool( &ic->acc->set, "oauth" ) && !td->oauth_info )
+		twitter_oauth_start( ic );
+	else if( g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) != 0 &&
+	         !( td->flags & TWITTER_HAVE_FRIENDS ) )
+	{
+		imcb_log( ic, "Getting contact list" );
+		twitter_get_statuses_friends( ic, -1 );
+	}
+	else
+		twitter_main_loop_start( ic );
+}
+
+static const struct oauth_service twitter_oauth =
+{
+	"http://api.twitter.com/oauth/request_token",
+	"http://api.twitter.com/oauth/access_token",
+	"https://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, "%s_%s", td->prefix, 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_login_finish( 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;
+	char *def_url;
+	char *def_oauth;
+	
+	if( strcmp( acc->prpl->name, "twitter" ) == 0 )
+	{
+		def_url = TWITTER_API_URL;
+		def_oauth = "true";
+	}
+	else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
+	{
+		def_url = IDENTICA_API_URL;
+		def_oauth = "false";
+	}
+	
+	s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, acc );
+	
+	s = set_add( &acc->set, "base_url", def_url, NULL, acc );
+	s->flags |= ACC_SET_OFFLINE_ONLY;
+	
+	s = set_add( &acc->set, "commands", "true", set_eval_bool, acc );
+	
+	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", def_oauth, 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;
+	char name[strlen(acc->user)+9];
+	url_t url;
+
+	if( !url_set( &url, set_getstr( &ic->acc->set, "base_url" ) ) ||
+	    ( url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS ) )
+	{
+		imcb_error( ic, "Incorrect API base URL: %s", set_getstr( &ic->acc->set, "base_url" ) );
+		imc_logout( ic, FALSE );
+		return;
+	}
+	
+	twitter_connections = g_slist_append( twitter_connections, ic );
+	td = g_new0( struct twitter_data, 1 );
+	ic->proto_data = td;
+	
+	td->url_ssl = url.proto == PROTO_HTTPS;
+	td->url_port = url.port;
+	td->url_host = g_strdup( url.host );
+	if( strcmp( url.file, "/" ) != 0 )
+		td->url_path = g_strdup( url.file );
+	else
+		td->url_path = g_strdup( "" );
+	if( g_str_has_suffix( url.host, ".com" ) )
+		td->prefix = g_strndup( url.host, strlen( url.host ) - 4 );
+	else
+		td->prefix = g_strdup( url.host );
+	
+	td->user = acc->user;
+	if( strstr( acc->pass, "oauth_token=" ) )
+		td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth );
+	
+	sprintf( name, "%s_%s", td->prefix, acc->user );
+	imcb_add_buddy( ic, name, NULL );
+	imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
+	
+	imcb_log( ic, "Connecting" );
+	
+	twitter_login_finish( 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->prefix );
+		g_free( td->url_host );
+		g_free( td->url_path );
+		g_free( td->pass );
+		g_free( td );
+	}
+
+	twitter_connections = g_slist_remove( twitter_connections, ic );
+}
+
+static void twitter_handle_command( struct im_connection *ic, char *message );
+
+/**
+ *
+ */
+static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
+{
+	struct twitter_data *td = ic->proto_data;
+	int plen = strlen( td->prefix );
+	
+	if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' &&
+	    g_strcasecmp(who + plen + 1, ic->acc->user) == 0)
+	{
+		if( set_getbool( &ic->acc->set, "oauth" ) &&
+		    td->oauth_info && td->oauth_info->token == NULL )
+		{
+			char pin[strlen(message)+1], *s;
+			
+			strcpy( pin, message );
+			for( s = pin + sizeof( pin ) - 2; s > pin && isspace( *s ); s -- )
+				*s = '\0';
+			for( s = pin; *s && isspace( *s ); s ++ ) {}
+			
+			if( !oauth_access_token( s, td->oauth_info ) )
+			{
+				imcb_error( ic, "OAuth error: %s", "Failed to send access token request" );
+				imc_logout( ic, TRUE );
+				return FALSE;
+			}
+		}
+		else
+			twitter_handle_command(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 )
+{
+	twitter_friendships_create_destroy(ic, who, 1);
+}
+
+static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+	twitter_friendships_create_destroy(ic, who, 0);
+}
+
+static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
+{
+	if( c && message )
+		twitter_handle_command( 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;
+//}
+
+static void twitter_buddy_data_add( struct bee_user *bu )
+{
+	bu->data = g_new0( struct twitter_user_data, 1 );
+}
+
+static void twitter_buddy_data_free( struct bee_user *bu )
+{
+	g_free( bu->data );
+}
+
+static void twitter_handle_command( struct im_connection *ic, char *message )
+{
+	struct twitter_data *td = ic->proto_data;
+	char *cmds, **cmd;
+	
+	cmds = g_strdup( message );
+	cmd = split_command_parts( cmds );
+	
+	if( cmd[0] == NULL )
+	{
+		g_free( cmds );
+		return;
+	}
+	else if( !set_getbool( &ic->acc->set, "commands" ) )
+	{
+		/* Not supporting commands. */
+	}
+	else if( g_strcasecmp( cmd[0], "undo" ) == 0 )
+	{
+		guint64 id;
+		
+		if( cmd[1] )
+			id = g_ascii_strtoull( cmd[1], NULL, 10 );
+		else
+			id = td->last_status_id;
+		
+		/* TODO: User feedback. */
+		if( id )
+			twitter_status_destroy( ic, id );
+		
+		g_free( cmds );
+		return;
+	}
+	else if( g_strcasecmp( cmd[0], "follow" ) == 0 && cmd[1] )
+	{
+		twitter_add_buddy( ic, cmd[1], NULL );
+		g_free( cmds );
+		return;
+	}
+	else if( g_strcasecmp( cmd[0], "unfollow" ) == 0 && cmd[1] )
+	{
+		twitter_remove_buddy( ic, cmd[1], NULL );
+		g_free( cmds );
+		return;
+	}
+	else if( g_strcasecmp( cmd[0], "rt" ) == 0 && cmd[1] )
+	{
+		struct twitter_user_data *tud;
+		bee_user_t *bu;
+		guint64 id;
+		
+		if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) &&
+		    ( tud = bu->data ) && tud->last_id )
+			id = tud->last_id;
+		else
+			id = g_ascii_strtoull( cmd[1], NULL, 10 );
+		
+		td->last_status_id = 0;
+		if( id )
+			twitter_status_retweet( ic, id );
+		
+		g_free( cmds );
+		return;
+	}
+	else if( g_strcasecmp( cmd[0], "post" ) == 0 )
+	{
+		message += 5;
+	}
+	
+	{
+		guint64 in_reply_to = 0;
+		char *s, *new = NULL;
+		bee_user_t *bu;
+		
+		if( !twitter_length_check( ic, message ) )
+		{
+			g_free( cmds );
+		  	return;
+		}
+		
+		s = cmd[0] + strlen( cmd[0] ) - 1;
+		if( s > cmd[0] && ( *s == ':' || *s == ',' ) )
+		{
+			*s = '\0';
+			
+			if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[0] ) ) )
+			{
+				struct twitter_user_data *tud = bu->data;
+				
+				new = g_strdup_printf( "@%s %s", bu->handle,
+				                       message + ( s - cmd[0] ) + 2 );
+				message = new;
+				
+				if( time( NULL ) < tud->last_time +
+				    set_getint( &ic->acc->set, "auto_reply_timeout" ) )
+					in_reply_to = tud->last_id;
+			}
+		}
+		
+		/* If the user runs undo between this request and its response
+		   this would delete the second-last Tweet. Prevent that. */
+		td->last_status_id = 0;
+		twitter_post_status( ic, message, in_reply_to );
+		g_free( new );
+	}
+	g_free( cmds );
+}
+
+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->buddy_data_add = twitter_buddy_data_add;
+	ret->buddy_data_free = twitter_buddy_data_free;
+	ret->handle_cmp = g_strcasecmp;
+	
+	register_protocol(ret);
+
+	/* And an identi.ca variant: */
+	ret = g_memdup(ret, sizeof(struct prpl));
+	ret->name = "identica";
+	register_protocol(ret);
+
+	// Initialise the twitter_connections GSList.
+	twitter_connections = NULL;
+}
Index: protocols/twitter/twitter.h
===================================================================
--- protocols/twitter/twitter.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/twitter/twitter.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,75 @@
+/***************************************************************************\
+*                                                                           *
+*  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
+
+typedef enum
+{
+	TWITTER_HAVE_FRIENDS = 1,
+} twitter_flags_t;
+
+struct twitter_data
+{
+	char* user;
+	char* pass;
+	struct oauth_info *oauth_info;
+	guint64 home_timeline_id;
+	guint64 last_status_id; /* For undo */
+	gint main_loop_id;
+	struct groupchat *home_timeline_gc;
+	gint http_fails;
+	twitter_flags_t flags;
+	
+	gboolean url_ssl;
+	int url_port;
+	char *url_host;
+	char *url_path;
+
+	char *prefix; /* Used to generate contact + channel name. */
+};
+
+struct twitter_user_data
+{
+	guint64 last_id;
+	time_t last_time;
+};
+
+/**
+ * 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;
+
+void twitter_login_finish( struct im_connection *ic );
+
+#endif //_TWITTER_H
Index: protocols/twitter/twitter_http.c
===================================================================
--- protocols/twitter/twitter_http.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/twitter/twitter_http.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,142 @@
+/***************************************************************************\
+*                                                                           *
+*  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"
+
+
+static 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(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char** arguments, int arguments_len)
+{
+	struct twitter_data *td = ic->proto_data;
+	char *tmp;
+	GString *request = g_string_new("");
+	void *ret;
+	char *url_arguments;
+
+	url_arguments = g_strdup("");
+
+	// 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;
+		}
+	}
+
+	// Make the request.
+	g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"
+	                         "Host: %s\r\n"
+	                         "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
+	                         is_post ? "POST" : "GET",
+	                         td->url_path, url_string,
+	                         is_post ? "" : "?", is_post ? "" : url_arguments,
+	                         td->url_host);
+
+	// If a pass and user are given we append them to the request.
+	if (td->oauth_info)
+	{
+		char *full_header;
+		char *full_url;
+		
+		full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url" ), url_string, NULL);
+		full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET",
+		                                full_url, url_arguments);
+		
+		g_string_append_printf(request, "Authorization: %s\r\n", full_header);
+		g_free(full_header);
+		g_free(full_url);
+	}
+	else
+	{
+		char userpass[strlen(ic->acc->user)+2+strlen(ic->acc->pass)];
+		char *userpass_base64;
+		
+		g_snprintf(userpass, sizeof(userpass), "%s:%s", ic->acc->user, ic->acc->pass);
+		userpass_base64 = base64_encode((unsigned char*)userpass, strlen(userpass));
+		g_string_append_printf(request, "Authorization: Basic %s\r\n", userpass_base64);
+		g_free( userpass_base64 );
+	}
+
+	// Do POST stuff..
+	if (is_post)
+	{
+		// Append the Content-Type and url-encoded arguments.
+		g_string_append_printf(request,
+		                       "Content-Type: application/x-www-form-urlencoded\r\n"
+		                       "Content-Length: %zd\r\n\r\n%s",
+		                       strlen(url_arguments), url_arguments);
+	} else {
+		// Append an extra \r\n to end the request...
+		g_string_append(request, "\r\n");
+	}
+
+	ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data);
+
+	g_free( url_arguments );
+	g_string_free( request, TRUE );
+	return ret;
+}
+
+static 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 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/twitter/twitter_http.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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(struct im_connection *ic, char *url_string, http_input_function func,
+                   gpointer data, int is_post, char** arguments, int arguments_len);
+
+#endif //_TWITTER_HTTP_H
+
Index: protocols/twitter/twitter_lib.c
===================================================================
--- protocols/twitter/twitter_lib.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/twitter/twitter_lib.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,837 @@
+/***************************************************************************\
+*                                                                           *
+*  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(): */
+#if(__sun)
+#else
+#define _XOPEN_SOURCE
+#endif
+
+#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>
+
+/* GLib < 2.12.0 doesn't have g_ascii_strtoll(), work around using system strtoll(). */
+/* GLib < 2.12.4 can be buggy: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488013 */
+#if !GLIB_CHECK_VERSION(2,12,5)
+#include <stdlib.h>
+#include <limits.h>
+#define g_ascii_strtoll strtoll
+#endif
+
+#define TXL_STATUS 1
+#define TXL_USER 2
+#define TXL_ID 3
+
+struct twitter_xml_list {
+	int type;
+	gint64 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;
+};
+
+static void twitter_groupchat_init(struct im_connection *ic);
+
+/**
+ * Frees a twitter_xml_user struct.
+ */
+static void txu_free(struct twitter_xml_user *txu)
+{
+	if (txu == NULL)
+		return;
+	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;
+	if (txl == NULL)
+		return;
+	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 (!bee_user_by_handle( ic->bee, 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)
+		{
+			/* Necessary so that nicks always get translated to the
+			   exact Twitter username. */
+			imcb_buddy_nick_hint( ic, name, name );
+			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 );
+	}
+}
+
+/* Warning: May return a malloc()ed value, which will be free()d on the next
+   call. Only for short-term use. */
+static char *twitter_parse_error(struct http_request *req)
+{
+	static char *ret = NULL;
+	struct xt_parser *xp = NULL;
+	struct xt_node *node;
+	
+	g_free(ret);
+	ret = NULL;
+	
+	if (req->body_size > 0)
+	{
+		xp = xt_new(NULL, NULL);
+		xt_feed(xp, req->reply_body, req->body_size);
+		
+		if ((node = xt_find_node(xp->root, "hash")) &&
+		    (node = xt_find_node(node->children, "error")) &&
+		    node->text_len > 0)
+		{
+			ret = g_strdup_printf("%s (%s)", req->status_string, node->text);
+			xt_free(xp);
+			return ret;
+		}
+		
+		xt_free(xp);
+	}
+	
+	return req->status_string;
+}
+
+static void twitter_http_get_friends_ids(struct http_request *req);
+
+/**
+ * Get the friends ids.
+ */
+void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)
+{
+	// Primitive, but hey! It works...	
+	char* args[2];
+	args[0] = "cursor";
+	args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
+	twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, 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 )
+{
+	char *end = NULL;
+	
+	if( node->text )
+		txl->next_cursor = g_ascii_strtoll( node->text, &end, 10 );
+	if( end == NULL )
+		txl->next_cursor = -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: %s", twitter_parse_error(req));
+		
+		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, *rt;
+	gboolean truncated = FALSE;
+
+	// 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( "truncated", child->name ) == 0 && child->text)
+		{
+			truncated = bool2int(child->text);
+		}
+		else if (g_strcasecmp( "retweeted_status", child->name ) == 0)
+		{
+			rt = child;
+		}
+		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);
+		}
+	}
+	
+	/* If it's a truncated retweet, get the original because dots suck. */
+	if (truncated && rt)
+	{
+		struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
+		if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED)
+		{
+			txs_free(rtxs);
+			return XT_HANDLED;
+		}
+		
+		g_free(txs->text);
+		txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
+		txs_free(rtxs);
+	}
+	
+	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 im_connection *ic, struct xt_node *node, struct twitter_xml_list *txl )
+{
+	struct twitter_xml_status *txs;
+	struct xt_node *child;
+	bee_user_t *bu;
+
+	// 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);
+			
+			if (txs->user && txs->user->screen_name &&
+			    (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name)))
+			{
+				struct twitter_user_data *tud = bu->data;
+				
+				if (txs->id > tud->last_id)
+				{
+					tud->last_id = txs->id;
+					tud->last_time = txs->created_at;
+				}
+			}
+		}
+		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, gint64 next_cursor)
+{
+	struct twitter_data *td = ic->proto_data;
+
+	char* args[4];
+	args[0] = "cursor";
+	args[1] = g_strdup_printf ("%lld", (long long) 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(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->home_timeline_id ? 4 : 2);
+
+	g_free(args[1]);
+	if (td->home_timeline_id) {
+		g_free(args[3]);
+	}
+}
+
+static void twitter_groupchat_init(struct im_connection *ic)
+{
+	char *name_hint;
+	struct groupchat *gc;
+	struct twitter_data *td = ic->proto_data;
+	
+	td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
+	
+	name_hint = g_strdup_printf( "%s_%s", td->prefix, ic->acc->user );
+	imcb_chat_name_hint( gc, name_hint );
+	g_free( name_hint );
+}
+
+/**
+ * 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)
+		twitter_groupchat_init(ic);
+	
+	gc = td->home_timeline_gc;
+	if (!gc->joined)
+		imcb_chat_add_buddy( gc, ic->acc->user );
+
+	for ( l = list; l ; l = g_slist_next(l) )
+	{
+		status = l->data;
+		if (status->user == NULL || status->text == NULL)
+			continue;
+
+		twitter_add_buddy(ic, status->user->screen_name, status->user->name);
+		
+		strip_html(status->text);
+		
+		// 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, "%s_%s", td->prefix, ic->acc->user );
+		from[MAX_STRING-1] = '\0';
+	}
+	
+	for ( l = list; l ; l = g_slist_next(l) )
+	{
+		char *text = NULL;
+		
+		status = l->data;
+		
+		strip_html( status->text );
+		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 ": %s", twitter_parse_error(req));
+		
+		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(ic, 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 == 401)
+	{
+		imcb_error( ic, "Authentication failure" );
+		imc_logout( ic, FALSE );
+		return;
+	} else if (req->status_code != 200) {
+		// It didn't go well, output the error and return.
+		imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL ": %s", twitter_parse_error(req));
+		imc_logout( ic, TRUE );
+		return;
+	} else {
+		td->http_fails = 0;
+	}
+	
+	if( !td->home_timeline_gc &&
+	    g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "chat" ) == 0 )
+		twitter_groupchat_init( ic );
+
+	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);
+	}
+	else
+	{
+		td->flags |= TWITTER_HAVE_FRIENDS;
+		twitter_login_finish(ic);
+	}
+	
+	// Free the structure.
+	txl_free(txl);
+	g_free(txl);
+}
+
+/**
+ * Get the friends.
+ */
+void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor)
+{
+	char* args[2];
+	args[0] = "cursor";
+	args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
+
+	twitter_http(ic, TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, args, 2);
+
+	g_free(args[1]);
+}
+
+/**
+ * Callback to use after sending a post request to twitter.
+ */
+static void twitter_http_post(struct http_request *req)
+{
+	struct im_connection *ic = req->data;
+	struct twitter_data *td;
+
+	// Check if the connection is still active.
+	if( !g_slist_find( twitter_connections, ic ) )
+		return;
+
+	td = ic->proto_data;
+	td->last_status_id = 0;
+	
+	// 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, "HTTP error: %s", twitter_parse_error(req));
+		return;
+	}
+	
+	if (req->body_size > 0)
+	{
+		struct xt_parser *xp = NULL;
+		struct xt_node *node;
+		
+		xp = xt_new(NULL, NULL);
+		xt_feed(xp, req->reply_body, req->body_size);
+		
+		if ((node = xt_find_node(xp->root, "status")) &&
+		    (node = xt_find_node(node->children, "id")) && node->text)
+			td->last_status_id = g_ascii_strtoull( node->text, NULL, 10 );
+		
+		xt_free(xp);
+	}
+}
+
+/**
+ * Function to POST a new status to twitter.
+ */ 
+void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
+{
+	char* args[4] = {
+		"status", msg,
+		"in_reply_to_status_id",
+		g_strdup_printf("%llu", (unsigned long long) in_reply_to)
+	};
+	twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
+	             args, in_reply_to ? 4 : 2);
+	g_free(args[3]);
+}
+
+
+/**
+ * Function to POST a new message to twitter.
+ */
+void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
+{
+	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(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
+//	g_free(args[1]);
+//	g_free(args[3]);
+}
+
+void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
+{
+	char* args[2];
+	args[0] = "screen_name";
+	args[1] = who;
+	twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL, twitter_http_post, ic, 1, args, 2);
+}
+
+void twitter_status_destroy(struct im_connection *ic, guint64 id)
+{
+	char *url;
+	url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, (unsigned long long) id, ".xml");
+	twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
+	g_free(url);
+}
+
+void twitter_status_retweet(struct im_connection *ic, guint64 id)
+{
+	char *url;
+	url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, (unsigned long long) id, ".xml");
+	twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
+	g_free(url);
+}
Index: protocols/twitter/twitter_lib.h
===================================================================
--- protocols/twitter/twitter_lib.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
+++ protocols/twitter/twitter_lib.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -0,0 +1,91 @@
+/***************************************************************************\
+*                                                                           *
+*  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"
+#define IDENTICA_API_URL "http://identi.ca/api"
+
+/* Status URLs */
+#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml"
+#define TWITTER_STATUS_SHOW_URL "/statuses/show/"
+#define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/"
+#define TWITTER_STATUS_RETWEET_URL "/statuses/retweet/"
+
+/* Timeline URLs */
+#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml"
+#define TWITTER_FEATURED_USERS_URL "/statuses/featured.xml"
+#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.xml"
+#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.xml"
+#define TWITTER_MENTIONS_URL "/statuses/mentions.xml"
+#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.xml"
+
+/* Users URLs */
+#define TWITTER_SHOW_USERS_URL "/users/show.xml"
+#define TWITTER_SHOW_FRIENDS_URL "/statuses/friends.xml"
+#define TWITTER_SHOW_FOLLOWERS_URL "/statuses/followers.xml"
+
+/* Direct messages URLs */
+#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.xml"
+#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.xml"
+#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.xml"
+#define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/"
+
+/* Friendships URLs */
+#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.xml"
+#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.xml"
+#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.xml"
+
+/* Social graphs URLs */
+#define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml"
+#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml"
+
+/* Account URLs */
+#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml"
+
+/* Favorites URLs */
+#define TWITTER_FAVORITES_GET_URL "/favorites.xml"
+#define TWITTER_FAVORITE_CREATE_URL "/favorites/create/"
+#define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/"
+
+/* Block URLs */
+#define TWITTER_BLOCKS_CREATE_URL "/blocks/create/"
+#define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/"
+
+void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
+void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
+void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
+
+void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to);
+void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message);
+void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create);
+void twitter_status_destroy(struct im_connection *ic, guint64 id);
+void twitter_status_retweet(struct im_connection *ic, guint64 id);
+
+#endif //_TWITTER_LIB_H
+
Index: protocols/yahoo/Makefile
===================================================================
--- protocols/yahoo/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -8,9 +8,12 @@
 
 -include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/yahoo/
+endif
 
 # [SH] Program variables
 objects = yahoo.o crypt.o libyahoo2.o yahoo_fn.o yahoo_httplib.o yahoo_util.o
 
-CFLAGS += -Wall -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB
+CFLAGS += -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB
 LFLAGS += -r
 
@@ -33,5 +36,5 @@
 $(objects): ../../Makefile.settings Makefile
 
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
 	@echo '*' Compiling $<
 	@$(CC) -c $(CFLAGS) $< -o $@
Index: protocols/yahoo/libyahoo2.c
===================================================================
--- protocols/yahoo/libyahoo2.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/libyahoo2.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -3,4 +3,6 @@
  *
  * Some code copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net>
+ * YMSG16 code copyright (C) 2009, 
+ * 		Siddhesh Poyarekar <siddhesh dot poyarekar at gmail dot com>
  *
  * Yahoo Search copyright (C) 2003, Konstantin Klyagin <konst AT konst.org.ua>
@@ -27,4 +29,6 @@
  *     <hiro-y@kcn.ne.jp>
  *
+ * YMSG16 authentication code based mostly on write-up at:
+ * 	http://www.carbonize.co.uk/ymsg16.html
  *
  * This program is free software; you can redistribute it and/or modify
@@ -92,7 +96,7 @@
 
 #ifdef USE_STRUCT_CALLBACKS
-struct yahoo_callbacks *yc=NULL;
-
-void yahoo_register_callbacks(struct yahoo_callbacks * tyc)
+struct yahoo_callbacks *yc = NULL;
+
+void yahoo_register_callbacks(struct yahoo_callbacks *tyc)
 {
 	yc = tyc;
@@ -104,7 +108,9 @@
 #endif
 
-static int yahoo_send_data(int fd, void *data, int len);
-
-int yahoo_log_message(char * fmt, ...)
+static int yahoo_send_data(void *fd, void *data, int len);
+static void _yahoo_http_connected(int id, void *fd, int error, void *data);
+static void yahoo_connected(void *fd, int error, void *data);
+
+int yahoo_log_message(char *fmt, ...)
 {
 	char out[1024];
@@ -113,10 +119,10 @@
 	vsnprintf(out, sizeof(out), fmt, ap);
 	va_end(ap);
-	return YAHOO_CALLBACK(ext_yahoo_log)("%s", out);
-}
-
-int yahoo_connect(char * host, int port)
-{
-	return YAHOO_CALLBACK(ext_yahoo_connect)(host, port);
+	return YAHOO_CALLBACK(ext_yahoo_log) ("%s", out);
+}
+
+int yahoo_connect(char *host, int port)
+{
+	return YAHOO_CALLBACK(ext_yahoo_connect) (host, port);
 }
 
@@ -136,95 +142,28 @@
 
 /* default values for servers */
-static char pager_host[] = "scs.msg.yahoo.com"; 
+static char *default_pager_hosts[] = {	"scs.msg.yahoo.com",
+					"scsa.msg.yahoo.com",
+					"scsb.msg.yahoo.com",
+					"scsc.msg.yahoo.com",
+					NULL};
+
 static int pager_port = 5050;
-static int fallback_ports[]={23, 25, 80, 20, 119, 8001, 8002, 5050, 0};
-static char filetransfer_host[]="filetransfer.msg.yahoo.com";
-static int filetransfer_port=80;
-static char webcam_host[]="webcam.yahoo.com";
-static int webcam_port=5100;
-static char webcam_description[]="";
-static char local_host[]="";
-static int conn_type=Y_WCM_DSL;
+static int fallback_ports[] = { 23, 25, 80, 20, 119, 8001, 8002, 5050, 0 };
+
+static char filetransfer_host[] = "filetransfer.msg.yahoo.com";
+static int filetransfer_port = 80;
+static char webcam_host[] = "webcam.yahoo.com";
+static int webcam_port = 5100;
+static char webcam_description[] = "";
+static char local_host[] = "";
+static int conn_type = Y_WCM_DSL;
 
 static char profile_url[] = "http://profiles.yahoo.com/";
 
-enum yahoo_service { /* these are easier to see in hex */
-	YAHOO_SERVICE_LOGON = 1,
-	YAHOO_SERVICE_LOGOFF,
-	YAHOO_SERVICE_ISAWAY,
-	YAHOO_SERVICE_ISBACK,
-	YAHOO_SERVICE_IDLE, /* 5 (placemarker) */
-	YAHOO_SERVICE_MESSAGE,
-	YAHOO_SERVICE_IDACT,
-	YAHOO_SERVICE_IDDEACT,
-	YAHOO_SERVICE_MAILSTAT,
-	YAHOO_SERVICE_USERSTAT, /* 0xa */
-	YAHOO_SERVICE_NEWMAIL,
-	YAHOO_SERVICE_CHATINVITE,
-	YAHOO_SERVICE_CALENDAR,
-	YAHOO_SERVICE_NEWPERSONALMAIL,
-	YAHOO_SERVICE_NEWCONTACT,
-	YAHOO_SERVICE_ADDIDENT, /* 0x10 */
-	YAHOO_SERVICE_ADDIGNORE,
-	YAHOO_SERVICE_PING,
-	YAHOO_SERVICE_GOTGROUPRENAME, /* < 1, 36(old), 37(new) */
-	YAHOO_SERVICE_SYSMESSAGE = 0x14,
-	YAHOO_SERVICE_SKINNAME = 0x15,
-	YAHOO_SERVICE_PASSTHROUGH2 = 0x16,
-	YAHOO_SERVICE_CONFINVITE = 0x18,
-	YAHOO_SERVICE_CONFLOGON,
-	YAHOO_SERVICE_CONFDECLINE,
-	YAHOO_SERVICE_CONFLOGOFF,
-	YAHOO_SERVICE_CONFADDINVITE,
-	YAHOO_SERVICE_CONFMSG,
-	YAHOO_SERVICE_CHATLOGON,
-	YAHOO_SERVICE_CHATLOGOFF,
-	YAHOO_SERVICE_CHATMSG = 0x20,
-	YAHOO_SERVICE_GAMELOGON = 0x28,
-	YAHOO_SERVICE_GAMELOGOFF,
-	YAHOO_SERVICE_GAMEMSG = 0x2a,
-	YAHOO_SERVICE_FILETRANSFER = 0x46,
-	YAHOO_SERVICE_VOICECHAT = 0x4A,
-	YAHOO_SERVICE_NOTIFY,
-	YAHOO_SERVICE_VERIFY,
-	YAHOO_SERVICE_P2PFILEXFER,
-	YAHOO_SERVICE_PEERTOPEER = 0x4F,	/* Checks if P2P possible */
-	YAHOO_SERVICE_WEBCAM,
-	YAHOO_SERVICE_AUTHRESP = 0x54,
-	YAHOO_SERVICE_LIST,
-	YAHOO_SERVICE_AUTH = 0x57,
-	YAHOO_SERVICE_AUTHBUDDY = 0x6d,
-	YAHOO_SERVICE_ADDBUDDY = 0x83,
-	YAHOO_SERVICE_REMBUDDY,
-	YAHOO_SERVICE_IGNORECONTACT,	/* > 1, 7, 13 < 1, 66, 13, 0*/
-	YAHOO_SERVICE_REJECTCONTACT,
-	YAHOO_SERVICE_GROUPRENAME = 0x89, /* > 1, 65(new), 66(0), 67(old) */
-	YAHOO_SERVICE_Y7_PING = 0x8A, /* 0 - id and that's it?? */
-	YAHOO_SERVICE_CHATONLINE = 0x96, /* > 109(id), 1, 6(abcde) < 0,1*/
-	YAHOO_SERVICE_CHATGOTO,
-	YAHOO_SERVICE_CHATJOIN,	/* > 1 104-room 129-1600326591 62-2 */
-	YAHOO_SERVICE_CHATLEAVE,
-	YAHOO_SERVICE_CHATEXIT = 0x9b,
-	YAHOO_SERVICE_CHATADDINVITE = 0x9d,
-	YAHOO_SERVICE_CHATLOGOUT = 0xa0,
-	YAHOO_SERVICE_CHATPING,
-	YAHOO_SERVICE_COMMENT = 0xa8,
-	YAHOO_SERVICE_STEALTH = 0xb9,
-	YAHOO_SERVICE_PICTURE_CHECKSUM = 0xbd,
-	YAHOO_SERVICE_PICTURE = 0xbe,
-	YAHOO_SERVICE_PICTURE_UPDATE = 0xc1,
-	YAHOO_SERVICE_PICTURE_UPLOAD = 0xc2,
-	YAHOO_SERVICE_Y6_VISIBILITY=0xc5,
-	YAHOO_SERVICE_Y6_STATUS_UPDATE=0xc6,
-	YAHOO_PHOTOSHARE_INIT=0xd2,	
-	YAHOO_SERVICE_CONTACT_YMSG13=0xd6,
-	YAHOO_PHOTOSHARE_PREV=0xd7,
-	YAHOO_PHOTOSHARE_KEY=0xd8,
-	YAHOO_PHOTOSHARE_TRANS=0xda,
-	YAHOO_FILE_TRANSFER_INIT_YMSG13=0xdc,
-	YAHOO_FILE_TRANSFER_GET_YMSG13=0xdd,
-	YAHOO_FILE_TRANSFER_PUT_YMSG13=0xde,
-	YAHOO_SERVICE_YMSG15_STATUS=0xf0,
-	YAHOO_SERVICE_YMSG15_BUDDY_LIST=0xf1,
+struct connect_callback_data {
+	struct yahoo_data *yd;
+	int tag;
+	int i;
+	int server_i;
 };
 
@@ -242,13 +181,13 @@
 
 struct yahoo_search_state {
-	int   lsearch_type;
-	char  *lsearch_text;
-	int   lsearch_gender;
-	int   lsearch_agerange;
-	int   lsearch_photo;
-	int   lsearch_yahoo_only;
-	int   lsearch_nstart;
-	int   lsearch_nfound;
-	int   lsearch_ntotal;
+	int lsearch_type;
+	char *lsearch_text;
+	int lsearch_gender;
+	int lsearch_agerange;
+	int lsearch_photo;
+	int lsearch_yahoo_only;
+	int lsearch_nstart;
+	int lsearch_nfound;
+	int lsearch_ntotal;
 };
 
@@ -264,32 +203,50 @@
 	struct yahoo_search_state *ys;
 
-	int   fd;
+	void *fd;
 	enum yahoo_connection_type type;
-	
-	unsigned char	*rxqueue;
-	int   rxlen;
-	int   read_tag;
+
+	unsigned char *rxqueue;
+	int rxlen;
+	int read_tag;
 
 	YList *txqueues;
-	int   write_tag;
+	int write_tag;
 };
 
 struct yahoo_server_settings {
 	char *pager_host;
-	int   pager_port;
+	int pager_port;
 	char *filetransfer_host;
-	int   filetransfer_port;
+	int filetransfer_port;
 	char *webcam_host;
-	int   webcam_port;
+	int webcam_port;
 	char *webcam_description;
 	char *local_host;
-	int   conn_type;
+	int conn_type;
+	char **pager_host_list;
 };
 
-static void * _yahoo_default_server_settings()
-{
-	struct yahoo_server_settings *yss = y_new0(struct yahoo_server_settings, 1);
-
-	yss->pager_host = strdup(pager_host);
+static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over);
+
+static void yahoo_process_filetransfer(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt);
+static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt);
+static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt);
+
+static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn);
+
+static void *_yahoo_default_server_settings()
+{
+	struct yahoo_server_settings *yss =
+		y_new0(struct yahoo_server_settings, 1);
+
+	/* Give preference to the default host list
+	 * Make sure that only one of the two is set at any time 
+	 */
+	yss->pager_host = NULL;
+	yss->pager_host_list = default_pager_hosts;
+
 	yss->pager_port = pager_port;
 	yss->filetransfer_host = strdup(filetransfer_host);
@@ -304,52 +261,59 @@
 }
 
-static void * _yahoo_assign_server_settings(va_list ap)
+static void *_yahoo_assign_server_settings(va_list ap)
 {
 	struct yahoo_server_settings *yss = _yahoo_default_server_settings();
 	char *key;
 	char *svalue;
-	int   nvalue;
-
-	while(1) {
+	int nvalue;
+	char **pvalue;
+
+	while (1) {
 		key = va_arg(ap, char *);
-		if(key == NULL)
-			break;
-
-		if(!strcmp(key, "pager_host")) {
+		if (key == NULL)
+			break;
+
+		if (!strcmp(key, "pager_host")) {
 			svalue = va_arg(ap, char *);
 			free(yss->pager_host);
 			yss->pager_host = strdup(svalue);
-		} else if(!strcmp(key, "pager_port")) {
+			yss->pager_host_list = NULL;
+		} else if (!strcmp(key, "pager_host_list")) {
+			pvalue = va_arg(ap, char **);
+			yss->pager_host_list = pvalue;
+			free(yss->pager_host);
+			yss->pager_host = NULL;
+		} else if (!strcmp(key, "pager_port")) {
 			nvalue = va_arg(ap, int);
 			yss->pager_port = nvalue;
-		} else if(!strcmp(key, "filetransfer_host")) {
+		} else if (!strcmp(key, "filetransfer_host")) {
 			svalue = va_arg(ap, char *);
 			free(yss->filetransfer_host);
 			yss->filetransfer_host = strdup(svalue);
-		} else if(!strcmp(key, "filetransfer_port")) {
+		} else if (!strcmp(key, "filetransfer_port")) {
 			nvalue = va_arg(ap, int);
 			yss->filetransfer_port = nvalue;
-		} else if(!strcmp(key, "webcam_host")) {
+		} else if (!strcmp(key, "webcam_host")) {
 			svalue = va_arg(ap, char *);
 			free(yss->webcam_host);
 			yss->webcam_host = strdup(svalue);
-		} else if(!strcmp(key, "webcam_port")) {
+		} else if (!strcmp(key, "webcam_port")) {
 			nvalue = va_arg(ap, int);
 			yss->webcam_port = nvalue;
-		} else if(!strcmp(key, "webcam_description")) {
+		} else if (!strcmp(key, "webcam_description")) {
 			svalue = va_arg(ap, char *);
 			free(yss->webcam_description);
 			yss->webcam_description = strdup(svalue);
-		} else if(!strcmp(key, "local_host")) {
+		} else if (!strcmp(key, "local_host")) {
 			svalue = va_arg(ap, char *);
 			free(yss->local_host);
 			yss->local_host = strdup(svalue);
-		} else if(!strcmp(key, "conn_type")) {
+		} else if (!strcmp(key, "conn_type")) {
 			nvalue = va_arg(ap, int);
 			yss->conn_type = nvalue;
 		} else {
 			WARNING(("Unknown key passed to yahoo_init, "
-				"perhaps you didn't terminate the list "
-				"with NULL"));
+					"perhaps you didn't terminate the list "
+					"with NULL"));
 		}
 	}
@@ -360,5 +324,5 @@
 static void yahoo_free_server_settings(struct yahoo_server_settings *yss)
 {
-	if(!yss)
+	if (!yss)
 		return;
 
@@ -372,7 +336,7 @@
 }
 
-static YList *conns=NULL;
-static YList *inputs=NULL;
-static int last_id=0;
+static YList *conns = NULL;
+static YList *inputs = NULL;
+static int last_id = 0;
 
 static void add_to_list(struct yahoo_data *yd)
@@ -380,14 +344,16 @@
 	conns = y_list_prepend(conns, yd);
 }
-static struct yahoo_data * find_conn_by_id(int id)
+
+static struct yahoo_data *find_conn_by_id(int id)
 {
 	YList *l;
-	for(l = conns; l; l = y_list_next(l)) {
+	for (l = conns; l; l = y_list_next(l)) {
 		struct yahoo_data *yd = l->data;
-		if(yd->client_id == id)
+		if (yd->client_id == id)
 			return yd;
 	}
 	return NULL;
 }
+
 static void del_from_list(struct yahoo_data *yd)
 {
@@ -396,4 +362,5 @@
 
 /* call repeatedly to get the next one */
+/*
 static struct yahoo_input_data * find_input_by_id(int id)
 {
@@ -406,15 +373,18 @@
 	return NULL;
 }
-
-static struct yahoo_input_data * find_input_by_id_and_webcam_user(int id, const char * who)
+*/
+
+static struct yahoo_input_data *find_input_by_id_and_webcam_user(int id,
+	const char *who)
 {
 	YList *l;
 	LOG(("find_input_by_id_and_webcam_user"));
-	for(l = inputs; l; l = y_list_next(l)) {
+	for (l = inputs; l; l = y_list_next(l)) {
 		struct yahoo_input_data *yid = l->data;
-		if(yid->type == YAHOO_CONNECTION_WEBCAM && yid->yd->client_id == id 
-				&& yid->wcm && 
-				((who && yid->wcm->user && !strcmp(who, yid->wcm->user)) ||
-				 !(yid->wcm->user && !who)))
+		if (yid->type == YAHOO_CONNECTION_WEBCAM
+			&& yid->yd->client_id == id && yid->wcm && ((who
+					&& yid->wcm->user
+					&& !strcmp(who, yid->wcm->user))
+				|| !(yid->wcm->user && !who)))
 			return yid;
 	}
@@ -422,11 +392,12 @@
 }
 
-static struct yahoo_input_data * find_input_by_id_and_type(int id, enum yahoo_connection_type type)
+static struct yahoo_input_data *find_input_by_id_and_type(int id,
+	enum yahoo_connection_type type)
 {
 	YList *l;
 	LOG(("find_input_by_id_and_type"));
-	for(l = inputs; l; l = y_list_next(l)) {
+	for (l = inputs; l; l = y_list_next(l)) {
 		struct yahoo_input_data *yid = l->data;
-		if(yid->type == type && yid->yd->client_id == id)
+		if (yid->type == type && yid->yd->client_id == id)
 			return yid;
 	}
@@ -434,11 +405,11 @@
 }
 
-static struct yahoo_input_data * find_input_by_id_and_fd(int id, int fd)
+static struct yahoo_input_data *find_input_by_id_and_fd(int id, void *fd)
 {
 	YList *l;
 	LOG(("find_input_by_id_and_fd"));
-	for(l = inputs; l; l = y_list_next(l)) {
+	for (l = inputs; l; l = y_list_next(l)) {
 		struct yahoo_input_data *yid = l->data;
-		if(yid->fd == fd && yid->yd->client_id == id)
+		if (yid->fd == fd && yid->yd->client_id == id)
 			return yid;
 	}
@@ -448,10 +419,10 @@
 static int count_inputs_with_id(int id)
 {
-	int c=0;
+	int c = 0;
 	YList *l;
 	LOG(("counting %d", id));
-	for(l = inputs; l; l = y_list_next(l)) {
+	for (l = inputs; l; l = y_list_next(l)) {
 		struct yahoo_input_data *yid = l->data;
-		if(yid->yd->client_id == id)
+		if (yid->yd->client_id == id)
 			c++;
 	}
@@ -460,16 +431,14 @@
 }
 
-
 extern char *yahoo_crypt(char *, char *);
 
 /* Free a buddy list */
-static void yahoo_free_buddies(YList * list)
+static void yahoo_free_buddies(YList *list)
 {
 	YList *l;
 
-	for(l = list; l; l = l->next)
-	{
+	for (l = list; l; l = l->next) {
 		struct yahoo_buddy *bud = l->data;
-		if(!bud)
+		if (!bud)
 			continue;
 
@@ -477,5 +446,5 @@
 		FREE(bud->id);
 		FREE(bud->real_name);
-		if(bud->yab_entry) {
+		if (bud->yab_entry) {
 			FREE(bud->yab_entry->fname);
 			FREE(bud->yab_entry->lname);
@@ -496,5 +465,5 @@
 
 /* Free an identities list */
-static void yahoo_free_identities(YList * list)
+static void yahoo_free_identities(YList *list)
 {
 	while (list) {
@@ -525,4 +494,5 @@
 	FREE(yd->cookie_y);
 	FREE(yd->cookie_t);
+	FREE(yd->cookie_b);
 	FREE(yd->cookie_c);
 	FREE(yd->login_cookie);
@@ -540,6 +510,6 @@
 #define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4)
 
-static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, 
-		enum yahoo_status status, int id)
+static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service,
+	enum ypacket_status status, int id)
 {
 	struct yahoo_packet *pkt = y_new0(struct yahoo_packet, 1);
@@ -552,5 +522,6 @@
 }
 
-static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, const char *value)
+static void yahoo_packet_hash(struct yahoo_packet *pkt, int key,
+	const char *value)
 {
 	struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1);
@@ -597,5 +568,6 @@
 			 (((*((buf)+3))&0xff)))
 
-static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, int len)
+static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data,
+	int len)
 {
 	int pos = 0;
@@ -650,5 +622,6 @@
 			FREE(value);
 			pkt->hash = y_list_append(pkt->hash, pair);
-			DEBUG_MSG(("Key: %d  \tValue: %s", pair->key, pair->value));
+			DEBUG_MSG(("Key: %d  \tValue: %s", pair->key,
+					pair->value));
 		} else {
 			FREE(pair);
@@ -690,28 +663,27 @@
 }
 
-
 static void yahoo_packet_dump(unsigned char *data, int len)
 {
-	if(yahoo_get_log_level() >= YAHOO_LOG_DEBUG) {
+	if (yahoo_get_log_level() >= YAHOO_LOG_DEBUG) {
 		int i;
 		for (i = 0; i < len; i++) {
 			if ((i % 8 == 0) && i)
-				YAHOO_CALLBACK(ext_yahoo_log)(" ");
+				YAHOO_CALLBACK(ext_yahoo_log) (" ");
 			if ((i % 16 == 0) && i)
-				YAHOO_CALLBACK(ext_yahoo_log)("\n");
-			YAHOO_CALLBACK(ext_yahoo_log)("%02x ", data[i]);
-		}
-		YAHOO_CALLBACK(ext_yahoo_log)("\n");
+				YAHOO_CALLBACK(ext_yahoo_log) ("\n");
+			YAHOO_CALLBACK(ext_yahoo_log) ("%02x ", data[i]);
+		}
+		YAHOO_CALLBACK(ext_yahoo_log) ("\n");
 		for (i = 0; i < len; i++) {
 			if ((i % 8 == 0) && i)
-				YAHOO_CALLBACK(ext_yahoo_log)(" ");
+				YAHOO_CALLBACK(ext_yahoo_log) (" ");
 			if ((i % 16 == 0) && i)
-				YAHOO_CALLBACK(ext_yahoo_log)("\n");
+				YAHOO_CALLBACK(ext_yahoo_log) ("\n");
 			if (isprint(data[i]))
-				YAHOO_CALLBACK(ext_yahoo_log)(" %c ", data[i]);
+				YAHOO_CALLBACK(ext_yahoo_log) (" %c ", data[i]);
 			else
-				YAHOO_CALLBACK(ext_yahoo_log)(" . ");
-		}
-		YAHOO_CALLBACK(ext_yahoo_log)("\n");
+				YAHOO_CALLBACK(ext_yahoo_log) (" . ");
+		}
+		YAHOO_CALLBACK(ext_yahoo_log) ("\n");
 	}
 }
@@ -723,5 +695,6 @@
 }
 
-static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data, int length)
+static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data,
+	int length)
 {
 	struct data_queue *tx = y_new0(struct data_queue, 1);
@@ -732,13 +705,15 @@
 	yid->txqueues = y_list_append(yid->txqueues, tx);
 
-	if(!yid->write_tag)
-		yid->write_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, yid->fd, YAHOO_INPUT_WRITE, yid);
-}
-
-static void yahoo_send_packet(struct yahoo_input_data *yid, struct yahoo_packet *pkt, int extra_pad)
+	if (!yid->write_tag)
+		yid->write_tag =
+			YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->
+			client_id, yid->fd, YAHOO_INPUT_WRITE, yid);
+}
+
+static void yahoo_send_packet(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt, int extra_pad)
 {
 	int pktlen = yahoo_packet_length(pkt);
 	int len = YAHOO_PACKET_HDRLEN + pktlen;
-
 	unsigned char *data;
 	int pos = 0;
@@ -749,17 +724,18 @@
 	data = y_new0(unsigned char, len + 1);
 
-	memcpy(data + pos, "YMSG", 4); pos += 4;
-	pos += yahoo_put16(data + pos, YAHOO_PROTO_VER);
-	pos += yahoo_put16(data + pos, 0x0000);
-	pos += yahoo_put16(data + pos, pktlen + extra_pad);
-	pos += yahoo_put16(data + pos, pkt->service);
-	pos += yahoo_put32(data + pos, pkt->status);
-	pos += yahoo_put32(data + pos, pkt->id);
+	memcpy(data + pos, "YMSG", 4);
+	pos += 4;
+	pos += yahoo_put16(data + pos, YAHOO_PROTO_VER);	/* version [latest 12 0x000c] */
+	pos += yahoo_put16(data + pos, 0x0000);	/* HIWORD pkt length??? */
+	pos += yahoo_put16(data + pos, pktlen + extra_pad);	/* LOWORD pkt length? */
+	pos += yahoo_put16(data + pos, pkt->service);	/* service */
+	pos += yahoo_put32(data + pos, pkt->status);	/* status [4bytes] */
+	pos += yahoo_put32(data + pos, pkt->id);	/* session [4bytes] */
 
 	yahoo_packet_write(pkt, data + pos);
 
 	yahoo_packet_dump(data, len);
-	
-	if( yid->type == YAHOO_CONNECTION_FT )
+
+	if (yid->type == YAHOO_CONNECTION_FT)
 		yahoo_send_data(yid->fd, data, len);
 	else
@@ -782,10 +758,10 @@
 }
 
-static int yahoo_send_data(int fd, void *data, int len)
+static int yahoo_send_data(void *fd, void *data, int len)
 {
 	int ret;
 	int e;
 
-	if (fd < 0)
+	if (fd == NULL)
 		return -1;
 
@@ -793,9 +769,9 @@
 
 	do {
-		ret = write(fd, data, len);
-	} while(ret == -1 && errno==EINTR);
-	e=errno;
-
-	if (ret == -1)  {
+		ret = YAHOO_CALLBACK(ext_yahoo_write) (fd, data, len);
+	} while (ret == -1 && errno == EINTR);
+	e = errno;
+
+	if (ret == -1) {
 		LOG(("wrote data: ERR %s", strerror(errno)));
 	} else {
@@ -803,13 +779,12 @@
 	}
 
-	errno=e;
+	errno = e;
 	return ret;
 }
 
-void yahoo_close(int id) 
+void yahoo_close(int id)
 {
 	struct yahoo_data *yd = find_conn_by_id(id);
-	
-	if(!yd)
+	if (!yd)
 		return;
 
@@ -817,29 +792,31 @@
 
 	yahoo_free_data(yd);
-	if(id == last_id)
+	if (id == last_id)
 		last_id--;
 }
 
-static void yahoo_input_close(struct yahoo_input_data *yid) 
+static void yahoo_input_close(struct yahoo_input_data *yid)
 {
 	inputs = y_list_remove(inputs, yid);
 
-	LOG(("yahoo_input_close(read)")); 
-	YAHOO_CALLBACK(ext_yahoo_remove_handler)(yid->yd->client_id, yid->read_tag);
-	LOG(("yahoo_input_close(write)")); 
-	YAHOO_CALLBACK(ext_yahoo_remove_handler)(yid->yd->client_id, yid->write_tag);
+	LOG(("yahoo_input_close(read)"));
+	YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id,
+		yid->read_tag);
+	LOG(("yahoo_input_close(write)"));
+	YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id,
+		yid->write_tag);
 	yid->read_tag = yid->write_tag = 0;
-	if(yid->fd)
-		close(yid->fd);
+	if (yid->fd)
+		YAHOO_CALLBACK(ext_yahoo_close) (yid->fd);
 	yid->fd = 0;
 	FREE(yid->rxqueue);
-	if(count_inputs_with_id(yid->yd->client_id) == 0) {
+	if (count_inputs_with_id(yid->yd->client_id) == 0) {
 		LOG(("closing %d", yid->yd->client_id));
 		yahoo_close(yid->yd->client_id);
 	}
 	yahoo_free_webcam(yid->wcm);
-	if(yid->wcd)
+	if (yid->wcd)
 		FREE(yid->wcd);
-	if(yid->ys) {
+	if (yid->ys) {
 		FREE(yid->ys->lsearch_text);
 		FREE(yid->ys);
@@ -848,5 +825,6 @@
 }
 
-static int is_same_bud(const void * a, const void * b) {
+static int is_same_bud(const void *a, const void *b)
+{
 	const struct yahoo_buddy *subject = a;
 	const struct yahoo_buddy *object = b;
@@ -855,17 +833,17 @@
 }
 
-static char * getcookie(char *rawcookie)
-{
-	char * cookie=NULL;
-	char * tmpcookie; 
-	char * cookieend;
-
-	if (strlen(rawcookie) < 2) 
+static char *getcookie(char *rawcookie)
+{
+	char *cookie = NULL;
+	char *tmpcookie;
+	char *cookieend;
+
+	if (strlen(rawcookie) < 2)
 		return NULL;
 
-	tmpcookie = strdup(rawcookie+2);
+	tmpcookie = strdup(rawcookie + 2);
 	cookieend = strchr(tmpcookie, ';');
 
-	if(cookieend)
+	if (cookieend)
 		*cookieend = '\0';
 
@@ -877,5 +855,5 @@
 }
 
-static char * getlcookie(char *cookie)
+static char *getlcookie(char *cookie)
 {
 	char *tmp;
@@ -884,9 +862,9 @@
 
 	tmpend = strstr(cookie, "n=");
-	if(tmpend) {
-		tmp = strdup(tmpend+2);
+	if (tmpend) {
+		tmp = strdup(tmpend + 2);
 		tmpend = strchr(tmp, '&');
-		if(tmpend)
-			*tmpend='\0';
+		if (tmpend)
+			*tmpend = '\0';
 		login_cookie = strdup(tmp);
 		FREE(tmp);
@@ -896,5 +874,6 @@
 }
 
-static void yahoo_process_notify(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_process_notify(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -927,13 +906,15 @@
 	if (!msg)
 		return;
-	
-	if (!strncasecmp(msg, "TYPING", strlen("TYPING"))) 
-		YAHOO_CALLBACK(ext_yahoo_typing_notify)(yd->client_id, to, from, stat);
-	else if (!strncasecmp(msg, "GAME", strlen("GAME"))) 
-		YAHOO_CALLBACK(ext_yahoo_game_notify)(yd->client_id, to, from, stat);
-	else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) 
-	{
+
+	if (!strncasecmp(msg, "TYPING", strlen("TYPING")))
+		YAHOO_CALLBACK(ext_yahoo_typing_notify) (yd->client_id, to,
+			from, stat);
+	else if (!strncasecmp(msg, "GAME", strlen("GAME")))
+		YAHOO_CALLBACK(ext_yahoo_game_notify) (yd->client_id, to, from,
+			stat, ind);
+	else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) {
 		if (!strcmp(ind, " ")) {
-			YAHOO_CALLBACK(ext_yahoo_webcam_invite)(yd->client_id, to, from);
+			YAHOO_CALLBACK(ext_yahoo_webcam_invite) (yd->client_id,
+				to, from);
 		} else {
 			accept = atoi(ind);
@@ -941,68 +922,13 @@
 			if (accept < 0)
 				accept = 0;
-			YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply)(yd->client_id, to, from, accept);
-		}
-	}
-	else
+			YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply) (yd->
+				client_id, to, from, accept);
+		}
+	} else
 		LOG(("Got unknown notification: %s", msg));
 }
 
-static void yahoo_process_filetransfer(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
-{
-	struct yahoo_data *yd = yid->yd;
-	char *from=NULL;
-	char *to=NULL;
-	char *msg=NULL;
-	char *url=NULL;
-	long expires=0;
-
-	char *service=NULL;
-
-	char *filename=NULL;
-	unsigned long filesize=0L;
-
-	YList *l;
-	for (l = pkt->hash; l; l = l->next) {
-		struct yahoo_pair *pair = l->data;
-		if (pair->key == 4)
-			from = pair->value;
-		if (pair->key == 5)
-			to = pair->value;
-		if (pair->key == 14)
-			msg = pair->value;
-		if (pair->key == 20)
-			url = pair->value;
-		if (pair->key == 38)
-			expires = atol(pair->value);
-
-		if (pair->key == 27)
-			filename = pair->value;
-		if (pair->key == 28)
-			filesize = atol(pair->value);
-
-		if (pair->key == 49)
-			service = pair->value;
-	}
-
-	if(pkt->service == YAHOO_SERVICE_P2PFILEXFER) {
-		if(strcmp("FILEXFER", service) != 0) {
-			WARNING(("unhandled service 0x%02x", pkt->service));
-			yahoo_dump_unhandled(pkt);
-			return;
-		}
-	}
-
-	if(msg) {
-		char *tmp;
-		tmp = strchr(msg, '\006');
-		if(tmp)
-			*tmp = '\0';
-	}
-	if(url && from)
-		YAHOO_CALLBACK(ext_yahoo_got_file)(yd->client_id, to, from, url, expires, msg, filename, filesize);
-
-}
-
-static void yahoo_process_conference(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_process_conference(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -1012,23 +938,23 @@
 	char *room = NULL;
 	char *id = NULL;
-	int  utf8 = 0;
+	int utf8 = 0;
 	YList *members = NULL;
 	YList *l;
-	
+
 	for (l = pkt->hash; l; l = l->next) {
 		struct yahoo_pair *pair = l->data;
 		if (pair->key == 50)
 			host = pair->value;
-		
-		if (pair->key == 52) {		/* invite */
+
+		if (pair->key == 52) {	/* invite */
 			members = y_list_append(members, strdup(pair->value));
 		}
-		if (pair->key == 53)		/* logon */
+		if (pair->key == 53)	/* logon */
 			who = pair->value;
-		if (pair->key == 54)		/* decline */
+		if (pair->key == 54)	/* decline */
 			who = pair->value;
-		if (pair->key == 55)		/* unavailable (status == 2) */
+		if (pair->key == 55)	/* unavailable (status == 2) */
 			who = pair->value;
-		if (pair->key == 56)		/* logoff */
+		if (pair->key == 56)	/* logoff */
 			who = pair->value;
 
@@ -1036,17 +962,16 @@
 			room = pair->value;
 
-		if (pair->key == 58)		/* join message */
+		if (pair->key == 58)	/* join message */
 			msg = pair->value;
-		if (pair->key == 14)		/* decline/conf message */
+		if (pair->key == 14)	/* decline/conf message */
 			msg = pair->value;
 
-		if (pair->key == 13)
-			;
-		if (pair->key == 16)		/* error */
+		if (pair->key == 13) ;
+		if (pair->key == 16)	/* error */
 			msg = pair->value;
 
-		if (pair->key == 1)		/* my id */
+		if (pair->key == 1)	/* my id */
 			id = pair->value;
-		if (pair->key == 3)		/* message sender */
+		if (pair->key == 3)	/* message sender */
 			who = pair->value;
 
@@ -1055,53 +980,58 @@
 	}
 
-	if(!room)
-		return;
-
-	if(host) {
-		for(l = members; l; l = l->next) {
-			char * w = l->data;
-			if(!strcmp(w, host))
+	if (!room)
+		return;
+
+	if (host) {
+		for (l = members; l; l = l->next) {
+			char *w = l->data;
+			if (!strcmp(w, host))
 				break;
 		}
-		if(!l)
+		if (!l)
 			members = y_list_append(members, strdup(host));
 	}
 	/* invite, decline, join, left, message -> status == 1 */
 
-	switch(pkt->service) {
+	switch (pkt->service) {
 	case YAHOO_SERVICE_CONFINVITE:
-		if(pkt->status == 2)
-			;
-		else if(members)
-			YAHOO_CALLBACK(ext_yahoo_got_conf_invite)(yd->client_id, id, host, room, msg, members);
-		else if(msg)
-			YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, msg, 0, E_CONFNOTAVAIL);
+		if (pkt->status == 2) ;
+		else if (members)
+			YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd->
+				client_id, id, host, room, msg, members);
+		else if (msg)
+			YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, msg, 0,
+				E_CONFNOTAVAIL);
 		break;
 	case YAHOO_SERVICE_CONFADDINVITE:
-		if(pkt->status == 2)
-			;
-		else
-			YAHOO_CALLBACK(ext_yahoo_got_conf_invite)(yd->client_id, id, host, room, msg, members);
+		if (pkt->status == 1)
+			YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd->
+				client_id, id, host, room, msg, members);
 		break;
 	case YAHOO_SERVICE_CONFDECLINE:
-		if(who)
-			YAHOO_CALLBACK(ext_yahoo_conf_userdecline)(yd->client_id, id, who, room, msg);
+		if (who)
+			YAHOO_CALLBACK(ext_yahoo_conf_userdecline) (yd->
+				client_id, id, who, room, msg);
 		break;
 	case YAHOO_SERVICE_CONFLOGON:
-		if(who)
-			YAHOO_CALLBACK(ext_yahoo_conf_userjoin)(yd->client_id, id, who, room);
+		if (who)
+			YAHOO_CALLBACK(ext_yahoo_conf_userjoin) (yd->client_id,
+				id, who, room);
 		break;
 	case YAHOO_SERVICE_CONFLOGOFF:
-		if(who)
-			YAHOO_CALLBACK(ext_yahoo_conf_userleave)(yd->client_id, id, who, room);
+		if (who)
+			YAHOO_CALLBACK(ext_yahoo_conf_userleave) (yd->client_id,
+				id, who, room);
 		break;
 	case YAHOO_SERVICE_CONFMSG:
-		if(who)
-			YAHOO_CALLBACK(ext_yahoo_conf_message)(yd->client_id, id, who, room, msg, utf8);
+		if (who)
+			YAHOO_CALLBACK(ext_yahoo_conf_message) (yd->client_id,
+				id, who, room, msg, utf8);
 		break;
 	}
 }
 
-static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_process_chat(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	char *msg = NULL;
@@ -1112,11 +1042,11 @@
 	YList *members = NULL;
 	struct yahoo_chat_member *currentmember = NULL;
-	int  msgtype = 1;
-	int  utf8 = 0;
-	int  firstjoin = 0;
-	int  membercount = 0;
-	int  chaterr=0;
+	int msgtype = 1;
+	int utf8 = 0;
+	int firstjoin = 0;
+	int membercount = 0;
+	int chaterr = 0;
 	YList *l;
-	
+
 	yahoo_dump_unhandled(pkt);
 	for (l = pkt->hash; l; l = l->next) {
@@ -1148,5 +1078,6 @@
 
 			if (pkt->service == YAHOO_SERVICE_CHATJOIN) {
-				currentmember = y_new0(struct yahoo_chat_member, 1);
+				currentmember =
+					y_new0(struct yahoo_chat_member, 1);
 				currentmember->id = strdup(pair->value);
 				members = y_list_append(members, currentmember);
@@ -1178,5 +1109,4 @@
 		}
 
-
 		if (pair->key == 130) {
 			/* first join */
@@ -1196,17 +1126,19 @@
 			/* message error not sure what all the pair values mean */
 			/* but -1 means no session in room */
-			chaterr= atoi(pair->value);
-		}
-	}
-
-	if(!room) {
-		if (pkt->service == YAHOO_SERVICE_CHATLOGOUT) { /* yahoo originated chat logout */
-			YAHOO_CALLBACK(ext_yahoo_chat_yahoologout)(yid->yd->client_id, id);
-			return ;
-		}
-		if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr)  {
-			YAHOO_CALLBACK(ext_yahoo_chat_yahooerror)(yid->yd->client_id, id);
+			chaterr = atoi(pair->value);
+		}
+	}
+
+	if (!room) {
+		if (pkt->service == YAHOO_SERVICE_CHATLOGOUT) {	/* yahoo originated chat logout */
+			YAHOO_CALLBACK(ext_yahoo_chat_yahoologout) (yid->yd->
+				client_id, id);
 			return;
 		}
+		if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr) {
+			YAHOO_CALLBACK(ext_yahoo_chat_yahooerror) (yid->yd->
+				client_id, id);
+			return;
+		}
 
 		WARNING(("We didn't get a room name, ignoring packet"));
@@ -1214,33 +1146,37 @@
 	}
 
-	switch(pkt->service) {
+	switch (pkt->service) {
 	case YAHOO_SERVICE_CHATJOIN:
-		if(y_list_length(members) != membercount) {
+		if (y_list_length(members) != membercount) {
 			WARNING(("Count of members doesn't match No. of members we got"));
 		}
-		if(firstjoin && members) {
-			YAHOO_CALLBACK(ext_yahoo_chat_join)(yid->yd->client_id, id, room, topic, members, yid->fd);
-		} else if(who) {
-			if(y_list_length(members) != 1) {
+		if (firstjoin && members) {
+			YAHOO_CALLBACK(ext_yahoo_chat_join) (yid->yd->client_id,
+				id, room, topic, members, yid->fd);
+		} else if (who) {
+			if (y_list_length(members) != 1) {
 				WARNING(("Got more than 1 member on a normal join"));
 			}
 			/* this should only ever have one, but just in case */
-			while(members) {
+			while (members) {
 				YList *n = members->next;
 				currentmember = members->data;
-				YAHOO_CALLBACK(ext_yahoo_chat_userjoin)(yid->yd->client_id, id, room, currentmember);
+				YAHOO_CALLBACK(ext_yahoo_chat_userjoin) (yid->
+					yd->client_id, id, room, currentmember);
 				y_list_free_1(members);
-				members=n;
+				members = n;
 			}
 		}
 		break;
 	case YAHOO_SERVICE_CHATEXIT:
-		if(who) {
-			YAHOO_CALLBACK(ext_yahoo_chat_userleave)(yid->yd->client_id, id, room, who);
+		if (who) {
+			YAHOO_CALLBACK(ext_yahoo_chat_userleave) (yid->yd->
+				client_id, id, room, who);
 		}
 		break;
 	case YAHOO_SERVICE_COMMENT:
-		if(who) {
-			YAHOO_CALLBACK(ext_yahoo_chat_message)(yid->yd->client_id, id, who, room, msg, msgtype, utf8);
+		if (who) {
+			YAHOO_CALLBACK(ext_yahoo_chat_message) (yid->yd->
+				client_id, id, who, room, msg, msgtype, utf8);
 		}
 		break;
@@ -1248,28 +1184,28 @@
 }
 
-static void yahoo_process_message(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_process_message(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
 	YList *l;
-	YList * messages = NULL;
+	YList *messages = NULL;
 
 	struct m {
-		int  i_31;
-		int  i_32;
+		int i_31;
+		int i_32;
 		char *to;
 		char *from;
 		long tm;
 		char *msg;
-		int  utf8;
+		int utf8;
+		char *gunk;
 	} *message = y_new0(struct m, 1);
 
 	for (l = pkt->hash; l; l = l->next) {
 		struct yahoo_pair *pair = l->data;
-		if (pair->key == 1 || pair->key == 4)
-		{
-			if(!message->from)
+		if (pair->key == 1 || pair->key == 4) {
+			if (!message->from)
 				message->from = pair->value;
-		}
-		else if (pair->key == 5)
+		} else if (pair->key == 5)
 			message->to = pair->value;
 		else if (pair->key == 15)
@@ -1277,33 +1213,59 @@
 		else if (pair->key == 97)
 			message->utf8 = atoi(pair->value);
-			/* user message */  /* sys message */
+		/* This comes when the official client sends us a message */
+		else if (pair->key == 429)
+			message->gunk = pair->value;
+		/* user message *//* sys message */
 		else if (pair->key == 14 || pair->key == 16)
 			message->msg = pair->value;
 		else if (pair->key == 31) {
-			if(message->i_31) {
+			if (message->i_31) {
 				messages = y_list_append(messages, message);
 				message = y_new0(struct m, 1);
 			}
 			message->i_31 = atoi(pair->value);
-		}
-		else if (pair->key == 32)
+		} else if (pair->key == 32)
 			message->i_32 = atoi(pair->value);
 		else
-			LOG(("yahoo_process_message: status: %d, key: %d, value: %s",
-					pkt->status, pair->key, pair->value));
+			LOG(("yahoo_process_message: status: %d, key: %d, value: %s", pkt->status, pair->key, pair->value));
 	}
 
 	messages = y_list_append(messages, message);
 
-	for (l = messages; l; l=l->next) {
+	for (l = messages; l; l = l->next) {
 		message = l->data;
 		if (pkt->service == YAHOO_SERVICE_SYSMESSAGE) {
-			YAHOO_CALLBACK(ext_yahoo_system_message)(yd->client_id, message->msg);
+			YAHOO_CALLBACK(ext_yahoo_system_message) (yd->client_id,
+				message->to, message->from, message->msg);
 		} else if (pkt->status <= 2 || pkt->status == 5) {
-			YAHOO_CALLBACK(ext_yahoo_got_im)(yd->client_id, message->to, message->from, message->msg, message->tm, pkt->status, message->utf8);
+			/* Confirm message receipt if we got the gunk */
+			if(message->gunk) {
+				struct yahoo_packet *outpkt;
+                        
+				outpkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_CONFIRM,
+					YPACKET_STATUS_DEFAULT, 0);
+				yahoo_packet_hash(outpkt, 1, yd->user);
+				yahoo_packet_hash(outpkt, 5, message->from);
+				yahoo_packet_hash(outpkt, 302, "430");
+				yahoo_packet_hash(outpkt, 430, message->gunk);
+				yahoo_packet_hash(outpkt, 303, "430");
+				yahoo_packet_hash(outpkt, 450, "0");
+				yahoo_send_packet(yid, outpkt, 0);
+                        
+				yahoo_packet_free(outpkt);
+			}
+
+			if (!strcmp(message->msg, "<ding>"))
+				YAHOO_CALLBACK(ext_yahoo_got_buzz) (yd->client_id,
+					message->to, message->from, message->tm);
+			else
+				YAHOO_CALLBACK(ext_yahoo_got_im) (yd->client_id,
+					message->to, message->from, message->msg,
+					message->tm, pkt->status, message->utf8);
 		} else if (pkt->status == 0xffffffff) {
-			YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, message->msg, 0, E_SYSTEM);
-		}
-		free(message);
+			YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id,
+				message->msg, 0, E_SYSTEM);
+		}
+		FREE(message);
 	}
 
@@ -1311,4 +1273,27 @@
 }
 
+/*
+ * Here's what multi-level packets look like. Data in brackets is the value.
+ *
+ * 3 level:
+ * =======
+ * 
+ * 302 (318) - Beginning level 1
+ * 	300 (318) - Begin level 2
+ * 	302 (319) - End level 2 header
+ * 		300 (319) - Begin level 3
+ * 		301 (319) - End level 3
+ * 	303 (319) - End level 2
+ * 303 (318) - End level 1
+ *
+ * 2 level:
+ * =======
+ *
+ * 302 (315) - Beginning level 1
+ * 	300 (315) - Begin level 2
+ * 	301 (315) - End level 2
+ * 303 (315) - End level 1
+ *
+ */
 static void yahoo_process_status(struct yahoo_input_data *yid,
 	struct yahoo_packet *pkt)
@@ -1327,8 +1312,10 @@
 	}
 
-	/* Status updates may be spread accross multiple packets and not
-	   even on buddy boundaries, so keeping some state is important.
-	   So, continue where we left off, and only add a user entry to
-	   the list once it's complete (301-315 End buddy). */
+	/* 
+	 * Status updates may be spread accross multiple packets and not 
+	 * even on buddy boundaries, so keeping some state is important. 
+	 * So, continue where we left off, and only add a user entry to 
+	 * the list once it's complete (301-315 End buddy).
+	 */
 	u = yd->half_user;
 
@@ -1594,15 +1581,18 @@
 }
 
-static void yahoo_process_verify(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_process_verify(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
 
-	if(pkt->status != 0x01) {
+	if (pkt->status != 0x01) {
 		DEBUG_MSG(("expected status: 0x01, got: %d", pkt->status));
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, YAHOO_LOGIN_LOCK, "");
-		return;
-	}
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, yd->session_id);
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+			YAHOO_LOGIN_LOCK, "");
+		return;
+	}
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
 
 	yahoo_packet_hash(pkt, 1, yd->user);
@@ -1613,5 +1603,6 @@
 }
 
-static void yahoo_process_picture_checksum( struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_process_picture_checksum(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -1621,28 +1612,28 @@
 	YList *l;
 
-	for(l = pkt->hash; l; l = l->next)
-	{
+	for (l = pkt->hash; l; l = l->next) {
 		struct yahoo_pair *pair = l->data;
 
-		switch(pair->key)
-		{
-			case 1:
-			case 4:
-				from = pair->value;
-			case 5:
-				to = pair->value;
-				break;
-			case 212:
-				break;
-			case 192:
-				checksum = atoi( pair->value );
-				break;
-		}
-	}
-
-	YAHOO_CALLBACK(ext_yahoo_got_buddyicon_checksum)(yd->client_id,to,from,checksum);
-}
-
-static void yahoo_process_picture(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+		switch (pair->key) {
+		case 1:
+		case 4:
+			from = pair->value;
+		case 5:
+			to = pair->value;
+			break;
+		case 212:
+			break;
+		case 192:
+			checksum = atoi(pair->value);
+			break;
+		}
+	}
+
+	YAHOO_CALLBACK(ext_yahoo_got_buddyicon_checksum) (yd->client_id, to,
+		from, checksum);
+}
+
+static void yahoo_process_picture(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -1653,42 +1644,42 @@
 	int checksum = 0;
 	YList *l;
-	
-	for(l = pkt->hash; l; l = l->next)
-	{
+
+	for (l = pkt->hash; l; l = l->next) {
 		struct yahoo_pair *pair = l->data;
 
-		switch(pair->key)
-		{
+		switch (pair->key) {
 		case 1:
-		case 4:		/* sender */
+		case 4:	/* sender */
 			from = pair->value;
 			break;
-		case 5:		/* we */
+		case 5:	/* we */
 			to = pair->value;
 			break;
-		case 13:		/* request / sending */
-			status = atoi( pair->value );
-			break;
-		case 20:		/* url */
+		case 13:	/* request / sending */
+			status = atoi(pair->value);
+			break;
+		case 20:	/* url */
 			url = pair->value;
 			break;
 		case 192:	/*checksum */
-			checksum = atoi( pair->value );
-			break;
-		}
-	}
-
-	switch( status )
-	{
-		case 1:	/* this is a request, ignore for now */
-			YAHOO_CALLBACK(ext_yahoo_got_buddyicon_request)(yd->client_id, to, from);
-			break;
-		case 2:	/* this is cool - we get a picture :) */
-			YAHOO_CALLBACK(ext_yahoo_got_buddyicon)(yd->client_id,to, from, url, checksum);
-			break;
-	}
-}
-
-static void yahoo_process_picture_upload(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+			checksum = atoi(pair->value);
+			break;
+		}
+	}
+
+	switch (status) {
+	case 1:		/* this is a request, ignore for now */
+		YAHOO_CALLBACK(ext_yahoo_got_buddyicon_request) (yd->client_id,
+			to, from);
+		break;
+	case 2:		/* this is cool - we get a picture :) */
+		YAHOO_CALLBACK(ext_yahoo_got_buddyicon) (yd->client_id, to,
+			from, url, checksum);
+		break;
+	}
+}
+
+static void yahoo_process_picture_upload(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -1696,562 +1687,63 @@
 	char *url = NULL;
 
-	if ( pkt->status != 1 )
+	if (pkt->status != 1)
 		return;		/* something went wrong */
-	
-	for(l = pkt->hash; l; l = l->next)
-	{
+
+	for (l = pkt->hash; l; l = l->next) {
 		struct yahoo_pair *pair = l->data;
 
-		switch(pair->key)
-		{
-			case 5:		/* we */
-				break;
-			case 20:		/* url */
-				url = pair->value;
-				break;
-			case 27:		/* local filename */
-				break;
-			case 38:		/* time */
-				break;
-		}
-	}
-
-	YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded)(yd->client_id, url);
-}
-
-static void yahoo_process_auth_pre_0x0b(struct yahoo_input_data *yid, 
-		const char *seed, const char *sn)
-{
-	struct yahoo_data *yd = yid->yd;
-	
-	/* So, Yahoo has stopped supporting its older clients in India, and 
-	 * undoubtedly will soon do so in the rest of the world.
-	 * 
-	 * The new clients use this authentication method.  I warn you in 
-	 * advance, it's bizzare, convoluted, inordinately complicated.  
-	 * It's also no more secure than crypt() was.  The only purpose this 
-	 * scheme could serve is to prevent third part clients from connecting 
-	 * to their servers.
-	 *
-	 * Sorry, Yahoo.
+		switch (pair->key) {
+		case 5:	/* we */
+			break;
+		case 20:	/* url */
+			url = pair->value;
+			break;
+		case 27:	/* local filename */
+			break;
+		case 38:	/* time */
+			break;
+		}
+	}
+
+	YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded) (yd->client_id, url);
+}
+
+void yahoo_login(int id, int initial)
+{
+	struct yahoo_data *yd = find_conn_by_id(id);
+	struct connect_callback_data *ccd;
+	struct yahoo_server_settings *yss;
+	int tag;
+
+	char *host;
+
+	struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
+	yid->yd = yd;
+	yid->type = YAHOO_CONNECTION_PAGER;
+	inputs = y_list_prepend(inputs, yid);
+
+	yd->initial_status = initial;
+	yss = yd->server_settings;
+
+	ccd = y_new0(struct connect_callback_data, 1);
+	ccd->yd = yd;
+
+	host = yss->pager_host;
+
+	if (!host)
+		host = yss->pager_host_list[0];
+
+	tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id,
+		host, yss->pager_port, yahoo_connected, ccd, 0);
+
+	/*
+	 * if tag <= 0, then callback has already been called
+	 * so ccd will have been freed
 	 */
-
-	struct yahoo_packet *pack;
-	
-	md5_byte_t result[16];
-	md5_state_t ctx;
-	char *crypt_result;
-	unsigned char *password_hash = malloc(25);
-	unsigned char *crypt_hash = malloc(25);
-	unsigned char *hash_string_p = malloc(50 + strlen(sn));
-	unsigned char *hash_string_c = malloc(50 + strlen(sn));
-	
-	char checksum;
-	
-	int sv;
-	
-	unsigned char *result6 = malloc(25);
-	unsigned char *result96 = malloc(25);
-
-	sv = seed[15];
-	sv = (sv % 8) % 5;
-
-	md5_init(&ctx);
-	md5_append(&ctx, (md5_byte_t *)yd->password, strlen(yd->password));
-	md5_finish(&ctx, result);
-	to_y64(password_hash, result, 16);
-	
-	md5_init(&ctx);
-	crypt_result = yahoo_crypt(yd->password, "$1$_2S43d5f$");  
-	md5_append(&ctx, (md5_byte_t *)crypt_result, strlen(crypt_result));
-	md5_finish(&ctx, result);
-	to_y64(crypt_hash, result, 16);
-	free(crypt_result);
-
-	switch (sv) {
-	case 0:
-		checksum = seed[seed[7] % 16];
-		snprintf((char *)hash_string_p, strlen(sn) + 50,
-			"%c%s%s%s", checksum, password_hash, yd->user, seed);
-		snprintf((char *)hash_string_c, strlen(sn) + 50,
-			"%c%s%s%s", checksum, crypt_hash, yd->user, seed);
-		break;
-	case 1:
-		checksum = seed[seed[9] % 16];
-		snprintf((char *)hash_string_p, strlen(sn) + 50,
-			"%c%s%s%s", checksum, yd->user, seed, password_hash);
-		snprintf((char *)hash_string_c, strlen(sn) + 50,
-			"%c%s%s%s", checksum, yd->user, seed, crypt_hash);
-		break;
-	case 2:
-		checksum = seed[seed[15] % 16];
-		snprintf((char *)hash_string_p, strlen(sn) + 50,
-			"%c%s%s%s", checksum, seed, password_hash, yd->user);
-		snprintf((char *)hash_string_c, strlen(sn) + 50,
-			"%c%s%s%s", checksum, seed, crypt_hash, yd->user);
-		break;
-	case 3:
-		checksum = seed[seed[1] % 16];
-		snprintf((char *)hash_string_p, strlen(sn) + 50,
-			"%c%s%s%s", checksum, yd->user, password_hash, seed);
-		snprintf((char *)hash_string_c, strlen(sn) + 50,
-			"%c%s%s%s", checksum, yd->user, crypt_hash, seed);
-		break;
-	case 4:
-		checksum = seed[seed[3] % 16];
-		snprintf((char *)hash_string_p, strlen(sn) + 50,
-			"%c%s%s%s", checksum, password_hash, seed, yd->user);
-		snprintf((char *)hash_string_c, strlen(sn) + 50,
-			"%c%s%s%s", checksum, crypt_hash, seed, yd->user);
-		break;
-	}
-		
-	md5_init(&ctx);  
-	md5_append(&ctx, (md5_byte_t *)hash_string_p, strlen((char *)hash_string_p));
-	md5_finish(&ctx, result);
-	to_y64(result6, result, 16);
-
-	md5_init(&ctx);  
-	md5_append(&ctx, (md5_byte_t *)hash_string_c, strlen((char *)hash_string_c));
-	md5_finish(&ctx, result);
-	to_y64(result96, result, 16);
-
-	pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id);
-	yahoo_packet_hash(pack, 0, yd->user);
-	yahoo_packet_hash(pack, 6, (char *)result6);
-	yahoo_packet_hash(pack, 96, (char *)result96);
-	yahoo_packet_hash(pack, 1, yd->user);
-		
-	yahoo_send_packet(yid, pack, 0);
-		
-	FREE(result6);
-	FREE(result96);
-	FREE(password_hash);
-	FREE(crypt_hash);
-	FREE(hash_string_p);
-	FREE(hash_string_c);
-
-	yahoo_packet_free(pack);
-
-}
-
-/*
- * New auth protocol cracked by Cerulean Studios and sent in to Gaim
- */
-static void yahoo_process_auth_0x0b(struct yahoo_input_data *yid, const char *seed, const char *sn)
-{
-	struct yahoo_packet *pack = NULL;
-	struct yahoo_data *yd = yid->yd;
-
-	md5_byte_t         result[16];
-	md5_state_t        ctx;
-
-	sha1_state_t       ctx1;
-	sha1_state_t       ctx2;
-
-	char *alphabet1 = "FBZDWAGHrJTLMNOPpRSKUVEXYChImkwQ";
-	char *alphabet2 = "F0E1D2C3B4A59687abcdefghijklmnop";
-
-	char *challenge_lookup = "qzec2tb3um1olpar8whx4dfgijknsvy5";
-	char *operand_lookup = "+|&%/*^-";
-	char *delimit_lookup = ",;";
-
-	unsigned char *password_hash = malloc(25);
-	unsigned char *crypt_hash = malloc(25);
-	char *crypt_result = NULL;
-	unsigned char pass_hash_xor1[64];
-	unsigned char pass_hash_xor2[64];
-	unsigned char crypt_hash_xor1[64];
-	unsigned char crypt_hash_xor2[64];
-	unsigned char chal[7];
-	char resp_6[100];
-	char resp_96[100];
-
-	unsigned char digest1[20];
-	unsigned char digest2[20];
-	unsigned char magic_key_char[4];
-	const unsigned char *magic_ptr;
-
-	unsigned int  magic[64];
-	unsigned int  magic_work=0;
-
-	char comparison_src[20];
-
-	int x, j, i;
-	int cnt = 0;
-	int magic_cnt = 0;
-	int magic_len;
-	int depth =0, table =0;
-
-	memset(&pass_hash_xor1, 0, 64);
-	memset(&pass_hash_xor2, 0, 64);
-	memset(&crypt_hash_xor1, 0, 64);
-	memset(&crypt_hash_xor2, 0, 64);
-	memset(&digest1, 0, 20);
-	memset(&digest2, 0, 20);
-	memset(&magic, 0, 64);
-	memset(&resp_6, 0, 100);
-	memset(&resp_96, 0, 100);
-	memset(&magic_key_char, 0, 4);
-
-	/* 
-	 * Magic: Phase 1.  Generate what seems to be a 30 
-	 * byte value (could change if base64
-	 * ends up differently?  I don't remember and I'm 
-	 * tired, so use a 64 byte buffer.
-	 */
-
-	magic_ptr = (unsigned char *)seed;
-
-	while (*magic_ptr != 0) {
-		char *loc;
-
-		/* Ignore parentheses.  */
-
-		if (*magic_ptr == '(' || *magic_ptr == ')') {
-			magic_ptr++;
-			continue;
-		}
-
-		/* Characters and digits verify against 
-		   the challenge lookup.
-		*/
-
-		if (isalpha(*magic_ptr) || isdigit(*magic_ptr)) {
-			loc = strchr(challenge_lookup, *magic_ptr);
-			if (!loc) {
-				/* This isn't good */
-				continue;
-			}
-
-			/* Get offset into lookup table and lsh 3. */
-
-			magic_work = loc - challenge_lookup;
-			magic_work <<= 3;
-
-			magic_ptr++;
-			continue;
-		} else {
-			unsigned int local_store;
-
-			loc = strchr(operand_lookup, *magic_ptr);
-			if (!loc) {
-				/* Also not good. */
-				continue;
-			}
-
-			local_store = loc - operand_lookup;
-
-			/* Oops; how did this happen? */
-			if (magic_cnt >= 64) 
-				break;
-
-			magic[magic_cnt++] = magic_work | local_store;
-			magic_ptr++;
-			continue;
-		}
-	}
-
-	magic_len = magic_cnt;
-	magic_cnt = 0;
-
-	/* Magic: Phase 2.  Take generated magic value and 
-	 * sprinkle fairy dust on the values. */
-
-	for (magic_cnt = magic_len-2; magic_cnt >= 0; magic_cnt--) {
-		unsigned char byte1;
-		unsigned char byte2;
-
-		/* Bad.  Abort.
-		 */
-		if (magic_cnt >= magic_len) {
-			WARNING(("magic_cnt(%d)  magic_len(%d)", magic_cnt, magic_len))
-			break;
-		}
-
-		byte1 = magic[magic_cnt];
-		byte2 = magic[magic_cnt+1];
-
-		byte1 *= 0xcd;
-		byte1 ^= byte2;
-
-		magic[magic_cnt+1] = byte1;
-	}
-
-	/* Magic: Phase 3.  This computes 20 bytes.  The first 4 bytes are used as our magic 
-	 * key (and may be changed later); the next 16 bytes are an MD5 sum of the magic key 
-	 * plus 3 bytes.  The 3 bytes are found by looping, and they represent the offsets 
-	 * into particular functions we'll later call to potentially alter the magic key. 
-	 * 
-	 * %-) 
-	 */ 
-	
-	magic_cnt = 1; 
-	x = 0; 
-	
-	do { 
-		unsigned int     bl = 0;  
-		unsigned int     cl = magic[magic_cnt++]; 
-		
-		if (magic_cnt >= magic_len) 
-			break; 
-		
-		if (cl > 0x7F) { 
-			if (cl < 0xe0)  
-				bl = cl = (cl & 0x1f) << 6;  
-			else { 
-				bl = magic[magic_cnt++];  
-                              cl = (cl & 0x0f) << 6;  
-                              bl = ((bl & 0x3f) + cl) << 6;  
-			}  
-			
-			cl = magic[magic_cnt++];  
-			bl = (cl & 0x3f) + bl;  
-		} else 
-			bl = cl;  
-		
-		comparison_src[x++] = (bl & 0xff00) >> 8;  
-		comparison_src[x++] = bl & 0xff;  
-	} while (x < 20); 
-
-	/* Dump magic key into a char for SHA1 action. */
-	
-		
-	for(x = 0; x < 4; x++) 
-		magic_key_char[x] = comparison_src[x];
-
-	/* Compute values for recursive function table! */
-	memcpy( chal, magic_key_char, 4 );
-        x = 1;
-	for( i = 0; i < 0xFFFF && x; i++ )
-	{
-		for( j = 0; j < 5 && x; j++ )
-		{
-			chal[4] = i;
-			chal[5] = i >> 8;
-			chal[6] = j;
-			md5_init( &ctx );
-			md5_append( &ctx, chal, 7 );
-			md5_finish( &ctx, result );
-			if( memcmp( comparison_src + 4, result, 16 ) == 0 )
-			{
-				depth = i;
-				table = j;
-				x = 0;
-			}
-		}
-	}
-
-	/* Transform magic_key_char using transform table */
-	x = magic_key_char[3] << 24  | magic_key_char[2] << 16 
-		| magic_key_char[1] << 8 | magic_key_char[0];
-	x = yahoo_xfrm( table, depth, x );
-	x = yahoo_xfrm( table, depth, x );
-	magic_key_char[0] = x & 0xFF;
-	magic_key_char[1] = x >> 8 & 0xFF;
-	magic_key_char[2] = x >> 16 & 0xFF;
-	magic_key_char[3] = x >> 24 & 0xFF;
-
-	/* Get password and crypt hashes as per usual. */
-	md5_init(&ctx);
-	md5_append(&ctx, (md5_byte_t *)yd->password,  strlen(yd->password));
-	md5_finish(&ctx, result);
-	to_y64(password_hash, result, 16);
-
-	md5_init(&ctx);
-	crypt_result = yahoo_crypt(yd->password, "$1$_2S43d5f$");  
-	md5_append(&ctx, (md5_byte_t *)crypt_result, strlen(crypt_result));
-	md5_finish(&ctx, result);
-	to_y64(crypt_hash, result, 16);
-	free(crypt_result);
-
-	/* Our first authentication response is based off 
-	 * of the password hash. */
-
-	for (x = 0; x < (int)strlen((char *)password_hash); x++) 
-		pass_hash_xor1[cnt++] = password_hash[x] ^ 0x36;
-
-	if (cnt < 64) 
-		memset(&(pass_hash_xor1[cnt]), 0x36, 64-cnt);
-
-	cnt = 0;
-
-	for (x = 0; x < (int)strlen((char *)password_hash); x++) 
-		pass_hash_xor2[cnt++] = password_hash[x] ^ 0x5c;
-
-	if (cnt < 64) 
-		memset(&(pass_hash_xor2[cnt]), 0x5c, 64-cnt);
-
-	sha1_init(&ctx1);
-	sha1_init(&ctx2);
-
-	/* The first context gets the password hash XORed 
-	 * with 0x36 plus a magic value
-	 * which we previously extrapolated from our 
-	 * challenge. */
-
-	sha1_append(&ctx1, pass_hash_xor1, 64);
-	if (j >= 3 )
-		ctx1.Length_Low = 0x1ff;
-	sha1_append(&ctx1, magic_key_char, 4);
-	sha1_finish(&ctx1, digest1);
-
-	 /* The second context gets the password hash XORed 
-	  * with 0x5c plus the SHA-1 digest
-	  * of the first context. */
-
-	sha1_append(&ctx2, pass_hash_xor2, 64);
-	sha1_append(&ctx2, digest1, 20);
-	sha1_finish(&ctx2, digest2);
-
-	/* Now that we have digest2, use it to fetch 
-	 * characters from an alphabet to construct
-	 * our first authentication response. */
-
-	for (x = 0; x < 20; x += 2) {
-		unsigned int    val = 0;
-		unsigned int    lookup = 0;
-		char            byte[6];
-
-		memset(&byte, 0, 6);
-
-		/* First two bytes of digest stuffed 
-		 *  together.
-		 */
-
-		val = digest2[x];
-		val <<= 8;
-		val += digest2[x+1];
-
-		lookup = (val >> 0x0b);
-		lookup &= 0x1f;
-		if (lookup >= strlen(alphabet1))
-			break;
-		sprintf(byte, "%c", alphabet1[lookup]);
-		strcat(resp_6, byte);
-		strcat(resp_6, "=");
-
-		lookup = (val >> 0x06);
-		lookup &= 0x1f;
-		if (lookup >= strlen(alphabet2))
-			break;
-		sprintf(byte, "%c", alphabet2[lookup]);
-		strcat(resp_6, byte);
-
-		lookup = (val >> 0x01);
-		lookup &= 0x1f;
-		if (lookup >= strlen(alphabet2))
-			break;
-		sprintf(byte, "%c", alphabet2[lookup]);
-		strcat(resp_6, byte);
-
-		lookup = (val & 0x01);
-		if (lookup >= strlen(delimit_lookup))
-			break;
-		sprintf(byte, "%c", delimit_lookup[lookup]);
-		strcat(resp_6, byte);
-	}
-
-	/* Our second authentication response is based off 
-	 * of the crypto hash. */
-
-	cnt = 0;
-	memset(&digest1, 0, 20);
-	memset(&digest2, 0, 20);
-
-	for (x = 0; x < (int)strlen((char *)crypt_hash); x++) 
-		crypt_hash_xor1[cnt++] = crypt_hash[x] ^ 0x36;
-
-	if (cnt < 64) 
-		memset(&(crypt_hash_xor1[cnt]), 0x36, 64-cnt);
-
-	cnt = 0;
-
-	for (x = 0; x < (int)strlen((char *)crypt_hash); x++) 
-		crypt_hash_xor2[cnt++] = crypt_hash[x] ^ 0x5c;
-
-	if (cnt < 64) 
-		memset(&(crypt_hash_xor2[cnt]), 0x5c, 64-cnt);
-
-	sha1_init(&ctx1);
-	sha1_init(&ctx2);
-
-	/* The first context gets the password hash XORed 
-	 * with 0x36 plus a magic value
-	 * which we previously extrapolated from our 
-	 * challenge. */
-
-	sha1_append(&ctx1, crypt_hash_xor1, 64);
-	if (j >= 3 )
-		ctx1.Length_Low = 0x1ff;
-	sha1_append(&ctx1, magic_key_char, 4);
-	sha1_finish(&ctx1, digest1);
-
-	/* The second context gets the password hash XORed 
-	 * with 0x5c plus the SHA-1 digest
-	 * of the first context. */
-
-	sha1_append(&ctx2, crypt_hash_xor2, 64);
-	sha1_append(&ctx2, digest1, 20);
-	sha1_finish(&ctx2, digest2);
-
-	/* Now that we have digest2, use it to fetch 
-	 * characters from an alphabet to construct
-	 * our first authentication response.  */
-
-	for (x = 0; x < 20; x += 2) {
-		unsigned int val = 0;
-		unsigned int lookup = 0;
-
-		char byte[6];
-
-		memset(&byte, 0, 6);
-
-		/* First two bytes of digest stuffed 
-		 *  together. */
-
-		val = digest2[x];
-		val <<= 8;
-		val += digest2[x+1];
-
-		lookup = (val >> 0x0b);
-		lookup &= 0x1f;
-		if (lookup >= strlen(alphabet1))
-			break;
-		sprintf(byte, "%c", alphabet1[lookup]);
-		strcat(resp_96, byte);
-		strcat(resp_96, "=");
-
-		lookup = (val >> 0x06);
-		lookup &= 0x1f;
-		if (lookup >= strlen(alphabet2))
-			break;
-		sprintf(byte, "%c", alphabet2[lookup]);
-		strcat(resp_96, byte);
-
-		lookup = (val >> 0x01);
-		lookup &= 0x1f;
-		if (lookup >= strlen(alphabet2))
-			break;
-		sprintf(byte, "%c", alphabet2[lookup]);
-		strcat(resp_96, byte);
-
-		lookup = (val & 0x01);
-		if (lookup >= strlen(delimit_lookup))
-			break;
-		sprintf(byte, "%c", delimit_lookup[lookup]);
-		strcat(resp_96, byte);
-	}
-
-	pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id);
-	yahoo_packet_hash(pack, 0, sn);
-	yahoo_packet_hash(pack, 6, resp_6);
-	yahoo_packet_hash(pack, 96, resp_96);
-	yahoo_packet_hash(pack, 1, sn);
-	yahoo_send_packet(yid, pack, 0);
-	yahoo_packet_free(pack);
-
-	free(password_hash);
-	free(crypt_hash);
+	if (tag > 0)
+		ccd->tag = tag;
+	else if (tag < 0)
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+			YAHOO_LOGIN_SOCK, NULL);
 }
 
@@ -2303,5 +1795,5 @@
 }
 
-static void yahoo_process_auth_0x10(struct yahoo_input_data *yid, const char *seed, const char *sn)
+static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn)
 {
 	struct yahoo_https_auth_data *had = g_new0(struct yahoo_https_auth_data, 1);
@@ -2353,19 +1845,20 @@
 	
 	if (req->status_code != 200) {
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 2000 + req->status_code, NULL);
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL);
 		goto fail;
 	}
 	
 	if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) {
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, yahoo_https_status_parse(st), NULL);
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL);
 		goto fail;
 	}
 	
 	if ((had->token = yahoo_ha_find_key(req->reply_body, "ymsgr")) == NULL) {
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 3001, NULL);
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3001, NULL);
 		goto fail;
 	}
 	
-	return yahoo_https_auth_init(had);
+	yahoo_https_auth_init(had);
+	return;
 	
 fail:
@@ -2409,10 +1902,10 @@
 
 	if (req->status_code != 200) {
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 2000 + req->status_code, NULL);
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL);
 		goto fail;
 	}
 	
 	if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) {
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, yahoo_https_status_parse(st), NULL);
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL);
 		goto fail;
 	}
@@ -2421,5 +1914,5 @@
 	    (yd->cookie_t = yahoo_ha_find_key(req->reply_body, "T")) == NULL ||
 	    (crumb = yahoo_ha_find_key(req->reply_body, "crumb")) == NULL) {
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 3002, NULL);
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3002, NULL);
 		goto fail;
 	}
@@ -2454,50 +1947,51 @@
 }
 
-static void yahoo_process_auth(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_process_auth(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	char *seed = NULL;
-	char *sn   = NULL;
+	char *sn = NULL;
 	YList *l = pkt->hash;
 	int m = 0;
+	struct yahoo_data *yd = yid->yd;
 
 	while (l) {
 		struct yahoo_pair *pair = l->data;
-		if (pair->key == 94)
+
+		switch (pair->key) {
+		case 94:
 			seed = pair->value;
-		if (pair->key == 1)
+			break;
+		case 1:
 			sn = pair->value;
-		if (pair->key == 13)
+			break;
+		case 13:
 			m = atoi(pair->value);
+			break;
+		}
 		l = l->next;
 	}
 
-	if (!seed) 
-		return;
-
-	switch (m) {
-		case 0:
-			yahoo_process_auth_pre_0x0b(yid, seed, sn);
-			break;
-		case 1:
-			yahoo_process_auth_0x0b(yid, seed, sn);
-			break;
-		case 2:
-			yahoo_process_auth_0x10(yid, seed, sn);
-			break;
-		default:
-			/* call error */
-			WARNING(("unknown auth type %d", m));
-			yahoo_process_auth_0x0b(yid, seed, sn);
-			break;
-	}
-}
-
-static void yahoo_process_auth_resp(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+	if (!seed)
+		return;
+
+	if (m==2)
+		yahoo_https_auth(yid, seed, sn);
+	else {
+		/* call error */
+		WARNING(("unknown auth type %d", m));
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+			YAHOO_LOGIN_UNKNOWN, NULL);
+	}
+}
+
+static void yahoo_process_auth_resp(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
 	char *login_id;
 	char *handle;
-	char *url=NULL;
-	int  login_status=0;
+	char *url = NULL;
+	int login_status = -1;
 
 	YList *l;
@@ -2515,11 +2009,13 @@
 	}
 
-	if(pkt->status == 0xffffffff) {
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, login_status, url);
-	/*	yahoo_logoff(yd->client_id);*/
-	}
-}
-
-static void yahoo_process_mail(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+	if (pkt->status == YPACKET_STATUS_DISCONNECTED) {
+		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+			login_status, url);
+		/*      yahoo_logoff(yd->client_id); */
+	}
+}
+
+static void yahoo_process_mail(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -2547,10 +2043,44 @@
 		char from[1024];
 		snprintf(from, sizeof(from), "%s (%s)", who, email);
-		YAHOO_CALLBACK(ext_yahoo_mail_notify)(yd->client_id, from, subj, count);
-	} else if(count > 0)
-		YAHOO_CALLBACK(ext_yahoo_mail_notify)(yd->client_id, NULL, NULL, count);
-}
-
-static void yahoo_process_contact(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+		YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, from,
+			subj, count);
+	} else if (count > 0)
+		YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, NULL,
+			NULL, count);
+}
+
+static void yahoo_process_new_contact(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
+{
+	struct yahoo_data *yd = yid->yd;
+	char *me = NULL;
+	char *who = NULL;
+	char *msg = NULL;
+	int online = -1;
+
+	YList *l;
+
+	for (l = pkt->hash; l; l = l->next) {
+		struct yahoo_pair *pair = l->data;
+		if (pair->key == 4)
+			who = pair->value;
+		else if (pair->key == 5)
+			me = pair->value;
+		else if (pair->key == 14)
+			msg = pair->value;
+		else if (pair->key == 13)
+			online = strtol(pair->value, NULL, 10);
+	}
+
+	if (who && online < 0)
+		YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, me, who,
+			msg);
+	else if (online == 2)
+		YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg);
+}
+
+/* UNUSED? */
+static void yahoo_process_contact(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -2561,5 +2091,5 @@
 	long tm = 0L;
 	int state = YAHOO_STATUS_AVAILABLE;
-	int online = FALSE;
+	int online = 0;
 	int away = 0;
 	int idle = 0;
@@ -2590,16 +2120,19 @@
 		else if (pair->key == 60)
 			mobile = strtol(pair->value, NULL, 10);
-		
+
 	}
 
 	if (id)
-		YAHOO_CALLBACK(ext_yahoo_contact_added)(yd->client_id, id, who, msg);
+		YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, id, who,
+			msg);
 	else if (name)
-		YAHOO_CALLBACK(ext_yahoo_status_changed)(yd->client_id, name, state, msg, away, idle, mobile);
-	else if(pkt->status == 0x07)
-		YAHOO_CALLBACK(ext_yahoo_rejected)(yd->client_id, who, msg);
-}
-
-static void yahoo_process_buddyadd(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+		YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, name,
+			state, msg, away, idle, mobile);
+	else if (pkt->status == 0x07)
+		YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg);
+}
+
+static void yahoo_process_buddyadd(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -2609,5 +2142,5 @@
 	char *me = NULL;
 
-	struct yahoo_buddy *bud=NULL;
+	struct yahoo_buddy *bud = NULL;
 
 	YList *l;
@@ -2624,49 +2157,28 @@
 	}
 
-	yahoo_dump_unhandled(pkt);
-
-	if(!who)
-		return;
-	if(!where)
+	if (!who)
+		return;
+	if (!where)
 		where = "Unknown";
 
-	/* status: 0 == Successful, 1 == Error (does not exist), 2 == Already in list */
-	if( status == 0 ) {
-		bud = y_new0(struct yahoo_buddy, 1);
-		bud->id = strdup(who);
-		bud->group = strdup(where);
-		bud->real_name = NULL;
-		
-		yd->buddies = y_list_append(yd->buddies, bud);
-	
-		/* Possibly called already, but at least the call above doesn't
-		   seem to happen every time (not anytime I tried). */
-		YAHOO_CALLBACK(ext_yahoo_contact_added)(yd->client_id, me, who, NULL);
-	}
-
-/*	YAHOO_CALLBACK(ext_yahoo_status_changed)(yd->client_id, who, status, NULL, (status==YAHOO_STATUS_AVAILABLE?0:1)); */
-}
-
-static void yahoo_process_contact_ymsg13(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
-{
-	char* who=NULL;
-	char* me=NULL;	
-	char* msg=NULL;
-	YList *l;
-	for (l = pkt->hash; l; l = l->next) {
-		struct yahoo_pair *pair = l->data;
-		if (pair->key == 4)
-			who = pair->value;
-		else if (pair->key == 5)
-			me = pair->value;
-		else
-			DEBUG_MSG(("unknown key: %d = %s", pair->key, pair->value));
-	}
-
-	if(pkt->status==3)
-		YAHOO_CALLBACK(ext_yahoo_contact_auth_request)(yid->yd->client_id, me, who, msg);
-}
-
-static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+	bud = y_new0(struct yahoo_buddy, 1);
+	bud->id = strdup(who);
+	bud->group = strdup(where);
+	bud->real_name = NULL;
+
+	yd->buddies = y_list_append(yd->buddies, bud);
+
+	/* A non-zero status (i've seen 2) seems to mean the buddy is already 
+	 * added and is online */
+	if (status) {
+		LOG(("Setting online see packet for info"));
+		yahoo_dump_unhandled(pkt);
+		YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, who,
+			YAHOO_STATUS_AVAILABLE, NULL, 0, 0, 0);
+	}
+}
+
+static void yahoo_process_buddydel(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = yid->yd;
@@ -2691,10 +2203,11 @@
 			unk_66 = strtol(pair->value, NULL, 10);
 		else
-			DEBUG_MSG(("unknown key: %d = %s", pair->key, pair->value));
-	}
-
-	if(!who || !where)
-		return;
-	
+			DEBUG_MSG(("unknown key: %d = %s", pair->key,
+					pair->value));
+	}
+
+	if (!who || !where)
+		return;
+
 	bud = y_new0(struct yahoo_buddy, 1);
 	bud->id = strdup(who);
@@ -2707,5 +2220,5 @@
 	FREE(bud);
 
-	if(buddy) {
+	if (buddy) {
 		bud = buddy->data;
 		yd->buddies = y_list_remove_link(yd->buddies, buddy);
@@ -2717,14 +2230,15 @@
 		FREE(bud);
 
-		bud=NULL;
-	}
-}
-
-static void yahoo_process_ignore(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+		bud = NULL;
+	}
+}
+
+static void yahoo_process_ignore(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	char *who = NULL;
-	int  status = 0;
+	int status = 0;
 	char *me = NULL;
-	int  un_ignore = 0;
+	int un_ignore = 0;
 
 	YList *l;
@@ -2735,25 +2249,25 @@
 		if (pair->key == 1)
 			me = pair->value;
-		if (pair->key == 13) /* 1 == ignore, 2 == unignore */ 
+		if (pair->key == 13)	/* 1 == ignore, 2 == unignore */
 			un_ignore = strtol(pair->value, NULL, 10);
-		if (pair->key == 66) 
+		if (pair->key == 66)
 			status = strtol(pair->value, NULL, 10);
 	}
-
 
 	/*
 	 * status
-	 * 	0  - ok
-	 * 	2  - already in ignore list, could not add
-	 * 	3  - not in ignore list, could not delete
-	 * 	12 - is a buddy, could not add
+	 *      0  - ok
+	 *      2  - already in ignore list, could not add
+	 *      3  - not in ignore list, could not delete
+	 *      12 - is a buddy, could not add
 	 */
 
 /*	if(status)
 		YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, who, 0, status);
-*/	
-}
-
-static void yahoo_process_voicechat(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+*/
+}
+
+static void yahoo_process_voicechat(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	char *who = NULL;
@@ -2770,10 +2284,11 @@
 			me = pair->value;
 		if (pair->key == 13)
-			voice_room=pair->value;
-		if (pair->key == 57) 
-			room=pair->value;
-	}
-
-	NOTICE(("got voice chat invite from %s in %s to identity %s", who, room, me));
+			voice_room = pair->value;
+		if (pair->key == 57)
+			room = pair->value;
+	}
+
+	NOTICE(("got voice chat invite from %s in %s to identity %s", who, room,
+			me));
 	/* 
 	 * send: s:0 1:me 5:who 57:room 13:1
@@ -2787,8 +2302,9 @@
 }
 
-static void yahoo_process_ping(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_process_ping(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	char *errormsg = NULL;
-	
+
 	YList *l;
 	for (l = pkt->hash; l; l = l->next) {
@@ -2797,10 +2313,35 @@
 			errormsg = pair->value;
 	}
-	
+
 	NOTICE(("got ping packet"));
-	YAHOO_CALLBACK(ext_yahoo_got_ping)(yid->yd->client_id, errormsg);
-}
-
-static void _yahoo_webcam_get_server_connected(int fd, int error, void *d)
+	YAHOO_CALLBACK(ext_yahoo_got_ping) (yid->yd->client_id, errormsg);
+}
+
+static void yahoo_process_buddy_change_group(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
+{
+	YList *l;
+	char *me = NULL;
+	char *who = NULL;
+	char *old_group = NULL;
+	char *new_group = NULL;
+
+	for (l = pkt->hash; l; l = l->next) {
+		struct yahoo_pair *pair = l->data;
+		if (pair->key == 1)
+			me = pair->value;
+		if (pair->key == 7)
+			who = pair->value;
+		if (pair->key == 224)
+			old_group = pair->value;
+		if (pair->key == 264)
+			new_group = pair->value;
+	}
+
+	YAHOO_CALLBACK(ext_yahoo_got_buddy_change_group) (yid->yd->client_id,
+		me, who, old_group, new_group);
+}
+
+static void _yahoo_webcam_get_server_connected(void *fd, int error, void *d)
 {
 	struct yahoo_input_data *yid = d;
@@ -2808,10 +2349,10 @@
 	char *data = NULL;
 	char *packet = NULL;
-	unsigned char magic_nr[] = {0, 1, 0};
+	unsigned char magic_nr[] = { 0, 1, 0 };
 	unsigned char header_len = 8;
 	unsigned int len = 0;
 	unsigned int pos = 0;
 
-	if(error || fd <= 0) {
+	if (error || !fd) {
 		FREE(who);
 		FREE(yid);
@@ -2821,5 +2362,5 @@
 	yid->fd = fd;
 	inputs = y_list_prepend(inputs, yid);
-	
+
 	/* send initial packet */
 	if (who)
@@ -2831,6 +2372,5 @@
 
 	/* send data */
-	if (who)
-	{
+	if (who) {
 		data = strdup("g=");
 		data = y_string_append(data, who);
@@ -2851,8 +2391,11 @@
 	FREE(data);
 
-	yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, fd, YAHOO_INPUT_READ, yid);
-}
-
-static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who, char *key)
+	yid->read_tag =
+		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
+		YAHOO_INPUT_READ, yid);
+}
+
+static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who,
+	char *key)
 {
 	struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
@@ -2862,15 +2405,17 @@
 	yid->yd = y->yd;
 	yid->wcm = y_new0(struct yahoo_webcam, 1);
-	yid->wcm->user = who?strdup(who):NULL;
-	yid->wcm->direction = who?YAHOO_WEBCAM_DOWNLOAD:YAHOO_WEBCAM_UPLOAD;
+	yid->wcm->user = who ? strdup(who) : NULL;
+	yid->wcm->direction = who ? YAHOO_WEBCAM_DOWNLOAD : YAHOO_WEBCAM_UPLOAD;
 	yid->wcm->key = strdup(key);
 
-	YAHOO_CALLBACK(ext_yahoo_connect_async)(yid->yd->client_id, yss->webcam_host, yss->webcam_port, 
-			_yahoo_webcam_get_server_connected, yid);
-
-}
-
-static YList *webcam_queue=NULL;
-static void yahoo_process_webcam_key(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+	YAHOO_CALLBACK(ext_yahoo_connect_async) (yid->yd->client_id,
+		yss->webcam_host, yss->webcam_port,
+		_yahoo_webcam_get_server_connected, yid, 0);
+
+}
+
+static YList *webcam_queue = NULL;
+static void yahoo_process_webcam_key(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	char *me = NULL;
@@ -2879,15 +2424,15 @@
 
 	YList *l;
-	// yahoo_dump_unhandled(pkt);
+	yahoo_dump_unhandled(pkt);
 	for (l = pkt->hash; l; l = l->next) {
 		struct yahoo_pair *pair = l->data;
 		if (pair->key == 5)
 			me = pair->value;
-		if (pair->key == 61) 
-			key=pair->value;
+		if (pair->key == 61)
+			key = pair->value;
 	}
 
 	l = webcam_queue;
-	if(!l)
+	if (!l)
 		return;
 	who = l->data;
@@ -2898,10 +2443,9 @@
 }
 
-static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_packet *pkt)
+static void yahoo_packet_process(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
 {
 	DEBUG_MSG(("yahoo_packet_process: 0x%02x", pkt->service));
-	yahoo_dump_unhandled(pkt);
-	switch (pkt->service)
-	{
+	switch (pkt->service) {
 	case YAHOO_SERVICE_USERSTAT:
 	case YAHOO_SERVICE_LOGON:
@@ -2914,5 +2458,5 @@
 	case YAHOO_SERVICE_IDDEACT:
 	case YAHOO_SERVICE_Y6_STATUS_UPDATE:
-	case YAHOO_SERVICE_YMSG15_STATUS:
+	case YAHOO_SERVICE_Y8_STATUS:
 		yahoo_process_status(yid, pkt);
 		break;
@@ -2928,5 +2472,7 @@
 		yahoo_process_mail(yid, pkt);
 		break;
-	case YAHOO_SERVICE_REJECTCONTACT:
+	case YAHOO_SERVICE_Y7_AUTHORIZATION:
+		yahoo_process_new_contact(yid, pkt);
+		break;
 	case YAHOO_SERVICE_NEWCONTACT:
 		yahoo_process_contact(yid, pkt);
@@ -2963,12 +2509,15 @@
 		break;
 	case YAHOO_SERVICE_P2PFILEXFER:
-	case YAHOO_SERVICE_FILETRANSFER:
+	case YAHOO_SERVICE_Y7_FILETRANSFER:
 		yahoo_process_filetransfer(yid, pkt);
+		break;
+	case YAHOO_SERVICE_Y7_FILETRANSFERINFO:
+		yahoo_process_filetransferinfo(yid, pkt);
+		break;
+	case YAHOO_SERVICE_Y7_FILETRANSFERACCEPT:
+		yahoo_process_filetransferaccept(yid, pkt);
 		break;
 	case YAHOO_SERVICE_ADDBUDDY:
 		yahoo_process_buddyadd(yid, pkt);
-		break;
-	case YAHOO_SERVICE_CONTACT_YMSG13:
-		yahoo_process_contact_ymsg13(yid,pkt);
 		break;
 	case YAHOO_SERVICE_REMBUDDY:
@@ -2987,4 +2536,7 @@
 		yahoo_process_ping(yid, pkt);
 		break;
+	case YAHOO_SERVICE_Y7_CHANGE_GROUP:
+		yahoo_process_buddy_change_group(yid, pkt);
+		break;
 	case YAHOO_SERVICE_IDLE:
 	case YAHOO_SERVICE_MAILSTAT:
@@ -3000,4 +2552,5 @@
 	case YAHOO_SERVICE_CHATLOGOFF:
 	case YAHOO_SERVICE_CHATMSG:
+	case YAHOO_SERVICE_REJECTCONTACT:
 	case YAHOO_SERVICE_PEERTOPEER:
 		WARNING(("unhandled service 0x%02x", pkt->service));
@@ -3012,7 +2565,8 @@
 	case YAHOO_SERVICE_PICTURE_UPLOAD:
 		yahoo_process_picture_upload(yid, pkt);
-		break;	
-	case YAHOO_SERVICE_YMSG15_BUDDY_LIST:	/* Buddy List */
+		break;
+	case YAHOO_SERVICE_Y8_LIST:	/* Buddy List */
 		yahoo_process_buddy_list(yid, pkt);
+		break;
 	default:
 		WARNING(("unknown service 0x%02x", pkt->service));
@@ -3022,5 +2576,5 @@
 }
 
-static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid)
+static struct yahoo_packet *yahoo_getdata(struct yahoo_input_data *yid)
 {
 	struct yahoo_packet *pkt;
@@ -3029,5 +2583,5 @@
 	int pktlen;
 
-	if(!yd)
+	if (!yd)
 		return NULL;
 
@@ -3038,11 +2592,11 @@
 	}
 
-	pos += 4; /* YMSG */
+	pos += 4;		/* YMSG */
 	pos += 2;
 	pos += 2;
 
-	pktlen = yahoo_get16(yid->rxqueue + pos); pos += 2;
-	DEBUG_MSG(("%d bytes to read, rxlen is %d", 
-			pktlen, yid->rxlen));
+	pktlen = yahoo_get16(yid->rxqueue + pos);
+	pos += 2;
+	DEBUG_MSG(("%d bytes to read, rxlen is %d", pktlen, yid->rxlen));
 
 	if (yid->rxlen < (YAHOO_PACKET_HDRLEN + pktlen)) {
@@ -3056,9 +2610,12 @@
 	pkt = yahoo_packet_new(0, 0, 0);
 
-	pkt->service = yahoo_get16(yid->rxqueue + pos); pos += 2;
-	pkt->status = yahoo_get32(yid->rxqueue + pos); pos += 4;
+	pkt->service = yahoo_get16(yid->rxqueue + pos);
+	pos += 2;
+	pkt->status = yahoo_get32(yid->rxqueue + pos);
+	pos += 4;
 	DEBUG_MSG(("Yahoo Service: 0x%02x Status: %d", pkt->service,
-				pkt->status));
-	pkt->id = yahoo_get32(yid->rxqueue + pos); pos += 4;
+			pkt->status));
+	pkt->id = yahoo_get32(yid->rxqueue + pos);
+	pos += 4;
 
 	yd->session_id = pkt->id;
@@ -3068,10 +2625,11 @@
 	yid->rxlen -= YAHOO_PACKET_HDRLEN + pktlen;
 	DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
-	if (yid->rxlen>0) {
-		unsigned char *tmp = y_memdup(yid->rxqueue + YAHOO_PACKET_HDRLEN 
-				+ pktlen, yid->rxlen);
+	if (yid->rxlen > 0) {
+		unsigned char *tmp = y_memdup(yid->rxqueue + YAHOO_PACKET_HDRLEN
+			+ pktlen, yid->rxlen);
 		FREE(yid->rxqueue);
 		yid->rxqueue = tmp;
-		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
+		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
+				yid->rxqueue));
 	} else {
 		DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
@@ -3082,116 +2640,194 @@
 }
 
-static void yahoo_yab_read(struct yab *yab, unsigned char *d, int len)
+static struct yab *yahoo_yab_read(unsigned char *d, int len)
 {
 	char *st, *en;
 	char *data = (char *)d;
-	data[len]='\0';
+	struct yab *yab = NULL;
+
+	data[len] = '\0';
 
 	DEBUG_MSG(("Got yab: %s", data));
-	st = en = strstr(data, "userid=\"");
-	if(st) {
-		st += strlen("userid=\"");
-		en = strchr(st, '"'); *en++ = '\0';
+	st = en = strstr(data, "e0=\"");
+	if (st) {
+		yab = y_new0(struct yab, 1);
+
+		st += strlen("e0=\"");
+		en = strchr(st, '"');
+		*en++ = '\0';
+		yab->email = yahoo_xmldecode(st);
+	}
+
+	if (!en)
+		return NULL;
+
+	st = strstr(en, "id=\"");
+	if (st) {
+		st += strlen("id=\"");
+		en = strchr(st, '"');
+		*en++ = '\0';
+		yab->yid = atoi(yahoo_xmldecode(st));
+	}
+
+	st = strstr(en, "fn=\"");
+	if (st) {
+		st += strlen("fn=\"");
+		en = strchr(st, '"');
+		*en++ = '\0';
+		yab->fname = yahoo_xmldecode(st);
+	}
+
+	st = strstr(en, "ln=\"");
+	if (st) {
+		st += strlen("ln=\"");
+		en = strchr(st, '"');
+		*en++ = '\0';
+		yab->lname = yahoo_xmldecode(st);
+	}
+
+	st = strstr(en, "nn=\"");
+	if (st) {
+		st += strlen("nn=\"");
+		en = strchr(st, '"');
+		*en++ = '\0';
+		yab->nname = yahoo_xmldecode(st);
+	}
+
+	st = strstr(en, "yi=\"");
+	if (st) {
+		st += strlen("yi=\"");
+		en = strchr(st, '"');
+		*en++ = '\0';
 		yab->id = yahoo_xmldecode(st);
 	}
 
-	st = strstr(en, "fname=\"");
-	if(st) {
-		st += strlen("fname=\"");
-		en = strchr(st, '"'); *en++ = '\0';
-		yab->fname = yahoo_xmldecode(st);
-	}
-
-	st = strstr(en, "lname=\"");
-	if(st) {
-		st += strlen("lname=\"");
-		en = strchr(st, '"'); *en++ = '\0';
-		yab->lname = yahoo_xmldecode(st);
-	}
-
-	st = strstr(en, "nname=\"");
-	if(st) {
-		st += strlen("nname=\"");
-		en = strchr(st, '"'); *en++ = '\0';
-		yab->nname = yahoo_xmldecode(st);
-	}
-
-	st = strstr(en, "email=\"");
-	if(st) {
-		st += strlen("email=\"");
-		en = strchr(st, '"'); *en++ = '\0';
-		yab->email = yahoo_xmldecode(st);
-	}
-
 	st = strstr(en, "hphone=\"");
-	if(st) {
+	if (st) {
 		st += strlen("hphone=\"");
-		en = strchr(st, '"'); *en++ = '\0';
+		en = strchr(st, '"');
+		*en++ = '\0';
 		yab->hphone = yahoo_xmldecode(st);
 	}
 
 	st = strstr(en, "wphone=\"");
-	if(st) {
+	if (st) {
 		st += strlen("wphone=\"");
-		en = strchr(st, '"'); *en++ = '\0';
+		en = strchr(st, '"');
+		*en++ = '\0';
 		yab->wphone = yahoo_xmldecode(st);
 	}
 
 	st = strstr(en, "mphone=\"");
-	if(st) {
+	if (st) {
 		st += strlen("mphone=\"");
-		en = strchr(st, '"'); *en++ = '\0';
+		en = strchr(st, '"');
+		*en++ = '\0';
 		yab->mphone = yahoo_xmldecode(st);
 	}
 
 	st = strstr(en, "dbid=\"");
-	if(st) {
+	if (st) {
 		st += strlen("dbid=\"");
-		en = strchr(st, '"'); *en++ = '\0';
+		en = strchr(st, '"');
+		*en++ = '\0';
 		yab->dbid = atoi(st);
 	}
-}
-
-static struct yab * yahoo_getyab(struct yahoo_input_data *yid)
+
+	return yab;
+}
+
+static struct yab *yahoo_getyab(struct yahoo_input_data *yid)
 {
 	struct yab *yab = NULL;
-	int pos = 0, end=0;
+	int pos = 0, end = 0;
 	struct yahoo_data *yd = yid->yd;
 
-	if(!yd)
+	if (!yd)
 		return NULL;
 
+	do {
+		DEBUG_MSG(("rxlen is %d", yid->rxlen));
+
+		if (yid->rxlen <= strlen("<ct"))
+			return NULL;
+
+		/* start with <ct */
+		while (pos < yid->rxlen - strlen("<ct") + 1
+			&& memcmp(yid->rxqueue + pos, "<ct", strlen("<ct")))
+			pos++;
+
+		if (pos >= yid->rxlen - 1)
+			return NULL;
+
+		end = pos + 2;
+		/* end with > */
+		while (end < yid->rxlen - strlen(">")
+			&& memcmp(yid->rxqueue + end, ">", strlen(">")))
+			end++;
+
+		if (end >= yid->rxlen - 1)
+			return NULL;
+
+		yab = yahoo_yab_read(yid->rxqueue + pos, end + 2 - pos);
+
+		yid->rxlen -= end + 1;
+		DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen,
+				yid->rxqueue));
+		if (yid->rxlen > 0) {
+			unsigned char *tmp =
+				y_memdup(yid->rxqueue + end + 1, yid->rxlen);
+			FREE(yid->rxqueue);
+			yid->rxqueue = tmp;
+			DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
+					yid->rxqueue));
+		} else {
+			DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
+			FREE(yid->rxqueue);
+		}
+
+	} while (!yab && end < yid->rxlen - 1);
+
+	return yab;
+}
+
+static char *yahoo_getwebcam_master(struct yahoo_input_data *yid)
+{
+	unsigned int pos = 0;
+	unsigned int len = 0;
+	unsigned int status = 0;
+	char *server = NULL;
+	struct yahoo_data *yd = yid->yd;
+
+	if (!yid || !yd)
+		return NULL;
+
 	DEBUG_MSG(("rxlen is %d", yid->rxlen));
 
-	if(yid->rxlen <= strlen("<record"))
+	len = yid->rxqueue[pos++];
+	if (yid->rxlen < len)
 		return NULL;
 
-	/* start with <record */
-	while(pos < yid->rxlen-strlen("<record")+1 
-			&& memcmp(yid->rxqueue + pos, "<record", strlen("<record")))
-		pos++;
-
-	if(pos >= yid->rxlen-1)
-		return NULL;
-
-	end = pos+2;
-	/* end with /> */
-	while(end < yid->rxlen-strlen("/>")+1 && memcmp(yid->rxqueue + end, "/>", strlen("/>")))
-		end++;
-
-	if(end >= yid->rxlen-1)
-		return NULL;
-
-	yab = y_new0(struct yab, 1);
-	yahoo_yab_read(yab, yid->rxqueue + pos, end+2-pos);
-	
-
-	yid->rxlen -= end+1;
+	/* extract status (0 = ok, 6 = webcam not online) */
+	status = yid->rxqueue[pos++];
+
+	if (status == 0) {
+		pos += 2;	/* skip next 2 bytes */
+		server = y_memdup(yid->rxqueue + pos, 16);
+		pos += 16;
+	} else if (status == 6) {
+		YAHOO_CALLBACK(ext_yahoo_webcam_closed)
+			(yd->client_id, yid->wcm->user, 4);
+	}
+
+	/* skip rest of the data */
+
+	yid->rxlen -= len;
 	DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
-	if (yid->rxlen>0) {
-		unsigned char *tmp = y_memdup(yid->rxqueue + end + 1, yid->rxlen);
+	if (yid->rxlen > 0) {
+		unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen);
 		FREE(yid->rxqueue);
 		yid->rxqueue = tmp;
-		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
+		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
+				yid->rxqueue));
 	} else {
 		DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
@@ -3199,88 +2835,36 @@
 	}
 
-
-	return yab;
-}
-
-static char * yahoo_getwebcam_master(struct yahoo_input_data *yid)
-{
-	unsigned int pos=0;
-	unsigned int len=0;
-	unsigned int status=0;
-	char *server=NULL;
+	return server;
+}
+
+static int yahoo_get_webcam_data(struct yahoo_input_data *yid)
+{
+	unsigned char reason = 0;
+	unsigned int pos = 0;
+	unsigned int begin = 0;
+	unsigned int end = 0;
+	unsigned int closed = 0;
+	unsigned char header_len = 0;
+	char *who;
+	int connect = 0;
 	struct yahoo_data *yd = yid->yd;
 
-	if(!yid || !yd)
-		return NULL;
+	if (!yd)
+		return -1;
+
+	if (!yid->wcm || !yid->wcd || !yid->rxlen)
+		return -1;
 
 	DEBUG_MSG(("rxlen is %d", yid->rxlen));
 
-	len = yid->rxqueue[pos++];
-	if (yid->rxlen < len)
-		return NULL;
-
-	/* extract status (0 = ok, 6 = webcam not online) */
-	status = yid->rxqueue[pos++];
-
-	if (status == 0)
-	{
-		pos += 2; /* skip next 2 bytes */
-		server =  y_memdup(yid->rxqueue+pos, 16);
-		pos += 16;
-	}
-	else if (status == 6)
-	{
-		YAHOO_CALLBACK(ext_yahoo_webcam_closed)
-			(yd->client_id, yid->wcm->user, 4);
-	}
-
-	/* skip rest of the data */
-
-	yid->rxlen -= len;
-	DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
-	if (yid->rxlen>0) {
-		unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen);
-		FREE(yid->rxqueue);
-		yid->rxqueue = tmp;
-		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
-	} else {
-		DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
-		FREE(yid->rxqueue);
-	}
-
-	return server;
-}
-
-static int yahoo_get_webcam_data(struct yahoo_input_data *yid)
-{
-	unsigned char reason=0;
-	unsigned int pos=0;
-	unsigned int begin=0;
-	unsigned int end=0;
-	unsigned int closed=0;
-	unsigned char header_len=0;
-	char *who;
-	int connect=0;
-	struct yahoo_data *yd = yid->yd;
-
-	if(!yd)
-		return -1;
-
-	if(!yid->wcm || !yid->wcd || !yid->rxlen)
-		return -1;
-
-	DEBUG_MSG(("rxlen is %d", yid->rxlen));
-
 	/* if we are not reading part of image then read header */
-	if (!yid->wcd->to_read)
-	{
-		header_len=yid->rxqueue[pos++];
-		yid->wcd->packet_type=0;
+	if (!yid->wcd->to_read) {
+		header_len = yid->rxqueue[pos++];
+		yid->wcd->packet_type = 0;
 
 		if (yid->rxlen < header_len)
 			return 0;
 
-		if (header_len >= 8)
-		{
+		if (header_len >= 8) {
 			reason = yid->rxqueue[pos++];
 			/* next 2 bytes should always be 05 00 */
@@ -3290,6 +2874,5 @@
 			yid->wcd->to_read = yid->wcd->data_size;
 		}
-		if (header_len >= 13)
-		{
+		if (header_len >= 13) {
 			yid->wcd->packet_type = yid->rxqueue[pos++];
 			yid->wcd->timestamp = yahoo_get32(yid->rxqueue + pos);
@@ -3303,5 +2886,6 @@
 	begin = pos;
 	pos += yid->wcd->to_read;
-	if (pos > yid->rxlen) pos = yid->rxlen;
+	if (pos > yid->rxlen)
+		pos = yid->rxlen;
 
 	/* if it is not an image then make sure we have the whole packet */
@@ -3316,81 +2900,82 @@
 
 	DEBUG_MSG(("packet type %.2X, data length %d", yid->wcd->packet_type,
-		yid->wcd->data_size));
+			yid->wcd->data_size));
 
 	/* find out what kind of packet we got */
-	switch (yid->wcd->packet_type)
-	{
-		case 0x00:
-			/* user requests to view webcam (uploading) */
-			if (yid->wcd->data_size &&
-				yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) {
-				end = begin;
-				while (end <= yid->rxlen &&
-					yid->rxqueue[end++] != 13);
-				if (end > begin)
-				{
-					who = y_memdup(yid->rxqueue + begin, end - begin);
-					who[end - begin - 1] = 0;
-					YAHOO_CALLBACK(ext_yahoo_webcam_viewer)(yd->client_id, who + 2, 2);
-					FREE(who);
-				}
-			}
-
-			if (yid->wcm->direction == YAHOO_WEBCAM_DOWNLOAD) {
-				/* timestamp/status field */
-				/* 0 = declined viewing permission */
-				/* 1 = accepted viewing permission */
-				if (yid->wcd->timestamp == 0) {
-					YAHOO_CALLBACK(ext_yahoo_webcam_closed)(yd->client_id, yid->wcm->user, 3);
-				}
-			}
-			break;
-		case 0x01: /* status packets?? */
-			/* timestamp contains status info */
-			/* 00 00 00 01 = we have data?? */
-			break;
-		case 0x02: /* image data */
-			YAHOO_CALLBACK(ext_yahoo_got_webcam_image)(yd->client_id, 
-					yid->wcm->user, yid->rxqueue + begin,
-					yid->wcd->data_size, pos - begin,
-					yid->wcd->timestamp);
-			break;
-		case 0x05: /* response packets when uploading */
-			if (!yid->wcd->data_size) {
-				YAHOO_CALLBACK(ext_yahoo_webcam_data_request)(yd->client_id, yid->wcd->timestamp);
-			}
-			break;
-		case 0x07: /* connection is closing */
-			switch(reason)
-			{
-				case 0x01: /* user closed connection */
-					closed = 1;
-					break;
-				case 0x0F: /* user cancelled permission */
-					closed = 2;
-					break;
-			}
-			YAHOO_CALLBACK(ext_yahoo_webcam_closed)(yd->client_id, yid->wcm->user, closed);
-			break;
-		case 0x0C: /* user connected */
-		case 0x0D: /* user disconnected */
-			if (yid->wcd->data_size) {
-				who = y_memdup(yid->rxqueue + begin, pos - begin + 1);
-				who[pos - begin] = 0;
-				if (yid->wcd->packet_type == 0x0C)
-					connect=1;
-				else
-					connect=0;
-				YAHOO_CALLBACK(ext_yahoo_webcam_viewer)(yd->client_id, who, connect);
+	switch (yid->wcd->packet_type) {
+	case 0x00:
+		/* user requests to view webcam (uploading) */
+		if (yid->wcd->data_size &&
+			yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) {
+			end = begin;
+			while (end <= yid->rxlen && yid->rxqueue[end++] != 13) ;
+			if (end > begin) {
+				who = y_memdup(yid->rxqueue + begin,
+					end - begin);
+				who[end - begin - 1] = 0;
+				YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->
+					client_id, who + 2, 2);
 				FREE(who);
 			}
-			break;
-		case 0x13: /* user data */
-			/* i=user_ip (ip of the user we are viewing) */
-			/* j=user_ext_ip (external ip of the user we */
- 			/*                are viewing) */
-			break;
-		case 0x17: /* ?? */
-			break;
+		}
+
+		if (yid->wcm->direction == YAHOO_WEBCAM_DOWNLOAD) {
+			/* timestamp/status field */
+			/* 0 = declined viewing permission */
+			/* 1 = accepted viewing permission */
+			if (yid->wcd->timestamp == 0) {
+				YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->
+					client_id, yid->wcm->user, 3);
+			}
+		}
+		break;
+	case 0x01:		/* status packets?? */
+		/* timestamp contains status info */
+		/* 00 00 00 01 = we have data?? */
+		break;
+	case 0x02:		/* image data */
+		YAHOO_CALLBACK(ext_yahoo_got_webcam_image) (yd->client_id,
+			yid->wcm->user, yid->rxqueue + begin,
+			yid->wcd->data_size, pos - begin, yid->wcd->timestamp);
+		break;
+	case 0x05:		/* response packets when uploading */
+		if (!yid->wcd->data_size) {
+			YAHOO_CALLBACK(ext_yahoo_webcam_data_request) (yd->
+				client_id, yid->wcd->timestamp);
+		}
+		break;
+	case 0x07:		/* connection is closing */
+		switch (reason) {
+		case 0x01:	/* user closed connection */
+			closed = 1;
+			break;
+		case 0x0F:	/* user cancelled permission */
+			closed = 2;
+			break;
+		}
+		YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id,
+			yid->wcm->user, closed);
+		break;
+	case 0x0C:		/* user connected */
+	case 0x0D:		/* user disconnected */
+		if (yid->wcd->data_size) {
+			who = y_memdup(yid->rxqueue + begin, pos - begin + 1);
+			who[pos - begin] = 0;
+			if (yid->wcd->packet_type == 0x0C)
+				connect = 1;
+			else
+				connect = 0;
+			YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->client_id,
+				who, connect);
+			FREE(who);
+		}
+		break;
+	case 0x13:		/* user data */
+		/* i=user_ip (ip of the user we are viewing) */
+		/* j=user_ext_ip (external ip of the user we */
+		/*                are viewing) */
+		break;
+	case 0x17:		/* ?? */
+		break;
 	}
 	yid->wcd->to_read -= pos - begin;
@@ -3398,9 +2983,10 @@
 	yid->rxlen -= pos;
 	DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
-	if (yid->rxlen>0) {
+	if (yid->rxlen > 0) {
 		unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen);
 		FREE(yid->rxqueue);
 		yid->rxqueue = tmp;
-		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
+		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
+				yid->rxqueue));
 	} else {
 		DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
@@ -3415,5 +3001,5 @@
 }
 
-int yahoo_write_ready(int id, int fd, void *data)
+int yahoo_write_ready(int id, void *fd, void *data)
 {
 	struct yahoo_input_data *yid = data;
@@ -3421,30 +3007,32 @@
 	struct data_queue *tx;
 
-	LOG(("write callback: id=%d fd=%d data=%p", id, fd, data));
-	if(!yid || !yid->txqueues || !find_conn_by_id(id))
+	LOG(("write callback: id=%d fd=%p data=%p", id, fd, data));
+	if (!yid || !yid->txqueues)
 		return -2;
-	
+
 	tx = yid->txqueues->data;
 	LOG(("writing %d bytes", tx->len));
 	len = yahoo_send_data(fd, tx->queue, MIN(1024, tx->len));
 
-	if(len == -1 && errno == EAGAIN)
+	if (len == -1 && errno == EAGAIN)
 		return 1;
 
-	if(len <= 0) {
+	if (len <= 0) {
 		int e = errno;
 		DEBUG_MSG(("len == %d (<= 0)", len));
-		while(yid->txqueues) {
-			YList *l=yid->txqueues;
+		while (yid->txqueues) {
+			YList *l = yid->txqueues;
 			tx = l->data;
 			free(tx->queue);
 			free(tx);
-			yid->txqueues = y_list_remove_link(yid->txqueues, yid->txqueues);
+			yid->txqueues =
+				y_list_remove_link(yid->txqueues,
+				yid->txqueues);
 			y_list_free_1(l);
 		}
-		LOG(("yahoo_write_ready(%d, %d) len < 0", id, fd));
-		YAHOO_CALLBACK(ext_yahoo_remove_handler)(id, yid->write_tag);
+		LOG(("yahoo_write_ready(%d, %p) len < 0", id, fd));
+		YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, yid->write_tag);
 		yid->write_tag = 0;
-		errno=e;
+		errno = e;
 		return 0;
 	}
@@ -3452,21 +3040,23 @@
 
 	tx->len -= len;
-	if(tx->len > 0) {
+	if (tx->len > 0) {
 		unsigned char *tmp = y_memdup(tx->queue + len, tx->len);
 		FREE(tx->queue);
 		tx->queue = tmp;
 	} else {
-		YList *l=yid->txqueues;
+		YList *l = yid->txqueues;
 		free(tx->queue);
 		free(tx);
-		yid->txqueues = y_list_remove_link(yid->txqueues, yid->txqueues);
+		yid->txqueues =
+			y_list_remove_link(yid->txqueues, yid->txqueues);
 		y_list_free_1(l);
 		/*
-		if(!yid->txqueues) 
-			LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd));
-		*/
-		if(!yid->txqueues) {
-			LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd));
-			YAHOO_CALLBACK(ext_yahoo_remove_handler)(id, yid->write_tag);
+		   if(!yid->txqueues) 
+		   LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd));
+		 */
+		if (!yid->txqueues) {
+			LOG(("yahoo_write_ready(%d, %p) !txqueues", id, fd));
+			YAHOO_CALLBACK(ext_yahoo_remove_handler) (id,
+				yid->write_tag);
 			yid->write_tag = 0;
 		}
@@ -3476,5 +3066,6 @@
 }
 
-static void yahoo_process_pager_connection(struct yahoo_input_data *yid, int over)
+static void yahoo_process_pager_connection(struct yahoo_input_data *yid,
+	int over)
 {
 	struct yahoo_packet *pkt;
@@ -3482,9 +3073,9 @@
 	int id = yd->client_id;
 
-	if(over)
-		return;
-
-	while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER) 
-			&& (pkt = yahoo_getdata(yid)) != NULL) {
+	if (over)
+		return;
+
+	while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER)
+		&& (pkt = yahoo_getdata(yid)) != NULL) {
 
 		yahoo_packet_process(yid, pkt);
@@ -3494,15 +3085,13 @@
 }
 
-static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over)
-{
-}
-
-static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid, int over)
-{
-	if(over)
-		return;
-
-	if (strstr((char*)yid->rxqueue+(yid->rxlen-20), "</content>")) {
-		YAHOO_CALLBACK(ext_yahoo_chat_cat_xml)(yid->yd->client_id, (char*)yid->rxqueue);
+static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid,
+	int over)
+{
+	if (over)
+		return;
+
+	if (strstr((char *)yid->rxqueue + (yid->rxlen - 20), "</content>")) {
+		YAHOO_CALLBACK(ext_yahoo_chat_cat_xml) (yid->yd->client_id,
+			(char *)yid->rxqueue);
 	}
 }
@@ -3513,109 +3102,141 @@
 	struct yab *yab;
 	YList *buds;
-	int changed=0;
+	int changed = 0;
 	int id = yd->client_id;
-
-	if(over)
-		return;
-
-	while(find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB) 
-			&& (yab = yahoo_getyab(yid)) != NULL) {
-		if(!yab->id)
+	int yab_used = 0;
+
+	LOG(("Got data for YAB"));
+
+	if (over)
+		return;
+
+	while (find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB)
+		&& (yab = yahoo_getyab(yid)) != NULL) {
+		if (!yab->id)
 			continue;
-		changed=1;
-		for(buds = yd->buddies; buds; buds=buds->next) {
-			struct yahoo_buddy * bud = buds->data;
-			if(!strcmp(bud->id, yab->id)) {
+
+		changed = 1;
+		yab_used = 0;
+		for (buds = yd->buddies; buds; buds = buds->next) {
+			struct yahoo_buddy *bud = buds->data;
+			if (!strcmp(bud->id, yab->id)) {
+				yab_used = 1;
 				bud->yab_entry = yab;
-				if(yab->nname) {
+				if (yab->nname) {
 					bud->real_name = strdup(yab->nname);
-				} else if(yab->fname && yab->lname) {
-					bud->real_name = y_new0(char, 
-							strlen(yab->fname)+
-							strlen(yab->lname)+2
-							);
+				} else if (yab->fname && yab->lname) {
+					bud->real_name = y_new0(char,
+						strlen(yab->fname) +
+						strlen(yab->lname) + 2);
 					sprintf(bud->real_name, "%s %s",
-							yab->fname, yab->lname);
-				} else if(yab->fname) {
+						yab->fname, yab->lname);
+				} else if (yab->fname) {
 					bud->real_name = strdup(yab->fname);
 				}
-				break; /* for */
+				break;	/* for */
 			}
 		}
-	}
-
-	if(changed)
-		YAHOO_CALLBACK(ext_yahoo_got_buddies)(yd->client_id, yd->buddies);
-}
-
-static void yahoo_process_search_connection(struct yahoo_input_data *yid, int over)
-{
-	struct yahoo_found_contact *yct=NULL;
+
+		if (!yab_used) {
+			FREE(yab->fname);
+			FREE(yab->lname);
+			FREE(yab->nname);
+			FREE(yab->id);
+			FREE(yab->email);
+			FREE(yab->hphone);
+			FREE(yab->wphone);
+			FREE(yab->mphone);
+			FREE(yab);
+		}
+
+	}
+
+	if (changed)
+		YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id,
+			yd->buddies);
+}
+
+static void yahoo_process_search_connection(struct yahoo_input_data *yid,
+	int over)
+{
+	struct yahoo_found_contact *yct = NULL;
 	char *p = (char *)yid->rxqueue, *np, *cp;
 	int k, n;
-	int start=0, found=0, total=0;
-	YList *contacts=NULL;
-	struct yahoo_input_data *pyid = find_input_by_id_and_type(yid->yd->client_id, YAHOO_CONNECTION_PAGER);
-
-	if(!over || !pyid)
-		return;
-
-	if(p && (p=strstr(p, "\r\n\r\n"))) {
+	int start = 0, found = 0, total = 0;
+	YList *contacts = NULL;
+	struct yahoo_input_data *pyid =
+		find_input_by_id_and_type(yid->yd->client_id,
+		YAHOO_CONNECTION_PAGER);
+
+	if (!over || !pyid)
+		return;
+
+	if (p && (p = strstr(p, "\r\n\r\n"))) {
 		p += 4;
 
-		for(k = 0; (p = strchr(p, 4)) && (k < 4); k++) {
+		for (k = 0; (p = strchr(p, 4)) && (k < 4); k++) {
 			p++;
 			n = atoi(p);
-			switch(k) {
-				case 0: found = pyid->ys->lsearch_nfound = n; break;
-				case 2: start = pyid->ys->lsearch_nstart = n; break;
-				case 3: total = pyid->ys->lsearch_ntotal = n; break;
+			switch (k) {
+			case 0:
+				found = pyid->ys->lsearch_nfound = n;
+				break;
+			case 2:
+				start = pyid->ys->lsearch_nstart = n;
+				break;
+			case 3:
+				total = pyid->ys->lsearch_ntotal = n;
+				break;
 			}
 		}
 
-		if(p)
+		if (p)
 			p++;
 
-		k=0;
-		while(p && *p) {
+		k = 0;
+		while (p && *p) {
 			cp = p;
 			np = strchr(p, 4);
 
-			if(!np)
+			if (!np)
 				break;
 			*np = 0;
-			p = np+1;
-
-			switch(k++) {
-				case 1:
-					if(strlen(cp) > 2 && y_list_length(contacts) < total) {
-						yct = y_new0(struct yahoo_found_contact, 1);
-						contacts = y_list_append(contacts, yct);
-						yct->id = cp+2;
-					} else {
-						*p = 0;
-					}
-					break;
-				case 2: 
-					yct->online = !strcmp(cp, "2") ? 1 : 0;
-					break;
-				case 3: 
-					yct->gender = cp;
-					break;
-				case 4: 
-					yct->age = atoi(cp);
-					break;
-				case 5: 
-					if(strcmp(cp, "5") != 0)
-						yct->location = cp;
-					k = 0;
-					break;
+			p = np + 1;
+
+			switch (k++) {
+			case 1:
+				if (strlen(cp) > 2
+					&& y_list_length(contacts) < total) {
+					yct = y_new0(struct yahoo_found_contact,
+						1);
+					contacts = y_list_append(contacts, yct);
+					yct->id = cp + 2;
+				} else {
+					*p = 0;
+				}
+				break;
+			case 2:
+				yct->online = !strcmp(cp, "2") ? 1 : 0;
+				break;
+			case 3:
+				yct->gender = cp;
+				break;
+			case 4:
+				yct->age = atoi(cp);
+				break;
+			case 5:
+				/* not worth the context switch for strcmp */
+				if (cp[0] != '\005' || cp[1] != '\000')
+					yct->location = cp;
+				k = 0;
+				break;
 			}
 		}
 	}
 
-	YAHOO_CALLBACK(ext_yahoo_got_search_result)(yid->yd->client_id, found, start, total, contacts);
-
-	while(contacts) {
+	YAHOO_CALLBACK(ext_yahoo_got_search_result) (yid->yd->client_id, found,
+		start, total, contacts);
+
+	while (contacts) {
 		YList *node = contacts;
 		contacts = y_list_remove_link(contacts, node);
@@ -3625,5 +3246,5 @@
 }
 
-static void _yahoo_webcam_connected(int fd, int error, void *d)
+static void _yahoo_webcam_connected(void *fd, int error, void *d)
 {
 	struct yahoo_input_data *yid = d;
@@ -3631,12 +3252,12 @@
 	struct yahoo_data *yd = yid->yd;
 	char conn_type[100];
-	char *data=NULL;
-	char *packet=NULL;
-	unsigned char magic_nr[] = {1, 0, 0, 0, 1};
-	unsigned header_len=0;
-	unsigned int len=0;
-	unsigned int pos=0;
-
-	if(error || fd <= 0) {
+	char *data = NULL;
+	char *packet = NULL;
+	unsigned char magic_nr[] = { 1, 0, 0, 0, 1 };
+	unsigned header_len = 0;
+	unsigned int len = 0;
+	unsigned int pos = 0;
+
+	if (error || !fd) {
 		FREE(yid);
 		return;
@@ -3648,14 +3269,13 @@
 	LOG(("Connected"));
 	/* send initial packet */
-	switch (wcm->direction)
-	{
-		case YAHOO_WEBCAM_DOWNLOAD:
-			data = strdup("<REQIMG>");
-			break;
-		case YAHOO_WEBCAM_UPLOAD:	
-			data = strdup("<SNDIMG>");
-			break;
-		default:
-			return;
+	switch (wcm->direction) {
+	case YAHOO_WEBCAM_DOWNLOAD:
+		data = strdup("<REQIMG>");
+		break;
+	case YAHOO_WEBCAM_UPLOAD:
+		data = strdup("<SNDIMG>");
+		break;
+	default:
+		return;
 	}
 	yahoo_add_to_send_queue(yid, data, strlen(data));
@@ -3663,36 +3283,35 @@
 
 	/* send data */
-	switch (wcm->direction)
-	{
-		case YAHOO_WEBCAM_DOWNLOAD:
-			header_len = 8;
-			data = strdup("a=2\r\nc=us\r\ne=21\r\nu=");
-			data = y_string_append(data, yd->user);
-			data = y_string_append(data, "\r\nt=");
-			data = y_string_append(data, wcm->key);
-			data = y_string_append(data, "\r\ni=");
-			data = y_string_append(data, wcm->my_ip);
-			data = y_string_append(data, "\r\ng=");
-			data = y_string_append(data, wcm->user);
-			data = y_string_append(data, "\r\no=w-2-5-1\r\np=");
-			snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type);
-			data = y_string_append(data, conn_type);
-			data = y_string_append(data, "\r\n");
-			break;
-		case YAHOO_WEBCAM_UPLOAD:
-			header_len = 13;
-			data = strdup("a=2\r\nc=us\r\nu=");
-			data = y_string_append(data, yd->user);
-			data = y_string_append(data, "\r\nt=");
-			data = y_string_append(data, wcm->key);
-			data = y_string_append(data, "\r\ni=");
-			data = y_string_append(data, wcm->my_ip);
-			data = y_string_append(data, "\r\no=w-2-5-1\r\np=");
-			snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type);
-			data = y_string_append(data, conn_type);
-			data = y_string_append(data, "\r\nb=");
-			data = y_string_append(data, wcm->description);
-			data = y_string_append(data, "\r\n");
-			break;
+	switch (wcm->direction) {
+	case YAHOO_WEBCAM_DOWNLOAD:
+		header_len = 8;
+		data = strdup("a=2\r\nc=us\r\ne=21\r\nu=");
+		data = y_string_append(data, yd->user);
+		data = y_string_append(data, "\r\nt=");
+		data = y_string_append(data, wcm->key);
+		data = y_string_append(data, "\r\ni=");
+		data = y_string_append(data, wcm->my_ip);
+		data = y_string_append(data, "\r\ng=");
+		data = y_string_append(data, wcm->user);
+		data = y_string_append(data, "\r\no=w-2-5-1\r\np=");
+		snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type);
+		data = y_string_append(data, conn_type);
+		data = y_string_append(data, "\r\n");
+		break;
+	case YAHOO_WEBCAM_UPLOAD:
+		header_len = 13;
+		data = strdup("a=2\r\nc=us\r\nu=");
+		data = y_string_append(data, yd->user);
+		data = y_string_append(data, "\r\nt=");
+		data = y_string_append(data, wcm->key);
+		data = y_string_append(data, "\r\ni=");
+		data = y_string_append(data, wcm->my_ip);
+		data = y_string_append(data, "\r\no=w-2-5-1\r\np=");
+		snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type);
+		data = y_string_append(data, conn_type);
+		data = y_string_append(data, "\r\nb=");
+		data = y_string_append(data, wcm->description);
+		data = y_string_append(data, "\r\n");
+		break;
 	}
 
@@ -3701,19 +3320,17 @@
 	packet[pos++] = header_len;
 	packet[pos++] = 0;
-	switch (wcm->direction)
-	{
-		case YAHOO_WEBCAM_DOWNLOAD:
-			packet[pos++] = 1;
-			packet[pos++] = 0;
-			break;
-		case YAHOO_WEBCAM_UPLOAD:
-			packet[pos++] = 5;
-			packet[pos++] = 0;
-			break;
+	switch (wcm->direction) {
+	case YAHOO_WEBCAM_DOWNLOAD:
+		packet[pos++] = 1;
+		packet[pos++] = 0;
+		break;
+	case YAHOO_WEBCAM_UPLOAD:
+		packet[pos++] = 5;
+		packet[pos++] = 0;
+		break;
 	}
 
 	pos += yahoo_put32(packet + pos, len);
-	if (wcm->direction == YAHOO_WEBCAM_UPLOAD)
-	{
+	if (wcm->direction == YAHOO_WEBCAM_UPLOAD) {
 		memcpy(packet + pos, magic_nr, sizeof(magic_nr));
 		pos += sizeof(magic_nr);
@@ -3724,5 +3341,7 @@
 	FREE(data);
 
-	yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, yid->fd, YAHOO_INPUT_READ, yid);
+	yid->read_tag =
+		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id,
+		yid->fd, YAHOO_INPUT_READ, yid);
 }
 
@@ -3749,21 +3368,21 @@
 
 	LOG(("Connecting to: %s:%d", wcm->server, wcm->port));
-	YAHOO_CALLBACK(ext_yahoo_connect_async)(y->yd->client_id, wcm->server, wcm->port,
-			_yahoo_webcam_connected, yid);
-
-}
-
-static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid, int over)
-{
-	char* server;
+	YAHOO_CALLBACK(ext_yahoo_connect_async) (y->yd->client_id, wcm->server,
+		wcm->port, _yahoo_webcam_connected, yid, 0);
+
+}
+
+static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid,
+	int over)
+{
+	char *server;
 	struct yahoo_server_settings *yss;
 
-	if(over)
+	if (over)
 		return;
 
 	server = yahoo_getwebcam_master(yid);
 
-	if (server)
-	{
+	if (server) {
 		yss = yid->yd->server_settings;
 		yid->wcm->server = strdup(server);
@@ -3778,28 +3397,28 @@
 }
 
-static void yahoo_process_webcam_connection(struct yahoo_input_data *yid, int over)
+static void yahoo_process_webcam_connection(struct yahoo_input_data *yid,
+	int over)
 {
 	int id = yid->yd->client_id;
-	int fd = yid->fd;
-
-	if(over)
+	void *fd = yid->fd;
+
+	if (over)
 		return;
 
 	/* as long as we still have packets available keep processing them */
-	while (find_input_by_id_and_fd(id, fd) 
-			&& yahoo_get_webcam_data(yid) == 1);
-}
-
-static void (*yahoo_process_connection[])(struct yahoo_input_data *, int over) = {
-	yahoo_process_pager_connection,
-	yahoo_process_ft_connection,
-	yahoo_process_yab_connection,
-	yahoo_process_webcam_master_connection,
-	yahoo_process_webcam_connection,
-	yahoo_process_chatcat_connection,
-	yahoo_process_search_connection,
-};
-
-int yahoo_read_ready(int id, int fd, void *data)
+	while (find_input_by_id_and_fd(id, fd)
+		&& yahoo_get_webcam_data(yid) == 1) ;
+}
+
+static void (*yahoo_process_connection[]) (struct yahoo_input_data *,
+	int over) = {
+yahoo_process_pager_connection, yahoo_process_ft_connection,
+		yahoo_process_yab_connection,
+		yahoo_process_webcam_master_connection,
+		yahoo_process_webcam_connection,
+		yahoo_process_chatcat_connection,
+		yahoo_process_search_connection};
+
+int yahoo_read_ready(int id, void *fd, void *data)
 {
 	struct yahoo_input_data *yid = data;
@@ -3807,14 +3426,13 @@
 	int len;
 
-	LOG(("read callback: id=%d fd=%d data=%p", id, fd, data));
-	if(!yid)
+	LOG(("read callback: id=%d fd=%p data=%p", id, fd, data));
+	if (!yid)
 		return -2;
 
-	
 	do {
-		len = read(fd, buf, sizeof(buf));
-	} while(len == -1 && errno == EINTR);
-
-	if(len == -1 && (errno == EAGAIN||errno == EINTR))	/* we'll try again later */
+		len = YAHOO_CALLBACK(ext_yahoo_read) (fd, buf, sizeof(buf));
+	} while (len == -1 && errno == EINTR);
+
+	if (len == -1 && (errno == EAGAIN || errno == EINTR))	/* we'll try again later */
 		return 1;
 
@@ -3823,25 +3441,28 @@
 		DEBUG_MSG(("len == %d (<= 0)", len));
 
-		if(yid->type == YAHOO_CONNECTION_PAGER) {
-			YAHOO_CALLBACK(ext_yahoo_error)(yid->yd->client_id, "Connection closed by server", 1, E_CONNECTION);
-		}
-
-		yahoo_process_connection[yid->type](yid, 1);
+		if (yid->type == YAHOO_CONNECTION_PAGER) {
+			YAHOO_CALLBACK(ext_yahoo_login_response) (yid->yd->
+				client_id, YAHOO_LOGIN_SOCK, NULL);
+		}
+
+		yahoo_process_connection[yid->type] (yid, 1);
 		yahoo_input_close(yid);
 
 		/* no need to return an error, because we've already fixed it */
-		if(len == 0)
+		if (len == 0)
 			return 1;
 
-		errno=e;
+		errno = e;
 		LOG(("read error: %s", strerror(errno)));
 		return -1;
 	}
 
-	yid->rxqueue = y_renew(unsigned char, yid->rxqueue, len + yid->rxlen);
+	yid->rxqueue =
+		y_renew(unsigned char, yid->rxqueue, len + yid->rxlen + 1);
 	memcpy(yid->rxqueue + yid->rxlen, buf, len);
 	yid->rxlen += len;
-
-	yahoo_process_connection[yid->type](yid, 0);
+	yid->rxqueue[yid->rxlen] = 0;
+
+	yahoo_process_connection[yid->type] (yid, 0);
 
 	return len;
@@ -3855,5 +3476,5 @@
 	yd = y_new0(struct yahoo_data, 1);
 
-	if(!yd)
+	if (!yd)
 		return 0;
 
@@ -3880,11 +3501,5 @@
 }
 
-struct connect_callback_data {
-	struct yahoo_data *yd;
-	int tag;
-	int i;
-};
-
-static void yahoo_connected(int fd, int error, void *data)
+static void yahoo_connected(void *fd, int error, void *data)
 {
 	struct connect_callback_data *ccd = data;
@@ -3894,16 +3509,35 @@
 	struct yahoo_server_settings *yss = yd->server_settings;
 
-	if(error) {
-		if(fallback_ports[ccd->i]) {
-			int tag;
+	if (error) {
+		int tag;
+		if (fallback_ports[ccd->i]) {
+			char *host = yss->pager_host;
+
+			if (!host)
+				host = yss->pager_host_list[ccd->server_i];
+
 			yss->pager_port = fallback_ports[ccd->i++];
-			tag = YAHOO_CALLBACK(ext_yahoo_connect_async)(yd->client_id, yss->pager_host,
-					yss->pager_port, yahoo_connected, ccd);
-
-			if(tag > 0)
-				ccd->tag=tag;
+			tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->
+				client_id, host, yss->pager_port,
+				yahoo_connected, ccd, 0);
+
+			if (tag > 0)
+				ccd->tag = tag;
+		} else if (yss->pager_host_list
+				&& yss->pager_host_list[ccd->server_i]) {
+
+			/* Get back to the default port */
+			yss->pager_port = pager_port;
+			ccd->server_i++;
+			LOG(("Fallback: Connecting to %s:%d", yss->pager_host_list[ccd->server_i], yss->pager_port));
+
+			ccd->i = 0;
+			tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id,
+				yss->pager_host_list[ccd->server_i], yss->pager_port,
+				yahoo_connected, ccd, 0);
 		} else {
 			FREE(ccd);
-			YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, YAHOO_LOGIN_SOCK, NULL);
+			YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
+				YAHOO_LOGIN_SOCK, NULL);
 		}
 		return;
@@ -3912,17 +3546,16 @@
 	FREE(ccd);
 
-	/* fd < 0 && error == 0 means connect was cancelled */
-	if(fd < 0)
-		return;
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	/* fd == NULL && error == 0 means connect was cancelled */
+	if (!fd)
+		return;
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
 	NOTICE(("Sending initial packet"));
 
 	yahoo_packet_hash(pkt, 1, yd->user);
 
-	yid = y_new0(struct yahoo_input_data, 1);
-	yid->yd = yd;
+	yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER);
 	yid->fd = fd;
-	inputs = y_list_prepend(inputs, yid);
 
 	yahoo_send_packet(yid, pkt, 0);
@@ -3930,41 +3563,14 @@
 	yahoo_packet_free(pkt);
 
-	yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, yid->fd, YAHOO_INPUT_READ, yid);
-}
-
-void yahoo_login(int id, int initial)
-{
-	struct yahoo_data *yd = find_conn_by_id(id);
-	struct connect_callback_data *ccd;
-	struct yahoo_server_settings *yss;
-	int tag;
-
-	if(!yd)
-		return;
-
-	yss = yd->server_settings;
-
-	yd->initial_status = initial;
-
-	ccd = y_new0(struct connect_callback_data, 1);
-	ccd->yd = yd;
-	tag = YAHOO_CALLBACK(ext_yahoo_connect_async)(yd->client_id, yss->pager_host, yss->pager_port, 
-			yahoo_connected, ccd);
-
-	/*
-	 * if tag <= 0, then callback has already been called
-	 * so ccd will have been freed
-	 */
-	if(tag > 0)
-		ccd->tag = tag;
-	else if(tag < 0)
-		YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, YAHOO_LOGIN_SOCK, NULL);
-}
-
-
-int yahoo_get_fd(int id)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
-	if(!yid)
+	yid->read_tag =
+		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id,
+		yid->fd, YAHOO_INPUT_READ, yid);
+}
+
+void *yahoo_get_fd(int id)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	if (!yid)
 		return 0;
 	else
@@ -3972,27 +3578,35 @@
 }
 
-void yahoo_send_im(int id, const char *from, const char *who, const char *what, int utf8, int picture)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_send_buzz(int id, const char *from, const char *who)
+{
+	yahoo_send_im(id, from, who, "<ding>", 1, 0);
+}
+
+void yahoo_send_im(int id, const char *from, const char *who, const char *what,
+	int utf8, int picture)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_packet *pkt = NULL;
 	struct yahoo_data *yd;
 	char pic_str[10];
 
-	if(!yid)
+	if (!yid)
 		return;
 
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE,
+		yd->session_id);
 
 	snprintf(pic_str, sizeof(pic_str), "%d", picture);
-	
-	if(from && strcmp(from, yd->user))
+
+	if (from && strcmp(from, yd->user))
 		yahoo_packet_hash(pkt, 0, yd->user);
-	yahoo_packet_hash(pkt, 1, from?from:yd->user);
+	yahoo_packet_hash(pkt, 1, from ? from : yd->user);
 	yahoo_packet_hash(pkt, 5, who);
 	yahoo_packet_hash(pkt, 14, what);
 
-	if(utf8)
+	if (utf8)
 		yahoo_packet_hash(pkt, 97, "1");
 
@@ -4001,5 +3615,4 @@
 	yahoo_packet_hash(pkt, 206, pic_str);
 
-
 	yahoo_send_packet(yid, pkt, 0);
 
@@ -4009,15 +3622,17 @@
 void yahoo_send_typing(int id, const char *from, const char *who, int typ)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
-	if(!yid)
+	if (!yid)
 		return;
 
 	yd = yid->yd;
-	pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_NOTIFY, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY,
+		yd->session_id);
 
 	yahoo_packet_hash(pkt, 5, who);
-	yahoo_packet_hash(pkt, 1, from?from:yd->user);
+	yahoo_packet_hash(pkt, 1, from ? from : yd->user);
 	yahoo_packet_hash(pkt, 14, " ");
 	yahoo_packet_hash(pkt, 13, typ ? "1" : "0");
@@ -4031,5 +3646,6 @@
 void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
@@ -4037,8 +3653,9 @@
 	char s[4];
 
-	if(!yid)
+	if (!yid)
 		return;
 
 	yd = yid->yd;
+
 	old_status = yd->current_status;
 	yd->current_status = state;
@@ -4046,5 +3663,6 @@
 	/* Thank you libpurple :) */
 	if (yd->current_status == YAHOO_STATUS_INVISIBLE) {
-		pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBILITY, YAHOO_STATUS_AVAILABLE, 0);
+		pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE,
+			YAHOO_STATUS_AVAILABLE, 0);
 		yahoo_packet_hash(pkt, 13, "2");
 		yahoo_send_packet(yid, pkt, 0);
@@ -4054,5 +3672,6 @@
 	}
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, yd->current_status, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE,
+		yd->current_status, yd->session_id);
 	snprintf(s, sizeof(s), "%d", yd->current_status);
 	yahoo_packet_hash(pkt, 10, s);
@@ -4062,6 +3681,7 @@
 	yahoo_packet_free(pkt);
 
-	if(old_status == YAHOO_STATUS_INVISIBLE) {
-		pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBILITY, YAHOO_STATUS_AVAILABLE, 0);
+	if (old_status == YAHOO_STATUS_INVISIBLE) {
+		pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE,
+			YAHOO_STATUS_AVAILABLE, 0);
 		yahoo_packet_hash(pkt, 13, "1");
 		yahoo_send_packet(yid, pkt, 0);
@@ -4072,9 +3692,10 @@
 void yahoo_logoff(int id)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
@@ -4082,9 +3703,10 @@
 	LOG(("yahoo_logoff: current status: %d", yd->current_status));
 
-	if(yd->current_status != -1 && 0) {
+	if (yd->current_status != -1 && 0) {
 		/* Meh. Don't send this. The event handlers are not going to
 		   get to do this so it'll just leak memory. And the TCP
 		   connection reset will hopefully be clear enough. */
-		pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF, YAHOO_STATUS_AVAILABLE, yd->session_id);
+		pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF,
+			YPACKET_STATUS_DEFAULT, yd->session_id);
 		yd->current_status = -1;
 
@@ -4095,20 +3717,23 @@
 	}
 
-	do {
+/*	do {
 		yahoo_input_close(yid);
-	} while((yid = find_input_by_id(id)));
+	} while((yid = find_input_by_id(id)));*/
+
 }
 
 void yahoo_get_list(int id)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
 	yahoo_packet_hash(pkt, 1, yd->user);
 	if (pkt) {
@@ -4118,8 +3743,8 @@
 }
 
-static void _yahoo_http_connected(int id, int fd, int error, void *data)
+static void _yahoo_http_connected(int id, void *fd, int error, void *data)
 {
 	struct yahoo_input_data *yid = data;
-	if(fd <= 0) {
+	if (fd == NULL || error) {
 		inputs = y_list_remove(inputs, yid);
 		FREE(yid);
@@ -4128,42 +3753,78 @@
 
 	yid->fd = fd;
-	yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, fd, YAHOO_INPUT_READ, yid);
-}
-
+	yid->read_tag =
+		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
+		YAHOO_INPUT_READ, yid);
+}
+
+/* FIXME Get address book from address.yahoo.com instead */
 void yahoo_get_yab(int id)
 {
+	struct yahoo_data *yd = find_conn_by_id(id);
+	struct yahoo_input_data *yid;
+	char url[1024];
+	char buff[2048];
+
+	if (!yd)
+		return;
+
+	yid = y_new0(struct yahoo_input_data, 1);
+	yid->yd = yd;
+	yid->type = YAHOO_CONNECTION_YAB;
+
+	LOG(("Sending request for Address Book"));
+
+	snprintf(url, 1024,
+		"http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us"
+		"&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252");
+
+	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+
+	inputs = y_list_prepend(inputs, yid);
+
+	yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
+		_yahoo_http_connected, yid);
+}
+
+struct yahoo_post_data {
+	struct yahoo_input_data *yid;
+	char *data;
+};
+
+static void _yahoo_http_post_connected(int id, void *fd, int error, void *data)
+{
+	struct yahoo_post_data *yad = data;
+	struct yahoo_input_data *yid = yad->yid;
+	char *buff = yad->data;
+
+	if (!fd) {
+		inputs = y_list_remove(inputs, yid);
+		FREE(yid);
+		return;
+	}
+
+	YAHOO_CALLBACK(ext_yahoo_write) (fd, buff, strlen(buff));
+
+	yid->fd = fd;
+	yid->read_tag =
+		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
+		YAHOO_INPUT_READ, yid);
+
+	FREE(buff);
+	FREE(yad);
+}
+
+/* FIXME This is also likely affected */
+void yahoo_set_yab(int id, struct yab *yab)
+{
+	struct yahoo_post_data *yad = y_new0(struct yahoo_post_data, 1);
 	struct yahoo_data *yd = find_conn_by_id(id);
 	struct yahoo_input_data *yid;
 	char url[1024];
 	char buff[1024];
-
-	if(!yd)
-		return;
-
-	yid = y_new0(struct yahoo_input_data, 1);
-	yid->yd = yd;
-	yid->type = YAHOO_CONNECTION_YAB;
-
-	snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?ab2=0");
-
-	snprintf(buff, sizeof(buff), "Y=%s; T=%s",
-			yd->cookie_y, yd->cookie_t);
-
-	inputs = y_list_prepend(inputs, yid);
-
-	yahoo_http_get(yid->yd->client_id, url, buff, 
-			_yahoo_http_connected, yid);
-}
-
-void yahoo_set_yab(int id, struct yab * yab)
-{
-	struct yahoo_data *yd = find_conn_by_id(id);
-	struct yahoo_input_data *yid;
-	char url[1024];
-	char buff[1024];
-	char *temp;
-	int size = sizeof(url)-1;
-
-	if(!yd)
+	char post[1024];
+	int size = 0;
+
+	if (!yd)
 		return;
 
@@ -4172,83 +3833,44 @@
 	yid->yd = yd;
 
-	strncpy(url, "http://insider.msg.yahoo.com/ycontent/?addab2=0", size);
-
-	if(yab->dbid) {
-		/* change existing yab */
-		char tmp[32];
-		strncat(url, "&ee=1&ow=1&id=", size - strlen(url));
-		snprintf(tmp, sizeof(tmp), "%d", yab->dbid);
-		strncat(url, tmp, size - strlen(url));
-	}
-
-	if(yab->fname) {
-		strncat(url, "&fn=", size - strlen(url));
-		temp = yahoo_urlencode(yab->fname);
-		strncat(url, temp, size - strlen(url));
-		free(temp);
-	}
-	if(yab->lname) {
-		strncat(url, "&ln=", size - strlen(url));
-		temp = yahoo_urlencode(yab->lname);
-		strncat(url, temp, size - strlen(url));
-		free(temp);
-	}
-	strncat(url, "&yid=", size - strlen(url));
-	temp = yahoo_urlencode(yab->id);
-	strncat(url, temp, size - strlen(url));
-	free(temp);
-	if(yab->nname) {
-		strncat(url, "&nn=", size - strlen(url));
-		temp = yahoo_urlencode(yab->nname);
-		strncat(url, temp, size - strlen(url));
-		free(temp);
-	}
-	if(yab->email) {
-		strncat(url, "&e=", size - strlen(url));
-		temp = yahoo_urlencode(yab->email);
-		strncat(url, temp, size - strlen(url));
-		free(temp);
-	}
-	if(yab->hphone) {
-		strncat(url, "&hp=", size - strlen(url));
-		temp = yahoo_urlencode(yab->hphone);
-		strncat(url, temp, size - strlen(url));
-		free(temp);
-	}
-	if(yab->wphone) {
-		strncat(url, "&wp=", size - strlen(url));
-		temp = yahoo_urlencode(yab->wphone);
-		strncat(url, temp, size - strlen(url));
-		free(temp);
-	}
-	if(yab->mphone) {
-		strncat(url, "&mp=", size - strlen(url));
-		temp = yahoo_urlencode(yab->mphone);
-		strncat(url, temp, size - strlen(url));
-		free(temp);
-	}
-	strncat(url, "&pp=0", size - strlen(url));
-
-	snprintf(buff, sizeof(buff), "Y=%s; T=%s",
-			yd->cookie_y, yd->cookie_t);
+	if(yab->yid)
+		size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+			"<ab k=\"%s\" cc=\"%d\">"
+			"<ct id=\"%d\" e=\"1\" yi=\"%s\" nn=\"%s\" />"
+			"</ab>", yd->user, 9, yab->yid,	/* Don't know why */
+			yab->id, yab->nname?yab->nname:"");
+	else
+		size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+			"<ab k=\"%s\" cc=\"%d\">"
+			"<ct a=\"1\" yi=\"%s\" nn=\"%s\" />"
+			"</ab>", yd->user, 1,	/* Don't know why */
+			yab->id, yab->nname?yab->nname:"");
+
+	yad->yid = yid;
+	yad->data = strdup(post);
+
+	strcpy(url, "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us"
+		"&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252");
+
+	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
 
 	inputs = y_list_prepend(inputs, yid);
 
-	yahoo_http_get(yid->yd->client_id, url, buff, 
-			_yahoo_http_connected, yid);
-}
-
-void yahoo_set_identity_status(int id, const char * identity, int active)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	yahoo_http_post(yid->yd->client_id, url, buff, size,
+		_yahoo_http_post_connected, yad);
+}
+
+void yahoo_set_identity_status(int id, const char *identity, int active)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(active?YAHOO_SERVICE_IDACT:YAHOO_SERVICE_IDDEACT,
-			YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(active ? YAHOO_SERVICE_IDACT :
+		YAHOO_SERVICE_IDDEACT, YPACKET_STATUS_DEFAULT, yd->session_id);
 	yahoo_packet_hash(pkt, 3, identity);
 	if (pkt) {
@@ -4260,13 +3882,15 @@
 void yahoo_refresh(int id)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
 	if (pkt) {
 		yahoo_send_packet(yid, pkt, 0);
@@ -4277,39 +3901,45 @@
 void yahoo_keepalive(int id)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
-	struct yahoo_packet *pkt=NULL;
-	if(!yid)
+	struct yahoo_packet *pkt = NULL;
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
 	yahoo_send_packet(yid, pkt, 0);
 	yahoo_packet_free(pkt);
 }
 
-void yahoo_chat_keepalive (int id)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type (id, YAHOO_CONNECTION_PAGER);
+void yahoo_chat_keepalive(int id)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
 
 	if (!yid)
-	    return;
+		return;
 
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new (YAHOO_SERVICE_CHATPING, YAHOO_STATUS_AVAILABLE, yd->session_id);
-	yahoo_send_packet (yid, pkt, 0);
-	yahoo_packet_free (pkt);
-}
-
-void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+	yahoo_send_packet(yid, pkt, 0);
+	yahoo_packet_free(pkt);
+}
+
+void yahoo_add_buddy(int id, const char *who, const char *group,
+	const char *msg)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
@@ -4318,11 +3948,10 @@
 		return;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YPACKET_STATUS_DEFAULT, yd->session_id);
-
-	if (msg != NULL) /* add message/request "it's me add me" */
+	pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+	if (msg != NULL)	/* add message/request "it's me add me" */
 		yahoo_packet_hash(pkt, 14, msg);
 	else
-		yahoo_packet_hash(pkt,14,"");
-
+		yahoo_packet_hash(pkt, 14, "");
 	yahoo_packet_hash(pkt, 65, group);
 	yahoo_packet_hash(pkt, 97, "1");
@@ -4334,6 +3963,4 @@
 	yahoo_packet_hash(pkt, 301, "319");
 	yahoo_packet_hash(pkt, 303, "319");
-
-
 	yahoo_send_packet(yid, pkt, 0);
 	yahoo_packet_free(pkt);
@@ -4342,13 +3969,15 @@
 void yahoo_remove_buddy(int id, const char *who, const char *group)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
 
 	yahoo_packet_hash(pkt, 1, yd->user);
@@ -4359,54 +3988,71 @@
 }
 
-void yahoo_accept_buddy_ymsg13(int id,const char* me,const char* who){
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_confirm_buddy(int id, const char *who, int reject, const char *msg)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
-
-	if(!yid)
+	struct yahoo_packet *pkt;
+
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	struct yahoo_packet* pkt=NULL;
-	pkt= yahoo_packet_new(YAHOO_SERVICE_CONTACT_YMSG13,YAHOO_STATUS_AVAILABLE,0);
-
-	yahoo_packet_hash(pkt,1,me ?: yd->user);	
-	yahoo_packet_hash(pkt,5,who);
-	yahoo_packet_hash(pkt,13,"1");
-	yahoo_packet_hash(pkt,334,"0");
+	if (!yd->logged_in)
+		return;
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_AUTHORIZATION,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
+	yahoo_packet_hash(pkt, 1, yd->user);
+	yahoo_packet_hash(pkt, 5, who);
+	if (reject)
+		yahoo_packet_hash(pkt, 13, "2");
+	else {
+		yahoo_packet_hash(pkt, 241, "0");
+		yahoo_packet_hash(pkt, 13, "1");
+	}
+
+	yahoo_packet_hash(pkt, 334, "0");
+
+	if (reject) {
+		yahoo_packet_hash(pkt, 14, msg ? msg : "");
+		yahoo_packet_hash(pkt, 97, "1");
+	}
+
 	yahoo_send_packet(yid, pkt, 0);
 	yahoo_packet_free(pkt);
 }
 
-void yahoo_reject_buddy_ymsg13(int id,const char* me,const char* who,const char* msg){
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_ignore_buddy(int id, const char *who, int unignore)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
-
-	if(!yid)
+	struct yahoo_packet *pkt;
+
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	struct yahoo_packet* pkt=NULL;
-	pkt= yahoo_packet_new(YAHOO_SERVICE_CONTACT_YMSG13,YAHOO_STATUS_AVAILABLE,0);
-
-	yahoo_packet_hash(pkt,1,me ?: yd->user);	
-	yahoo_packet_hash(pkt,5,who);
-//	yahoo_packet_hash(pkt,241,YAHOO_PROTO_VER);
-	yahoo_packet_hash(pkt,13,"2");
-	yahoo_packet_hash(pkt,334,"0");
-	yahoo_packet_hash(pkt,97,"1");
-	yahoo_packet_hash(pkt,14,msg?:"");
-
+	if (!yd->logged_in)
+		return;
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
+	yahoo_packet_hash(pkt, 1, yd->user);
+	yahoo_packet_hash(pkt, 7, who);
+	yahoo_packet_hash(pkt, 13, unignore ? "2" : "1");
 	yahoo_send_packet(yid, pkt, 0);
 	yahoo_packet_free(pkt);
-
-}
-
-void yahoo_reject_buddy(int id, const char *who, const char *msg)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+}
+
+void yahoo_stealth_buddy(int id, const char *who, int unstealth)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
@@ -4415,50 +4061,9 @@
 		return;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_REJECTCONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH_PERM,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
 	yahoo_packet_hash(pkt, 1, yd->user);
 	yahoo_packet_hash(pkt, 7, who);
-	yahoo_packet_hash(pkt, 14, msg);
-	yahoo_send_packet(yid, pkt, 0);
-	yahoo_packet_free(pkt);
-}
-
-void yahoo_ignore_buddy(int id, const char *who, int unignore)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
-	struct yahoo_data *yd;
-	struct yahoo_packet *pkt;
-
-	if(!yid)
-		return;
-	yd = yid->yd;
-
-	if (!yd->logged_in)
-		return;
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id);
-	yahoo_packet_hash(pkt, 1, yd->user);
-	yahoo_packet_hash(pkt, 7, who);
-	yahoo_packet_hash(pkt, 13, unignore?"2":"1");
-	yahoo_send_packet(yid, pkt, 0);
-	yahoo_packet_free(pkt);
-}
-
-void yahoo_stealth_buddy(int id, const char *who, int unstealth)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
-	struct yahoo_data *yd;
-	struct yahoo_packet *pkt;
-
-	if(!yid)
-		return;
-	yd = yid->yd;
-
-	if (!yd->logged_in)
-		return;
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH, YAHOO_STATUS_AVAILABLE, yd->session_id);
-	yahoo_packet_hash(pkt, 1, yd->user);
-	yahoo_packet_hash(pkt, 7, who);
-	yahoo_packet_hash(pkt, 31, unstealth?"2":"1");
+	yahoo_packet_hash(pkt, 31, unstealth ? "2" : "1");
 	yahoo_packet_hash(pkt, 13, "2");
 	yahoo_send_packet(yid, pkt, 0);
@@ -4466,42 +4071,44 @@
 }
 
-void yahoo_change_buddy_group(int id, const char *who, const char *old_group, const char *new_group)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_change_buddy_group(int id, const char *who, const char *old_group,
+	const char *new_group)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_CHANGE_GROUP,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
 	yahoo_packet_hash(pkt, 1, yd->user);
+	yahoo_packet_hash(pkt, 302, "240");
+	yahoo_packet_hash(pkt, 300, "240");
 	yahoo_packet_hash(pkt, 7, who);
-	yahoo_packet_hash(pkt, 65, new_group);
-	yahoo_packet_hash(pkt, 14, " ");
+	yahoo_packet_hash(pkt, 224, old_group);
+	yahoo_packet_hash(pkt, 264, new_group);
+	yahoo_packet_hash(pkt, 301, "240");
+	yahoo_packet_hash(pkt, 303, "240");
 
 	yahoo_send_packet(yid, pkt, 0);
 	yahoo_packet_free(pkt);
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id);
-	yahoo_packet_hash(pkt, 1, yd->user);
-	yahoo_packet_hash(pkt, 7, who);
-	yahoo_packet_hash(pkt, 65, old_group);
-	yahoo_send_packet(yid, pkt, 0);
-	yahoo_packet_free(pkt);
 }
 
 void yahoo_group_rename(int id, const char *old_group, const char *new_group)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt = NULL;
 
-	if(!yid)
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
 	yahoo_packet_hash(pkt, 1, yd->user);
 	yahoo_packet_hash(pkt, 65, old_group);
@@ -4512,22 +4119,25 @@
 }
 
-void yahoo_conference_addinvite(int id, const char * from, const char *who, const char *room, const YList * members, const char *msg)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_conference_addinvite(int id, const char *from, const char *who,
+	const char *room, const YList *members, const char *msg)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
 	yahoo_packet_hash(pkt, 51, who);
 	yahoo_packet_hash(pkt, 57, room);
 	yahoo_packet_hash(pkt, 58, msg);
 	yahoo_packet_hash(pkt, 13, "0");
-	for(; members; members = members->next) {
+	for (; members; members = members->next) {
 		yahoo_packet_hash(pkt, 52, (char *)members->data);
 		yahoo_packet_hash(pkt, 53, (char *)members->data);
@@ -4540,19 +4150,22 @@
 }
 
-void yahoo_conference_invite(int id, const char * from, YList *who, const char *room, const char *msg)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_conference_invite(int id, const char *from, YList *who,
+	const char *room, const char *msg)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
 	yahoo_packet_hash(pkt, 50, yd->user);
-	for(; who; who = who->next) {
+	for (; who; who = who->next) {
 		yahoo_packet_hash(pkt, 52, (char *)who->data);
 	}
@@ -4566,21 +4179,24 @@
 }
 
-void yahoo_conference_logon(int id, const char *from, YList *who, const char *room)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_conference_logon(int id, const char *from, YList *who,
+	const char *room)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
-	for(; who; who = who->next) {
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+	yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
+	yahoo_packet_hash(pkt, 57, room);
+	for (; who; who = who->next)
 		yahoo_packet_hash(pkt, 3, (char *)who->data);
-	}
-	yahoo_packet_hash(pkt, 57, room);
 
 	yahoo_send_packet(yid, pkt, 0);
@@ -4589,20 +4205,23 @@
 }
 
-void yahoo_conference_decline(int id, const char * from, YList *who, const char *room, const char *msg)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_conference_decline(int id, const char *from, YList *who,
+	const char *room, const char *msg)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
-	for(; who; who = who->next) {
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+	yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
+	for (; who; who = who->next)
 		yahoo_packet_hash(pkt, 3, (char *)who->data);
-	}
 	yahoo_packet_hash(pkt, 57, room);
 	yahoo_packet_hash(pkt, 14, msg);
@@ -4613,20 +4232,24 @@
 }
 
-void yahoo_conference_logoff(int id, const char * from, YList *who, const char *room)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_conference_logoff(int id, const char *from, YList *who,
+	const char *room)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
-	for(; who; who = who->next) {
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+	yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
+	for (; who; who = who->next)
 		yahoo_packet_hash(pkt, 3, (char *)who->data);
-	}
+
 	yahoo_packet_hash(pkt, 57, room);
 
@@ -4636,24 +4259,28 @@
 }
 
-void yahoo_conference_message(int id, const char * from, YList *who, const char *room, const char *msg, int utf8)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_conference_message(int id, const char *from, YList *who,
+	const char *room, const char *msg, int utf8)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
-	for(; who; who = who->next) {
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
+	yahoo_packet_hash(pkt, 53, (from ? from : yd->user));
+	for (; who; who = who->next)
 		yahoo_packet_hash(pkt, 53, (char *)who->data);
-	}
+
 	yahoo_packet_hash(pkt, 57, room);
 	yahoo_packet_hash(pkt, 14, msg);
 
-	if(utf8)
+	if (utf8)
 		yahoo_packet_hash(pkt, 97, "1");
 
@@ -4670,5 +4297,5 @@
 	char buff[1024];
 
-	if(!yd)
+	if (!yd)
 		return;
 
@@ -4678,7 +4305,10 @@
 
 	if (chatroomid == 0) {
-		snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?chatcat=0");
+		snprintf(url, 1024,
+			"http://insider.msg.yahoo.com/ycontent/?chatcat=0");
 	} else {
-		snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?chatroom_%d=0",chatroomid);
+		snprintf(url, 1024,
+			"http://insider.msg.yahoo.com/ycontent/?chatroom_%d=0",
+			chatroomid);
 	}
 
@@ -4687,21 +4317,25 @@
 	inputs = y_list_prepend(inputs, yid);
 
-	yahoo_http_get(yid->yd->client_id, url, buff, _yahoo_http_connected, yid);
-}
-
-void yahoo_chat_logon(int id, const char *from, const char *room, const char *roomid)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
+		_yahoo_http_connected, yid);
+}
+
+void yahoo_chat_logon(int id, const char *from, const char *room,
+	const char *roomid)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
 	yahoo_packet_hash(pkt, 109, yd->user);
 	yahoo_packet_hash(pkt, 6, "abcde");
@@ -4711,10 +4345,11 @@
 	yahoo_packet_free(pkt);
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
 	yahoo_packet_hash(pkt, 104, room);
 	yahoo_packet_hash(pkt, 129, roomid);
-	yahoo_packet_hash(pkt, 62, "2"); /* ??? */
+	yahoo_packet_hash(pkt, 62, "2");	/* ??? */
 
 	yahoo_send_packet(yid, pkt, 0);
@@ -4723,27 +4358,29 @@
 }
 
-
-void  yahoo_chat_message(int id, const char *from, const char *room, const char *msg, const int msgtype, const int utf8)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_chat_message(int id, const char *from, const char *room,
+	const char *msg, const int msgtype, const int utf8)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 	char buf[2];
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
+	pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
 	yahoo_packet_hash(pkt, 104, room);
 	yahoo_packet_hash(pkt, 117, msg);
-	
+
 	snprintf(buf, sizeof(buf), "%d", msgtype);
 	yahoo_packet_hash(pkt, 124, buf);
 
-	if(utf8)
+	if (utf8)
 		yahoo_packet_hash(pkt, 97, "1");
 
@@ -4753,19 +4390,20 @@
 }
 
-
 void yahoo_chat_logoff(int id, const char *from)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	yahoo_packet_hash(pkt, 1, (from?from:yd->user));
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
 
 	yahoo_send_packet(yid, pkt, 0);
@@ -4776,14 +4414,16 @@
 void yahoo_buddyicon_request(int id, const char *who)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 
-	if( !yid )
+	if (!yid)
 		return;
 
 	yd = yid->yd;
-	
-	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YAHOO_STATUS_AVAILABLE, 0);
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT,
+		0);
 	yahoo_packet_hash(pkt, 4, yd->user);
 	yahoo_packet_hash(pkt, 5, who);
@@ -4794,12 +4434,14 @@
 }
 
-void yahoo_send_picture_info(int id, const char *who, const char *url, int checksum)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+void yahoo_send_picture_info(int id, const char *who, const char *url,
+	int checksum)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 	char checksum_str[10];
 
-	if( !yid )
+	if (!yid)
 		return;
 
@@ -4808,5 +4450,6 @@
 	snprintf(checksum_str, sizeof(checksum_str), "%d", checksum);
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YAHOO_STATUS_AVAILABLE, 0);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT,
+		0);
 	yahoo_packet_hash(pkt, 1, yd->user);
 	yahoo_packet_hash(pkt, 4, yd->user);
@@ -4822,10 +4465,11 @@
 void yahoo_send_picture_update(int id, const char *who, int type)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 	char type_str[10];
 
-	if( !yid )
+	if (!yid)
 		return;
 
@@ -4834,5 +4478,6 @@
 	snprintf(type_str, sizeof(type_str), "%d", type);
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE, YAHOO_STATUS_AVAILABLE, 0);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE,
+		YPACKET_STATUS_DEFAULT, 0);
 	yahoo_packet_hash(pkt, 1, yd->user);
 	yahoo_packet_hash(pkt, 5, who);
@@ -4845,19 +4490,21 @@
 void yahoo_send_picture_checksum(int id, const char *who, int checksum)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 	char checksum_str[10];
 
-	if( !yid )
+	if (!yid)
 		return;
 
 	yd = yid->yd;
-	
+
 	snprintf(checksum_str, sizeof(checksum_str), "%d", checksum);
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM, YAHOO_STATUS_AVAILABLE, 0);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM,
+		YPACKET_STATUS_DEFAULT, 0);
 	yahoo_packet_hash(pkt, 1, yd->user);
-	if( who != 0 )
+	if (who != 0)
 		yahoo_packet_hash(pkt, 5, who);
 	yahoo_packet_hash(pkt, 192, checksum_str);
@@ -4870,7 +4517,8 @@
 void yahoo_webcam_close_feed(int id, const char *who)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_webcam_user(id, who);
-
-	if(yid)
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_webcam_user(id, who);
+
+	if (yid)
 		yahoo_input_close(yid);
 }
@@ -4878,9 +4526,10 @@
 void yahoo_webcam_get_feed(int id, const char *who)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
+
+	if (!yid)
 		return;
 
@@ -4892,9 +4541,10 @@
 	 * The queue is popped in yahoo_process_webcam_key
 	 */
-	webcam_queue = y_list_append(webcam_queue, who?strdup(who):NULL);
+	webcam_queue = y_list_append(webcam_queue, who ? strdup(who) : NULL);
 
 	yd = yid->yd;
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YPACKET_STATUS_DEFAULT,
+		yd->session_id);
 
 	yahoo_packet_hash(pkt, 1, yd->user);
@@ -4906,7 +4556,9 @@
 }
 
-void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, unsigned int timestamp)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM);
+void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length,
+	unsigned int timestamp)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM);
 	unsigned char *packet;
 	unsigned char header_len = 13;
@@ -4920,8 +4572,8 @@
 	packet[pos++] = header_len;
 	packet[pos++] = 0;
-	packet[pos++] = 5; /* version byte?? */
+	packet[pos++] = 5;	/* version byte?? */
 	packet[pos++] = 0;
 	pos += yahoo_put32(packet + pos, length);
-	packet[pos++] = 2; /* packet type, image */
+	packet[pos++] = 2;	/* packet type, image */
 	pos += yahoo_put32(packet + pos, timestamp);
 	yahoo_add_to_send_queue(yid, packet, header_len);
@@ -4932,7 +4584,8 @@
 }
 
-void yahoo_webcam_accept_viewer(int id, const char* who, int accept)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM);
+void yahoo_webcam_accept_viewer(int id, const char *who, int accept)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM);
 	char *packet = NULL;
 	char *data = NULL;
@@ -4945,5 +4598,5 @@
 
 	data = strdup("u=");
-	data = y_string_append(data, (char*)who);
+	data = y_string_append(data, (char *)who);
 	data = y_string_append(data, "\r\n");
 	len = strlen(data);
@@ -4952,8 +4605,8 @@
 	packet[pos++] = header_len;
 	packet[pos++] = 0;
-	packet[pos++] = 5; /* version byte?? */
+	packet[pos++] = 5;	/* version byte?? */
 	packet[pos++] = 0;
 	pos += yahoo_put32(packet + pos, len);
-	packet[pos++] = 0; /* packet type */
+	packet[pos++] = 0;	/* packet type */
 	pos += yahoo_put32(packet + pos, accept);
 	memcpy(packet + pos, data, len);
@@ -4965,11 +4618,13 @@
 void yahoo_webcam_invite(int id, const char *who)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_packet *pkt;
-		
-	if(!yid)
-		return;
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_NOTIFY, yid->yd->session_id);
+
+	if (!yid)
+		return;
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY,
+		yid->yd->session_id);
 
 	yahoo_packet_hash(pkt, 49, "WEBCAMINVITE");
@@ -4983,5 +4638,6 @@
 }
 
-static void yahoo_search_internal(int id, int t, const char *text, int g, int ar, int photo, int yahoo_only, int startpos, int total)
+static void yahoo_search_internal(int id, int t, const char *text, int g,
+	int ar, int photo, int yahoo_only, int startpos, int total)
 {
 	struct yahoo_data *yd = find_conn_by_id(id);
@@ -4991,5 +4647,5 @@
 	char *ctext, *p;
 
-	if(!yd)
+	if (!yd)
 		return;
 
@@ -4999,17 +4655,19 @@
 
 	/*
-	age range
-	.ar=1 - 13-18, 2 - 18-25, 3 - 25-35, 4 - 35-50, 5 - 50-70, 6 - 70+
-	*/
-
-	snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total, startpos);
+	   age range
+	   .ar=1 - 13-18, 2 - 18-25, 3 - 25-35, 4 - 35-50, 5 - 50-70, 6 - 70+
+	 */
+
+	snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total,
+		startpos);
 
 	ctext = strdup(text);
-	while((p = strchr(ctext, ' ')))
+	while ((p = strchr(ctext, ' ')))
 		*p = '+';
 
-	snprintf(url, 1024, "http://members.yahoo.com/interests?.oc=m&.kw=%s&.sb=%d&.g=%d&.ar=0%s%s%s",
-			ctext, t, g, photo ? "&.p=y" : "", yahoo_only ? "&.pg=y" : "",
-			startpos ? buff : "");
+	snprintf(url, 1024,
+		"http://members.yahoo.com/interests?.oc=m&.kw=%s&.sb=%d&.g=%d&.ar=0%s%s%s",
+		ctext, t, g, photo ? "&.p=y" : "", yahoo_only ? "&.pg=y" : "",
+		startpos ? buff : "");
 
 	FREE(ctext);
@@ -5018,17 +4676,20 @@
 
 	inputs = y_list_prepend(inputs, yid);
-	yahoo_http_get(yid->yd->client_id, url, buff, _yahoo_http_connected, yid);
-}
-
-void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo_search_gender g, enum yahoo_search_agerange ar, 
-		int photo, int yahoo_only)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
+		_yahoo_http_connected, yid);
+}
+
+void yahoo_search(int id, enum yahoo_search_type t, const char *text,
+	enum yahoo_search_gender g, enum yahoo_search_agerange ar, int photo,
+	int yahoo_only)
+{
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_search_state *yss;
 
-	if(!yid)
-		return;
-
-	if(!yid->ys)
+	if (!yid)
+		return;
+
+	if (!yid->ys)
 		yid->ys = y_new0(struct yahoo_search_state, 1);
 
@@ -5048,38 +4709,143 @@
 void yahoo_search_again(int id, int start)
 {
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	struct yahoo_input_data *yid =
+		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
 	struct yahoo_search_state *yss;
 
-	if(!yid || !yid->ys)
+	if (!yid || !yid->ys)
 		return;
 
 	yss = yid->ys;
 
-	if(start == -1)
+	if (start == -1)
 		start = yss->lsearch_nstart + yss->lsearch_nfound;
 
-	yahoo_search_internal(id, yss->lsearch_type, yss->lsearch_text, 
-			yss->lsearch_gender, yss->lsearch_agerange, 
-			yss->lsearch_photo, yss->lsearch_yahoo_only, 
-			start, yss->lsearch_ntotal);
-}
+	yahoo_search_internal(id, yss->lsearch_type, yss->lsearch_text,
+		yss->lsearch_gender, yss->lsearch_agerange,
+		yss->lsearch_photo, yss->lsearch_yahoo_only,
+		start, yss->lsearch_ntotal);
+}
+
+void yahoo_send_picture(int id, const char *name, unsigned long size,
+	yahoo_get_fd_callback callback, void *data)
+{
+	/* Not Implemented */
+}
+
+/* File Transfer */
+static YList *active_file_transfers = NULL;
+
+enum {
+	FT_STATE_HEAD = 1,
+	FT_STATE_RECV,
+	FT_STATE_RECV_START,
+	FT_STATE_SEND
+};
 
 struct send_file_data {
-	struct yahoo_packet *pkt;
+	int client_id;
+	char *id;
+	char *who;
+	char *filename;
+	char *ip_addr;
+	char *token;
+	int size;
+
+	struct yahoo_input_data *yid;
+	int state;
+
 	yahoo_get_fd_callback callback;
-	void *user_data;
+	void *data;
 };
 
-static void _yahoo_send_picture_connected(int id, int fd, int error, void *data)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_FT);
+static char *yahoo_get_random(void)
+{
+	int i = 0;
+	int r = 0;
+	int c = 0;
+	char out[25];
+
+	out[24] = '\0';
+	out[23] = '$';
+	out[22] = '$';
+	
+	for (i = 0; i < 22; i++) {
+		if(r == 0)
+			r = rand();
+
+		c = r%61;
+
+		if(c<26)
+			out[i] = c + 'a';
+		else if (c<52)
+			out[i] = c - 26 + 'A';
+		else
+			out[i] = c - 52 + '0';
+
+		r /= 61;
+	}
+
+	return strdup(out);
+}
+
+static int _are_same_id(const void *sfd1, const void *id)
+{
+	return strcmp(((struct send_file_data *)sfd1)->id, (char *)id);
+}
+
+static int _are_same_yid(const void *sfd1, const void *yid)
+{
+	if(((struct send_file_data *)sfd1)->yid == yid)
+		return 0;
+	else
+		return 1;
+}
+
+static struct send_file_data *yahoo_get_active_transfer(char *id)
+{
+	YList *l = y_list_find_custom(active_file_transfers, id,
+		_are_same_id);
+
+	if(l)
+		return (struct send_file_data *)l->data;
+	
+	return NULL;
+}
+
+static struct send_file_data *yahoo_get_active_transfer_with_yid(void *yid)
+{
+	YList *l = y_list_find_custom(active_file_transfers, yid,
+		_are_same_yid);
+
+	if(l)
+		return (struct send_file_data *)l->data;
+	
+	return NULL;
+}
+
+static void yahoo_add_active_transfer(struct send_file_data *sfd)
+{
+	active_file_transfers = y_list_prepend(active_file_transfers, sfd);
+}
+
+static void yahoo_remove_active_transfer(struct send_file_data *sfd)
+{
+	if (sfd == NULL)
+		return;
+	
+	active_file_transfers = y_list_remove(active_file_transfers, sfd);
+	free(sfd->id);
+	free(sfd->who);
+	free(sfd->filename);
+	free(sfd->ip_addr);
+	FREE(sfd);
+}
+
+static void _yahoo_ft_upload_connected(int id, void *fd, int error, void *data)
+{
 	struct send_file_data *sfd = data;
-	struct yahoo_packet *pkt = sfd->pkt;
-	unsigned char buff[1024];
-
-	if(fd <= 0) {
-		sfd->callback(id, fd, error, sfd->user_data);
-		FREE(sfd);
-		yahoo_packet_free(pkt);
+	struct yahoo_input_data *yid = sfd->yid;
+
+	if (!fd) {
 		inputs = y_list_remove(inputs, yid);
 		FREE(yid);
@@ -5087,233 +4853,566 @@
 	}
 
+	sfd->callback(id, fd, error, sfd->data);
+
 	yid->fd = fd;
-	yahoo_send_packet(yid, pkt, 8);
+	yid->read_tag =
+		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
+		YAHOO_INPUT_READ, yid);
+}
+
+static void yahoo_file_transfer_upload(struct yahoo_data *yd,
+	struct send_file_data *sfd)
+{
+	char url[256];
+	char buff[4096];
+	char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;
+
+	struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
+
+	yid->yd = yd;
+	yid->type = YAHOO_CONNECTION_FT;
+
+	inputs = y_list_prepend(inputs, yid);
+	sfd->yid = yid;
+	sfd->state = FT_STATE_SEND;
+
+	token_enc = yahoo_urlencode(sfd->token);
+	sender_enc = yahoo_urlencode(yd->user);
+	recv_enc = yahoo_urlencode(sfd->who);
+
+	snprintf(url, sizeof(url), 
+		"http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
+		token_enc, sender_enc, recv_enc);
+
+	snprintf(buff, sizeof(buff), "T=%s; Y=%s", yd->cookie_t, yd->cookie_y);
+
+	yahoo_http_post(yd->client_id, url, buff, sfd->size,
+		_yahoo_ft_upload_connected, sfd);
+
+	FREE(token_enc);
+	FREE(sender_enc);
+	FREE(recv_enc);
+}
+
+static void yahoo_init_ft_recv(struct yahoo_data *yd,
+	struct send_file_data *sfd)
+{
+	char url[256];
+	char buff[1024];
+	char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;
+
+	struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
+
+	yid->yd = yd;
+	yid->type = YAHOO_CONNECTION_FT;
+
+	inputs = y_list_prepend(inputs, yid);
+	sfd->yid = yid;
+	sfd->state = FT_STATE_HEAD;
+
+	token_enc = yahoo_urlencode(sfd->token);
+	sender_enc = yahoo_urlencode(sfd->who);
+	recv_enc = yahoo_urlencode(yd->user);
+
+	snprintf(url, sizeof(url), 
+		"http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
+		token_enc, sender_enc, recv_enc);
+
+	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+
+	yahoo_http_head(yid->yd->client_id, url, buff, 0, NULL,
+		_yahoo_http_connected, yid);
+
+	FREE(token_enc);
+	FREE(sender_enc);
+	FREE(recv_enc);
+}
+
+static void yahoo_file_transfer_accept(struct yahoo_input_data *yid,
+	struct send_file_data *sfd)
+{
+	struct yahoo_packet *pkt;
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERACCEPT,
+		YPACKET_STATUS_DEFAULT, yid->yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, yid->yd->user);
+	yahoo_packet_hash(pkt, 5, sfd->who);
+	yahoo_packet_hash(pkt, 265, sfd->id);
+	yahoo_packet_hash(pkt, 27, sfd->filename);
+	yahoo_packet_hash(pkt, 249, "3");
+	yahoo_packet_hash(pkt, 251, sfd->token);
+
+	yahoo_send_packet(yid, pkt, 0);
+
 	yahoo_packet_free(pkt);
 
-	snprintf((char *)buff, sizeof(buff), "29");
-	buff[2] = 0xc0;
-	buff[3] = 0x80;
-	
-	write(yid->fd, buff, 4);
-
-	/*	YAHOO_CALLBACK(ext_yahoo_add_handler)(nyd->fd, YAHOO_INPUT_READ); */
-
-	sfd->callback(id, fd, error, sfd->user_data);
-	FREE(sfd);
-	inputs = y_list_remove(inputs, yid);
-	/*
-	while(yahoo_tcp_readline(buff, sizeof(buff), nyd->fd) > 0) {
-	if(!strcmp(buff, ""))
-	break;
-}
-
-	*/
-	yahoo_input_close(yid);
-}
-
-void yahoo_send_picture(int id, const char *name, unsigned long size,
-							yahoo_get_fd_callback callback, void *data)
-{
-	struct yahoo_data *yd = find_conn_by_id(id);
+	yahoo_init_ft_recv(yid->yd, sfd);
+}
+
+static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
+{
+	YList *l;
+	struct send_file_data *sfd;
+	char *who = NULL;
+	char *filename = NULL;
+	char *id = NULL;
+	char *token = NULL;
+
+	for (l = pkt->hash; l; l = l->next) {
+		struct yahoo_pair *pair = l->data;
+		switch (pair->key) {
+		case 4:
+			who = pair->value;
+			break;
+		case 5:
+			/* Me... don't care */
+			break;
+		case 249:
+			break;
+		case 265:
+			id = pair->value;
+			break;
+		case 251:
+			token = pair->value;
+			break;
+		case 27:
+			filename = pair->value;
+			break;
+		}
+	}
+
+	sfd = yahoo_get_active_transfer(id);
+
+	if (sfd) {
+		sfd->token = strdup(token);
+
+		yahoo_file_transfer_upload(yid->yd, sfd);
+	}
+	else {
+		YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+			(yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN,
+			sfd ? sfd->data : NULL);
+
+		yahoo_remove_active_transfer(sfd);
+	}
+}
+
+static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
+{
+	YList *l;
+	char *who = NULL;
+	char *filename = NULL;
+	char *id = NULL;
+	char *token = NULL;
+	char *ip_addr = NULL;
+
+	struct send_file_data *sfd;
+
+	for (l = pkt->hash; l; l = l->next) {
+		struct yahoo_pair *pair = l->data;
+		switch (pair->key) {
+		case 1:
+		case 4:
+			who = pair->value;
+			break;
+		case 5:
+			/* Me... don't care */
+			break;
+		case 249:
+			break;
+		case 265:
+			id = pair->value;
+			break;
+		case 250:
+			ip_addr = pair->value;
+			break;
+		case 251:
+			token = pair->value;
+			break;
+		case 27:
+			filename = pair->value;
+			break;
+		}
+	}
+
+	sfd = yahoo_get_active_transfer(id);
+
+	if (sfd) {
+		sfd->token = strdup(token);
+		sfd->ip_addr = strdup(ip_addr);
+
+		yahoo_file_transfer_accept(yid, sfd);
+	}
+	else {
+		YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+			(yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN,
+			sfd ? sfd->data : NULL);
+
+		yahoo_remove_active_transfer(sfd);
+	}
+}
+
+static void yahoo_send_filetransferinfo(struct yahoo_data *yd,
+	struct send_file_data *sfd)
+{
 	struct yahoo_input_data *yid;
-	struct yahoo_server_settings *yss;
+	struct yahoo_packet *pkt;
+
+	yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER);
+	sfd->ip_addr = YAHOO_CALLBACK(ext_yahoo_get_ip_addr)("relay.yahoo.com");
+
+	if (!sfd->ip_addr) {
+		YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+			(yd->client_id, YAHOO_FILE_TRANSFER_RELAY, sfd->data);
+
+		yahoo_remove_active_transfer(sfd);
+
+		return;
+	}
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERINFO,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
+
+	yahoo_packet_hash(pkt, 1, yd->user);
+	yahoo_packet_hash(pkt, 5, sfd->who);
+	yahoo_packet_hash(pkt, 265, sfd->id);
+	yahoo_packet_hash(pkt, 27, sfd->filename);
+	yahoo_packet_hash(pkt, 249, "3");
+	yahoo_packet_hash(pkt, 250, sfd->ip_addr);
+
+	yahoo_send_packet(yid, pkt, 0);
+
+	yahoo_packet_free(pkt);
+}
+
+static void yahoo_process_filetransfer(struct yahoo_input_data *yid,
+	struct yahoo_packet *pkt)
+{
+	YList *l;
+	char *who = NULL;
+	char *filename = NULL;
+	char *msg = NULL;
+	char *id = NULL;
+	int action = 0;
+	int size = 0;
+	struct yahoo_data *yd = yid->yd;
+
+	struct send_file_data *sfd;
+
+	for (l = pkt->hash; l; l = l->next) {
+		struct yahoo_pair *pair = l->data;
+		switch (pair->key) {
+		case 4:
+			who = pair->value;
+			break;
+		case 5:
+			/* Me... don't care */
+			break;
+		case 222:
+			action = atoi(pair->value);
+			break;
+		case 265:
+			id = pair->value;
+			break;
+		case 266: /* Don't know */
+			break;
+		case 302: /* Start Data? */
+			break;
+		case 300:
+			break;
+		case 27:
+			filename = pair->value;
+			break;
+		case 28:
+			size = atoi(pair->value);
+			break;
+		case 14:
+			msg = pair->value;
+		case 301: /* End Data? */
+			break;
+		case 303:
+			break;
+
+		}
+	}
+
+	if (action == YAHOO_FILE_TRANSFER_INIT) {
+		/* Received a FT request from buddy */
+		sfd = y_new0(struct send_file_data, 1);
+        
+		sfd->client_id = yd->client_id;
+		sfd->id = strdup(id);
+		sfd->who = strdup(who);
+		sfd->filename = strdup(filename);
+		sfd->size = size;
+        
+		yahoo_add_active_transfer(sfd);
+
+		YAHOO_CALLBACK(ext_yahoo_got_file) (yd->client_id, yd->user,
+			who, msg, filename, size, sfd->id);
+	}
+	else {
+		/* Response to our request */
+		sfd = yahoo_get_active_transfer(id);
+
+		if (sfd && action == YAHOO_FILE_TRANSFER_ACCEPT) {
+			yahoo_send_filetransferinfo(yd, sfd);
+		}
+		else if (!sfd || action == YAHOO_FILE_TRANSFER_REJECT) {
+			YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+				(yd->client_id, YAHOO_FILE_TRANSFER_REJECT,
+				sfd ? sfd->data : NULL);
+
+			yahoo_remove_active_transfer(sfd);
+		}
+	}
+}
+
+void yahoo_send_file(int id, const char *who, const char *msg,
+	const char *name, unsigned long size,
+	yahoo_get_fd_callback callback, void *data)
+{
 	struct yahoo_packet *pkt = NULL;
 	char size_str[10];
-	char expire_str[10];
-	long content_length=0;
-	unsigned char buff[1024];
-	char url[255];
+	struct yahoo_input_data *yid;
+	struct yahoo_data *yd;
 	struct send_file_data *sfd;
-
-	if(!yd)
-		return;
-
-	yss = yd->server_settings;
-
-	yid = y_new0(struct yahoo_input_data, 1);
-	yid->yd = yd;
-	yid->type = YAHOO_CONNECTION_FT;
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPLOAD, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	
+	yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
+	yd = find_conn_by_id(id);
+	sfd = y_new0(struct send_file_data, 1);
+
+	sfd->client_id = id;
+	sfd->id = yahoo_get_random();
+	sfd->who = strdup(who);
+	sfd->filename = strdup(name);
+	sfd->size = size;
+	sfd->callback = callback;
+	sfd->data = data;
+
+	yahoo_add_active_transfer(sfd);
+
+	if (!yd)
+		return;
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER,
+		YPACKET_STATUS_DEFAULT, yd->session_id);
 
 	snprintf(size_str, sizeof(size_str), "%ld", size);
-	snprintf(expire_str, sizeof(expire_str), "%ld", (long)604800);
-
-	yahoo_packet_hash(pkt, 0, yd->user);
+
 	yahoo_packet_hash(pkt, 1, yd->user);
-	yahoo_packet_hash(pkt, 14, "");
+	yahoo_packet_hash(pkt, 5, who);
+	yahoo_packet_hash(pkt, 265, sfd->id);
+	yahoo_packet_hash(pkt, 222, "1");
+	yahoo_packet_hash(pkt, 266, "1");
+	yahoo_packet_hash(pkt, 302, "268");
+	yahoo_packet_hash(pkt, 300, "268");
 	yahoo_packet_hash(pkt, 27, name);
 	yahoo_packet_hash(pkt, 28, size_str);
-	yahoo_packet_hash(pkt, 38, expire_str);
+	yahoo_packet_hash(pkt, 301, "268");
+	yahoo_packet_hash(pkt, 303, "268");
+
+	yahoo_send_packet(yid, pkt, 0);
+
+	yahoo_packet_free(pkt);
+}
+
+void yahoo_send_file_transfer_response(int client_id, int response, char *id, void *data)
+{
+	struct yahoo_packet *pkt = NULL;
+	char resp[2];
+	struct yahoo_input_data *yid;
 	
-
-	content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt);
-
-	snprintf(url, sizeof(url), "http://%s:%d/notifyft",
-				yss->filetransfer_host, yss->filetransfer_port);
-	snprintf((char *)buff, sizeof(buff), "Y=%s; T=%s",
-				 yd->cookie_y, yd->cookie_t);
-	inputs = y_list_prepend(inputs, yid);
-
-	sfd = y_new0(struct send_file_data, 1);
-	sfd->pkt = pkt;
-	sfd->callback = callback;
-	sfd->user_data = data;
-	yahoo_http_post(yid->yd->client_id, url, (char *)buff, content_length+4+size,
-						_yahoo_send_picture_connected, sfd);
-}
-
-static void _yahoo_send_file_connected(int id, int fd, int error, void *data)
-{
-	struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_FT);
-	struct send_file_data *sfd = data;
-	struct yahoo_packet *pkt = sfd->pkt;
-	unsigned char buff[1024];
-
-	if(fd <= 0) {
-		sfd->callback(id, fd, error, sfd->user_data);
-		FREE(sfd);
-		yahoo_packet_free(pkt);
-		inputs = y_list_remove(inputs, yid);
-		FREE(yid);
-		return;
-	}
-
-	yid->fd = fd;
-	yahoo_send_packet(yid, pkt, 8);
+	struct send_file_data *sfd = yahoo_get_active_transfer(id);
+
+	sfd->data = data;
+
+	yid = find_input_by_id_and_type(client_id, YAHOO_CONNECTION_PAGER);
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER,
+		YPACKET_STATUS_DEFAULT, yid->yd->session_id);
+
+	snprintf(resp, sizeof(resp), "%d", response);
+
+	yahoo_packet_hash(pkt, 1, yid->yd->user);
+	yahoo_packet_hash(pkt, 5, sfd->who);
+	yahoo_packet_hash(pkt, 265, sfd->id);
+	yahoo_packet_hash(pkt, 222, resp);
+
+	yahoo_send_packet(yid, pkt, 0);
+
 	yahoo_packet_free(pkt);
 
-	snprintf((char *)buff, sizeof(buff), "29");
-	buff[2] = 0xc0;
-	buff[3] = 0x80;
+	if(response == YAHOO_FILE_TRANSFER_REJECT)
+		yahoo_remove_active_transfer(sfd);
+}
+
+static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over)
+{
+	struct send_file_data *sfd;
+	struct yahoo_data *yd = yid->yd;
 	
-	write(yid->fd, buff, 4);
-
-/*	YAHOO_CALLBACK(ext_yahoo_add_handler)(nyd->fd, YAHOO_INPUT_READ); */
-
-	sfd->callback(id, fd, error, sfd->user_data);
-	FREE(sfd);
-	inputs = y_list_remove(inputs, yid);
-	/*
-	while(yahoo_tcp_readline(buff, sizeof(buff), nyd->fd) > 0) {
-		if(!strcmp(buff, ""))
-			break;
-	}
-
-	*/
-	yahoo_input_close(yid);
-}
-
-void yahoo_send_file(int id, const char *who, const char *msg, 
-		const char *name, unsigned long size, 
-		yahoo_get_fd_callback callback, void *data)
+	sfd = yahoo_get_active_transfer_with_yid(yid);
+
+	if (!sfd) {
+		LOG(("Something funny happened. yid %p has no sfd.\n", yid));
+		return;
+	}
+
+	/* 
+	 * We want to handle only the complete data with HEAD since we don't
+	 * want a situation where both the GET and HEAD are active.
+	 * With SEND, we really can't do much with partial response
+	 */
+	if ((sfd->state == FT_STATE_HEAD || sfd->state == FT_STATE_SEND) 
+			&& !over)
+		return;
+
+	if (sfd->state == FT_STATE_HEAD) {
+		/* Do a GET */
+		char url[256];
+		char buff[1024];
+		char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;
+
+		struct yahoo_input_data *yid_ft = 
+			y_new0(struct yahoo_input_data, 1);
+        
+		yid_ft->yd = yid->yd;
+		yid_ft->type = YAHOO_CONNECTION_FT;
+        
+		inputs = y_list_prepend(inputs, yid_ft);
+		sfd->yid = yid_ft;
+		sfd->state = FT_STATE_RECV;
+
+		token_enc = yahoo_urlencode(sfd->token);
+		sender_enc = yahoo_urlencode(sfd->who);
+		recv_enc = yahoo_urlencode(yd->user);
+        
+		snprintf(url, sizeof(url), 
+			"http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
+			token_enc, sender_enc, recv_enc);
+
+		snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y,
+			yd->cookie_t);
+
+
+		yahoo_http_get(yd->client_id, url, buff, 1, 1,
+			_yahoo_http_connected, yid_ft);
+
+		FREE(token_enc);
+		FREE(sender_enc);
+		FREE(recv_enc);
+	}
+	else if (sfd->state == FT_STATE_RECV || 
+		sfd->state == FT_STATE_RECV_START) {
+
+		unsigned char *data_begin = NULL;
+
+		if (yid->rxlen == 0)
+			yahoo_remove_active_transfer(sfd);
+
+		if (sfd->state != FT_STATE_RECV_START &&
+			(data_begin = 
+				(unsigned char *)strstr((char *)yid->rxqueue,
+				"\r\n\r\n"))) {
+
+			sfd->state = FT_STATE_RECV_START;
+
+			yid->rxlen -= 4+(data_begin-yid->rxqueue)/sizeof(char);
+			data_begin += 4;
+
+			if (yid->rxlen > 0)
+				YAHOO_CALLBACK(ext_yahoo_got_ft_data) 
+					(yd->client_id, data_begin,
+					yid->rxlen, sfd->data);
+		}
+		else if (sfd->state == FT_STATE_RECV_START)
+			YAHOO_CALLBACK(ext_yahoo_got_ft_data) (yd->client_id,
+				yid->rxqueue, yid->rxlen, sfd->data);
+
+		FREE(yid->rxqueue);
+		yid->rxqueue = NULL;
+		yid->rxlen = 0;
+	}
+	else if (sfd->state == FT_STATE_SEND) {
+		/* Sent file completed */
+		int len = 0;
+		char *off = strstr((char *)yid->rxqueue, "Content-Length: ");
+
+		if (off) {
+			off += 16;
+			len = atoi(off);
+		}
+
+		if (len < sfd->size)
+			YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+				(yd->client_id,
+				YAHOO_FILE_TRANSFER_FAILED, sfd->data);
+		else
+			YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
+				(yd->client_id,
+				YAHOO_FILE_TRANSFER_DONE, sfd->data);
+
+		yahoo_remove_active_transfer(sfd);
+	}
+}
+
+/* End File Transfer */
+
+enum yahoo_status yahoo_current_status(int id)
 {
 	struct yahoo_data *yd = find_conn_by_id(id);
-	struct yahoo_input_data *yid;
-	struct yahoo_server_settings *yss;
-	struct yahoo_packet *pkt = NULL;
-	char size_str[10];
-	long content_length=0;
-	unsigned char buff[1024];
-	char url[255];
-	struct send_file_data *sfd;
-
-	if(!yd)
-		return;
-
-	yss = yd->server_settings;
-
-	yid = y_new0(struct yahoo_input_data, 1);
-	yid->yd = yd;
-	yid->type = YAHOO_CONNECTION_FT;
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANSFER, YAHOO_STATUS_AVAILABLE, yd->session_id);
-
-	snprintf(size_str, sizeof(size_str), "%ld", size);
-
-	yahoo_packet_hash(pkt, 0, yd->user);
-	yahoo_packet_hash(pkt, 5, who);
-	yahoo_packet_hash(pkt, 14, msg);
-	yahoo_packet_hash(pkt, 27, name);
-	yahoo_packet_hash(pkt, 28, size_str);
-
-	content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt);
-
-	snprintf(url, sizeof(url), "http://%s:%d/notifyft", 
-			yss->filetransfer_host, yss->filetransfer_port);
-	snprintf((char *)buff, sizeof(buff), "Y=%s; T=%s",
-			yd->cookie_y, yd->cookie_t);
-	inputs = y_list_prepend(inputs, yid);
-
-	sfd = y_new0(struct send_file_data, 1);
-	sfd->pkt = pkt;
-	sfd->callback = callback;
-	sfd->user_data = data;
-	yahoo_http_post(yid->yd->client_id, url, (char *)buff, content_length+4+size,
-			_yahoo_send_file_connected, sfd);
-}
-
-
-enum yahoo_status yahoo_current_status(int id)
-{
-	struct yahoo_data *yd = find_conn_by_id(id);
-	if(!yd)
+	if (!yd)
 		return YAHOO_STATUS_OFFLINE;
 	return yd->current_status;
 }
 
-const YList * yahoo_get_buddylist(int id)
+const YList *yahoo_get_buddylist(int id)
 {
 	struct yahoo_data *yd = find_conn_by_id(id);
-	if(!yd)
+	if (!yd)
 		return NULL;
 	return yd->buddies;
 }
 
-const YList * yahoo_get_ignorelist(int id)
+const YList *yahoo_get_ignorelist(int id)
 {
 	struct yahoo_data *yd = find_conn_by_id(id);
-	if(!yd)
+	if (!yd)
 		return NULL;
 	return yd->ignore;
 }
 
-const YList * yahoo_get_identities(int id)
+const YList *yahoo_get_identities(int id)
 {
 	struct yahoo_data *yd = find_conn_by_id(id);
-	if(!yd)
+	if (!yd)
 		return NULL;
 	return yd->identities;
 }
 
-const char * yahoo_get_cookie(int id, const char *which)
+const char *yahoo_get_cookie(int id, const char *which)
 {
 	struct yahoo_data *yd = find_conn_by_id(id);
-	if(!yd)
+	if (!yd)
 		return NULL;
-	if(!strncasecmp(which, "y", 1))
+	if (!strncasecmp(which, "y", 1))
 		return yd->cookie_y;
-	if(!strncasecmp(which, "t", 1))
+	if (!strncasecmp(which, "b", 1))
+		return yd->cookie_b;
+	if (!strncasecmp(which, "t", 1))
 		return yd->cookie_t;
-	if(!strncasecmp(which, "c", 1))
+	if (!strncasecmp(which, "c", 1))
 		return yd->cookie_c;
-	if(!strncasecmp(which, "login", 5))
+	if (!strncasecmp(which, "login", 5))
 		return yd->login_cookie;
 	return NULL;
 }
 
-void yahoo_get_url_handle(int id, const char *url, 
-		yahoo_get_url_handle_callback callback, void *data)
-{
-	struct yahoo_data *yd = find_conn_by_id(id);
-	if(!yd)
-		return;
-
-	yahoo_get_url_fd(id, url, yd, callback, data);
-}
-
-const char * yahoo_get_profile_url( void )
+const char *yahoo_get_profile_url(void)
 {
 	return profile_url;
 }
-
Index: protocols/yahoo/yahoo.c
===================================================================
--- protocols/yahoo/yahoo.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -2,5 +2,5 @@
  * libyahoo2 wrapper to BitlBee
  *
- * Mostly Copyright 2004 Wilmer van der Gaast <wilmer@gaast.net>
+ * Mostly Copyright 2004-2010 Wilmer van der Gaast <wilmer@gaast.net>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -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" );
@@ -153,5 +158,5 @@
 	
 	while( ic->groupchats )
-		imcb_chat_free( ic->groupchats );
+		imcb_chat_free( ic->groupchats->data );
 	
 	for( l = yd->buddygroups; l; l = l->next )
@@ -266,6 +271,30 @@
 {
 	struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
-	
-	yahoo_add_buddy( yd->y2_id, who, group ? group : BYAHOO_DEFAULT_GROUP, NULL );
+	bee_user_t *bu;
+	
+	if( group && ( bu = bee_user_by_handle( ic->bee, ic, who ) ) && bu->group )
+	{
+		GSList *bgl;
+		
+		/* If the person is in our list already, this is a group change. */
+		yahoo_change_buddy_group( yd->y2_id, who, bu->group->name, group );
+		
+		/* No idea how often people have people in multiple groups and
+		   BitlBee doesn't currently support this anyway .. but keep
+		   this struct up-to-date for now. */
+		for( bgl = yd->buddygroups; bgl; bgl = bgl->next )
+		{
+			struct byahoo_buddygroups *bg = bgl->data;
+			
+			if( g_strcasecmp( bg->buddy, who ) == 0 &&
+			    g_strcasecmp( bg->group, bu->group->name ) == 0 )
+			{
+				g_free( bg->group );
+				bg->group = g_strdup( group );
+			}
+		}
+	}
+	else
+		yahoo_add_buddy( yd->y2_id, who, group ? group : BYAHOO_DEFAULT_GROUP, NULL );
 }
 
@@ -336,5 +365,5 @@
 	struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
 	
-	yahoo_accept_buddy_ymsg13( yd->y2_id, NULL, who );
+	yahoo_confirm_buddy( yd->y2_id, who, 0, "" );
 }
 
@@ -343,5 +372,5 @@
 	struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;
 	
-	yahoo_reject_buddy_ymsg13( yd->y2_id, NULL, who, NULL );
+	yahoo_confirm_buddy( yd->y2_id, who, 1, "" );
 }
 
@@ -416,5 +445,5 @@
 	}
 	
-	d->callback( d->fd, 0, d->data );
+	d->callback( NULL + d->fd, 0, d->data );
 	g_free( d );
 }
@@ -436,5 +465,5 @@
 		return FALSE;
 	
-	yahoo_read_ready( d->id, d->fd, d->data );
+	yahoo_read_ready( d->id, NULL + d->fd, d->data );
 	
 	return TRUE;
@@ -453,5 +482,5 @@
 	struct byahoo_write_ready_data *d = data;
 	
-	return yahoo_write_ready( d->id, d->fd, d->data );
+	return yahoo_write_ready( d->id, NULL + d->fd, d->data );
 }
 
@@ -482,5 +511,5 @@
 	{
 		char *errstr;
-		int allow_reconnect = TRUE;
+		int allow_reconnect = FALSE;
 		
 		yd->logged_in = FALSE;
@@ -492,11 +521,13 @@
 		else if( succ == YAHOO_LOGIN_LOCK )
 			errstr = "Yahoo! account locked";
+		else if( succ == 1236 )
+			errstr = "Yahoo! account locked or machine temporarily banned";
 		else if( succ == YAHOO_LOGIN_DUPL )
+			errstr = "Logged in on a different machine or device";
+		else if( succ == YAHOO_LOGIN_SOCK )
 		{
-			errstr = "Logged in on a different machine or device";
-			allow_reconnect = FALSE;
+			errstr = "Socket problem";
+			allow_reconnect = TRUE;
 		}
-		else if( succ == YAHOO_LOGIN_SOCK )
-			errstr = "Socket problem";
 		else
 			errstr = "Unknown error";
@@ -601,15 +632,14 @@
 		flags = 0;
 		break;
-	case YAHOO_STATUS_NOTIFY:
-		state_string = "Notify";
-		break;
 	}
 	
 	imcb_buddy_status( ic, who, flags, state_string, msg );
 	
-	/* Not implemented yet...
 	if( stat == YAHOO_STATUS_IDLE )
-		imcb_buddy_times( ic, who, 0, away );
-	*/
+		imcb_buddy_times( ic, who, 0, idle );
+}
+
+void ext_yahoo_got_buzz( int id, const char *me, const char *who, long tm )
+{
 }
 
@@ -627,11 +657,18 @@
 }
 
-void ext_yahoo_got_file( int id,
-                         const char *ignored,
-                         const char *who, const char *url, long expires, const char *msg, const char *fname, unsigned long fesize )
+void ext_yahoo_got_file( int id, const char *ignored, const char *who, const char *msg,
+                         const char *fname, unsigned long fesize, char *trid )
 {
 	struct im_connection *ic = byahoo_get_ic_by_id( id );
 	
 	imcb_log( ic, "Got a file transfer (file = %s) from %s. Ignoring for now due to lack of support.", fname, who );
+}
+
+void ext_yahoo_got_ft_data( int id, const unsigned char *in, int len, void *data )
+{
+}
+
+void ext_yahoo_file_transfer_done( int id, int result, void *data )
+{
 }
 
@@ -646,5 +683,5 @@
 }
 
-void ext_yahoo_system_message( int id, const char *msg )
+void ext_yahoo_system_message( int id, const char *me, const char *who, const char *msg )
 {
 	struct im_connection *ic = byahoo_get_ic_by_id( id );
@@ -668,7 +705,8 @@
 
 /* TODO: Clear up the mess of inp and d structures */
-int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *data )
+int ext_yahoo_add_handler( int id, void *fd_, yahoo_input_condition cond, void *data )
 {
 	struct byahoo_input_data *inp = g_new0( struct byahoo_input_data, 1 );
+	int fd = (long) fd_;
 	
 	if( cond == YAHOO_INPUT_READ )
@@ -681,5 +719,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,15 +730,15 @@
 		
 		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
 	{
 		g_free( inp );
-		return( -1 );
+		return -1;
 		/* Panic... */
 	}
 	
 	byahoo_inputs = g_slist_append( byahoo_inputs, inp );
-	return( inp->h );
+	return inp->h;
 }
 
@@ -726,5 +764,5 @@
 }
 
-int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_callback callback, void *data )
+int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_callback callback, void *data, int use_ssl )
 {
 	struct byahoo_connect_callback_data *d;
@@ -742,5 +780,33 @@
 	d->id = id;
 	
-	return( fd );
+	return fd;
+}
+
+char *ext_yahoo_get_ip_addr( const char *domain )
+{
+	return NULL;
+}
+
+int ext_yahoo_write( void *fd, char *buf, int len )
+{
+	return write( (long) fd, buf, len );
+}
+
+int ext_yahoo_read( void *fd, char *buf, int len )
+{
+	return read( (long) fd, buf, len );
+}
+
+void ext_yahoo_close( void *fd )
+{
+	close( (long) fd );
+}
+
+void ext_yahoo_got_buddy_change_group( int id, const char *me, const char *who,
+                                       const char *old_group, const char *new_group )
+{
+	struct im_connection *ic = byahoo_get_ic_by_id( id );
+	
+	imcb_add_buddy( ic, who, new_group );
 }
 
@@ -749,39 +815,4 @@
 int ext_yahoo_connect(const char *host, int port)
 {
-#if 0
-	struct sockaddr_in serv_addr;
-	static struct hostent *server;
-	static char last_host[256];
-	int servfd;
-	char **p;
-
-	if(last_host[0] || g_strcasecmp(last_host, host)!=0) {
-		if(!(server = gethostbyname(host))) {
-			return -1;
-		}
-		strncpy(last_host, host, 255);
-	}
-
-	if((servfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
-		return -1;
-	}
-
-	for (p = server->h_addr_list; *p; p++)
-	{
-		memset(&serv_addr, 0, sizeof(serv_addr));
-		serv_addr.sin_family = AF_INET;
-		memcpy(&serv_addr.sin_addr.s_addr, *p, server->h_length);
-		serv_addr.sin_port = htons(port);
-
-		if(connect(servfd, (struct sockaddr *) &serv_addr, 
-					sizeof(serv_addr)) == -1) {
-			return -1;
-		} else {
-			return servfd;
-		}
-	}
-
-	closesocket(servfd);
-#endif
 	return -1;
 }
@@ -790,9 +821,13 @@
 {
 	struct byahoo_conf_invitation *inv = data;
-	struct groupchat *b;
-	
-	for( b = inv->ic->groupchats; b; b = b->next )
+	struct groupchat *b = NULL;
+	GSList *l;
+	
+	for( l = inv->ic->groupchats; l; l = l->next )
+	{
+		b = l->data;
 		if( b == inv->c )
 			break;
+	}
 	
 	if( b != NULL )
@@ -827,4 +862,8 @@
 	char txt[1024];
 	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 ) );
@@ -856,7 +895,5 @@
 {
 	struct im_connection *ic = byahoo_get_ic_by_id( id );
-	struct groupchat *c;
-	
-	for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
+	struct groupchat *c = bee_chat_by_title( ic->bee, ic, room );
 	
 	if( c )
@@ -868,7 +905,5 @@
 {
 	struct im_connection *ic = byahoo_get_ic_by_id( id );
-	struct groupchat *c;
-	
-	for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
+	struct groupchat *c = bee_chat_by_title( ic->bee, ic, room );
 	
 	if( c )
@@ -880,7 +915,5 @@
 	struct im_connection *ic = byahoo_get_ic_by_id( id );
 	char *m = byahoo_strip( msg );
-	struct groupchat *c;
-	
-	for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
+	struct groupchat *c = bee_chat_by_title( ic->bee, ic, room );
 	
 	if( c )
@@ -893,5 +926,5 @@
 }
 
-void ext_yahoo_chat_join( int id, const char *who, const char *room, const char *topic, YList *members, int fd )
+void ext_yahoo_chat_join( int id, const char *who, const char *room, const char *topic, YList *members, void *fd )
 {
 }
@@ -921,16 +954,9 @@
 }
 
-void ext_yahoo_contact_auth_request( int id, const char *myid, const char *who, const char *msg )
-{
-	struct im_connection *ic = byahoo_get_ic_by_id( id );
-	
-	imcb_ask_auth( ic, who, NULL );
-}
-
 void ext_yahoo_contact_added( int id, const char *myid, const char *who, const char *msg )
 {
 	struct im_connection *ic = byahoo_get_ic_by_id( id );
 	
-	imcb_add_buddy( ic, (char*) who, NULL );
+	imcb_ask_auth( ic, who, msg );
 }
 
@@ -939,5 +965,5 @@
 }
 
-void ext_yahoo_game_notify( int id, const char *me, const char *who, int stat )
+void ext_yahoo_game_notify( int id, const char *me, const char *who, int stat, const char *msg )
 {
 }
@@ -984,5 +1010,5 @@
 }
 
-void ext_yahoo_got_ping( int id, const char *msg)
+void ext_yahoo_got_ping( int id, const char *msg )
 {
 }
Index: protocols/yahoo/yahoo2.h
===================================================================
--- protocols/yahoo/yahoo2.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo2.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -51,16 +51,15 @@
 #include "yahoo2_types.h"
 
-/* returns the socket descriptor for a given pager connection. shouldn't be needed */
-int  yahoo_get_fd(int id);
+/* returns the socket descriptor object for a given pager connection. shouldn't be needed */
+	void *yahoo_get_fd(int id);
 
 /* says how much logging to do */
 /* see yahoo2_types.h for the different values */
-int  yahoo_set_log_level(enum yahoo_log_level level);
-enum yahoo_log_level  yahoo_get_log_level( void );
+	int yahoo_set_log_level(enum yahoo_log_level level);
+	enum yahoo_log_level yahoo_get_log_level(void);
 
 /* these functions should be self explanatory */
 /* who always means the buddy you're acting on */
 /* id is the successful value returned by yahoo_init */
-
 
 /* init returns a connection id used to identify the connection hereon */
@@ -88,74 +87,91 @@
  * You should set at least local_host if you intend to use webcams
  */
-int  yahoo_init_with_attributes(const char *username, const char *password, ...);
+	int yahoo_init_with_attributes(const char *username,
+		const char *password, ...);
 
 /* yahoo_init does the same as yahoo_init_with_attributes, assuming defaults
  * for all attributes */
-int  yahoo_init(const char *username, const char *password);
-
-
+	int yahoo_init(const char *username, const char *password);
 
 /* release all resources held by this session */
 /* you need to call yahoo_close for a session only if
  * yahoo_logoff is never called for it (ie, it was never logged in) */
-void yahoo_close(int id);
+	void yahoo_close(int id);
 /* login logs in to the server */
 /* initial is of type enum yahoo_status.  see yahoo2_types.h */
-void yahoo_login(int id, int initial);
-void yahoo_logoff(int id);
+	void yahoo_login(int id, int initial);
+	void yahoo_logoff(int id);
 /* reloads status of all buddies */
-void yahoo_refresh(int id);
+	void yahoo_refresh(int id);
 /* activates/deactivates an identity */
-void yahoo_set_identity_status(int id, const char * identity, int active);
+	void yahoo_set_identity_status(int id, const char *identity,
+		int active);
 /* regets the entire buddy list from the server */
-void yahoo_get_list(int id);
+	void yahoo_get_list(int id);
 /* download buddy contact information from your yahoo addressbook */
-void yahoo_get_yab(int id);
+	void yahoo_get_yab(int id);
 /* add/modify an address book entry.  if yab->dbid is set, it will */
 /* modify that entry else it creates a new entry */
-void yahoo_set_yab(int id, struct yab * yab);
-void yahoo_keepalive(int id);
-void yahoo_chat_keepalive(int id);
+	void yahoo_set_yab(int id, struct yab *yab);
+	void yahoo_keepalive(int id);
+	void yahoo_chat_keepalive(int id);
 
 /* from is the identity you're sending from.  if NULL, the default is used */
 /* utf8 is whether msg is a utf8 string or not. */
-void yahoo_send_im(int id, const char *from, const char *who, const char *msg, int utf8, int picture);
+	void yahoo_send_im(int id, const char *from, const char *who,
+		const char *msg, int utf8, int picture);
+	void yahoo_send_buzz(int id, const char *from, const char *who);
 /* if type is true, send typing notice, else send stopped typing notice */
-void yahoo_send_typing(int id, const char *from, const char *who, int typ);
+	void yahoo_send_typing(int id, const char *from, const char *who,
+		int typ);
 
 /* used to set away/back status. */
 /* away says whether the custom message is an away message or a sig */
-void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away);
-
-void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg);
-void yahoo_remove_buddy(int id, const char *who, const char *group);
-void yahoo_reject_buddy(int id, const char *who, const char *msg);
-void yahoo_stealth_buddy(int id, const char *who, int unstealth);
+	void yahoo_set_away(int id, enum yahoo_status state, const char *msg,
+		int away);
+
+	void yahoo_add_buddy(int id, const char *who, const char *group,
+		const char *msg);
+	void yahoo_remove_buddy(int id, const char *who, const char *group);
+	void yahoo_confirm_buddy(int id, const char *who, int reject,
+		const char *msg);
+	void yahoo_stealth_buddy(int id, const char *who, int unstealth);
 /* if unignore is true, unignore, else ignore */
-void yahoo_ignore_buddy(int id, const char *who, int unignore);
-void yahoo_change_buddy_group(int id, const char *who, const char *old_group, const char *new_group);
-void yahoo_group_rename(int id, const char *old_group, const char *new_group);
-
-void yahoo_conference_invite(int id, const char * from, YList *who, const char *room, const char *msg);
-void yahoo_conference_addinvite(int id, const char * from, const char *who, const char *room, const YList * members, const char *msg);
-void yahoo_conference_decline(int id, const char * from, YList *who, const char *room, const char *msg);
-void yahoo_conference_message(int id, const char * from, YList *who, const char *room, const char *msg, int utf8);
-void yahoo_conference_logon(int id, const char * from, YList *who, const char *room);
-void yahoo_conference_logoff(int id, const char * from, YList *who, const char *room);
+	void yahoo_ignore_buddy(int id, const char *who, int unignore);
+	void yahoo_change_buddy_group(int id, const char *who,
+		const char *old_group, const char *new_group);
+	void yahoo_group_rename(int id, const char *old_group,
+		const char *new_group);
+
+	void yahoo_conference_invite(int id, const char *from, YList *who,
+		const char *room, const char *msg);
+	void yahoo_conference_addinvite(int id, const char *from,
+		const char *who, const char *room, const YList *members,
+		const char *msg);
+	void yahoo_conference_decline(int id, const char *from, YList *who,
+		const char *room, const char *msg);
+	void yahoo_conference_message(int id, const char *from, YList *who,
+		const char *room, const char *msg, int utf8);
+	void yahoo_conference_logon(int id, const char *from, YList *who,
+		const char *room);
+	void yahoo_conference_logoff(int id, const char *from, YList *who,
+		const char *room);
 
 /* Get a list of chatrooms */
-void yahoo_get_chatrooms(int id,int chatroomid);
+	void yahoo_get_chatrooms(int id, int chatroomid);
 /* join room with specified roomname and roomid */
-void yahoo_chat_logon(int id, const char *from, const char *room, const char *roomid);
+	void yahoo_chat_logon(int id, const char *from, const char *room,
+		const char *roomid);
 /* Send message "msg" to room with specified roomname, msgtype is 1-normal message or 2-/me mesage */
-void yahoo_chat_message(int id, const char *from, const char *room, const char *msg, const int msgtype, const int utf8);
+	void yahoo_chat_message(int id, const char *from, const char *room,
+		const char *msg, const int msgtype, const int utf8);
 /* Log off chat */
-void yahoo_chat_logoff(int id, const char *from);
+	void yahoo_chat_logoff(int id, const char *from);
 
 /* requests a webcam feed */
 /* who is the person who's webcam you would like to view */
 /* if who is null, then you're the broadcaster */
-void yahoo_webcam_get_feed(int id, const char *who);
-void yahoo_webcam_close_feed(int id, const char *who);
+	void yahoo_webcam_get_feed(int id, const char *who);
+	void yahoo_webcam_close_feed(int id, const char *who);
 
 /* sends an image when uploading */
@@ -163,13 +179,14 @@
 /* in bytes. The timestamp is the time in milliseconds since we started the */
 /* webcam. */
-void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, unsigned int timestamp);
+	void yahoo_webcam_send_image(int id, unsigned char *image,
+		unsigned int length, unsigned int timestamp);
 
 /* this function should be called if we want to allow a user to watch the */
 /* webcam. Who is the user we want to accept. */
 /* Accept user (accept = 1), decline user (accept = 0) */
-void yahoo_webcam_accept_viewer(int id, const char* who, int accept);
+	void yahoo_webcam_accept_viewer(int id, const char *who, int accept);
 
 /* send an invitation to a user to view your webcam */
-void yahoo_webcam_invite(int id, const char *who);
+	void yahoo_webcam_invite(int id, const char *who);
 
 /* will set up a connection and initiate file transfer.
@@ -177,10 +194,20 @@
  * the file data to
  */
-void yahoo_send_file(int id, const char *who, const char *msg, const char *name, unsigned long size,
+	void yahoo_send_file(int id, const char *who, const char *msg,
+		const char *name, unsigned long size,
 		yahoo_get_fd_callback callback, void *data);
 
+/*
+ * Respond to a file transfer request. Be sure to provide the callback data
+ * since that is your only chance to recognize future callbacks
+ */
+	void yahoo_send_file_transfer_response(int client_id, int response,
+		char *id, void *data);
+
+
 /* send a search request
  */
-void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo_search_gender g, enum yahoo_search_agerange ar,
+	void yahoo_search(int id, enum yahoo_search_type t, const char *text,
+		enum yahoo_search_gender g, enum yahoo_search_agerange ar,
 		int photo, int yahoo_only);
 
@@ -190,33 +217,26 @@
  * where the above three are passed to ext_yahoo_got_search_result
  */
-void yahoo_search_again(int id, int start);
-
-/* returns a socket fd to a url for downloading a file. */
-void yahoo_get_url_handle(int id, const char *url, 
-		yahoo_get_url_handle_callback callback, void *data);
+	void yahoo_search_again(int id, int start);
 
 /* these should be called when input is available on a fd */
 /* registered by ext_yahoo_add_handler */
 /* if these return negative values, errno may be set */
-int  yahoo_read_ready(int id, int fd, void *data);
-int  yahoo_write_ready(int id, int fd, void *data);
+	int yahoo_read_ready(int id, void *fd, void *data);
+	int yahoo_write_ready(int id, void *fd, void *data);
 
 /* utility functions. these do not hit the server */
-enum yahoo_status yahoo_current_status(int id);
-const YList * yahoo_get_buddylist(int id);
-const YList * yahoo_get_ignorelist(int id);
-const YList * yahoo_get_identities(int id);
+	enum yahoo_status yahoo_current_status(int id);
+	const YList *yahoo_get_buddylist(int id);
+	const YList *yahoo_get_ignorelist(int id);
+	const YList *yahoo_get_identities(int id);
 /* 'which' could be y, t, c or login.  This may change in later versions. */
-const char  * yahoo_get_cookie(int id, const char *which);
+	const char *yahoo_get_cookie(int id, const char *which);
 
 /* returns the url used to get user profiles - you must append the user id */
 /* as of now this is http://profiles.yahoo.com/ */
 /* You'll have to do urlencoding yourself, but see yahoo_httplib.h first */
-const char  * yahoo_get_profile_url( void );
-
-void yahoo_buddyicon_request(int id, const char *who);
-
-void yahoo_accept_buddy_ymsg13(int,const char*,const char*);
-void yahoo_reject_buddy_ymsg13(int,const char*,const char*,const char*);
+	const char *yahoo_get_profile_url(void);
+
+	void yahoo_buddyicon_request(int id, const char *who);
 
 #include "yahoo_httplib.h"
@@ -225,4 +245,3 @@
 }
 #endif
-
 #endif
Index: protocols/yahoo/yahoo2_callbacks.h
===================================================================
--- protocols/yahoo/yahoo2_callbacks.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo2_callbacks.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -30,5 +30,4 @@
  */
 
-
 #ifndef YAHOO2_CALLBACKS_H
 #define YAHOO2_CALLBACKS_H
@@ -46,9 +45,9 @@
  */
 
-typedef enum {
-	YAHOO_INPUT_READ = 1 << 0,
-	YAHOO_INPUT_WRITE = 1 << 1,
-	YAHOO_INPUT_EXCEPTION = 1 << 2
-} yahoo_input_condition;
+	typedef enum {
+		YAHOO_INPUT_READ = 1 << 0,
+		YAHOO_INPUT_WRITE = 1 << 1,
+		YAHOO_INPUT_EXCEPTION = 1 << 2
+	} yahoo_input_condition;
 
 /*
@@ -56,5 +55,6 @@
  * 
  * Params:
- *     fd    - The file descriptor that has been connected, or -1 on error
+ *     fd    - The file descriptor object that has been connected, or NULL on 
+ *             error
  *     error - The value of errno set by the call to connect or 0 if no error
  *	       Set both fd and error to 0 if the connect was cancelled by the
@@ -63,6 +63,6 @@
  *	       function
  */
-typedef void (*yahoo_connect_callback)(int fd, int error, void *callback_data);
-
+	typedef void (*yahoo_connect_callback) (void *fd, int error,
+		void *callback_data);
 
 /*
@@ -94,6 +94,6 @@
  * 	url  - url to reactivate account if locked
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response)(int id, int succ, const char *url);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response) (int id, int succ,
+		const char *url);
 
 /*
@@ -104,6 +104,5 @@
  * 	buds - the buddy list
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies)(int id, YList * buds);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies) (int id, YList *buds);
 
 /*
@@ -114,6 +113,5 @@
  * 	igns - the ignore list
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore)(int id, YList * igns);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore) (int id, YList *igns);
 
 /*
@@ -124,6 +122,5 @@
  * 	ids  - the identity list
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities)(int id, YList * ids);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities) (int id, YList *ids);
 
 /*
@@ -133,6 +130,5 @@
  * 	id   - the id that identifies the server connection
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies)(int id);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies) (int id);
 
 /*
@@ -143,6 +139,6 @@
  *  errormsg - optional error message
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping)(int id, const char *errormsg);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping) (int id,
+		const char *errormsg);
 
 /*
@@ -159,6 +155,19 @@
  *	TODO: add support for pager, chat, and game states
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed)(int id, const char *who, int stat, const char *msg, int away, int idle, int mobile);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed) (int id,
+		const char *who, int stat, const char *msg, int away, int idle,
+		int mobile);
+
+/*
+ * Name: ext_yahoo_got_buzz
+ * 	Called when remote user sends you a buzz.
+ * Params:
+ * 	id   - the id that identifies the server connection
+ * 	me   - the identity the message was sent to
+ * 	who  - the handle of the remote user
+ * 	tm   - timestamp of message if offline
+ */
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buzz) (int id, const char *me,
+		const char *who, long tm);
 
 /*
@@ -177,6 +186,6 @@
  * 	utf8 - whether the message is encoded as utf8 or not
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im)(int id, const char *me, const char *who, const char *msg, long tm, int stat, int utf8);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im) (int id, const char *me,
+		const char *who, const char *msg, long tm, int stat, int utf8);
 
 /*
@@ -191,6 +200,7 @@
  *	members - the initial members of the conference (null terminated list)
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite)(int id, const char *me, const char *who, const char *room, const char *msg, YList *members);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite) (int id,
+		const char *me, const char *who, const char *room,
+		const char *msg, YList *members);
 
 /*
@@ -204,6 +214,7 @@
  * 	msg  - the declining message
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline)(int id, const char *me, const char *who, const char *room, const char *msg);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline) (int id,
+		const char *me, const char *who, const char *room,
+		const char *msg);
 
 /*
@@ -216,6 +227,6 @@
  * 	room - the room joined
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin)(int id, const char *me, const char *who, const char *room);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin) (int id,
+		const char *me, const char *who, const char *room);
 
 /*
@@ -228,6 +239,6 @@
  * 	room - the room left
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave)(int id, const char *me, const char *who, const char *room);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave) (int id,
+		const char *me, const char *who, const char *room);
 
 /*
@@ -238,6 +249,6 @@
  * 	xml     - ?
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml)(int id, const char *xml);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml) (int id,
+		const char *xml);
 
 /*
@@ -252,8 +263,8 @@
  *	members - the initial members of the chatroom (null terminated YList 
  *	          of yahoo_chat_member's) Must be freed by the client
- *	fd	- the socket where the connection is coming from (for tracking)
- */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join)(int id, const char *me, const char *room, const char *topic, YList *members, int fd);
-
+ *	fd	- the object where the connection is coming from (for tracking)
+ */
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join) (int id, const char *me,
+		const char *room, const char *topic, YList *members, void *fd);
 
 /*
@@ -266,6 +277,7 @@
  * 	who  - the user who has joined, Must be freed by the client
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin)(int id, const char *me, const char *room, struct yahoo_chat_member *who);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin) (int id,
+		const char *me, const char *room,
+		struct yahoo_chat_member *who);
 
 /*
@@ -278,6 +290,6 @@
  * 	who  - the user who has left (Just the User ID)
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave)(int id, const char *me, const char *room, const char *who);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave) (int id,
+		const char *me, const char *room, const char *who);
 
 /*
@@ -294,6 +306,7 @@
  * 	utf8 - whether the message is utf8 encoded or not
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message)(int id, const char *me, const char *who, const char *room, const char *msg, int msgtype, int utf8);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message) (int id,
+		const char *me, const char *who, const char *room,
+		const char *msg, int msgtype, int utf8);
 
 /*
@@ -310,6 +323,6 @@
  *	nothing.
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout)(int id, const char *me);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout) (int id,
+		const char *me);
 
 /*
@@ -327,6 +340,6 @@
  *	nothing.
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror)(int id, const char *me);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror) (int id,
+		const char *me);
 
 /*
@@ -341,6 +354,7 @@
  * 	utf8 - whether the message is utf8 encoded or not
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message)(int id, const char *me, const char *who, const char *room, const char *msg, int utf8);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message) (int id,
+		const char *me, const char *who, const char *room,
+		const char *msg, int utf8);
 
 /*
@@ -351,24 +365,40 @@
  * 	me   - the identity the file was sent to
  * 	who  - the user who sent the file
- * 	url  - the file url
- * 	expires  - the expiry date of the file on the server (timestamp)
  * 	msg  - the message
  * 	fname- the file name if direct transfer
  * 	fsize- the file size if direct transfer
- */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_file)(int id, const char *me, const char *who, const char *url, long expires, const char *msg, const char *fname, unsigned long fesize);
-
-
-/*
- * Name: ext_yahoo_contact_auth_request
- * 	Called when a contact wants to add you to his/her contact list
- * Params:
- * 	id   - the id that identifies the server connection
- * 	myid - the identity s/he added
- * 	who  - who did it
- * 	msg  - any message sent
- */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_auth_request)(int id, const char *myid, const char *who, const char *msg);
-
+ * 	trid - transfer id. Unique for this transfer
+ *
+ * NOTE: Subsequent callbacks for file transfer do not send all of this
+ * information again since it is wasteful. Implementations are expected to
+ * save this information and supply it as callback data when the file or
+ * confirmation is sent
+ */
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_file) (int id, const char *me,
+		const char *who, const char *msg, const char *fname,
+		unsigned long fesize, char *trid);
+
+/*
+ * Name: ext_yahoo_got_ft_data
+ * 	Called multiple times when parts of the file are received
+ * Params:
+ * 	id   - the id that identifies the server connection
+ * 	in   - The data
+ * 	len  - Length of the data
+ * 	data - callback data
+ */
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ft_data) (int id,
+		const unsigned char *in, int len, void *data);
+
+/*
+ * Name: ext_yahoo_file_transfer_done
+ * 	File transfer is done
+ * Params:
+ * 	id     - the id that identifies the server connection
+ * 	result - To notify if it finished successfully or with a failure
+ * 	data   - callback data
+ */
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_file_transfer_done) (int id,
+		int result, void *data);
 
 /*
@@ -381,6 +411,6 @@
  * 	msg  - any message sent
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added)(int id, const char *myid, const char *who, const char *msg);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added) (int id,
+		const char *myid, const char *who, const char *msg);
 
 /*
@@ -392,6 +422,6 @@
  * 	msg  - any message sent
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected)(int id, const char *who, const char *msg);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected) (int id, const char *who,
+		const char *msg);
 
 /*
@@ -404,6 +434,6 @@
  * 	stat - 1 if typing, 0 if stopped typing
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify)(int id, const char *me, const char *who, int stat);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify) (int id,
+		const char *me, const char *who, int stat);
 
 /*
@@ -415,7 +445,8 @@
  * 	who  - the handle of the remote user
  * 	stat - 1 if game, 0 if stopped gaming
- */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify)(int id, const char *me, const char *who, int stat);
-
+ * 	msg  - game description and/or other text
+ */
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify) (int id, const char *me,
+		const char *who, int stat, const char *msg);
 
 /*
@@ -428,6 +459,6 @@
  * 	cnt  - mail count - 0 if new mail notification
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_mail_notify)(int id, const char *from, const char *subj, int cnt);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_mail_notify) (int id,
+		const char *from, const char *subj, int cnt);
 
 /*
@@ -436,7 +467,10 @@
  * Params:
  * 	id   - the id that identifies the server connection
+ * 	me   - the handle of the identity the notification is sent to
+ * 	who  - the source of the system message (there are different types)
  * 	msg  - the message
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message)(int id, const char *msg);
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message) (int id,
+		const char *me, const char *who, const char *msg);
 
 /*
@@ -450,5 +484,6 @@
  * 	checksum - the checksum of the icon content
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon)(int id, const char *me, const char *who, const char *url, int checksum);
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon) (int id,
+		const char *me, const char *who, const char *url, int checksum);
 
 /*
@@ -461,5 +496,6 @@
  * 	checksum - the checksum of the icon content
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum)(int id, const char *me,const char *who, int checksum);
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum) (int id,
+		const char *me, const char *who, int checksum);
 
 /*
@@ -471,5 +507,6 @@
  * 	who - the yahoo id of the buddy that requested the buddy icon
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request)(int id, const char *me, const char *who);
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request) (int id,
+		const char *me, const char *who);
 
 /*
@@ -480,5 +517,6 @@
  * 	url - remote url, the uploaded buddy icon can be fetched from
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded)(int id, const char *url);
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded) (int id,
+		const char *url);
 
 /*
@@ -505,8 +543,8 @@
  *	a timestamp to stay in sync
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image)(int id, const char * who,
-		const unsigned char *image, unsigned int image_size, unsigned int real_size,
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image) (int id,
+		const char *who, const unsigned char *image,
+		unsigned int image_size, unsigned int real_size,
 		unsigned int timestamp);
-
 
 /*
@@ -518,6 +556,6 @@
  * 	from - who the invitation is from
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite)(int id, const char *me, const char *from);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite) (int id,
+		const char *me, const char *from);
 
 /*
@@ -530,6 +568,6 @@
  *	accept - 0 (decline), 1 (accept)
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply)(int id, const char *me, const char *from, int accept);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply) (int id,
+		const char *me, const char *from, int accept);
 
 /*
@@ -545,6 +583,6 @@
  *	         4 = user does not have webcam online
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed)(int id, const char *who, int reason);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed) (int id,
+		const char *who, int reason);
 
 /*
@@ -560,6 +598,6 @@
  * 		   if you need to use the information, make a copy
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result)(int id, int found, int start, int total, YList *contacts);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result) (int id,
+		int found, int start, int total, YList *contacts);
 
 /*
@@ -572,6 +610,6 @@
  * 	num  - Which error is this
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_error)(int id, const char *err, int fatal, int num);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_error) (int id, const char *err,
+		int fatal, int num);
 
 /*
@@ -583,6 +621,6 @@
  *	connect - 0=disconnect 1=connect 2=request
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer)(int id, const char *who, int connect);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer) (int id,
+		const char *who, int connect);
 
 /*
@@ -593,6 +631,6 @@
  *	send - whether to send images or not
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request)(int id, int send);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request) (int id,
+		int send);
 
 /*
@@ -604,6 +642,5 @@
  * 	0
  */
-int YAHOO_CALLBACK_TYPE(ext_yahoo_log)(const char *fmt, ...);
-
+	int YAHOO_CALLBACK_TYPE(ext_yahoo_log) (const char *fmt, ...);
 
 /*
@@ -614,5 +651,5 @@
  * Params:
  * 	id   - the id that identifies the server connection
- * 	fd   - the fd on which to listen
+ * 	fd   - the fd object on which to listen
  * 	cond - the condition on which to call the callback
  * 	data - callback data to pass to yahoo_*_ready
@@ -620,6 +657,6 @@
  * Returns: a tag to be used when removing the handler
  */
-int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler)(int id, int fd, yahoo_input_condition cond, void *data);
-
+	int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler) (int id, void *fd,
+		yahoo_input_condition cond, void *data);
 
 /*
@@ -630,6 +667,5 @@
  * 	tag  - the handler tag to remove
  */
-void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler)(int id, int tag);
-
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler) (int id, int tag);
 
 /*
@@ -642,10 +678,9 @@
  * 	a unix file descriptor to the socket
  */
-int YAHOO_CALLBACK_TYPE(ext_yahoo_connect)(const char *host, int port);
-
+	int YAHOO_CALLBACK_TYPE(ext_yahoo_connect) (const char *host, int port);
 
 /*
  * Name: ext_yahoo_connect_async
- * 	Connect to a host:port asynchronously.  This function should return
+ * 	Connect to a host:port asynchronously. This function should return
  * 	immediately returing a tag used to identify the connection handler,
  * 	or a pre-connect error (eg: host name lookup failure).
@@ -660,9 +695,72 @@
  * 	callback - function to call when connect completes
  * 	callback_data - data to pass to the callback function
- * Returns:
- * 	a unix file descriptor to the socket
- */
-int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async)(int id, const char *host, int port, 
-		yahoo_connect_callback callback, void *callback_data);
+ * 	use_ssl - Whether we need an SSL connection
+ * Returns:
+ * 	a tag signifying the connection attempt
+ */
+	int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async) (int id,
+		const char *host, int port, yahoo_connect_callback callback,
+		void *callback_data, int use_ssl);
+
+/*
+ * Name: ext_yahoo_get_ip_addr
+ * 	get IP Address for a domain name
+ * Params:
+ * 	domain - Domain name
+ * Returns:
+ * 	Newly allocated string containing the IP Address in IPv4 notation
+ */
+	char *YAHOO_CALLBACK_TYPE(ext_yahoo_get_ip_addr) (const char *domain);
+
+/*
+ * Name: ext_yahoo_write
+ * 	Write data from the buffer into the socket for the specified connection
+ * Params:
+ * 	fd  - the file descriptor object that identifies this connection
+ * 	buf - Buffer to write the data from
+ * 	len - Length of the data
+ * Returns:
+ * 	Number of bytes written or -1 for error
+ */
+	int YAHOO_CALLBACK_TYPE(ext_yahoo_write) (void *fd, char *buf, int len);
+
+/*
+ * Name: ext_yahoo_read
+ * 	Read data into a buffer from socket for the specified connection
+ * Params:
+ * 	fd  - the file descriptor object that identifies this connection
+ * 	buf - Buffer to read the data into
+ * 	len - Max length to read
+ * Returns:
+ * 	Number of bytes read or -1 for error
+ */
+	int YAHOO_CALLBACK_TYPE(ext_yahoo_read) (void *fd, char *buf, int len);
+
+/*
+ * Name: ext_yahoo_close
+ * 	Close the file descriptor object and free its resources. Libyahoo2 will not
+ * 	use this object again.
+ * Params:
+ * 	fd  - the file descriptor object that identifies this connection
+ * Returns:
+ * 	Nothing
+ */
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_close) (void *fd);
+
+/*
+ * Name: ext_yahoo_got_buddy_change_group
+ * 	Acknowledgement of buddy changing group
+ * Params:
+ * 	id: client id
+ * 	me: The user
+ * 	who: Buddy name
+ * 	old_group: Old group name
+ * 	new_group: New group name
+ * Returns:
+ * 	Nothing
+ */
+	void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddy_change_group) (int id,
+		const char *me, const char *who, const char *old_group,
+		const char *new_group);
 
 #ifdef USE_STRUCT_CALLBACKS
@@ -673,5 +771,5 @@
  * before doing anything else
  */
-void yahoo_register_callbacks(struct yahoo_callbacks * tyc);
+void yahoo_register_callbacks(struct yahoo_callbacks *tyc);
 
 #undef YAHOO_CALLBACK_TYPE
@@ -684,3 +782,2 @@
 
 #endif
-
Index: protocols/yahoo/yahoo2_types.h
===================================================================
--- protocols/yahoo/yahoo2_types.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo2_types.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -29,72 +29,173 @@
 #endif
 
-enum yahoo_status {
-	YAHOO_STATUS_DISCONNECTED = -1,
-	YAHOO_STATUS_AVAILABLE = 0,
-	YAHOO_STATUS_BRB,
-	YAHOO_STATUS_BUSY,
-	YAHOO_STATUS_NOTATHOME,
-	YAHOO_STATUS_NOTATDESK,
-	YAHOO_STATUS_NOTINOFFICE,
-	YAHOO_STATUS_ONPHONE,
-	YAHOO_STATUS_ONVACATION,
-	YAHOO_STATUS_OUTTOLUNCH,
-	YAHOO_STATUS_STEPPEDOUT,
-	YAHOO_STATUS_INVISIBLE = 12,
-	YAHOO_STATUS_CUSTOM = 99,
-	YAHOO_STATUS_IDLE = 999,
-	YAHOO_STATUS_WEBLOGIN = 0x5a55aa55,
-	YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */
-	YAHOO_STATUS_NOTIFY = 0x16 /* TYPING */
-};
-#define YAHOO_STATUS_GAME	0x2 		/* Games don't fit into the regular status model */
-
-enum yahoo_login_status {
-	YAHOO_LOGIN_OK = 0,
-	YAHOO_LOGIN_LOGOFF = 2,
-	YAHOO_LOGIN_UNAME = 3,
-	YAHOO_LOGIN_PASSWD = 13,
-	YAHOO_LOGIN_LOCK = 14,
-	YAHOO_LOGIN_DUPL = 99,
-	YAHOO_LOGIN_SOCK = -1,
-};
-
-enum ypacket_status {
-	YPACKET_STATUS_DISCONNECTED = -1,
-	YPACKET_STATUS_DEFAULT = 0,
-	YPACKET_STATUS_SERVERACK = 1,
-	YPACKET_STATUS_GAME     = 0x2,
-	YPACKET_STATUS_AWAY     = 0x4,
-	YPACKET_STATUS_CONTINUED = 0x5,
-	YPACKET_STATUS_INVISIBLE = 12,
-	YPACKET_STATUS_NOTIFY = 0x16, /* TYPING */
-	YPACKET_STATUS_WEBLOGIN = 0x5a55aa55,
-	YPACKET_STATUS_OFFLINE = 0x5a55aa56
-};
-
-enum yahoo_error {
-	E_UNKNOWN = -1,
-	E_CONNECTION = -2,
-	E_SYSTEM = -3,
-	E_CUSTOM = 0,
-
-	/* responses from ignore buddy */
-	E_IGNOREDUP = 2,
-	E_IGNORENONE = 3,
-	E_IGNORECONF = 12,
-	
-	/* conference */
-	E_CONFNOTAVAIL = 20
-};
-
-enum yahoo_log_level {
-	YAHOO_LOG_NONE = 0,
-	YAHOO_LOG_FATAL,
-	YAHOO_LOG_ERR,
-	YAHOO_LOG_WARNING,
-	YAHOO_LOG_NOTICE,
-	YAHOO_LOG_INFO,
-	YAHOO_LOG_DEBUG
-};
+	enum yahoo_service {	/* these are easier to see in hex */
+		YAHOO_SERVICE_LOGON = 1,
+		YAHOO_SERVICE_LOGOFF,
+		YAHOO_SERVICE_ISAWAY,
+		YAHOO_SERVICE_ISBACK,
+		YAHOO_SERVICE_IDLE,	/* 5 (placemarker) */
+		YAHOO_SERVICE_MESSAGE,
+		YAHOO_SERVICE_IDACT,
+		YAHOO_SERVICE_IDDEACT,
+		YAHOO_SERVICE_MAILSTAT,
+		YAHOO_SERVICE_USERSTAT,	/* 0xa */
+		YAHOO_SERVICE_NEWMAIL,
+		YAHOO_SERVICE_CHATINVITE,
+		YAHOO_SERVICE_CALENDAR,
+		YAHOO_SERVICE_NEWPERSONALMAIL,
+		YAHOO_SERVICE_NEWCONTACT,
+		YAHOO_SERVICE_ADDIDENT,	/* 0x10 */
+		YAHOO_SERVICE_ADDIGNORE,
+		YAHOO_SERVICE_PING,
+		YAHOO_SERVICE_GOTGROUPRENAME,	/* < 1, 36(old), 37(new) */
+		YAHOO_SERVICE_SYSMESSAGE = 0x14,
+		YAHOO_SERVICE_SKINNAME = 0x15,
+		YAHOO_SERVICE_PASSTHROUGH2 = 0x16,
+		YAHOO_SERVICE_CONFINVITE = 0x18,
+		YAHOO_SERVICE_CONFLOGON,
+		YAHOO_SERVICE_CONFDECLINE,
+		YAHOO_SERVICE_CONFLOGOFF,
+		YAHOO_SERVICE_CONFADDINVITE,
+		YAHOO_SERVICE_CONFMSG,
+		YAHOO_SERVICE_CHATLOGON,
+		YAHOO_SERVICE_CHATLOGOFF,
+		YAHOO_SERVICE_CHATMSG = 0x20,
+		YAHOO_SERVICE_GAMELOGON = 0x28,
+		YAHOO_SERVICE_GAMELOGOFF,
+		YAHOO_SERVICE_GAMEMSG = 0x2a,
+		YAHOO_SERVICE_FILETRANSFER = 0x46,
+		YAHOO_SERVICE_VOICECHAT = 0x4A,
+		YAHOO_SERVICE_NOTIFY,
+		YAHOO_SERVICE_VERIFY,
+		YAHOO_SERVICE_P2PFILEXFER,
+		YAHOO_SERVICE_PEERTOPEER = 0x4F,	/* Checks if P2P possible */
+		YAHOO_SERVICE_WEBCAM,
+		YAHOO_SERVICE_AUTHRESP = 0x54,
+		YAHOO_SERVICE_LIST,
+		YAHOO_SERVICE_AUTH = 0x57,
+		YAHOO_SERVICE_AUTHBUDDY = 0x6d,
+		YAHOO_SERVICE_ADDBUDDY = 0x83,
+		YAHOO_SERVICE_REMBUDDY,
+		YAHOO_SERVICE_IGNORECONTACT,	/* > 1, 7, 13 < 1, 66, 13, 0 */
+		YAHOO_SERVICE_REJECTCONTACT,
+		YAHOO_SERVICE_GROUPRENAME = 0x89,	/* > 1, 65(new), 66(0), 67(old) */
+		YAHOO_SERVICE_Y7_PING = 0x8A,
+		YAHOO_SERVICE_CHATONLINE = 0x96,	/* > 109(id), 1, 6(abcde) < 0,1 */
+		YAHOO_SERVICE_CHATGOTO,
+		YAHOO_SERVICE_CHATJOIN,	/* > 1 104-room 129-1600326591 62-2 */
+		YAHOO_SERVICE_CHATLEAVE,
+		YAHOO_SERVICE_CHATEXIT = 0x9b,
+		YAHOO_SERVICE_CHATADDINVITE = 0x9d,
+		YAHOO_SERVICE_CHATLOGOUT = 0xa0,
+		YAHOO_SERVICE_CHATPING,
+		YAHOO_SERVICE_COMMENT = 0xa8,
+		YAHOO_SERVICE_GAME_INVITE = 0xb7,
+		YAHOO_SERVICE_STEALTH_PERM = 0xb9,
+		YAHOO_SERVICE_STEALTH_SESSION = 0xba,
+		YAHOO_SERVICE_AVATAR = 0xbc,
+		YAHOO_SERVICE_PICTURE_CHECKSUM = 0xbd,
+		YAHOO_SERVICE_PICTURE = 0xbe,
+		YAHOO_SERVICE_PICTURE_UPDATE = 0xc1,
+		YAHOO_SERVICE_PICTURE_UPLOAD = 0xc2,
+		YAHOO_SERVICE_YAB_UPDATE = 0xc4,
+		YAHOO_SERVICE_Y6_VISIBLE_TOGGLE = 0xc5,	/* YMSG13, key 13: 2 = invisible, 1 = visible */
+		YAHOO_SERVICE_Y6_STATUS_UPDATE = 0xc6,	/* YMSG13 */
+		YAHOO_SERVICE_PICTURE_STATUS = 0xc7,	/* YMSG13, key 213: 0 = none, 1 = avatar, 2 = picture */
+		YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8,
+		YAHOO_SERVICE_AUDIBLE = 0xd0,
+		YAHOO_SERVICE_Y7_PHOTO_SHARING = 0xd2,
+		YAHOO_SERVICE_Y7_CONTACT_DETAILS = 0xd3,	/* YMSG13 */
+		YAHOO_SERVICE_Y7_CHAT_SESSION = 0xd4,
+		YAHOO_SERVICE_Y7_AUTHORIZATION = 0xd6,	/* YMSG13 */
+		YAHOO_SERVICE_Y7_FILETRANSFER = 0xdc,	/* YMSG13 */
+		YAHOO_SERVICE_Y7_FILETRANSFERINFO,	/* YMSG13 */
+		YAHOO_SERVICE_Y7_FILETRANSFERACCEPT,	/* YMSG13 */
+		YAHOO_SERVICE_Y7_MINGLE = 0xe1,	/* YMSG13 */
+		YAHOO_SERVICE_Y7_CHANGE_GROUP = 0xe7,	/* YMSG13 */
+		YAHOO_SERVICE_MYSTERY = 0xef,	/* Don't know what this is for */
+		YAHOO_SERVICE_Y8_STATUS = 0xf0,	/* YMSG15 */
+		YAHOO_SERVICE_Y8_LIST = 0Xf1,	/* YMSG15 */
+		YAHOO_SERVICE_MESSAGE_CONFIRM = 0xfb,
+		YAHOO_SERVICE_WEBLOGIN = 0x0226,
+		YAHOO_SERVICE_SMS_MSG = 0x02ea
+	};
+
+	enum yahoo_status {
+		YAHOO_STATUS_AVAILABLE = 0,
+		YAHOO_STATUS_BRB,
+		YAHOO_STATUS_BUSY,
+		YAHOO_STATUS_NOTATHOME,
+		YAHOO_STATUS_NOTATDESK,
+		YAHOO_STATUS_NOTINOFFICE,
+		YAHOO_STATUS_ONPHONE,
+		YAHOO_STATUS_ONVACATION,
+		YAHOO_STATUS_OUTTOLUNCH,
+		YAHOO_STATUS_STEPPEDOUT,
+		YAHOO_STATUS_INVISIBLE = 12,
+		YAHOO_STATUS_CUSTOM = 99,
+		YAHOO_STATUS_IDLE = 999,
+		YAHOO_STATUS_OFFLINE = 0x5a55aa56	/* don't ask */
+	};
+
+	enum ypacket_status {
+		YPACKET_STATUS_DISCONNECTED = -1,
+		YPACKET_STATUS_DEFAULT = 0,
+		YPACKET_STATUS_SERVERACK = 1,
+		YPACKET_STATUS_GAME = 0x2,
+		YPACKET_STATUS_AWAY = 0x4,
+		YPACKET_STATUS_CONTINUED = 0x5,
+		YPACKET_STATUS_INVISIBLE = 12,
+		YPACKET_STATUS_NOTIFY = 0x16,	/* TYPING */
+		YPACKET_STATUS_WEBLOGIN = 0x5a55aa55,
+		YPACKET_STATUS_OFFLINE = 0x5a55aa56
+	};
+
+#define YAHOO_STATUS_GAME	0x2	/* Games don't fit into the regular status model */
+
+	enum yahoo_login_status {
+		YAHOO_LOGIN_OK = 0,
+		YAHOO_LOGIN_LOGOFF = 1,
+		YAHOO_LOGIN_UNAME = 3,
+		YAHOO_LOGIN_PASSWD = 13,
+		YAHOO_LOGIN_LOCK = 14,
+		YAHOO_LOGIN_DUPL = 99,
+		YAHOO_LOGIN_SOCK = -1,
+		YAHOO_LOGIN_UNKNOWN = 999
+	};
+
+	enum yahoo_error {
+		E_UNKNOWN = -1,
+		E_CONNECTION = -2,
+		E_SYSTEM = -3,
+		E_CUSTOM = 0,
+
+		/* responses from ignore buddy */
+		E_IGNOREDUP = 2,
+		E_IGNORENONE = 3,
+		E_IGNORECONF = 12,
+
+		/* conference */
+		E_CONFNOTAVAIL = 20
+	};
+
+	enum yahoo_log_level {
+		YAHOO_LOG_NONE = 0,
+		YAHOO_LOG_FATAL,
+		YAHOO_LOG_ERR,
+		YAHOO_LOG_WARNING,
+		YAHOO_LOG_NOTICE,
+		YAHOO_LOG_INFO,
+		YAHOO_LOG_DEBUG
+	};
+
+	enum yahoo_file_transfer {
+		YAHOO_FILE_TRANSFER_INIT = 1,
+		YAHOO_FILE_TRANSFER_ACCEPT = 3,
+		YAHOO_FILE_TRANSFER_REJECT = 4,
+		YAHOO_FILE_TRANSFER_DONE = 5,
+		YAHOO_FILE_TRANSFER_RELAY,
+		YAHOO_FILE_TRANSFER_FAILED,
+		YAHOO_FILE_TRANSFER_UNKNOWN
+	};
 
 #define YAHOO_PROTO_VER 0x0010
@@ -121,170 +222,175 @@
 #define YAHOO_STYLE_URLOFF "\033[xlm"
 
-enum yahoo_connection_type {
-	YAHOO_CONNECTION_PAGER=0,
-	YAHOO_CONNECTION_FT,
-	YAHOO_CONNECTION_YAB,
-	YAHOO_CONNECTION_WEBCAM_MASTER,
-	YAHOO_CONNECTION_WEBCAM,
-	YAHOO_CONNECTION_CHATCAT,
-	YAHOO_CONNECTION_SEARCH,
-	YAHOO_CONNECTION_AUTH,
-};
-
-enum yahoo_webcam_direction_type {
-        YAHOO_WEBCAM_DOWNLOAD=0,
-        YAHOO_WEBCAM_UPLOAD
-};
-
-enum yahoo_stealth_visibility_type {
-	YAHOO_STEALTH_DEFAULT = 0,
-  	YAHOO_STEALTH_ONLINE,
-  	YAHOO_STEALTH_PERM_OFFLINE
-};
+	enum yahoo_connection_type {
+		YAHOO_CONNECTION_PAGER = 0,
+		YAHOO_CONNECTION_FT,
+		YAHOO_CONNECTION_YAB,
+		YAHOO_CONNECTION_WEBCAM_MASTER,
+		YAHOO_CONNECTION_WEBCAM,
+		YAHOO_CONNECTION_CHATCAT,
+		YAHOO_CONNECTION_SEARCH,
+		YAHOO_CONNECTION_AUTH
+	};
+
+	enum yahoo_webcam_direction_type {
+		YAHOO_WEBCAM_DOWNLOAD = 0,
+		YAHOO_WEBCAM_UPLOAD
+	};
+
+	enum yahoo_stealth_visibility_type {
+		YAHOO_STEALTH_DEFAULT = 0,
+		YAHOO_STEALTH_ONLINE,
+		YAHOO_STEALTH_PERM_OFFLINE
+	};
 
 /* chat member attribs */
 #define YAHOO_CHAT_MALE 0x8000
 #define YAHOO_CHAT_FEMALE 0x10000
+#define YAHOO_CHAT_FEMALE 0x10000
 #define YAHOO_CHAT_DUNNO 0x400
 #define YAHOO_CHAT_WEBCAM 0x10
 
-enum yahoo_webcam_conn_type { Y_WCM_DIALUP, Y_WCM_DSL, Y_WCM_T1 };
-
-struct yahoo_webcam {
-	int direction;     /* Uploading or downloading */
-	int conn_type;     /* 0=Dialup, 1=DSL/Cable, 2=T1/Lan */
-
-	char *user;        /* user we are viewing */
-	char *server;      /* webcam server to connect to */
-	int   port;        /* webcam port to connect on */
-	char *key;         /* key to connect to the server with */
-	char *description; /* webcam description */
-	char *my_ip;       /* own ip number */
-};
-
-struct yahoo_webcam_data {
-	unsigned int data_size;
-	unsigned int to_read;
-	unsigned int timestamp;
-	unsigned char packet_type;
-};
-
-struct yahoo_data {
-	char  *user;
-	char  *password;
-
-	char  *cookie_y;
-	char  *cookie_t;
-	char  *cookie_c;
-	char  *login_cookie;
-
-	YList *buddies;
-	YList *ignore;
-	YList *identities;
-	char  *login_id;
-
-	int   current_status;
-	int   initial_status;
-	int   logged_in;
-
-	int   session_id;
-
-	int   client_id;
-
-	char  *rawbuddylist;
-	char  *ignorelist;
-
-	void  *server_settings;
-	
-	struct yahoo_process_status_entry *half_user;
-};
-
-struct yab {
-	char *id;
-	char *fname;
-	char *lname;
-	char *nname;
-	char *email;
-	char *hphone;
-	char *wphone;
-	char *mphone;
-	int  dbid;
-};
-
-struct yahoo_buddy {
-	char *group;
-	char *id;
-	char *real_name;
-	struct yab *yab_entry;
-};
-
-enum yahoo_search_type {
-	YAHOO_SEARCH_KEYWORD = 0,
-	YAHOO_SEARCH_YID,
-	YAHOO_SEARCH_NAME
-};
-
-enum yahoo_search_gender {
-	YAHOO_GENDER_NONE = 0,
-	YAHOO_GENDER_MALE,
-	YAHOO_GENDER_FEMALE
-};
-
-enum yahoo_search_agerange {
-	YAHOO_AGERANGE_NONE = 0
-};
-
-struct yahoo_found_contact {
-	char *id;
-	char *gender;
-	char *location;
-	int age;
-	int online;
-};
+	enum yahoo_webcam_conn_type { Y_WCM_DIALUP, Y_WCM_DSL, Y_WCM_T1 };
+
+	struct yahoo_webcam {
+		int direction;	/* Uploading or downloading */
+		int conn_type;	/* 0=Dialup, 1=DSL/Cable, 2=T1/Lan */
+
+		char *user;	/* user we are viewing */
+		char *server;	/* webcam server to connect to */
+		int port;	/* webcam port to connect on */
+		char *key;	/* key to connect to the server with */
+		char *description;	/* webcam description */
+		char *my_ip;	/* own ip number */
+	};
+
+	struct yahoo_webcam_data {
+		unsigned int data_size;
+		unsigned int to_read;
+		unsigned int timestamp;
+		unsigned char packet_type;
+	};
+
+	struct yahoo_data {
+		char *user;
+		char *password;
+
+		char *cookie_y;
+		char *cookie_t;
+		char *cookie_c;
+		char *cookie_b;
+		char *login_cookie;
+		char *crumb;
+		char *seed;
+
+		YList *buddies;
+		YList *ignore;
+		YList *identities;
+		char *login_id;
+
+		int current_status;
+		int initial_status;
+		int logged_in;
+
+		int session_id;
+
+		int client_id;
+
+		char *rawbuddylist;
+		char *ignorelist;
+
+		void *server_settings;
+
+		struct yahoo_process_status_entry *half_user;
+	};
+
+	struct yab {
+		int yid;
+		char *id;
+		char *fname;
+		char *lname;
+		char *nname;
+		char *email;
+		char *hphone;
+		char *wphone;
+		char *mphone;
+		int dbid;
+	};
+
+	struct yahoo_buddy {
+		char *group;
+		char *id;
+		char *real_name;
+		struct yab *yab_entry;
+	};
+
+	enum yahoo_search_type {
+		YAHOO_SEARCH_KEYWORD = 0,
+		YAHOO_SEARCH_YID,
+		YAHOO_SEARCH_NAME
+	};
+
+	enum yahoo_search_gender {
+		YAHOO_GENDER_NONE = 0,
+		YAHOO_GENDER_MALE,
+		YAHOO_GENDER_FEMALE
+	};
+
+	enum yahoo_search_agerange {
+		YAHOO_AGERANGE_NONE = 0
+	};
+
+	struct yahoo_found_contact {
+		char *id;
+		char *gender;
+		char *location;
+		int age;
+		int online;
+	};
 
 /*
  * Function pointer to be passed to http get/post and send file
  */
-typedef void (*yahoo_get_fd_callback)(int id, int fd, int error, void *data);
+	typedef void (*yahoo_get_fd_callback) (int id, void *fd, int error,
+		void *data);
 
 /*
  * Function pointer to be passed to yahoo_get_url_handle
  */
-typedef void (*yahoo_get_url_handle_callback)(int id, int fd, int error,
-		const char *filename, unsigned long size, void *data);
-
-
-struct yahoo_chat_member {
-	char *id;
-	int  age;
-	int  attribs;
-	char *alias;
-	char *location;
-};
-
-struct yahoo_process_status_entry {
-	char *name;	/* 7      name */
-	int state;	/* 10     state */
-	int flags;	/* 13     flags, bit 0 = pager, bit 1 = chat, bit 2 = game */
-	int mobile;	/* 60     mobile */
-	char *msg;	/* 19     custom status message */
-	int away;	/* 47     away (or invisible) */
-	int buddy_session; /* 11  state */
-	int f17;	/* 17     in chat? then what about flags? */
-	int idle;	/* 137    seconds idle */
-	int f138;	/* 138    state */
-	char *f184;	/* 184    state */
-	int f192;	/* 192    state */
-	int f10001;	/* 10001  state */
-	int f10002;	/* 10002  state */
-	int f198;	/* 198    state */
-	char *f197;	/* 197    state */
-	char *f205;	/* 205    state */
-	int f213;	/* 213    state */
-};
+	typedef void (*yahoo_get_url_handle_callback) (int id, void *fd,
+		int error, const char *filename, unsigned long size,
+		void *data);
+
+	struct yahoo_chat_member {
+		char *id;
+		int age;
+		int attribs;
+		char *alias;
+		char *location;
+	};
+
+	struct yahoo_process_status_entry { 
+		char *name;     /* 7      name */ 
+		int state;      /* 10     state */ 
+		int flags;      /* 13     flags, bit 0 = pager, bit 1 = chat, bit 2 = game */ 
+		int mobile;     /* 60     mobile */ 
+		char *msg;      /* 19     custom status message */ 
+		int away;       /* 47     away (or invisible) */ 
+		int buddy_session; /* 11  state */ 
+		int f17;        /* 17     in chat? then what about flags? */ 
+		int idle;       /* 137    seconds idle */ 
+		int f138;       /* 138    state */ 
+		char *f184;     /* 184    state */ 
+		int f192;       /* 192    state */ 
+		int f10001;     /* 10001  state */ 
+		int f10002;     /* 10002  state */ 
+		int f198;       /* 198    state */ 
+		char *f197;     /* 197    state */ 
+		char *f205;     /* 205    state */ 
+		int f213;       /* 213    state */ 
+	};
 
 #ifdef __cplusplus
 }
 #endif
-
 #endif
Index: protocols/yahoo/yahoo_fn.h
===================================================================
--- protocols/yahoo/yahoo_fn.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo_fn.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -19,15 +19,14 @@
  */
 
-#define IDENT  1 /* identify function */
-#define XOR    2 /* xor with arg1 */
-#define MULADD 3 /* multipy by arg1 then add arg2 */
-#define LOOKUP 4 /* lookup each byte in the table pointed to by arg1 */
-#define BITFLD 5 /* reorder bits according to table pointed to by arg1 */
+#define IDENT  1		/* identify function */
+#define XOR    2		/* xor with arg1 */
+#define MULADD 3		/* multipy by arg1 then add arg2 */
+#define LOOKUP 4		/* lookup each byte in the table pointed to by arg1 */
+#define BITFLD 5		/* reorder bits according to table pointed to by arg1 */
 
-struct yahoo_fn
-{
-	int type; 
+struct yahoo_fn {
+	int type;
 	long arg1, arg2;
 };
 
-int yahoo_xfrm( int table, int depth, int seed );
+int yahoo_xfrm(int table, int depth, int seed);
Index: protocols/yahoo/yahoo_httplib.c
===================================================================
--- protocols/yahoo/yahoo_httplib.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo_httplib.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -30,5 +30,5 @@
 #  define strrchr rindex
 # endif
-char *strchr (), *strrchr ();
+char *strchr(), *strrchr();
 # if !HAVE_MEMCPY
 #  define memcpy(d, s, n) bcopy ((s), (d), (n))
@@ -36,5 +36,4 @@
 # endif
 #endif
-
 
 #include <errno.h>
@@ -63,5 +62,5 @@
 extern enum yahoo_log_level log_level;
 
-int yahoo_tcp_readline(char *ptr, int maxlen, int fd)
+int yahoo_tcp_readline(char *ptr, int maxlen, void *fd)
 {
 	int n, rc;
@@ -71,9 +70,9 @@
 
 		do {
-			rc = read(fd, &c, 1);
-		} while(rc == -1 && (errno == EINTR || errno == EAGAIN)); /* this is bad - it should be done asynchronously */
+			rc = YAHOO_CALLBACK(ext_yahoo_read) (fd, &c, 1);
+		} while (rc == -1 && (errno == EINTR || errno == EAGAIN));	/* this is bad - it should be done asynchronously */
 
 		if (rc == 1) {
-			if(c == '\r')			/* get rid of \r */
+			if (c == '\r')	/* get rid of \r */
 				continue;
 			*ptr = c;
@@ -83,7 +82,7 @@
 		} else if (rc == 0) {
 			if (n == 1)
-				return (0);		/* EOF, no data */
+				return (0);	/* EOF, no data */
 			else
-				break;			/* EOF, w/ data */
+				break;	/* EOF, w/ data */
 		} else {
 			return -1;
@@ -96,10 +95,10 @@
 
 static int url_to_host_port_path(const char *url,
-		char *host, int *port, char *path)
-{
-	char *urlcopy=NULL;
-	char *slash=NULL;
-	char *colon=NULL;
-	
+	char *host, int *port, char *path, int *ssl)
+{
+	char *urlcopy = NULL;
+	char *slash = NULL;
+	char *colon = NULL;
+
 	/*
 	 * http://hostname
@@ -111,8 +110,12 @@
 	 * http://hostname:port/path
 	 * http://hostname:port/path:foo
+	 * and https:// variants of the above
 	 */
 
-	if(strstr(url, "http://") == url) {
-		urlcopy = strdup(url+7);
+	if (strstr(url, "http://") == url) {
+		urlcopy = strdup(url + 7);
+	} else if (strstr(url, "https://") == url) {
+		urlcopy = strdup(url + 8);
+		*ssl = 1;
 	} else {
 		WARNING(("Weird url - unknown protocol: %s", url));
@@ -123,12 +126,15 @@
 	colon = strchr(urlcopy, ':');
 
-	if(!colon || (slash && slash < colon)) {
-		*port = 80;
+	if (!colon || (slash && slash < colon)) {
+		if (*ssl)
+			*port = 443;
+		else
+			*port = 80;
 	} else {
 		*colon = 0;
-		*port = atoi(colon+1);
-	}
-
-	if(!slash) {
+		*port = atoi(colon + 1);
+	}
+
+	if (!slash) {
 		strcpy(path, "/");
 	} else {
@@ -138,5 +144,5 @@
 
 	strcpy(host, urlcopy);
-	
+
 	FREE(urlcopy);
 
@@ -146,31 +152,31 @@
 static int isurlchar(unsigned char c)
 {
-	return (isalnum(c) || '-' == c || '_' == c);
+	return (isalnum(c));
 }
 
 char *yahoo_urlencode(const char *instr)
 {
-	int ipos=0, bpos=0;
+	int ipos = 0, bpos = 0;
 	char *str = NULL;
 	int len = strlen(instr);
 
-	if(!(str = y_new(char, 3*len + 1) ))
-		return "";
-
-	while(instr[ipos]) {
-		while(isurlchar(instr[ipos]))
+	if (!(str = y_new(char, 3 *len + 1)))
+		 return "";
+
+	while (instr[ipos]) {
+		while (isurlchar(instr[ipos]))
 			str[bpos++] = instr[ipos++];
-		if(!instr[ipos])
+		if (!instr[ipos])
 			break;
-		
-		snprintf(&str[bpos], 4, "%%%.2x", instr[ipos]);
-		bpos+=3;
+
+		snprintf(&str[bpos], 4, "%%%02x", instr[ipos] & 0xff);
+		bpos += 3;
 		ipos++;
 	}
-	str[bpos]='\0';
+	str[bpos] = '\0';
 
 	/* free extra alloc'ed mem. */
 	len = strlen(str);
-	str = y_renew(char, str, len+1);
+	str = y_renew(char, str, len + 1);
 
 	return (str);
@@ -179,27 +185,27 @@
 char *yahoo_urldecode(const char *instr)
 {
-	int ipos=0, bpos=0;
+	int ipos = 0, bpos = 0;
 	char *str = NULL;
-	char entity[3]={0,0,0};
+	char entity[3] = { 0, 0, 0 };
 	unsigned dec;
 	int len = strlen(instr);
 
-	if(!(str = y_new(char, len+1) ))
-		return "";
-
-	while(instr[ipos]) {
-		while(instr[ipos] && instr[ipos]!='%')
-			if(instr[ipos]=='+') {
-				str[bpos++]=' ';
+	if (!(str = y_new(char, len + 1)))
+		 return "";
+
+	while (instr[ipos]) {
+		while (instr[ipos] && instr[ipos] != '%')
+			if (instr[ipos] == '+') {
+				str[bpos++] = ' ';
 				ipos++;
 			} else
 				str[bpos++] = instr[ipos++];
-		if(!instr[ipos])
+		if (!instr[ipos])
 			break;
-		
-		if(instr[ipos+1] && instr[ipos+2]) {
+
+		if (instr[ipos + 1] && instr[ipos + 2]) {
 			ipos++;
-			entity[0]=instr[ipos++];
-			entity[1]=instr[ipos++];
+			entity[0] = instr[ipos++];
+			entity[1] = instr[ipos++];
 			sscanf(entity, "%2x", &dec);
 			str[bpos++] = (char)dec;
@@ -208,9 +214,9 @@
 		}
 	}
-	str[bpos]='\0';
+	str[bpos] = '\0';
 
 	/* free extra alloc'ed mem. */
 	len = strlen(str);
-	str = y_renew(char, str, len+1);
+	str = y_renew(char, str, len + 1);
 
 	return (str);
@@ -219,12 +225,12 @@
 char *yahoo_xmldecode(const char *instr)
 {
-	int ipos=0, bpos=0, epos=0;
+	int ipos = 0, bpos = 0, epos = 0;
 	char *str = NULL;
-	char entity[4]={0,0,0,0};
-	char *entitymap[5][2]={
-		{"amp;",  "&"}, 
+	char entity[4] = { 0, 0, 0, 0 };
+	char *entitymap[5][2] = {
+		{"amp;", "&"},
 		{"quot;", "\""},
-		{"lt;",   "<"}, 
-		{"gt;",   "<"}, 
+		{"lt;", "<"},
+		{"gt;", "<"},
 		{"nbsp;", " "}
 	};
@@ -232,23 +238,23 @@
 	int len = strlen(instr);
 
-	if(!(str = y_new(char, len+1) ))
-		return "";
-
-	while(instr[ipos]) {
-		while(instr[ipos] && instr[ipos]!='&')
-			if(instr[ipos]=='+') {
-				str[bpos++]=' ';
+	if (!(str = y_new(char, len + 1)))
+		 return "";
+
+	while (instr[ipos]) {
+		while (instr[ipos] && instr[ipos] != '&')
+			if (instr[ipos] == '+') {
+				str[bpos++] = ' ';
 				ipos++;
 			} else
 				str[bpos++] = instr[ipos++];
-		if(!instr[ipos] || !instr[ipos+1])
+		if (!instr[ipos] || !instr[ipos + 1])
 			break;
 		ipos++;
 
-		if(instr[ipos] == '#') {
+		if (instr[ipos] == '#') {
 			ipos++;
-			epos=0;
-			while(instr[ipos] != ';')
-				entity[epos++]=instr[ipos++];
+			epos = 0;
+			while (instr[ipos] != ';')
+				entity[epos++] = instr[ipos++];
 			sscanf(entity, "%u", &dec);
 			str[bpos++] = (char)dec;
@@ -256,8 +262,8 @@
 		} else {
 			int i;
-			for (i=0; i<5; i++) 
-				if(!strncmp(instr+ipos, entitymap[i][0], 
-					       strlen(entitymap[i][0]))) {
-				       	str[bpos++] = entitymap[i][1][0];
+			for (i = 0; i < 5; i++)
+				if (!strncmp(instr + ipos, entitymap[i][0],
+						strlen(entitymap[i][0]))) {
+					str[bpos++] = entitymap[i][1][0];
 					ipos += strlen(entitymap[i][0]);
 					break;
@@ -265,14 +271,14 @@
 		}
 	}
-	str[bpos]='\0';
+	str[bpos] = '\0';
 
 	/* free extra alloc'ed mem. */
 	len = strlen(str);
-	str = y_renew(char, str, len+1);
+	str = y_renew(char, str, len + 1);
 
 	return (str);
 }
 
-typedef void (*http_connected)(int id, int fd, int error);
+typedef void (*http_connected) (int id, void *fd, int error);
 
 struct callback_data {
@@ -283,28 +289,30 @@
 };
 
-static void connect_complete(int fd, int error, void *data)
+static void connect_complete(void *fd, int error, void *data)
 {
 	struct callback_data *ccd = data;
-	if(error == 0 && fd > 0)
-		write(fd, ccd->request, strlen(ccd->request));
-	FREE(ccd->request);
+	if (error == 0)
+		YAHOO_CALLBACK(ext_yahoo_write) (fd, ccd->request,
+			strlen(ccd->request));
+	free(ccd->request);
 	ccd->callback(ccd->id, fd, error, ccd->user_data);
 	FREE(ccd);
 }
 
-static void yahoo_send_http_request(int id, char *host, int port, char *request, 
-		yahoo_get_fd_callback callback, void *data)
-{
-	struct callback_data *ccd=y_new0(struct callback_data, 1);
+static void yahoo_send_http_request(int id, char *host, int port, char *request,
+	yahoo_get_fd_callback callback, void *data, int use_ssl)
+{
+	struct callback_data *ccd = y_new0(struct callback_data, 1);
 	ccd->callback = callback;
 	ccd->id = id;
 	ccd->request = strdup(request);
 	ccd->user_data = data;
-	
-	YAHOO_CALLBACK(ext_yahoo_connect_async)(id, host, port, connect_complete, ccd);
-}
-
-void yahoo_http_post(int id, const char *url, const char *cookies, long content_length,
-		yahoo_get_fd_callback callback, void *data)
+
+	YAHOO_CALLBACK(ext_yahoo_connect_async) (id, host, port,
+		connect_complete, ccd, use_ssl);
+}
+
+void yahoo_http_post(int id, const char *url, const char *cookies,
+	long content_length, yahoo_get_fd_callback callback, void *data)
 {
 	char host[255];
@@ -312,120 +320,85 @@
 	char path[255];
 	char buff[1024];
-	
-	if(!url_to_host_port_path(url, host, &port, path))
+	int ssl = 0;
+
+	if (!url_to_host_port_path(url, host, &port, path, &ssl))
 		return;
 
-	snprintf(buff, sizeof(buff), 
-			"POST %s HTTP/1.0\r\n"
-			"Content-length: %ld\r\n"
-			"User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n"
-			"Host: %s:%d\r\n"
-			"Cookie: %s\r\n"
-			"\r\n",
-			path, content_length, 
-			host, port,
-			cookies);
-
-	yahoo_send_http_request(id, host, port, buff, callback, data);
-}
-
-void yahoo_http_get(int id, const char *url, const char *cookies,
-		yahoo_get_fd_callback callback, void *data)
+	/* thanks to kopete dumpcap */
+	snprintf(buff, sizeof(buff),
+		"POST %s HTTP/1.1\r\n"
+		"Cookie: %s\r\n"
+		"User-Agent: Mozilla/5.0\r\n"
+		"Host: %s\r\n"
+		"Content-Length: %ld\r\n"
+		"Cache-Control: no-cache\r\n"
+		"\r\n", path, cookies, host, content_length);
+
+	yahoo_send_http_request(id, host, port, buff, callback, data, ssl);
+}
+
+void yahoo_http_get(int id, const char *url, const char *cookies, int http11,
+	int keepalive, yahoo_get_fd_callback callback, void *data)
 {
 	char host[255];
 	int port = 80;
 	char path[255];
-	char buff[1024];
-	
-	if(!url_to_host_port_path(url, host, &port, path))
+	char buff[2048];
+	char cookiebuff[1024];
+	int ssl = 0;
+
+	if (!url_to_host_port_path(url, host, &port, path, &ssl))
 		return;
 
-	snprintf(buff, sizeof(buff), 
-			"GET %s HTTP/1.0\r\n"
-			"Host: %s:%d\r\n"
-			"User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n"
-			"Cookie: %s\r\n"
-			"\r\n",
-			path, host, port, cookies);
-
-	yahoo_send_http_request(id, host, port, buff, callback, data);
-}
-
-struct url_data {
-	yahoo_get_url_handle_callback callback;
-	void *user_data;
-};
-
-static void yahoo_got_url_fd(int id, int fd, int error, void *data)
-{
-	char *tmp=NULL;
-	char buff[1024];
-	unsigned long filesize=0;
-	char *filename=NULL;
-	int n;
-
-	struct url_data *ud = data;
-
-	if(error || fd < 0) {
-		ud->callback(id, fd, error, filename, filesize, ud->user_data);
-		FREE(ud);
+	/* Allow cases when we don't need to send a cookie */
+	if (cookies)
+		snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n",
+			cookies);
+	else
+		cookiebuff[0] = '\0';
+
+	snprintf(buff, sizeof(buff),
+		"GET %s HTTP/1.%s\r\n"
+		"%sHost: %s\r\n"
+		"User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n"
+		"Accept: */*\r\n"
+		"%s" "\r\n", path, http11?"1":"0", cookiebuff, host,
+		keepalive? "Connection: Keep-Alive\r\n":"Connection: close\r\n");
+
+	yahoo_send_http_request(id, host, port, buff, callback, data, ssl);
+}
+
+void yahoo_http_head(int id, const char *url, const char *cookies, int len,
+	char *payload, yahoo_get_fd_callback callback, void *data)
+{
+	char host[255];
+	int port = 80;
+	char path[255];
+	char buff[2048];
+	char cookiebuff[1024];
+	int ssl = 0;
+
+	if (!url_to_host_port_path(url, host, &port, path, &ssl))
 		return;
-	}
-
-	while((n=yahoo_tcp_readline(buff, sizeof(buff), fd)) > 0) {
-		LOG(("Read:%s:\n", buff));
-		if(!strcmp(buff, ""))
-			break;
-
-		if( !strncasecmp(buff, "Content-length:", 
-				strlen("Content-length:")) ) {
-			tmp = strrchr(buff, ' ');
-			if(tmp)
-				filesize = atol(tmp);
-		}
-
-		if( !strncasecmp(buff, "Content-disposition:", 
-				strlen("Content-disposition:")) ) {
-			tmp = strstr(buff, "name=");
-			if(tmp) {
-				tmp+=strlen("name=");
-				if(tmp[0] == '"') {
-					char *tmp2;
-					tmp++;
-					tmp2 = strchr(tmp, '"');
-					if(tmp2)
-						*tmp2 = '\0';
-				} else {
-					char *tmp2;
-					tmp2 = strchr(tmp, ';');
-					if(!tmp2)
-						tmp2 = strchr(tmp, '\r');
-					if(!tmp2)
-						tmp2 = strchr(tmp, '\n');
-					if(tmp2)
-						*tmp2 = '\0';
-				}
-
-				filename = strdup(tmp);
-			}
-		}
-	}
-
-	LOG(("n == %d\n", n));
-	LOG(("Calling callback, filename:%s, size: %ld\n", filename, filesize));
-	ud->callback(id, fd, error, filename, filesize, ud->user_data);
-	FREE(ud);
-	FREE(filename);
-}
-
-void yahoo_get_url_fd(int id, const char *url, const struct yahoo_data *yd,
-		yahoo_get_url_handle_callback callback, void *data)
-{
-	char buff[1024];
-	struct url_data *ud = y_new0(struct url_data, 1);
-	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
-	ud->callback = callback;
-	ud->user_data = data;
-	yahoo_http_get(id, url, buff, yahoo_got_url_fd, ud);
-}
-
+
+	/* Allow cases when we don't need to send a cookie */
+	if (cookies)
+		snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n",
+			cookies);
+	else
+		cookiebuff[0] = '\0';
+
+	snprintf(buff, sizeof(buff),
+		"HEAD %s HTTP/1.0\r\n"
+		"Accept: */*\r\n"
+		"Host: %s:%d\r\n"
+		"User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n"
+		"%s"
+		"Content-Length: %d\r\n"
+		"Cache-Control: no-cache\r\n"
+		"\r\n%s", path, host, port, cookiebuff, len,
+		payload?payload:"");
+
+	yahoo_send_http_request(id, host, port, buff, callback, data, ssl);
+}
+
Index: protocols/yahoo/yahoo_httplib.h
===================================================================
--- protocols/yahoo/yahoo_httplib.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo_httplib.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -29,20 +29,20 @@
 #include "yahoo2_types.h"
 
-char *yahoo_urlencode(const char *instr);
-char *yahoo_urldecode(const char *instr);
-char *yahoo_xmldecode(const char *instr);
+	char *yahoo_urlencode(const char *instr);
+	char *yahoo_urldecode(const char *instr);
+	char *yahoo_xmldecode(const char *instr);
 
-int yahoo_tcp_readline(char *ptr, int maxlen, int fd);
-void yahoo_http_post(int id, const char *url, const char *cookies, long size,
-		yahoo_get_fd_callback callback, void *data);
-void yahoo_http_get(int id, const char *url, const char *cookies,
-		yahoo_get_fd_callback callback, void *data);
-void yahoo_get_url_fd(int id, const char *url, const struct yahoo_data *yd,
-		yahoo_get_url_handle_callback callback, void *data);
-
+	int yahoo_tcp_readline(char *ptr, int maxlen, void *fd);
+	void yahoo_http_post(int id, const char *url, const char *cookies,
+		long size, yahoo_get_fd_callback callback, void *data);
+	void yahoo_http_get(int id, const char *url, const char *cookies,
+		int http11, int keepalive, yahoo_get_fd_callback callback,
+		void *data);
+	void yahoo_http_head(int id, const char *url, const char *cookies,
+		int size, char *payload, yahoo_get_fd_callback callback,
+		void *data);
 
 #ifdef __cplusplus
 }
 #endif
-
 #endif
Index: protocols/yahoo/yahoo_list.h
===================================================================
--- protocols/yahoo/yahoo_list.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo_list.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -24,5 +24,5 @@
 #define __YLIST_H__
 
-/* GLib has linked list already, so I don't see why libyahoo2 has to copy this... */
+/* BitlBee already uses GLib so use it. */
 
 typedef GList YList;
Index: protocols/yahoo/yahoo_util.c
===================================================================
--- protocols/yahoo/yahoo_util.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo_util.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -36,10 +36,10 @@
 #include "yahoo_util.h"
 
-char * y_string_append(char * string, char * append)
+char *y_string_append(char *string, char *append)
 {
 	int size = strlen(string) + strlen(append) + 1;
-	char * new_string = y_renew(char, string, size);
+	char *new_string = y_renew(char, string, size);
 
-	if(new_string == NULL) {
+	if (new_string == NULL) {
 		new_string = y_new(char, size);
 		strcpy(new_string, string);
Index: protocols/yahoo/yahoo_util.h
===================================================================
--- protocols/yahoo/yahoo_util.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ protocols/yahoo/yahoo_util.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -61,15 +61,17 @@
 # define y_renew(type, mem, n)	(type *)realloc(mem, n)
 
-void * y_memdup(const void * addr, int n);
-char ** y_strsplit(char * str, char * sep, int nelem);
-void y_strfreev(char ** vector);
+void *y_memdup(const void *addr, int n);
+char **y_strsplit(char *str, char *sep, int nelem);
+void y_strfreev(char **vector);
 
-int strncasecmp(const char * s1, const char * s2, size_t n);
-int strcasecmp(const char * s1, const char * s2);
+#ifndef _WIN32
+int strncasecmp(const char *s1, const char *s2, size_t n);
+int strcasecmp(const char *s1, const char *s2);
 
-char * strdup(const char *s);
+char *strdup(const char *s);
 
 int snprintf(char *str, size_t size, const char *format, ...);
 int vsnprintf(char *str, size_t size, const char *format, va_list ap);
+#endif
 
 #endif
@@ -95,7 +97,7 @@
  * You must free it yourself
  */
-char * y_string_append(char * str, char * append);
-char * y_str_to_utf8(const char * in);
-char * y_utf8_to_str(const char * in);
+char *y_string_append(char *str, char *append);
+char *y_str_to_utf8(const char *in);
+char *y_utf8_to_str(const char *in);
 
 #endif
Index: query.c
===================================================================
--- query.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ query.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -31,5 +31,6 @@
 
 query_t *query_add( irc_t *irc, struct im_connection *ic, char *question,
-                    query_callback yes, query_callback no, void *data )
+                    query_callback yes, query_callback no, query_callback free,
+                    void *data )
 {
 	query_t *q = g_new0( query_t, 1 );
@@ -39,4 +40,5 @@
 	q->yes = yes;
 	q->no = no;
+	q->free = free;
 	q->data = data;
 	
@@ -64,5 +66,5 @@
 	}
 	
-	if( g_strcasecmp( set_getstr( &irc->set, "query_order" ), "lifo" ) == 0 || irc->queries == q )
+	if( g_strcasecmp( set_getstr( &irc->b->set, "query_order" ), "lifo" ) == 0 || irc->queries == q )
 		query_display( irc, q );
 	
@@ -94,5 +96,6 @@
 	
 	g_free( q->question );
-	if( q->data ) g_free( q->data ); /* Memory leak... */
+	if( q->free && q->data )
+		q->free( q->data );
 	g_free( q );
 }
@@ -179,5 +182,5 @@
 	query_t *q;
 	
-	if( g_strcasecmp( set_getstr( &irc->set, "query_order" ), "fifo" ) == 0 )
+	if( g_strcasecmp( set_getstr( &irc->b->set, "query_order" ), "fifo" ) == 0 )
 		q = irc->queries;
 	else
Index: query.h
===================================================================
--- query.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ query.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -33,5 +33,5 @@
 	struct im_connection *ic;
 	char *question;
-	query_callback yes, no;
+	query_callback yes, no, free;
 	void *data;
 	struct query *next;
@@ -39,5 +39,6 @@
 
 query_t *query_add( irc_t *irc, struct im_connection *ic, char *question,
-                    query_callback yes, query_callback no, void *data );
+                    query_callback yes, query_callback no, query_callback free,
+                    void *data );
 void query_del( irc_t *irc, query_t *q );
 void query_del_by_conn( irc_t *irc, struct im_connection *ic );
Index: root_commands.c
===================================================================
--- root_commands.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ root_commands.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -26,55 +26,11 @@
 #define BITLBEE_CORE
 #include "commands.h"
-#include "crypting.h"
 #include "bitlbee.h"
 #include "help.h"
-#include "chat.h"
-
-#include <string.h>
-
-void root_command_string( irc_t *irc, user_t *u, char *command, int flags )
-{
-	char *cmd[IRC_MAX_ARGS];
-	char *s;
-	int k;
-	char q = 0;
-	
-	memset( cmd, 0, sizeof( cmd ) );
-	cmd[0] = command;
-	k = 1;
-	for( s = command; *s && k < ( IRC_MAX_ARGS - 1 ); s ++ )
-		if( *s == ' ' && !q )
-		{
-			*s = 0;
-			while( *++s == ' ' );
-			if( *s == '"' || *s == '\'' )
-			{
-				q = *s;
-				s ++;
-			}
-			if( *s )
-			{
-				cmd[k++] = s;
-				s --;
-			}
-			else
-			{
-				break;
-			}
-		}
-		else if( *s == '\\' && ( ( !q && s[1] ) || ( q && q == s[1] ) ) )
-		{
-			char *cpy;
-			
-			for( cpy = s; *cpy; cpy ++ )
-				cpy[0] = cpy[1];
-		}
-		else if( *s == q )
-		{
-			q = *s = 0;
-		}
-	cmd[k] = NULL;
-	
-	root_command( irc, cmd );
+#include "ipc.h"
+
+void root_command_string( irc_t *irc, char *command )
+{
+	root_command( irc, split_command_parts( command ) );
 }
 
@@ -93,12 +49,18 @@
 void root_command( irc_t *irc, char *cmd[] )
 {	
-	int i;
+	int i, len;
 	
 	if( !cmd[0] )
 		return;
 	
+	len = strlen( cmd[0] );
 	for( i = 0; commands[i].command; i++ )
-		if( g_strcasecmp( commands[i].command, cmd[0] ) == 0 )
-		{
+		if( g_strncasecmp( commands[i].command, cmd[0], len ) == 0 )
+		{
+			if( commands[i+1].command &&
+		            g_strncasecmp( commands[i+1].command, cmd[0], len ) == 0 )
+		        	/* Only match on the first letters if the match is unique. */
+		        	break;
+		        
 			MIN_ARGS( commands[i].required_parameters );
 			
@@ -141,12 +103,42 @@
 static void cmd_identify( irc_t *irc, char **cmd )
 {
-	storage_status_t status = storage_load( irc, cmd[1] );
-	char *account_on[] = { "account", "on", NULL };
-	
-	if( strchr( irc->umode, 'R' ) != NULL )
+	storage_status_t status;
+	gboolean load = TRUE;
+	char *password = cmd[1];
+	
+	if( irc->status & USTATUS_IDENTIFIED )
 	{
 		irc_usermsg( irc, "You're already logged in." );
 		return;
 	}
+	
+	if( strncmp( cmd[1], "-no", 3 ) == 0 )
+	{
+		load = FALSE;
+		password = cmd[2];
+	}
+	else if( strncmp( cmd[1], "-force", 6 ) == 0 )
+	{
+		password = cmd[2];
+	}
+	else if( irc->b->accounts != NULL )
+	{
+		irc_usermsg( irc,
+		             "You're trying to identify yourself, but already have "
+		             "at least one IM account set up. "
+		             "Use \x02identify -noload\x02 or \x02identify -force\x02 "
+		             "instead (see \x02help identify\x02)." );
+		return;
+	}
+	
+	if( password == NULL )
+	{
+		MIN_ARGS( 2 );
+	}
+	
+	if( load )
+		status = storage_load( irc, password );
+	else
+		status = storage_check_pass( irc->user->nick, password );
 	
 	switch (status) {
@@ -158,10 +150,35 @@
 		break;
 	case STORAGE_OK:
-		irc_usermsg( irc, "Password accepted, settings and accounts loaded" );
-		irc_setpass( irc, cmd[1] );
+		irc_usermsg( irc, "Password accepted%s",
+		             load ? ", settings and accounts loaded" : "" );
+		irc_setpass( irc, password );
 		irc->status |= USTATUS_IDENTIFIED;
 		irc_umode_set( irc, "+R", 1 );
-		if( set_getbool( &irc->set, "auto_connect" ) )
-			cmd_account( irc, account_on );
+		
+		/* The following code is a bit hairy now. With takeover
+		   support, we shouldn't immediately auto_connect in case
+		   we're going to offer taking over an existing session.
+		   Do it in 200ms since that should give the parent process
+		   enough time to come back to us. */
+		if( load )
+		{
+			irc_channel_auto_joins( irc, NULL );
+			if( !set_getbool( &irc->default_channel->set, "auto_join" ) )
+				irc_channel_del_user( irc->default_channel, irc->user,
+				                      IRC_CDU_PART, "auto_join disabled "
+				                      "for this channel." );
+			if( set_getbool( &irc->b->set, "auto_connect" ) )
+				irc->login_source_id = b_timeout_add( 200,
+					cmd_identify_finish, irc );
+		}
+		
+		/* If ipc_child_identify() returns FALSE, it means we're
+		   already sure that there's no takeover target (only
+		   possible in 1-process daemon mode). Start auto_connect
+		   immediately. */
+		if( !ipc_child_identify( irc ) && load &&
+		    set_getbool( &irc->b->set, "auto_connect" ) )
+			cmd_identify_finish( irc, 0, 0 );
+		
 		break;
 	case STORAGE_OTHER_ERROR:
@@ -170,4 +187,16 @@
 		break;
 	}
+}
+
+gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond )
+{
+	char *account_on[] = { "account", "on", NULL };
+	irc_t *irc = data;
+	
+	cmd_account( irc, account_on );
+	
+	b_event_remove( irc->login_source_id );
+	irc->login_source_id = -1;
+	return FALSE;
 }
 
@@ -202,5 +231,5 @@
 	storage_status_t status;
 	
-	status = storage_remove (irc->nick, cmd[1]);
+	status = storage_remove (irc->user->nick, cmd[1]);
 	switch (status) {
 	case STORAGE_NO_SUCH_USER:
@@ -214,5 +243,5 @@
 		irc->status &= ~USTATUS_IDENTIFIED;
 		irc_umode_set( irc, "-R", 1 );
-		irc_usermsg( irc, "Account `%s' removed", irc->nick );
+		irc_usermsg( irc, "Account `%s' removed", irc->user->nick );
 		break;
 	default:
@@ -222,36 +251,12 @@
 }
 
-struct cmd_account_del_data
-{
-	account_t *a;
-	irc_t *irc;
-};
-
-void cmd_account_del_yes( void *data )
-{
-	struct cmd_account_del_data *cad = data;
-	account_t *a;
-	
-	for( a = cad->irc->accounts; a && a != cad->a; a = a->next );
-	
-	if( a == NULL )
-	{
-		irc_usermsg( cad->irc, "Account already deleted" );
-	}
-	else if( a->ic )
-	{
-		irc_usermsg( cad->irc, "Account is still logged in, can't delete" );
-	}
-	else
-	{
-		account_del( cad->irc, a );
-		irc_usermsg( cad->irc, "Account deleted" );
-	}
-	g_free( data );
-}
-
-void cmd_account_del_no( void *data )
-{
-	g_free( data );
+static void cmd_save( irc_t *irc, char **cmd )
+{
+	if( ( irc->status & USTATUS_IDENTIFIED ) == 0 )
+		irc_usermsg( irc, "Please create an account first" );
+	else if( storage_save( irc, NULL, TRUE ) == STORAGE_OK )
+		irc_usermsg( irc, "Configuration saved" );
+	else
+		irc_usermsg( irc, "Configuration could not be saved!" );
 }
 
@@ -269,47 +274,22 @@
 typedef int (*cmd_set_checkflags)( irc_t*, set_t *set );
 
-static int cmd_set_real( irc_t *irc, char **cmd, cmd_set_findhead findhead, cmd_set_checkflags checkflags )
-{
-	char *set_full = NULL, *set_name = NULL, *tmp;
-	set_t **head;
+static int cmd_set_real( irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags )
+{
+	char *set_name = NULL, *value = NULL;
+	gboolean del = FALSE;
 	
 	if( cmd[1] && g_strncasecmp( cmd[1], "-del", 4 ) == 0 )
 	{
 		MIN_ARGS( 2, 0 );
-		set_full = cmd[2];
-	}
-	else
-		set_full = cmd[1];
-	
-	if( findhead == NULL )
-	{
-		set_name = set_full;
-		
-		head = &irc->set;
-	}
-	else 
-	{
-		char *id;
-		
-		if( ( tmp = strchr( set_full, '/' ) ) )
-		{
-			id = g_strndup( set_full, ( tmp - set_full ) );
-			set_name = tmp + 1;
-		}
-		else
-		{
-			id = g_strdup( set_full );
-		}
-		
-		if( ( head = findhead( irc, id ) ) == NULL )
-		{
-			g_free( id );
-			irc_usermsg( irc, "Could not find setting." );
-			return 0;
-		}
-		g_free( id );
-	}
-	
-	if( cmd[1] && cmd[2] && set_name )
+		set_name = cmd[2];
+		del = TRUE;
+	}
+	else
+	{
+		set_name = cmd[1];
+		value = cmd[2];
+	}
+	
+	if( set_name && ( value || del ) )
 	{
 		set_t *s = set_find( head, set_name );
@@ -319,11 +299,14 @@
 			return 0;
 		
-		if( g_strncasecmp( cmd[1], "-del", 4 ) == 0 )
+		if( del )
 			st = set_reset( head, set_name );
 		else
-			st = set_setstr( head, set_name, cmd[2] );
+			st = set_setstr( head, set_name, value );
 		
 		if( set_getstr( head, set_name ) == NULL )
 		{
+			/* This happens when changing the passwd, for example.
+			   Showing these msgs instead gives slightly clearer
+			   feedback. */
 			if( st )
 				irc_usermsg( irc, "Setting changed successfully" );
@@ -353,14 +336,4 @@
 }
 
-static set_t **cmd_account_set_findhead( irc_t *irc, char *id )
-{
-	account_t *a;
-	
-	if( ( a = account_get( irc, id ) ) )
-		return &a->set;
-	else
-		return NULL;
-}
-
 static int cmd_account_set_checkflags( irc_t *irc, set_t *s )
 {
@@ -384,4 +357,5 @@
 {
 	account_t *a;
+	int len;
 	
 	if( global.conf->authmode == AUTHMODE_REGISTERED && !( irc->status & USTATUS_IDENTIFIED ) )
@@ -391,5 +365,7 @@
 	}
 	
-	if( g_strcasecmp( cmd[1], "add" ) == 0 )
+	len = strlen( cmd[1] );
+	
+	if( len >= 1 && g_strncasecmp( cmd[1], "add", len ) == 0 )
 	{
 		struct prpl *prpl;
@@ -404,6 +380,12 @@
 			return;
 		}
-
-		a = account_add( irc, prpl, cmd[3], cmd[4] );
+		
+		for( a = irc->b->accounts; a; a = a->next )
+			if( a->prpl == prpl && prpl->handle_cmp( a->user, cmd[3] ) == 0 )
+				irc_usermsg( irc, "Warning: You already have an account with "
+				             "protocol `%s' and username `%s'. Are you accidentally "
+				             "trying to add it twice?", prpl->name, cmd[3] );
+		
+		a = account_add( irc->b, prpl, cmd[3], cmd[4] );
 		if( cmd[5] )
 		{
@@ -414,36 +396,8 @@
 		
 		irc_usermsg( irc, "Account successfully added" );
-	}
-	else if( g_strcasecmp( cmd[1], "del" ) == 0 )
-	{
-		MIN_ARGS( 2 );
-
-		if( !( a = account_get( irc, cmd[2] ) ) )
-		{
-			irc_usermsg( irc, "Invalid account" );
-		}
-		else if( a->ic )
-		{
-			irc_usermsg( irc, "Account is still logged in, can't delete" );
-		}
-		else
-		{
-			struct cmd_account_del_data *cad;
-			char *msg;
-			
-			cad = g_malloc( sizeof( struct cmd_account_del_data ) );
-			cad->a = a;
-			cad->irc = irc;
-			
-			msg = g_strdup_printf( "If you remove this account (%s(%s)), BitlBee will "
-			                       "also forget all your saved nicknames. If you want "
-			                       "to change your username/password, use the `account "
-			                       "set' command. Are you sure you want to delete this "
-			                       "account?", a->prpl->name, a->user );
-			query_add( irc, NULL, msg, cmd_account_del_yes, cmd_account_del_no, cad );
-			g_free( msg );
-		}
-	}
-	else if( g_strcasecmp( cmd[1], "list" ) == 0 )
+		
+		return;
+	}
+	else if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 )
 	{
 		int i = 0;
@@ -452,5 +406,5 @@
 			irc_usermsg( irc, "Account list:" );
 		
-		for( a = irc->accounts; a; a = a->next )
+		for( a = irc->b->accounts; a; a = a->next )
 		{
 			char *con;
@@ -465,93 +419,178 @@
 				con = "";
 			
-			irc_usermsg( irc, "%2d. %s, %s%s", i, a->prpl->name, a->user, con );
+			irc_usermsg( irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con );
 			
 			i ++;
 		}
 		irc_usermsg( irc, "End of account list" );
-	}
-	else if( g_strcasecmp( cmd[1], "on" ) == 0 )
-	{
-		if( cmd[2] )
-		{
-			if( ( a = account_get( irc, cmd[2] ) ) )
-			{
-				if( a->ic )
-				{
-					irc_usermsg( irc, "Account already online" );
-					return;
-				}
-				else
-				{
-					account_on( irc, a );
-				}
-			}
-			else
-			{
-				irc_usermsg( irc, "Invalid account" );
-				return;
-			}
-		}
-		else
-		{
-			if ( irc->accounts ) {
-				irc_usermsg( irc, "Trying to get all accounts connected..." );
+		
+		return;
+	}
+	else if( cmd[2] )
+	{
+		/* Try the following two only if cmd[2] == NULL */
+	}
+	else if( len >= 2 && g_strncasecmp( cmd[1], "on", len ) == 0 )
+	{
+		if ( irc->b->accounts )
+		{
+			irc_usermsg( irc, "Trying to get all accounts connected..." );
+		
+			for( a = irc->b->accounts; a; a = a->next )
+				if( !a->ic && a->auto_connect )
+					account_on( irc->b, a );
+		} 
+		else
+		{
+			irc_usermsg( irc, "No accounts known. Use `account add' to add one." );
+		}
+		
+		return;
+	}
+	else if( len >= 2 && g_strncasecmp( cmd[1], "off", len ) == 0 )
+	{
+		irc_usermsg( irc, "Deactivating all active (re)connections..." );
+		
+		for( a = irc->b->accounts; a; a = a->next )
+		{
+			if( a->ic )
+				account_off( irc->b, a );
+			else if( a->reconnect )
+				cancel_auto_reconnect( a );
+		}
+		
+		return;
+	}
+	
+	MIN_ARGS( 2 );
+	len = strlen( cmd[2] );
+	
+	/* At least right now, don't accept on/off/set/del as account IDs even
+	   if they're a proper match, since people not familiar with the new
+	   syntax yet may get a confusing/nasty surprise. */
+	if( g_strcasecmp( cmd[1], "on" ) == 0 ||
+	    g_strcasecmp( cmd[1], "off" ) == 0 ||
+	    g_strcasecmp( cmd[1], "set" ) == 0 ||
+	    g_strcasecmp( cmd[1], "del" ) == 0 ||
+	    ( a = account_get( irc->b, cmd[1] ) ) == NULL )
+	{
+		irc_usermsg( irc, "Could not find account `%s'. Note that the syntax "
+		             "of the account command changed, see \x02help account\x02.", cmd[1] );
+		
+		return;
+	}
+	
+	if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 )
+	{
+		if( a->ic )
+		{
+			irc_usermsg( irc, "Account is still logged in, can't delete" );
+		}
+		else
+		{
+			account_del( irc->b, a );
+			irc_usermsg( irc, "Account deleted" );
+		}
+	}
+	else if( len >= 2 && g_strncasecmp( cmd[2], "on", len ) == 0 )
+	{
+		if( a->ic )
+			irc_usermsg( irc, "Account already online" );
+		else
+			account_on( irc->b, a );
+	}
+	else if( len >= 2 && g_strncasecmp( cmd[2], "off", len ) == 0 )
+	{
+		if( a->ic )
+		{
+			account_off( irc->b, a );
+		}
+		else if( a->reconnect )
+		{
+			cancel_auto_reconnect( a );
+			irc_usermsg( irc, "Reconnect cancelled" );
+		}
+		else
+		{
+			irc_usermsg( irc, "Account already offline" );
+		}
+	}
+	else if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
+	{
+		cmd_set_real( irc, cmd + 2, &a->set, cmd_account_set_checkflags );
+	}
+	else
+	{
+		irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[2] );
+	}
+}
+
+static void cmd_channel( irc_t *irc, char **cmd )
+{
+	irc_channel_t *ic;
+	int len;
+	
+	len = strlen( cmd[1] );
+	
+	if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 )
+	{
+		GSList *l;
+		int i = 0;
+		
+		if( strchr( irc->umode, 'b' ) )
+			irc_usermsg( irc, "Channel list:" );
+		
+		for( l = irc->channels; l; l = l->next )
+		{
+			irc_channel_t *ic = l->data;
 			
-				for( a = irc->accounts; a; a = a->next )
-					if( !a->ic && a->auto_connect )
-						account_on( irc, a );
-			} 
-			else
-			{
-				irc_usermsg( irc, "No accounts known. Use `account add' to add one." );
-			}
-		}
-	}
-	else if( g_strcasecmp( cmd[1], "off" ) == 0 )
-	{
-		if( !cmd[2] )
-		{
-			irc_usermsg( irc, "Deactivating all active (re)connections..." );
+			irc_usermsg( irc, "%2d. %s, %s channel%s", i, ic->name,
+			             set_getstr( &ic->set, "type" ),
+			             ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "" );
 			
-			for( a = irc->accounts; a; a = a->next )
-			{
-				if( a->ic )
-					account_off( irc, a );
-				else if( a->reconnect )
-					cancel_auto_reconnect( a );
-			}
-		}
-		else if( ( a = account_get( irc, cmd[2] ) ) )
-		{
-			if( a->ic )
-			{
-				account_off( irc, a );
-			}
-			else if( a->reconnect )
-			{
-				cancel_auto_reconnect( a );
-				irc_usermsg( irc, "Reconnect cancelled" );
-			}
-			else
-			{
-				irc_usermsg( irc, "Account already offline" );
-				return;
-			}
-		}
-		else
-		{
-			irc_usermsg( irc, "Invalid account" );
-			return;
-		}
-	}
-	else if( g_strcasecmp( cmd[1], "set" ) == 0 )
-	{
-		MIN_ARGS( 2 );
-		
-		cmd_set_real( irc, cmd + 1, cmd_account_set_findhead, cmd_account_set_checkflags );
-	}
-	else
-	{
-		irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[1] );
+			i ++;
+		}
+		irc_usermsg( irc, "End of channel list" );
+		
+		return;
+	}
+	
+	if( ( ic = irc_channel_get( irc, cmd[1] ) ) == NULL )
+	{
+		/* If this doesn't match any channel, maybe this is the short
+		   syntax (only works when used inside a channel). */
+		if( ( ic = irc->root->last_channel ) &&
+		    ( len = strlen( cmd[1] ) ) &&
+		    g_strncasecmp( cmd[1], "set", len ) == 0 )
+			cmd_set_real( irc, cmd + 1, &ic->set, NULL );
+		else
+			irc_usermsg( irc, "Could not find channel `%s'", cmd[1] );
+		
+		return;
+	}
+	
+	MIN_ARGS( 2 );
+	len = strlen( cmd[2] );
+	
+	if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
+	{
+		cmd_set_real( irc, cmd + 2, &ic->set, NULL );
+	}
+	else if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 )
+	{
+		if( !( ic->flags & IRC_CHANNEL_JOINED ) &&
+		    ic != ic->irc->default_channel )
+		{
+			irc_usermsg( irc, "Channel %s deleted.", ic->name );
+			irc_channel_free( ic );
+		}
+		else
+			irc_usermsg( irc, "Couldn't remove channel (main channel %s or "
+			                  "channels you're still in cannot be deleted).",
+			                  irc->default_channel->name );
+	}
+	else
+	{
+		irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] );
 	}
 }
@@ -569,5 +608,5 @@
 	}
 	
-	if( !( a = account_get( irc, cmd[1] ) ) )
+	if( !( a = account_get( irc->b, cmd[1] ) ) )
 	{
 		irc_usermsg( irc, "Invalid account" );
@@ -587,5 +626,5 @@
 			return;
 		}
-		else if( user_find( irc, cmd[3] ) )
+		else if( irc_user_by_name( irc, cmd[3] ) )
 		{
 			irc_usermsg( irc, "The requested nick `%s' already exists", cmd[3] );
@@ -594,17 +633,61 @@
 		else
 		{
-			nick_set( a, cmd[2], cmd[3] );
+			nick_set_raw( a, cmd[2], cmd[3] );
 		}
 	}
 	
 	if( add_on_server )
-		a->ic->acc->prpl->add_buddy( a->ic, cmd[2], NULL );
-	else
-		/* Yeah, officially this is a call-*back*... So if we just
-		   called add_buddy, we'll wait for the IM server to respond
-		   before we do this. */
-		imcb_add_buddy( a->ic, cmd[2], NULL );
-	
-	irc_usermsg( irc, "Adding `%s' to your contact list", cmd[2]  );
+	{
+		irc_channel_t *ic;
+		char *s, *group = NULL;;
+		
+		if( ( ic = irc->root->last_channel ) &&
+		    ( s = set_getstr( &ic->set, "fill_by" ) ) &&
+		    strcmp( s, "group" ) == 0 &&
+		    ( group = set_getstr( &ic->set, "group" ) ) )
+			irc_usermsg( irc, "Adding `%s' to contact list (group %s)",
+			             cmd[2], group );
+		else
+			irc_usermsg( irc, "Adding `%s' to contact list", cmd[2] );
+		
+		a->prpl->add_buddy( a->ic, cmd[2], group );
+	}
+	else
+	{
+		bee_user_t *bu;
+		irc_user_t *iu;
+		
+		/* Only for add -tmp. For regular adds, this callback will
+		   be called once the IM server confirms. */
+		if( ( bu = bee_user_new( irc->b, a->ic, cmd[2], BEE_USER_LOCAL ) ) &&
+		    ( iu = bu->ui_data ) )
+			irc_usermsg( irc, "Temporarily assigned nickname `%s' "
+			             "to contact `%s'", iu->nick, cmd[2] );
+	}
+	
+}
+
+static void cmd_remove( irc_t *irc, char **cmd )
+{
+	irc_user_t *iu;
+	bee_user_t *bu;
+	char *s;
+	
+	if( !( iu = irc_user_by_name( irc, cmd[1] ) ) || !( bu = iu->bu ) )
+	{
+		irc_usermsg( irc, "Buddy `%s' not found", cmd[1] );
+		return;
+	}
+	s = g_strdup( bu->handle );
+	
+	bu->ic->acc->prpl->remove_buddy( bu->ic, bu->handle, NULL );
+	nick_del( bu );
+	if( g_slist_find( irc->users, iu ) )
+		bee_user_free( irc->b, bu );
+	
+	irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
+	g_free( s );
+	
+	return;
 }
 
@@ -616,14 +699,14 @@
 	if( !cmd[2] )
 	{
-		user_t *u = user_find( irc, cmd[1] );
-		if( !u || !u->ic )
+		irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
+		if( !iu || !iu->bu )
 		{
 			irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
 			return;
 		}
-		ic = u->ic;
-		cmd[2] = u->handle;
-	}
-	else if( !( a = account_get( irc, cmd[1] ) ) )
+		ic = iu->bu->ic;
+		cmd[2] = iu->bu->handle;
+	}
+	else if( !( a = account_get( irc->b, cmd[1] ) ) )
 	{
 		irc_usermsg( irc, "Invalid account" );
@@ -648,54 +731,42 @@
 static void cmd_rename( irc_t *irc, char **cmd )
 {
-	user_t *u;
-	
-	if( g_strcasecmp( cmd[1], irc->nick ) == 0 )
-	{
-		irc_usermsg( irc, "Nick `%s' can't be changed", cmd[1] );
-	}
-	else if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
-	{
-		if( strchr( CTYPES, cmd[2][0] ) && nick_ok( cmd[2] + 1 ) )
-		{
-			u = user_find( irc, irc->nick );
-			
-			irc_part( irc, u, irc->channel );
-			g_free( irc->channel );
-			irc->channel = g_strdup( cmd[2] );
-			irc_join( irc, u, irc->channel );
-			
-			if( strcmp( cmd[0], "set_rename" ) != 0 )
-				set_setstr( &irc->set, "control_channel", cmd[2] );
-		}
-	}
-	else if( user_find( irc, cmd[2] ) && ( nick_cmp( cmd[1], cmd[2] ) != 0 ) )
+	irc_user_t *iu, *old;
+	
+	iu = irc_user_by_name( irc, cmd[1] );
+	
+	if( iu == NULL )
+	{
+		irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
+	}
+	else if( iu == irc->user )
+	{
+		irc_usermsg( irc, "Use /nick to change your own nickname" );
+	}
+	else if( !nick_ok( cmd[2] ) )
+	{
+		irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] );
+	}
+	else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu )
 	{
 		irc_usermsg( irc, "Nick `%s' already exists", cmd[2] );
 	}
-	else if( !nick_ok( cmd[2] ) )
-	{
-		irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] );
-	}
-	else if( !( u = user_find( irc, cmd[1] ) ) )
-	{
-		irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
-	}
-	else
-	{
-		user_rename( irc, cmd[1], cmd[2] );
-		irc_write( irc, ":%s!%s@%s NICK %s", cmd[1], u->user, u->host, cmd[2] );
-		if( g_strcasecmp( cmd[1], irc->mynick ) == 0 )
-		{
-			g_free( irc->mynick );
-			irc->mynick = g_strdup( cmd[2] );
-			
+	else
+	{
+		if( !irc_user_set_nick( iu, cmd[2] ) )
+		{
+			irc_usermsg( irc, "Error while changing nick" );
+			return;
+		}
+		
+		if( iu == irc->root )
+		{
 			/* If we're called internally (user did "set root_nick"),
 			   let's not go O(INF). :-) */
 			if( strcmp( cmd[0], "set_rename" ) != 0 )
-				set_setstr( &irc->set, "root_nick", cmd[2] );
-		}
-		else if( u->send_handler == buddy_send_handler )
-		{
-			nick_set( u->ic->acc, u->handle, cmd[2] );
+				set_setstr( &irc->b->set, "root_nick", cmd[2] );
+		}
+		else if( iu->bu )
+		{
+			nick_set( iu->bu, cmd[2] );
 		}
 		
@@ -708,48 +779,12 @@
 	irc_t *irc = set->data;
 	
-	if( strcmp( irc->mynick, new_nick ) != 0 )
-	{
-		char *cmd[] = { "set_rename", irc->mynick, new_nick, NULL };
+	if( strcmp( irc->root->nick, new_nick ) != 0 )
+	{
+		char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
 		
 		cmd_rename( irc, cmd );
 	}
 	
-	return strcmp( irc->mynick, new_nick ) == 0 ? new_nick : SET_INVALID;
-}
-
-char *set_eval_control_channel( set_t *set, char *new_name )
-{
-	irc_t *irc = set->data;
-	
-	if( strcmp( irc->channel, new_name ) != 0 )
-	{
-		char *cmd[] = { "set_rename", irc->channel, new_name, NULL };
-		
-		cmd_rename( irc, cmd );
-	}
-	
-	return strcmp( irc->channel, new_name ) == 0 ? new_name : SET_INVALID;
-}
-
-static void cmd_remove( irc_t *irc, char **cmd )
-{
-	user_t *u;
-	char *s;
-	
-	if( !( u = user_find( irc, cmd[1] ) ) || !u->ic )
-	{
-		irc_usermsg( irc, "Buddy `%s' not found", cmd[1] );
-		return;
-	}
-	s = g_strdup( u->handle );
-	
-	u->ic->acc->prpl->remove_buddy( u->ic, u->handle, NULL );
-	nick_del( u->ic->acc, u->handle );
-	user_del( irc, cmd[1] );
-	
-	irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
-	g_free( s );
-	
-	return;
+	return strcmp( irc->root->nick, new_nick ) == 0 ? new_nick : SET_INVALID;
 }
 
@@ -759,5 +794,5 @@
 	account_t *a;
 	
-	if( !cmd[2] && ( a = account_get( irc, cmd[1] ) ) && a->ic )
+	if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
 	{
 		char *format;
@@ -772,6 +807,7 @@
 		for( l = a->ic->deny; l; l = l->next )
 		{
-			user_t *u = user_findhandle( a->ic, l->data );
-			irc_usermsg( irc, format, l->data, u ? u->nick : "(none)" );
+			bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
+			irc_user_t *iu = bu ? bu->ui_data : NULL;
+			irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
 		}
 		irc_usermsg( irc, "End of list." );
@@ -781,14 +817,14 @@
 	else if( !cmd[2] )
 	{
-		user_t *u = user_find( irc, cmd[1] );
-		if( !u || !u->ic )
+		irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
+		if( !iu || !iu->bu )
 		{
 			irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
 			return;
 		}
-		ic = u->ic;
-		cmd[2] = u->handle;
-	}
-	else if( !( a = account_get( irc, cmd[1] ) ) )
+		ic = iu->bu->ic;
+		cmd[2] = iu->bu->handle;
+	}
+	else if( !( a = account_get( irc->b, cmd[1] ) ) )
 	{
 		irc_usermsg( irc, "Invalid account" );
@@ -809,5 +845,5 @@
 		imc_rem_allow( ic, cmd[2] );
 		imc_add_block( ic, cmd[2] );
-		irc_usermsg( irc, "Buddy `%s' moved from your allow- to your block-list", cmd[2] );
+		irc_usermsg( irc, "Buddy `%s' moved from allow- to block-list", cmd[2] );
 	}
 }
@@ -818,5 +854,5 @@
 	account_t *a;
 	
-	if( !cmd[2] && ( a = account_get( irc, cmd[1] ) ) && a->ic )
+	if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
 	{
 		char *format;
@@ -831,6 +867,7 @@
 		for( l = a->ic->permit; l; l = l->next )
 		{
-			user_t *u = user_findhandle( a->ic, l->data );
-			irc_usermsg( irc, format, l->data, u ? u->nick : "(none)" );
+			bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
+			irc_user_t *iu = bu ? bu->ui_data : NULL;
+			irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
 		}
 		irc_usermsg( irc, "End of list." );
@@ -840,14 +877,14 @@
 	else if( !cmd[2] )
 	{
-		user_t *u = user_find( irc, cmd[1] );
-		if( !u || !u->ic )
+		irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
+		if( !iu || !iu->bu )
 		{
 			irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
 			return;
 		}
-		ic = u->ic;
-		cmd[2] = u->handle;
-	}
-	else if( !( a = account_get( irc, cmd[1] ) ) )
+		ic = iu->bu->ic;
+		cmd[2] = iu->bu->handle;
+	}
+	else if( !( a = account_get( irc->b, cmd[1] ) ) )
 	{
 		irc_usermsg( irc, "Invalid account" );
@@ -869,5 +906,5 @@
 		imc_add_allow( ic, cmd[2] );
 		
-		irc_usermsg( irc, "Buddy `%s' moved from your block- to your allow-list", cmd[2] );
+		irc_usermsg( irc, "Buddy `%s' moved from block- to allow-list", cmd[2] );
 	}
 }
@@ -880,4 +917,33 @@
 	if( irc->queries == NULL )
 	{
+		/* Alright, alright, let's add a tiny easter egg here. */
+		static irc_t *last_irc = NULL;
+		static time_t last_time = 0;
+		static int times = 0;
+		static const char *msg[] = {
+			"Oh yeah, that's right.",
+			"Alright, alright. Now go back to work.",
+			"Buuuuuuuuuuuuuuuurp... Excuse me!",
+			"Yes?",
+			"No?",
+		};
+		
+		if( last_irc == irc && time( NULL ) - last_time < 15 )
+		{
+			if( ( ++times >= 3 ) )
+			{
+				irc_usermsg( irc, "%s", msg[rand()%(sizeof(msg)/sizeof(char*))] );
+				last_irc = NULL;
+				times = 0;
+				return;
+			}
+		}
+		else
+		{
+			last_time = time( NULL );
+			last_irc = irc;
+			times = 0;
+		}
+		
 		irc_usermsg( irc, "Did I ask you something?" );
 		return;
@@ -913,15 +979,5 @@
 static void cmd_set( irc_t *irc, char **cmd )
 {
-	cmd_set_real( irc, cmd, NULL, NULL );
-}
-
-static void cmd_save( irc_t *irc, char **cmd )
-{
-	if( ( irc->status & USTATUS_IDENTIFIED ) == 0 )
-		irc_usermsg( irc, "Please create an account first" );
-	else if( storage_save( irc, NULL, TRUE ) == STORAGE_OK )
-		irc_usermsg( irc, "Configuration saved" );
-	else
-		irc_usermsg( irc, "Configuration could not be saved!" );
+	cmd_set_real( irc, cmd, &irc->b->set, NULL );
 }
 
@@ -929,5 +985,5 @@
 {
 	int online = 0, away = 0, offline = 0;
-	user_t *u;
+	GSList *l;
 	char s[256];
 	char *format;
@@ -950,17 +1006,23 @@
 		format = "%-16.16s  %-40.40s  %s";
 	
-	irc_usermsg( irc, format, "Nick", "User/Host/Network", "Status" );
-	
-	for( u = irc->users; u; u = u->next ) if( u->ic && u->online && !u->away )
-	{
+	irc_usermsg( irc, format, "Nick", "Handle/Account", "Status" );
+	
+	for( l = irc->users; l; l = l->next )
+	{
+		irc_user_t *iu = l->data;
+		bee_user_t *bu = iu->bu;
+		
+		if( !bu || ( bu->flags & ( BEE_USER_ONLINE | BEE_USER_AWAY ) ) != BEE_USER_ONLINE )
+			continue;
+		
 		if( online == 1 )
 		{
 			char st[256] = "Online";
 			
-			if( u->status_msg )
-				g_snprintf( st, sizeof( st ) - 1, "Online (%s)", u->status_msg );
+			if( bu->status_msg )
+				g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg );
 			
-			g_snprintf( s, sizeof( s ) - 1, "%s@%s %s(%s)", u->user, u->host, u->ic->acc->prpl->name, u->ic->acc->user );
-			irc_usermsg( irc, format, u->nick, s, st );
+			g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
+			irc_usermsg( irc, format, iu->nick, s, st );
 		}
 		
@@ -968,20 +1030,32 @@
 	}
 
-	for( u = irc->users; u; u = u->next ) if( u->ic && u->online && u->away )
-	{
+	for( l = irc->users; l; l = l->next )
+	{
+		irc_user_t *iu = l->data;
+		bee_user_t *bu = iu->bu;
+		
+		if( !bu || !( bu->flags & BEE_USER_ONLINE ) || !( bu->flags & BEE_USER_AWAY ) )
+			continue;
+		
 		if( away == 1 )
 		{
-			g_snprintf( s, sizeof( s ) - 1, "%s@%s %s(%s)", u->user, u->host, u->ic->acc->prpl->name, u->ic->acc->user );
-			irc_usermsg( irc, format, u->nick, s, u->away );
+			g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
+			irc_usermsg( irc, format, iu->nick, s, irc_user_get_away( iu ) );
 		}
 		n_away ++;
 	}
 	
-	for( u = irc->users; u; u = u->next ) if( u->ic && !u->online )
-	{
+	for( l = irc->users; l; l = l->next )
+	{
+		irc_user_t *iu = l->data;
+		bee_user_t *bu = iu->bu;
+		
+		if( !bu || bu->flags & BEE_USER_ONLINE )
+			continue;
+		
 		if( offline == 1 )
 		{
-			g_snprintf( s, sizeof( s ) - 1, "%s@%s %s(%s)", u->user, u->host, u->ic->acc->prpl->name, u->ic->acc->user );
-			irc_usermsg( irc, format, u->nick, s, "Offline" );
+			g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
+			irc_usermsg( irc, format, iu->nick, s, "Offline" );
 		}
 		n_offline ++;
@@ -989,32 +1063,4 @@
 	
 	irc_usermsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline );
-}
-
-static void cmd_nick( irc_t *irc, char **cmd ) 
-{
-	account_t *a;
-
-	if( !cmd[1] || !( a = account_get( irc, cmd[1] ) ) )
-	{
-		irc_usermsg( irc, "Invalid account");
-	}
-	else if( !( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) )
-	{
-		irc_usermsg( irc, "That account is not on-line" );
-	}
-	else if ( !cmd[2] ) 
-	{
-		irc_usermsg( irc, "Your name is `%s'" , a->ic->displayname ? a->ic->displayname : "NULL" );
-	}
-	else if ( !a->prpl->set_my_name ) 
-	{
-		irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
-	}
-	else
-	{
-		irc_usermsg( irc, "Setting your name to `%s'", cmd[2] );
-		
-		a->prpl->set_my_name( a->ic, cmd[2] );
-	}
 }
 
@@ -1039,34 +1085,23 @@
 }
 
-static void cmd_join_chat( irc_t *irc, char **cmd )
-{
-	irc_usermsg( irc, "This command is now obsolete. "
-	                  "Please try the `chat' command instead." );
-}
-
-static set_t **cmd_chat_set_findhead( irc_t *irc, char *id )
-{
-	struct chat *c;
-	
-	if( ( c = chat_get( irc, id ) ) )
-		return &c->set;
-	else
-		return NULL;
-}
-
 static void cmd_chat( irc_t *irc, char **cmd )
 {
 	account_t *acc;
-	struct chat *c;
 	
 	if( g_strcasecmp( cmd[1], "add" ) == 0 )
 	{
 		char *channel, *s;
+		struct irc_channel *ic;
 		
 		MIN_ARGS( 3 );
 		
-		if( !( acc = account_get( irc, cmd[2] ) ) )
+		if( !( acc = account_get( irc->b, cmd[2] ) ) )
 		{
 			irc_usermsg( irc, "Invalid account" );
+			return;
+		}
+		else if( !acc->prpl->chat_join )
+		{
+			irc_usermsg( irc, "Named chatrooms not supported on that account." );
 			return;
 		}
@@ -1085,63 +1120,41 @@
 		if( strchr( CTYPES, channel[0] ) == NULL )
 		{
-			s = g_strdup_printf( "%c%s", CTYPES[0], channel );
+			s = g_strdup_printf( "#%s", channel );
 			g_free( channel );
 			channel = s;
-		}
-		
-		if( ( c = chat_add( irc, acc, cmd[3], channel ) ) )
-			irc_usermsg( irc, "Chatroom added successfully." );
-		else
+			
+			irc_channel_name_strip( channel );
+		}
+		
+		if( ( ic = irc_channel_new( irc, channel ) ) &&
+		    set_setstr( &ic->set, "type", "chat" ) &&
+		    set_setstr( &ic->set, "chat_type", "room" ) &&
+		    set_setstr( &ic->set, "account", cmd[2] ) &&
+		    set_setstr( &ic->set, "room", cmd[3] ) )
+		{
+			irc_usermsg( irc, "Chatroom successfully added." );
+		}
+		else
+		{
+			if( ic )
+				irc_channel_free( ic );
+			
 			irc_usermsg( irc, "Could not add chatroom." );
-		
+		}
 		g_free( channel );
 	}
-	else if( g_strcasecmp( cmd[1], "list" ) == 0 )
-	{
-		int i = 0;
-		
-		if( strchr( irc->umode, 'b' ) )
-			irc_usermsg( irc, "Chatroom list:" );
-		
-		for( c = irc->chatrooms; c; c = c->next )
-		{
-			irc_usermsg( irc, "%2d. %s(%s) %s, %s", i, c->acc->prpl->name,
-			                  c->acc->user, c->handle, c->channel );
-			
-			i ++;
-		}
-		irc_usermsg( irc, "End of chatroom list" );
-	}
-	else if( g_strcasecmp( cmd[1], "set" ) == 0 )
-	{
+	else if( g_strcasecmp( cmd[1], "with" ) == 0 )
+	{
+		irc_user_t *iu;
+		
 		MIN_ARGS( 2 );
 		
-		cmd_set_real( irc, cmd + 1, cmd_chat_set_findhead, NULL );
-	}
-	else if( g_strcasecmp( cmd[1], "del" ) == 0 )
-	{
-		MIN_ARGS( 2 );
-		
-		if( ( c = chat_get( irc, cmd[2] ) ) )
-		{
-			chat_del( irc, c );
-		}
-		else
-		{
-			irc_usermsg( irc, "Could not remove chat." );
-		}
-	}
-	else if( g_strcasecmp( cmd[1], "with" ) == 0 )
-	{
-		user_t *u;
-		
-		MIN_ARGS( 2 );
-		
-		if( ( u = user_find( irc, cmd[2] ) ) && u->ic && u->ic->acc->prpl->chat_with )
-		{
-			if( !u->ic->acc->prpl->chat_with( u->ic, u->handle ) )
+		if( ( iu = irc_user_by_name( irc, cmd[2] ) ) &&
+		    iu->bu && iu->bu->ic->acc->prpl->chat_with )
+		{
+			if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) )
 			{
 				irc_usermsg( irc, "(Possible) failure while trying to open "
-				                  "a groupchat with %s.", u->nick );
+				                  "a groupchat with %s.", iu->nick );
 			}
 		}
@@ -1151,4 +1164,11 @@
 		}
 	}
+	else if( g_strcasecmp( cmd[1], "list" ) == 0 ||
+	         g_strcasecmp( cmd[1], "set" ) == 0 ||
+	         g_strcasecmp( cmd[1], "del" ) == 0 )
+	{
+		irc_usermsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." );
+		cmd_channel( irc, cmd );
+	}
 	else
 	{
@@ -1157,25 +1177,116 @@
 }
 
+static void cmd_group( irc_t *irc, char **cmd )
+{
+	GSList *l;
+	int len;
+	
+	len = strlen( cmd[1] );
+	if( g_strncasecmp( cmd[1], "list", len ) == 0 )
+	{
+		int n = 0;
+		
+		if( strchr( irc->umode, 'b' ) )
+			irc_usermsg( irc, "Group list:" );
+		
+		for( l = irc->b->groups; l; l = l->next )
+		{
+			bee_group_t *bg = l->data;
+			irc_usermsg( irc, "%d. %s", n ++, bg->name );
+		}
+		irc_usermsg( irc, "End of group list" );
+	}
+	else
+	{
+		irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group", cmd[1] );
+	}
+}
+
+static void cmd_transfer( irc_t *irc, char **cmd )
+{
+	GSList *files = irc->file_transfers;
+	enum { LIST, REJECT, CANCEL };
+	int subcmd = LIST;
+	int fid;
+
+	if( !files )
+	{
+		irc_usermsg( irc, "No pending transfers" );
+		return;
+	}
+
+	if( cmd[1] && ( strcmp( cmd[1], "reject" ) == 0 ) )
+	{
+		subcmd = REJECT;
+	}
+	else if( cmd[1] && ( strcmp( cmd[1], "cancel" ) == 0 ) && 
+		 cmd[2] && ( sscanf( cmd[2], "%d", &fid ) == 1 ) )
+	{
+		subcmd = CANCEL;
+	}
+
+	for( ; files; files = g_slist_next( files ) )
+	{
+		file_transfer_t *file = files->data;
+		
+		switch( subcmd ) {
+		case LIST:
+			if ( file->status == FT_STATUS_LISTENING )
+				irc_usermsg( irc, 
+					"Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
+			else 
+			{
+				int kb_per_s = 0;
+				time_t diff = time( NULL ) - file->started ? : 1;
+				if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) )
+					kb_per_s = file->bytes_transferred / 1024 / diff;
+					
+				irc_usermsg( irc, 
+					"Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name, 
+					file->bytes_transferred/1024, file->file_size/1024, kb_per_s);
+			}
+			break;
+		case REJECT:
+			if( file->status == FT_STATUS_LISTENING )
+			{
+				irc_usermsg( irc, "Rejecting file transfer for %s", file->file_name );
+				imcb_file_canceled( file->ic, file, "Denied by user" );
+			}
+			break;
+		case CANCEL:
+			if( file->local_id == fid )
+			{
+				irc_usermsg( irc, "Canceling file transfer for %s", file->file_name );
+				imcb_file_canceled( file->ic, file, "Canceled by user" );
+			}
+			break;
+		}
+	}
+}
+
+/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
 const command_t commands[] = {
+	{ "account",        1, cmd_account,        0 },
+	{ "add",            2, cmd_add,            0 },
+	{ "allow",          1, cmd_allow,          0 },
+	{ "blist",          0, cmd_blist,          0 },
+	{ "block",          1, cmd_block,          0 },
+	{ "channel",        1, cmd_channel,        0 },
+	{ "chat",           1, cmd_chat,           0 },
+	{ "drop",           1, cmd_drop,           0 },
+	{ "ft",             0, cmd_transfer,       0 },
+	{ "group",          1, cmd_group,          0 },
 	{ "help",           0, cmd_help,           0 }, 
 	{ "identify",       1, cmd_identify,       0 },
+	{ "info",           1, cmd_info,           0 },
+	{ "no",             0, cmd_yesno,          0 },
+	{ "qlist",          0, cmd_qlist,          0 },
 	{ "register",       1, cmd_register,       0 },
-	{ "drop",           1, cmd_drop,           0 },
-	{ "account",        1, cmd_account,        0 },
-	{ "add",            2, cmd_add,            0 },
-	{ "info",           1, cmd_info,           0 },
+	{ "remove",         1, cmd_remove,         0 },
 	{ "rename",         2, cmd_rename,         0 },
-	{ "remove",         1, cmd_remove,         0 },
-	{ "block",          1, cmd_block,          0 },
-	{ "allow",          1, cmd_allow,          0 },
 	{ "save",           0, cmd_save,           0 },
 	{ "set",            0, cmd_set,            0 },
+	{ "transfer",       0, cmd_transfer,       0 },
 	{ "yes",            0, cmd_yesno,          0 },
-	{ "no",             0, cmd_yesno,          0 },
-	{ "blist",          0, cmd_blist,          0 },
-	{ "nick",           1, cmd_nick,           0 },
-	{ "qlist",          0, cmd_qlist,          0 },
-	{ "join_chat",      2, cmd_join_chat,      0 },
-	{ "chat",           1, cmd_chat,           0 },
 	{ NULL }
 };
Index: set.c
===================================================================
--- set.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ set.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -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;
@@ -69,5 +69,6 @@
 	while( s )
 	{
-		if( g_strcasecmp( s->key, key ) == 0 )
+		if( g_strcasecmp( s->key, key ) == 0 ||
+		    ( s->old_key && g_strcasecmp( s->old_key, key ) == 0 ) )
 			break;
 		s = s->next;
@@ -77,5 +78,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 +88,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 +102,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 +112,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 +151,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 +159,5 @@
 }
 
-void set_del( set_t **head, char *key )
+void set_del( set_t **head, const char *key )
 {
 	set_t *s = *head, *t = NULL;
@@ -176,11 +177,12 @@
 		
 		g_free( s->key );
-		if( s->value ) g_free( s->value );
-		if( s->def ) g_free( s->def );
+		g_free( s->old_key );
+		g_free( s->value );
+		g_free( s->def );
 		g_free( s );
 	}
 }
 
-int set_reset( set_t **head, char *key )
+int set_reset( set_t **head, const char *key )
 {
 	set_t *s;
@@ -211,4 +213,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;
 }
 
@@ -225,4 +242,5 @@
 }
 
+/*
 char *set_eval_ops( set_t *set, char *value )
 {
@@ -246,2 +264,3 @@
 	return value;
 }
+*/
Index: set.h
===================================================================
--- set.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ set.h	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -37,8 +37,5 @@
    from setting invalid values, you can write an evaluator function for
    every setting, which can check a new value and block it by returning
-   NULL, or replace it by returning a new value. See struct set.eval.
-   One thing that is really missing here is additional data for the
-   evaluator. This could be useful to add minimum and maximum values for
-   integers, for example. */
+   NULL, or replace it by returning a new value. See struct set.eval. */
 
 typedef char *(*set_eval) ( struct set *set, char *value );
@@ -54,4 +51,5 @@
 	
 	char *key;
+	char *old_key;  /* Previously known as; for smooth upgrades. */
 	char *value;
 	char *def;      /* Default value. If the set_setstr() function
@@ -65,36 +63,40 @@
 	                   this (yet?). */
 	
-	/* Eval: Returns SET_INVALID if the value is incorrect or exactly
-	   the passed value variable. When returning a corrected value,
-	   set_setstr() should be able to free() the returned string! */
+	/* Eval: Returns SET_INVALID if the value is incorrect, exactly
+	   the passed value variable, or a corrected value. In case of
+	   the latter, set_setstr() will 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.c
===================================================================
--- storage.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ storage.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -28,5 +28,4 @@
 #define BITLBEE_CORE
 #include "bitlbee.h"
-#include "crypting.h"
 
 extern storage_t storage_text;
@@ -66,5 +65,4 @@
 	storage_t *storage;
 	
-	register_storage_backend(&storage_text);
 	register_storage_backend(&storage_xml);
 	
Index: orage_text.c
===================================================================
--- storage_text.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,157 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2004 Wilmer van der Gaast and others                *
-  \********************************************************************/
-
-/* Storage backend that uses the same file format as <=1.0 */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-
-#define BITLBEE_CORE
-#include "bitlbee.h"
-#include "crypting.h"
-#ifdef _WIN32
-# define umask _umask
-# define mode_t int
-#endif
-
-#ifndef F_OK
-#define F_OK 0
-#endif
-
-static void text_init (void)
-{
-	/* Don't complain about the configuration directory anymore, leave it
-	   up to the XML storage module, which uses the same directory for it
-	   anyway. Nobody should be using just the text plugin anymore since
-	   it's read only! */
-}
-
-static storage_status_t text_load( irc_t *irc, const char* password )
-{
-	char s[512];
-	char *line;
-	int proto;
-	char nick[MAX_NICK_LENGTH+1];
-	FILE *fp;
-	user_t *ru = user_find( irc, ROOT_NICK );
-	account_t *acc, *acc_lookup[9];
-	
-	g_snprintf( s, 511, "%s%s%s", global.conf->configdir, irc->nick, ".accounts" );
-   	fp = fopen( s, "r" );
-   	if( !fp ) return STORAGE_NO_SUCH_USER;
-	
-	fscanf( fp, "%32[^\n]s", s );
-
-	if( checkpass( password, s ) != 0 )
-	{
-		fclose( fp );
-		return STORAGE_INVALID_PASSWORD;
-	}
-	
-	while( fscanf( fp, "%511[^\n]s", s ) > 0 )
-	{
-		fgetc( fp );
-		line = deobfucrypt( s, password );
-		if (line == NULL) return STORAGE_OTHER_ERROR;
-		root_command_string( irc, ru, line, 0 );
-		g_free( line );
-	}
-	fclose( fp );
-	
-	/* Build a list with the first listed account of every protocol
-	   number. So if the user had nicks defined for a second account on
-	   the same IM network, those nicks will be added to the wrong
-	   account, and the user should rename those buddies again. But at
-	   least from now on things will be saved properly. */
-	memset( acc_lookup, 0, sizeof( acc_lookup ) );
-	for( acc = irc->accounts; acc; acc = acc->next )
-	{
-		if( acc_lookup[0] == NULL && strcmp( acc->prpl->name, "oscar" ) == 0 )
-			acc_lookup[0] = acc_lookup[1] = acc_lookup[3] = acc;
-		else if( acc_lookup[2] == NULL && strcmp( acc->prpl->name, "yahoo" ) == 0 )
-			acc_lookup[2] = acc;
-		else if( acc_lookup[4] == NULL && strcmp( acc->prpl->name, "msn" ) == 0 )
-			acc_lookup[4] = acc;
-		else if( acc_lookup[8] == NULL && strcmp( acc->prpl->name, "jabber" ) == 0 )
-			acc_lookup[8] = acc;
-	}
-	
-	g_snprintf( s, 511, "%s%s%s", global.conf->configdir, irc->nick, ".nicks" );
-	fp = fopen( s, "r" );
-	if( !fp ) return STORAGE_NO_SUCH_USER;
-	while( fscanf( fp, "%s %d %s", s, &proto, nick ) > 0 )
-	{
-		if( proto < 0 || proto > 8 || ( acc = acc_lookup[proto] ) == NULL )
-			continue;
-		
-		http_decode( s );
-		nick_set( acc, s, nick );
-	}
-	fclose( fp );
-	
-	return STORAGE_OK;
-}
-
-static storage_status_t text_check_pass( const char *nick, const char *password )
-{
-	char s[512];
-	FILE *fp;
-	
-	g_snprintf( s, 511, "%s%s%s", global.conf->configdir, nick, ".accounts" );
-	fp = fopen( s, "r" );
-	if (!fp)
-		return STORAGE_NO_SUCH_USER;
-
-	fscanf( fp, "%32[^\n]s", s );
-	fclose( fp );
-
-	if (checkpass( password, s) == -1)
-		return STORAGE_INVALID_PASSWORD;
-
-	return STORAGE_OK;
-}
-
-static storage_status_t text_remove( const char *nick, const char *password )
-{
-	char s[512];
-	storage_status_t status;
-
-	status = text_check_pass( nick, password );
-	if (status != STORAGE_OK)
-		return status;
-
-	g_snprintf( s, 511, "%s%s%s", global.conf->configdir, nick, ".accounts" );
-	if (unlink( s ) == -1)
-		return STORAGE_OTHER_ERROR;
-	
-	g_snprintf( s, 511, "%s%s%s", global.conf->configdir, nick, ".nicks" );
-	if (unlink( s ) == -1)
-		return STORAGE_OTHER_ERROR;
-
-	return STORAGE_OK;
-}
-
-storage_t storage_text = {
-	.name = "text",
-	.init = text_init,
-	.check_pass = text_check_pass,
-	.remove = text_remove,
-	.load = text_load
-};
Index: storage_xml.c
===================================================================
--- storage_xml.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ storage_xml.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -29,5 +29,4 @@
 #include "arc.h"
 #include "md5.h"
-#include "chat.h"
 
 #if GLIB_CHECK_VERSION(2,8,0)
@@ -55,9 +54,10 @@
 	char *current_setting;
 	account_t *current_account;
-	struct chat *current_chat;
+	irc_channel_t *current_channel;
 	set_t **current_set_head;
 	char *given_nick;
 	char *given_pass;
 	xml_pass_st pass_st;
+	int unknown_tag;
 };
 
@@ -87,5 +87,9 @@
 	irc_t *irc = xd->irc;
 	
-	if( g_strcasecmp( element_name, "user" ) == 0 )
+	if( xd->unknown_tag > 0 )
+	{
+		xd->unknown_tag ++;
+	}
+	else if( g_strcasecmp( element_name, "user" ) == 0 )
 	{
 		char *nick = xml_attr( attr_names, attr_values, "nick" );
@@ -123,5 +127,5 @@
 	else if( g_strcasecmp( element_name, "account" ) == 0 )
 	{
-		char *protocol, *handle, *server, *password = NULL, *autoconnect;
+		char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag;
 		char *pass_b64 = NULL;
 		unsigned char *pass_cr = NULL;
@@ -133,4 +137,5 @@
 		server = xml_attr( attr_names, attr_values, "server" );
 		autoconnect = xml_attr( attr_names, attr_values, "autoconnect" );
+		tag = xml_attr( attr_names, attr_values, "tag" );
 		
 		protocol = xml_attr( attr_names, attr_values, "protocol" );
@@ -147,9 +152,11 @@
 		                         arc_decode( pass_cr, pass_len, &password, xd->given_pass ) )
 		{
-			xd->current_account = account_add( irc, prpl, handle, password );
+			xd->current_account = account_add( irc->b, prpl, handle, password );
 			if( server )
 				set_setstr( &xd->current_account->set, "server", server );
 			if( autoconnect )
 				set_setstr( &xd->current_account->set, "auto_connect", autoconnect );
+			if( tag )
+				set_setstr( &xd->current_account->set, "tag", tag );
 		}
 		else
@@ -176,10 +183,10 @@
 		if( ( setting = xml_attr( attr_names, attr_values, "name" ) ) )
 		{
-			if( xd->current_chat != NULL )
-				xd->current_set_head = &xd->current_chat->set;
+			if( xd->current_channel != NULL )
+				xd->current_set_head = &xd->current_channel->set;
 			else if( xd->current_account != NULL )
 				xd->current_set_head = &xd->current_account->set;
 			else
-				xd->current_set_head = &xd->irc->set;
+				xd->current_set_head = &xd->irc->b->set;
 			
 			xd->current_setting = g_strdup( setting );
@@ -198,5 +205,5 @@
 		if( xd->current_account && handle && nick )
 		{
-			nick_set( xd->current_account, handle, nick );
+			nick_set_raw( xd->current_account, handle, nick );
 		}
 		else
@@ -206,4 +213,28 @@
 		}
 	}
+	else if( g_strcasecmp( element_name, "channel" ) == 0 )
+	{
+		char *name, *type;
+		
+		name = xml_attr( attr_names, attr_values, "name" );
+		type = xml_attr( attr_names, attr_values, "type" );
+		
+		if( !name || !type )
+		{
+			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+			             "Missing attributes for %s element", element_name );
+			return;
+		}
+		
+		/* The channel may exist already, for example if it's &bitlbee.
+		   Also, it's possible that the user just reconnected and the
+		   IRC client already rejoined all channels it was in. They
+		   should still get the right settings. */
+		if( ( xd->current_channel = irc_channel_by_name( irc, name ) ) ||
+		    ( xd->current_channel = irc_channel_new( irc, name ) ) )
+			set_setstr( &xd->current_channel->set, "type", type );
+	}
+	/* Backward compatibility: Keep this around for a while for people
+	   switching from BitlBee 1.2.4+. */
 	else if( g_strcasecmp( element_name, "chat" ) == 0 )
 	{
@@ -215,5 +246,17 @@
 		if( xd->current_account && handle && channel )
 		{
-			xd->current_chat = chat_add( xd->irc, xd->current_account, handle, channel );
+			irc_channel_t *ic;
+			
+			if( ( ic = irc_channel_new( irc, channel ) ) &&
+			    set_setstr( &ic->set, "type", "chat" ) &&
+			    set_setstr( &ic->set, "chat_type", "room" ) &&
+			    set_setstr( &ic->set, "account", xd->current_account->tag ) &&
+			    set_setstr( &ic->set, "room", handle ) )
+			{
+				/* Try to pick up some settings where possible. */
+				xd->current_channel = ic;
+			}
+			else if( ic )
+				irc_channel_free( ic );
 		}
 		else
@@ -225,6 +268,13 @@
 	else
 	{
+		xd->unknown_tag ++;
+		irc_usermsg( irc, "Warning: Unknown XML tag found in configuration file (%s). "
+		                  "This may happen when downgrading BitlBee versions. "
+		                  "This tag will be skipped and the information will be lost "
+		                  "once you save your settings.", element_name );
+		/*
 		g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
 		             "Unkown element: %s", element_name );
+		*/
 	}
 }
@@ -234,5 +284,9 @@
 	struct xml_parsedata *xd = data;
 	
-	if( g_strcasecmp( element_name, "setting" ) == 0 && xd->current_setting )
+	if( xd->unknown_tag > 0 )
+	{
+		xd->unknown_tag --;
+	}
+	else if( g_strcasecmp( element_name, "setting" ) == 0 && xd->current_setting )
 	{
 		g_free( xd->current_setting );
@@ -243,7 +297,8 @@
 		xd->current_account = NULL;
 	}
-	else if( g_strcasecmp( element_name, "chat" ) == 0 )
-	{
-		xd->current_chat = NULL;
+	else if( g_strcasecmp( element_name, "channel" ) == 0 ||
+	         g_strcasecmp( element_name, "chat" ) == 0 )
+	{
+		xd->current_channel = NULL;
 	}
 }
@@ -353,5 +408,5 @@
 static storage_status_t xml_load( irc_t *irc, const char *password )
 {
-	return xml_load_real( irc, irc->nick, password, XML_PASS_UNKNOWN );
+	return xml_load_real( irc, irc->user->nick, password, XML_PASS_UNKNOWN );
 }
 
@@ -395,6 +450,7 @@
 	md5_byte_t pass_md5[21];
 	md5_state_t md5_state;
-	
-	path2 = g_strdup( irc->nick );
+	GSList *l;
+	
+	path2 = g_strdup( irc->user->nick );
 	nick_lc( path2 );
 	g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, path2, ".xml" );
@@ -404,6 +460,6 @@
 		return STORAGE_ALREADY_EXISTS;
 	
-	strcat( path, "~" );
-	if( ( fd = open( path, O_WRONLY | O_CREAT | O_TRUNC, 0600 ) ) < 0 )
+	strcat( path, ".XXXXXX" );
+	if( ( fd = mkstemp( path ) ) < 0 )
 	{
 		irc_usermsg( irc, "Error while opening configuration file." );
@@ -422,20 +478,19 @@
 	pass_buf = base64_encode( pass_md5, 21 );
 	
-	if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->nick, pass_buf, XML_FORMAT_VERSION ) )
+	if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->user->nick, pass_buf, XML_FORMAT_VERSION ) )
 		goto write_error;
 	
 	g_free( pass_buf );
 	
-	for( set = irc->set; set; set = set->next )
+	for( set = irc->b->set; set; set = set->next )
 		if( set->value )
 			if( !xml_printf( fd, 1, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
 				goto write_error;
 	
-	for( acc = irc->accounts; acc; acc = acc->next )
+	for( acc = irc->b->accounts; acc; acc = acc->next )
 	{
 		unsigned char *pass_cr;
 		char *pass_b64;
 		int pass_len;
-		struct chat *c;
 		
 		pass_len = arc_encode( acc->pass, strlen( acc->pass ), (unsigned char**) &pass_cr, irc->password, 12 );
@@ -443,5 +498,7 @@
 		g_free( pass_cr );
 		
-		if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" autoconnect=\"%d\"", acc->prpl->name, acc->user, pass_b64, acc->auto_connect ) )
+		if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" "
+		                        "autoconnect=\"%d\" tag=\"%s\"", acc->prpl->name, acc->user,
+		                        pass_b64, acc->auto_connect, acc->tag ) )
 		{
 			g_free( pass_b64 );
@@ -470,33 +527,35 @@
 			goto write_error;
 		
-		for( c = irc->chatrooms; c; c = c->next )
-		{
-			if( c->acc != acc )
-				continue;
-			
-			if( !xml_printf( fd, 2, "<chat handle=\"%s\" channel=\"%s\" type=\"%s\">\n",
-			                        c->handle, c->channel, "room" ) )
-				goto write_error;
-			
-			for( set = c->set; set; set = set->next )
-				if( set->value && !( set->flags & ACC_SET_NOSAVE ) )
-					if( !xml_printf( fd, 3, "<setting name=\"%s\">%s</setting>\n",
-					                        set->key, set->value ) )
-						goto write_error;
-
-			if( !xml_printf( fd, 2, "</chat>\n" ) )
-				goto write_error;
-		}
-		
 		if( !xml_printf( fd, 1, "</account>\n" ) )
 			goto write_error;
 	}
 	
+	for( l = irc->channels; l; l = l->next )
+	{
+		irc_channel_t *ic = l->data;
+		
+		if( ic->flags & IRC_CHANNEL_TEMP )
+			continue;
+		
+		if( !xml_printf( fd, 1, "<channel name=\"%s\" type=\"%s\">\n",
+		                 ic->name, set_getstr( &ic->set, "type" ) ) )
+			goto write_error;
+		
+		for( set = ic->set; set; set = set->next )
+			if( set->value && strcmp( set->key, "type" ) != 0 )
+				if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
+					goto write_error;
+		
+		if( !xml_printf( fd, 1, "</channel>\n" ) )
+			goto write_error;
+	}
+	
 	if( !xml_printf( fd, 0, "</user>\n" ) )
 		goto write_error;
 	
+	fsync( fd );
 	close( fd );
 	
-	path2 = g_strndup( path, strlen( path ) - 1 );
+	path2 = g_strndup( path, strlen( path ) - 7 );
 	if( rename( path, path2 ) != 0 )
 	{
Index: tests/Makefile
===================================================================
--- tests/Makefile	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ tests/Makefile	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -1,3 +1,6 @@
 -include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)tests/
+endif
 
 LFLAGS +=-lcheck
@@ -11,7 +14,7 @@
 distclean: clean
 
-main_objs = account.o bitlbee.o chat.o conf.o crypting.o help.o ipc.o irc.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o storage_xml.o storage_text.o user.o
+main_objs = bitlbee.o conf.o dcc.o help.o ipc.o irc.o irc_channel.o irc_commands.o irc_im.o irc_send.o irc_user.o irc_util.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o storage_xml.o
 
-test_objs = check.o check_util.o check_nick.o check_md5.o check_arc.o check_irc.o check_help.o check_user.o check_crypting.o check_set.o check_jabber_sasl.o check_jabber_util.o
+test_objs = check.o check_util.o check_nick.o check_md5.o check_arc.o check_irc.o check_help.o check_user.o check_set.o check_jabber_sasl.o check_jabber_util.o
 
 check: $(test_objs) $(addprefix ../, $(main_objs)) ../protocols/protocols.o ../lib/lib.o
@@ -19,5 +22,5 @@
 	@$(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) $(EFLAGS)
 
-%.o: %.c
+%.o: $(SRCDIR)%.c
 	@echo '*' Compiling $<
 	@$(CC) -c $(CFLAGS) $< -o $@
Index: tests/check.c
===================================================================
--- tests/check.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ tests/check.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -3,4 +3,5 @@
 #include <gmodule.h>
 #include <check.h>
+#include <locale.h>
 #include "bitlbee.h"
 #include "testsuite.h"
@@ -60,7 +61,4 @@
 Suite *user_suite(void);
 
-/* From check_crypting.c */
-Suite *crypting_suite(void);
-
 /* From check_set.c */
 Suite *set_suite(void);
@@ -95,4 +93,6 @@
 
 	log_init();
+	b_main_init();
+	setlocale(LC_CTYPE, "");
 
 	if (verbose) {
@@ -115,5 +115,4 @@
 	srunner_add_suite(sr, help_suite());
 	srunner_add_suite(sr, user_suite());
-	srunner_add_suite(sr, crypting_suite());
 	srunner_add_suite(sr, set_suite());
 	srunner_add_suite(sr, jabber_sasl_suite());
Index: sts/check_crypting.c
===================================================================
--- tests/check_crypting.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,47 +1,0 @@
-#include <stdlib.h>
-#include <glib.h>
-#include <gmodule.h>
-#include <check.h>
-#include <string.h>
-#include "bitlbee.h"
-#include "crypting.h"
-#include "testsuite.h"
-
-START_TEST(test_check_pass_valid)
-	fail_unless (checkpass ("foo", "acbd18db4cc2f85cedef654fccc4a4d8") == 0);
-	fail_unless (checkpass ("invalidpass", "acbd18db4cc2f85cedef654fccc4a4d8") == -1);
-
-END_TEST
-
-START_TEST(test_hashpass)
-	fail_unless (strcmp(hashpass("foo"), "acbd18db4cc2f85cedef654fccc4a4d8") == 0);
-END_TEST
-
-START_TEST(test_obfucrypt)
-	char *raw = obfucrypt("some line", "bla");
-	fail_unless(strcmp(raw, "\xd5\xdb\xce\xc7\x8c\xcd\xcb\xda\xc6") == 0);
-END_TEST
-
-START_TEST(test_deobfucrypt)
-	char *raw = deobfucrypt("\xd5\xdb\xce\xc7\x8c\xcd\xcb\xda\xc6", "bla");
-	fail_unless(strcmp(raw, "some line") == 0);
-END_TEST
-
-START_TEST(test_obfucrypt_bidirectional)
-	char *plain = g_strdup("this is a line");
-	char *raw = obfucrypt(plain, "foo");
-	fail_unless(strcmp(plain, deobfucrypt(raw, "foo")) == 0);
-END_TEST
-
-Suite *crypting_suite (void)
-{
-	Suite *s = suite_create("Crypting");
-	TCase *tc_core = tcase_create("Core");
-	suite_add_tcase (s, tc_core);
-	tcase_add_test (tc_core, test_check_pass_valid);
-	tcase_add_test (tc_core, test_hashpass);
-	tcase_add_test (tc_core, test_obfucrypt);
-	tcase_add_test (tc_core, test_deobfucrypt);
-	tcase_add_test (tc_core, test_obfucrypt_bidirectional);
-	return s;
-}
Index: tests/check_user.c
===================================================================
--- tests/check_user.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ tests/check_user.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -5,7 +5,7 @@
 #include <string.h>
 #include "bitlbee.h"
-#include "user.h"
 #include "testsuite.h"
 
+#if 0
 START_TEST(test_user_add)
 	irc_t *irc = torture_irc();
@@ -59,5 +59,5 @@
 	fail_if(user_find(irc, "bar") == NULL);
 END_TEST
-
+#endif
 Suite *user_suite (void)
 {
@@ -65,4 +65,5 @@
 	TCase *tc_core = tcase_create("Core");
 	suite_add_tcase (s, tc_core);
+#if 0
 	tcase_add_test (tc_core, test_user_add);
 	tcase_add_test (tc_core, test_user_add_invalid);
@@ -72,4 +73,5 @@
 	tcase_add_test (tc_core, test_user_del);
 	tcase_add_test (tc_core, test_user_rename);
+#endif
 	return s;
 }
Index: tests/check_util.c
===================================================================
--- tests/check_util.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ tests/check_util.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -161,4 +161,12 @@
 END_TEST
 
+START_TEST(test_http_encode)
+	char s[80];
+	
+	strcpy( s, "ee\xc3""\xab""ee!!..." );
+	http_encode( s );
+	fail_unless( strcmp( s, "ee%C3%ABee%21%21..." ) == 0 );
+END_TEST
+
 Suite *util_suite (void)
 {
@@ -174,4 +182,5 @@
 	tcase_add_test (tc_core, test_set_url_username_pwd);
 	tcase_add_test (tc_core, test_word_wrap);
+	tcase_add_test (tc_core, test_http_encode);
 	return s;
 }
Index: unix.c
===================================================================
--- unix.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ unix.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -39,4 +39,5 @@
 #include <sys/wait.h>
 #include <pwd.h>
+#include <locale.h>
 
 global_t global;	/* Against global namespace pollution */
@@ -52,8 +53,13 @@
 	struct sigaction sig, old;
 	
+	/* Required to make iconv to ASCII//TRANSLIT work. This makes BitlBee
+	   system-locale-sensitive. :-( */
+	setlocale( LC_CTYPE, "" );
+	
 	if( argc > 1 && strcmp( argv[1], "-x" ) == 0 )
 		return crypt_main( argc, argv );
 	
 	log_init();
+	
 	global.conf_file = g_strdup( CONF_FILE_DEF );
 	global.conf = conf_load( argc, argv );
@@ -62,8 +68,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 +88,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 +114,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 +131,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 +150,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();
@@ -150,10 +159,13 @@
 	{
 		char *fn = ipc_master_save_state();
+		char *env;
+		
+		env = g_strdup_printf( "_BITLBEE_RESTART_STATE=%s", fn );
+		putenv( env );
+		g_free( fn );
+		/* Looks like env should *not* be freed here as putenv
+		   doesn't make a copy. Odd. */
 		
 		chdir( old_cwd );
-		
-		setenv( "_BITLBEE_RESTART_STATE", fn, 1 );
-		g_free( fn );
-		
 		close( global.listen_socket );
 		
@@ -230,5 +242,5 @@
 	/* FIXME: Calling log_message() here is not a very good idea! */
 	
-	if( signal == SIGTERM )
+	if( signal == SIGTERM || signal == SIGQUIT || signal == SIGINT )
 	{
 		static int first = 1;
Index: er.c
===================================================================
--- user.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,231 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2004 Wilmer van der Gaast and others                *
-  \********************************************************************/
-
-/* Stuff to handle, save and search buddies                             */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-
-#define BITLBEE_CORE
-#include "bitlbee.h"
-
-user_t *user_add( irc_t *irc, char *nick )
-{
-	user_t *u, *lu = NULL;
-	char *key;
-	
-	if( !nick_ok( nick ) )
-		return( NULL );
-	
-	if( user_find( irc, nick ) != NULL )
-		return( NULL );
-	
-	if( ( u = irc->users ) )
-	{
-		while( u )
-		{
-			if( nick_cmp( nick, u->nick ) < 0 )
-				break;
-			
-			lu = u;
-			u = u->next;
-		}
-		
-		u = g_new0( user_t, 1 );
-		if( lu )
-		{
-			u->next = lu->next;
-			lu->next = u;
-		}
-		else
-		{
-			u->next = irc->users;
-			irc->users = u;
-		}
-	}
-	else
-	{
-		irc->users = u = g_new0( user_t, 1 );
-	}
-	
-	u->user = u->realname = u->host = u->nick = g_strdup( nick );
-	u->is_private = set_getbool( &irc->set, "private" );
-	
-	key = g_strdup( nick );
-	nick_lc( key );
-	g_hash_table_insert( irc->userhash, key, u );
-	
-	return( u );
-}
-
-int user_del( irc_t *irc, char *nick )
-{
-	user_t *u, *t;
-	char *key;
-	gpointer okey, ovalue;
-	
-	if( !nick_ok( nick ) )
-		return( 0 );
-	
-	u = irc->users;
-	t = NULL;
-	while( u )
-	{
-		if( nick_cmp( u->nick, nick ) == 0 )
-		{
-			/* Get this key now already, since "nick" might be free()d
-			   at the time we start playing with the hash... */
-			key = g_strdup( nick );
-			nick_lc( key );
-			
-			if( t )
-				t->next = u->next;
-			else
-				irc->users = u->next;
-			if( u->online )
-				irc_kill( irc, u );
-			g_free( u->nick );
-			if( u->nick != u->user ) g_free( u->user );
-			if( u->nick != u->host ) g_free( u->host );
-			if( u->nick != u->realname ) g_free( u->realname );
-			g_free( u->group );
-			g_free( u->away );
-			g_free( u->handle );
-			g_free( u->sendbuf );
-			if( u->sendbuf_timer ) b_event_remove( u->sendbuf_timer );
-			g_free( u );
-			
-			if( !g_hash_table_lookup_extended( irc->userhash, key, &okey, &ovalue ) || ovalue != u )
-			{
-				g_free( key );
-				return( 1 );	/* Although this is a severe error, the user is removed from the list... */
-			}
-			g_hash_table_remove( irc->userhash, key );
-			g_free( key );
-			g_free( okey );
-			
-			return( 1 );
-		}
-		u = (t=u)->next;
-	}
-	
-	return( 0 );
-}
-
-user_t *user_find( irc_t *irc, char *nick )
-{
-	char key[512] = "";
-	
-	strncpy( key, nick, sizeof( key ) - 1 );
-	if( nick_lc( key ) )
-		return( g_hash_table_lookup( irc->userhash, key ) );
-	else
-		return( NULL );
-}
-
-user_t *user_findhandle( struct im_connection *ic, const char *handle )
-{
-	user_t *u;
-	char *nick;
-	
-	/* First, let's try a hash lookup. If it works, it's probably faster. */
-	if( ( nick = g_hash_table_lookup( ic->acc->nicks, handle ) ) &&
-	    ( u = user_find( ic->irc, nick ) ) &&
-	    ( ic->acc->prpl->handle_cmp( handle, u->handle ) == 0 ) )
-		return u;
-	
-	/* However, it doesn't always work, so in that case we'll have to dig
-	   through the whole userlist. :-( */
-	for( u = ic->irc->users; u; u = u->next )
-		if( u->ic == ic && u->handle && ic->acc->prpl->handle_cmp( u->handle, handle ) == 0 )
-			return u;
-	
-	return NULL;
-}
-
-/* DO NOT PASS u->nick FOR oldnick !!! */
-void user_rename( irc_t *irc, char *oldnick, char *newnick )
-{
-	user_t *u = user_find( irc, oldnick );
-	gpointer okey, ovalue;
-	char *key;
-	
-	if( !u ) return;	/* Should've been checked by the caller... */
-	
-	g_free( u->nick );
-	if( u->nick == u->user ) u->user = NULL;
-	if( u->nick == u->host ) u->host = NULL;
-	if( u->nick == u->realname ) u->realname = NULL;
-	u->nick = g_strdup( newnick );
-	if( !u->user ) u->user = u->nick;
-	if( !u->host ) u->host = u->nick;
-	if( !u->realname ) u->realname = u->nick;
-	
-	/* Remove the old reference to this user from the hash and create a
-	   new one with the new nick. This is indeed a bit messy. */
-	key = g_strdup( oldnick );
-	nick_lc( key );
-	if( !g_hash_table_lookup_extended( irc->userhash, key, &okey, &ovalue ) || ovalue != u )
-	{
-		g_free( key );
-		return;		/* This really shouldn't happen! */	
-	}
-	g_hash_table_remove( irc->userhash, key );
-	g_free( key );
-	g_free( okey );
-	
-	key = g_strdup( newnick );
-	nick_lc( key );
-	g_hash_table_insert( irc->userhash, key, u );
-	
-	/* Also, let's try to keep the linked list nicely sorted. Fear this
-	   code. If my teacher would see this, she would cry. ;-) */
-	{
-		user_t *u1, *lu1;
-		
-		/* Remove the user from the old position in the chain. */
-		if( u == irc->users )
-		{
-			irc->users = u->next;
-		}
-		else
-		{
-			u1 = u;
-			for( lu1 = irc->users; lu1->next != u1; lu1 = lu1->next );
-			lu1->next = u1->next;
-		}
-		
-		/* Search for the new position. */
-		for( lu1 = NULL, u1 = irc->users; u1; u1 = u1->next )
-		{
-			if( nick_cmp( newnick, u1->nick ) < 0 )
-				break;
-			
-			lu1 = u1;
-		}
-		
-		/* Insert it at this new position. */
-		u->next = u1;
-		if( lu1 )
-			lu1->next = u;
-		else
-			irc->users = u;
-	}
-}
Index: er.h
===================================================================
--- user.h	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ 	(revision )
@@ -1,62 +1,0 @@
-  /********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2004 Wilmer van der Gaast and others                *
-  \********************************************************************/
-
-/* Stuff to handle, save and search buddies                             */
-
-/*
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-  Suite 330, Boston, MA  02111-1307  USA
-*/
-#ifndef __USER_H__
-#define __USER_H__
-
-typedef struct __USER
-{
-	char *nick;
-	char *user;
-	char *host;
-	char *realname;
-	
-	char *away;
-	char *status_msg; /* Non-IRC extension, but nice on IM. */
-	
-	char is_private;
-	char online;
-	
-	char *handle;
-	char *group;
-	struct im_connection *ic;
-
- 	char *sendbuf;
- 	time_t last_typing_notice;
- 	int sendbuf_len;
- 	guint sendbuf_timer;
-    	int sendbuf_flags;
-	
-	void (*send_handler) ( irc_t *irc, struct __USER *u, char *msg, int flags );
-	
-	struct __USER *next;
-} user_t;
-
-user_t *user_add( struct irc *irc, char *nick );
-int user_del( irc_t *irc, char *nick );
-G_MODULE_EXPORT user_t *user_find( irc_t *irc, char *nick );
-G_MODULE_EXPORT user_t *user_findhandle( struct im_connection *ic, const char *handle );
-void user_rename( irc_t *irc, char *oldnick, char *newnick );
-
-#endif /* __USER_H__ */
Index: win32.c
===================================================================
--- win32.c	(revision ee6cc946dc4ee82cb641df94a6ba101e99253af2)
+++ win32.c	(revision 2528cdad90271f80d2ffe0e679ff8258f3e94e4c)
@@ -27,5 +27,4 @@
 #include "bitlbee.h"
 #include "commands.h"
-#include "crypting.h"
 #include "protocols/nogaim.h"
 #include "help.h"
