Index: Makefile
===================================================================
--- Makefile	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ Makefile	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -10,6 +10,7 @@
 
 # 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 = chat.o
+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 = account.h 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 user.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/ft.h protocols/nogaim.h
 subdirs = lib protocols
 
Index: count.c
===================================================================
--- account.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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.h
===================================================================
--- bitlbee.h	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ bitlbee.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -126,4 +126,5 @@
 #define CONF_FILE_DEF ETCDIR "bitlbee.conf"
 
+#include "bee.h"
 #include "irc.h"
 #include "storage.h"
@@ -160,5 +161,5 @@
 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 bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond );
Index: at.c
===================================================================
--- chat.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ conf.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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;
@@ -315,4 +318,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 )
 			{
@@ -349,5 +376,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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ conf.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ configure	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -398,5 +398,5 @@
 fi
 
-STORAGES="text xml"
+STORAGES="xml"
 
 if [ "$ldap" = "auto" ]; then
Index: ypting.c
===================================================================
--- crypting.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ dcc.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,567 @@
+/********************************************************************\
+* 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++;
+	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, 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, GAIM_INPUT_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 'transfers 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 & GAIM_INPUT_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, GAIM_INPUT_READ, dccs_send_proto, df );
+
+		return FALSE;
+	}
+
+	if( cond & GAIM_INPUT_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, GAIM_INPUT_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 & GAIM_INPUT_WRITE ) &&
+	    ( ft->status & FT_STATUS_CONNECTING ) )
+	{
+		ft->status = FT_STATUS_TRANSFERRING;
+
+		//df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
+
+		df->watch_out = 0;
+		return FALSE;
+	}
+
+	if( cond & GAIM_INPUT_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, GAIM_INPUT_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, GAIM_INPUT_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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ dcc.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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: doc/user-guide/commands.xml
===================================================================
--- doc/user-guide/commands.xml	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ doc/user-guide/commands.xml	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -1092,3 +1092,46 @@
 
 	</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: ipc.c
===================================================================
--- ipc.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ ipc.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -138,5 +138,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 +147,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 +156,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 +176,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 +188,5 @@
 		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 );
 }
 
Index: irc.c
===================================================================
--- irc.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ irc.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -5,5 +5,5 @@
   \********************************************************************/
 
-/* The big hairy IRCd part of the project                               */
+/* The IRC-based UI (for now the only one)                              */
 
 /*
@@ -24,92 +24,11 @@
 */
 
-#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;
-	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;
-}
-
-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;
-}
+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 );
 
 irc_t *irc_new( int fd )
@@ -118,5 +37,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 );
@@ -130,10 +52,6 @@
 	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;
@@ -142,5 +60,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 ) 
@@ -151,5 +69,5 @@
 		                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
 		{
-			irc->myhost = g_strdup( ipv6_unwrap( buf ) );
+			myhost = g_strdup( ipv6_unwrap( buf ) );
 		}
 	}
@@ -162,57 +80,65 @@
 		                 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 );
-	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, "display_timestamps", "true", set_eval_bool, irc );
-	s = set_add( &irc->set, "handle_unknown", "root", NULL, irc );
-	s = set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc );
-	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, "timezone", "local", set_eval_timezone, irc );
-	s = set_add( &irc->set, "to_char", ": ", set_eval_to_char, irc );
-	s = set_add( &irc->set, "typing_notice", "false", set_eval_bool, irc );
+	b = irc->b = bee_new();
+	b->ui_data = irc;
+	b->ui = &irc_ui_funcs;
+	
+	s = set_add( &b->set, "away_devoice", "true", NULL/*set_eval_away_devoice*/, irc );
+	s = set_add( &b->set, "buddy_sendbuffer", "false", set_eval_bool, irc );
+	s = set_add( &b->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc );
+	s = set_add( &b->set, "charset", "utf-8", set_eval_charset, irc );
+	//s = set_add( &b->set, "control_channel", irc->channel, NULL/*set_eval_control_channel*/, 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", "root", NULL, irc );
+	s = set_add( &b->set, "lcnicks", "true", set_eval_bool, irc );
+	s = set_add( &b->set, "ops", "both", NULL/*set_eval_ops*/, 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, NULL/*set_eval_root_nick*/, 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" );
+	
+	g_free( myhost );
+	g_free( host );
+	
+	return irc;
 }
 
@@ -235,5 +161,5 @@
 		
 		ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
-	                           irc->nick ? irc->nick : "(NONE)", irc->host, reason );
+	                           irc->user->nick ? irc->user->nick : "(NONE)", irc->root->host, reason );
 	     	
 		g_free( reason );
@@ -245,5 +171,5 @@
 		
 		ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
-	        	           irc->nick ? irc->nick : "(NONE)", irc->host, "No reason given" );
+	        	           irc->user->nick ? irc->user->nick : "(NONE)", irc->root->host, "No reason given" );
 	}
 	
@@ -266,63 +192,28 @@
 }
 
-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 */
+static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data );
+
 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!" );
+	*/
 	
 	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 );
-		}
-	}
+	*/
+	
+	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 )
@@ -336,6 +227,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 );
@@ -350,16 +241,5 @@
 	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 );
@@ -373,7 +253,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);
@@ -386,4 +273,6 @@
 }
 
+static char **irc_splitlines( char *buffer );
+
 void irc_process( irc_t *irc )
 {
@@ -393,5 +282,5 @@
 	if( irc->readbuffer != NULL )
 	{
-		lines = irc_tokenize( irc->readbuffer );
+		lines = irc_splitlines( irc->readbuffer );
 		
 		for( i = 0; *lines[i] != '\0'; i ++ )
@@ -430,5 +319,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 );
@@ -437,5 +326,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." );
 						
@@ -475,7 +364,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;
@@ -608,34 +499,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, ... ) 
 {
@@ -648,4 +509,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, GAIM_INPUT_WRITE );
+		}
+		temp = temp->next;
+	}
+	
+	va_end( params );
+	return;
+} 
 
 void irc_vawrite( irc_t *irc, char *format, va_list params )
@@ -704,103 +594,60 @@
 }
 
-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 )
+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;
+			
+			/* 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 );
+			irc_send_login( irc );
+			
+			irc->umode[0] = '\0';
+			irc_umode_set( irc, "+" UMODE, TRUE );
+			
+			ic = irc_channel_new( irc, ROOT_CHAN );
+			irc_channel_set_topic( ic, CONTROL_TOPIC, irc->root );
+			irc_channel_add_user( ic, irc->user );
+			
+			irc->last_root_cmd = g_strdup( ROOT_CHAN );
+			
+			irc_send_msg( irc->root, "PRIVMSG", ROOT_CHAN,
+			              "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.", NULL );
+			
 			return 1;
 		}
@@ -813,133 +660,10 @@
 }
 
-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 )
+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;
@@ -949,6 +673,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 ++ )
@@ -956,5 +681,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)
@@ -973,327 +700,16 @@
 	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 );
+}
+
 
 /* Returns 0 if everything seems to be okay, a number >0 when there was a
@@ -1333,25 +749,46 @@
 }
 
-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;
+}
Index: irc.h
===================================================================
--- irc.h	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ irc.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -5,5 +5,5 @@
   \********************************************************************/
 
-/* The big hairy IRCd part of the project                               */
+/* The IRC-based UI (for now the only one)                              */
 
 /*
@@ -33,10 +33,12 @@
 #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 "nt"        /* Allowed modes */
+#define CMODE "t"          /* Default mode */
+#define UMODE "s"          /* Default mode */
+
+#define CTYPES "&#"        /* Valid channel name prefixes */
 
 typedef enum
@@ -48,4 +50,6 @@
 	USTATUS_SHUTDOWN = 8
 } irc_status_t;
+
+struct irc_user;
 
 typedef struct irc
@@ -59,43 +63,97 @@
 	GIConv iconv, oconv;
 
-	int sentbytes;
-	time_t oldtime;
-
+	struct irc_user *root;
+	struct irc_user *user;
+	
+	char *last_root_cmd;
+
+	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;
+	GHashTable *nick_user_hash;
+	GHashTable *watches;
+
+	gint r_watch_source_id;
+	gint w_watch_source_id;
+	gint ping_source_id;
+	
+	struct bee *b;
+} irc_t;
+
+typedef enum
+{
+	IRC_USER_PRIVATE = 1,
+} 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 sensitive searches */
+	char *key;
+	
+	irc_user_flags_t flags;
+	
+	char *sendbuf;
+	int sendbuf_len;
+	guint sendbuf_timer;
+	//int sendbuf_flags;
+	
+	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,
+} 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 set *set;
-
-	gint r_watch_source_id;
-	gint w_watch_source_id;
-	gint ping_source_id;
-} irc_t;
-
-#include "user.h"
-
+	
+	const struct irc_channel_funcs *f;
+} irc_channel_t;
+
+struct irc_channel_funcs
+{
+	gboolean (*privmsg)( irc_channel_t *iu, const char *msg );
+};
+
+extern const struct bee_ui_funcs irc_ui_funcs;
+
+/* irc.c */
 extern GSList *irc_connection_list;
 
@@ -103,40 +161,57 @@
 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 );
+
 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 );
+int irc_channel_free( 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 );
+gboolean 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 );
+gboolean irc_channel_name_ok( const char *name );
+
+/* 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_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 );
+
+/* 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_ );
+
+/* irc_util.c */
+char *set_eval_timezone( struct set *set, char *value );
+char *irc_format_timestamp( irc_t *irc, time_t msg_ts );
 
 #endif
Index: irc_channel.c
===================================================================
--- irc_channel.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ irc_channel.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,160 @@
+  /********************************************************************\
+  * 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 const struct irc_channel_funcs control_channel_funcs;
+
+irc_channel_t *irc_channel_new( irc_t *irc, const char *name )
+{
+	irc_channel_t *ic;
+	
+	if( strchr( CTYPES, name[0] ) == NULL || !nick_ok( name + 1 ) )
+		return NULL;
+	
+	ic = g_new0( irc_channel_t, 1 );
+	ic->f = &control_channel_funcs;
+	ic->irc = irc;
+	ic->name = g_strdup( name );
+	strcpy( ic->mode, CMODE );
+	
+	irc_channel_add_user( ic, irc->root );
+	
+	irc->channels = g_slist_prepend( irc->channels, ic );
+	
+	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( name[0] == ic->name[0] && nick_cmp( name + 1, ic->name + 1 ) == 0 )
+			return ic;
+	}
+	
+	return NULL;
+}
+
+int irc_channel_free( irc_channel_t *ic )
+{
+	irc_t *irc = ic->irc;
+	
+	if( ic->flags & IRC_CHANNEL_JOINED )
+		irc_channel_del_user( ic, irc->user );
+	
+	irc->channels = g_slist_remove( irc->channels, ic );
+	g_slist_free( ic->users );
+	
+	g_free( ic->name );
+	g_free( ic->topic );
+	g_free( ic );
+	
+	return 1;
+}
+
+int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu )
+{
+	if( irc_channel_has_user( ic, iu ) )
+		return 0;
+	
+	ic->users = g_slist_insert_sorted( ic->users, iu, irc_user_cmp );
+	
+	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 )
+{
+	if( !irc_channel_has_user( ic, iu ) )
+		return 0;
+	
+	ic->users = g_slist_remove( ic->users, iu );
+	
+	if( ic->flags & IRC_CHANNEL_JOINED )
+		irc_send_part( ic, iu, "" );
+	
+	if( iu == ic->irc->user )
+		ic->flags &= ~IRC_CHANNEL_JOINED;
+	
+	return 1;
+}
+
+/* Currently a fairly stupid one-liner but I fear it's going to get worse. :-) */
+gboolean irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu )
+{
+	return g_slist_find( ic->users, iu ) != 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;
+}
+
+gboolean irc_channel_name_ok( const char *name )
+{
+	return strchr( CTYPES, name[0] ) != NULL && nick_ok( name + 1 );
+}
+
+/* Channel-type dependent functions, for control channels: */
+static gboolean control_channel_privmsg( irc_channel_t *ic, const char *msg )
+{
+	char cmd[strlen(msg)+1];
+	
+	g_free( ic->irc->last_root_cmd );
+	ic->irc->last_root_cmd = g_strdup( ic->name );
+	
+	strcpy( cmd, msg );
+	root_command_string( ic->irc, cmd );
+	
+	return TRUE;
+}
+
+static const struct irc_channel_funcs control_channel_funcs = {
+	control_channel_privmsg,
+};
Index: irc_commands.c
===================================================================
--- irc_commands.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ irc_commands.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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                *
   \********************************************************************/
 
@@ -53,10 +53,10 @@
 	else if( global.conf->auth_pass )
 	{
-		irc_reply( irc, 464, ":Incorrect password" );
+		irc_send_num( irc, 464, ":Incorrect password" );
 	}
 	else
 	{
 		/* Remember the password and try to identify after USER/NICK. */
-		irc_setpass( irc, cmd[1] );
+		/*irc_setpass( irc, cmd[1] ); */
 		irc_check_login( irc );
 	}
@@ -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,27 @@
 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" );
+	if( irc_user_by_name( irc, cmd[1] ) )
+	{
+		irc_send_num( irc, 433, ":This nick is already in use" );
 	}
 	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, ":This nick contains invalid characters" );
+	}
+	else if( irc->user->nick )
+	{
+		if( irc->status & USTATUS_IDENTIFIED )
+		{
+			irc_setpass( irc, NULL );
+			irc->status &= ~USTATUS_IDENTIFIED;
+			irc_umode_set( irc, "-R", 1 );
+		}
+		
+		irc_user_set_nick( irc->user, cmd[1] );
+	}
+	else
+	{
+		irc->user->nick = g_strdup( cmd[1] );
 		
 		irc_check_login( irc );
@@ -105,130 +111,192 @@
 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 );
-}
-
-static void irc_cmd_oper( irc_t *irc, char **cmd )
-{
-	if( global.conf->oper_pass &&
-	    ( strncmp( global.conf->oper_pass, "md5:", 4 ) == 0 ?
-	        md5_verify_password( cmd[2], global.conf->oper_pass + 4 ) == 0 :
-	        strcmp( cmd[2], global.conf->oper_pass ) == 0 ) )
-	{
-		irc_umode_set( irc, "+o", 1 );
-		irc_reply( irc, 381, ":Password accepted" );
-	}
-	else
-	{
-		irc_reply( irc, 432, ":Incorrect password" );
-	}
+	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 )
+{
+	irc_channel_t *ic;
+	
+	if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL )
+		ic = irc_channel_new( irc, cmd[1] );
+	
+	if( ic == NULL )
+	{
+		irc_send_num( irc, 479, "%s :Invalid channel name", cmd[1] );
+		return;
+	}
+	
+	if( ic->flags & IRC_CHANNEL_JOINED )
+		return; /* Dude, you're already there...
+		           RFC doesn't have any reply for that though? */
+	
+	irc_channel_add_user( ic, irc->user );
+}
+
+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_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( strchr( CTYPES, *cmd[1] ) )
-	{
-		if( cmd[2] )
+	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_reply( irc, 477, "%s :Can't change channel modes", cmd[1] );
+				irc_send_num( 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] );
+				irc_send_num( 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 )
+			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_reply( irc, 221, "+%s", irc->umode );
+				irc_send_num( 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] );
-	}
-}
-
-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 );
+			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 )
 {
-	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] ); 
+	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;
+		strcpy( cmd[2], "/me" );
+		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( 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->f->privmsg( iu, cmd[2] );
+	}
+	else
+	{
+		irc_send_num( irc, 401, "%s :No such nick/channel", cmd[1] );
+	}
+
+
+#if 0
+	else if( irc->nick && g_strcasecmp( cmd[1], irc->nick ) == 0 ) 
+	{
 	}
 	else 
@@ -271,36 +339,48 @@
 		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:"**" );
+#endif
+}
+
+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 );
+}
+
+
+
+#if 0
+static void irc_cmd_oper( irc_t *irc, char **cmd )
+{
+	if( global.conf->oper_pass &&
+	    ( strncmp( global.conf->oper_pass, "md5:", 4 ) == 0 ?
+	        md5_verify_password( cmd[2], global.conf->oper_pass + 4 ) == 0 :
+	        strcmp( cmd[2], global.conf->oper_pass ) == 0 ) )
+	{
+		irc_umode_set( irc, "+o", 1 );
+		irc_send_num( irc, 381, ":Password accepted" );
+	}
+	else
+	{
+		irc_send_num( irc, 432, ":Incorrect password" );
+	}
+}
+
+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_send_num( irc, 341, "%s %s", nick, channel );
+			return;
+		}
+	
+	irc_send_num( irc, 482, "%s :Invite impossible; User/Channel non-existent or incompatible", channel );
 }
 
@@ -320,7 +400,7 @@
 		{
 			if( u->online && u->away )
-				irc_reply( irc, 302, ":%s=-%s@%s", u->nick, u->user, u->host );
+				irc_send_num( 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_send_num( irc, 302, ":%s=+%s@%s", u->nick, u->user, u->host );
 		}
 }
@@ -377,5 +457,5 @@
 		buff[strlen(buff)-1] = '\0';
 	
-	irc_reply( irc, 303, ":%s", buff );
+	irc_send_num( irc, 303, ":%s", buff );
 }
 
@@ -406,7 +486,7 @@
 			
 			if( u && u->online )
-				irc_reply( irc, 604, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "is online" );
+				irc_send_num( irc, 604, "%s %s %s %d :%s", u->nick, u->user, u->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 +499,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" );
 			}
 		}
@@ -443,103 +523,38 @@
 	}
 }
+#endif
 
 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 )
-	{
+	char *set;
+	
+	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, 320, "%s :%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 = away;
+	}
+	else
+	{
+		irc_send_num( irc, 305, ":Welcome back" );
+		set = NULL;
+	}
+	
+	set_setstr( &irc->b->set, "away", set );
+}
+
+#if 0
 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->myhost, ARCH, CPU );
 }
 
@@ -572,6 +587,7 @@
 		ipc_to_master( cmd );
 	
-	irc_reply( irc, 382, "%s :Rehashing", global.conf_file );
-}
+	irc_send_num( irc, 382, "%s :Rehashing", global.conf_file );
+}
+#endif
 
 static const command_t irc_commands[] = {
@@ -581,24 +597,25 @@
 	{ "quit",        0, irc_cmd_quit,        0 },
 	{ "ping",        0, irc_cmd_ping,        0 },
+	{ "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 },
+	{ "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
+	{ "privmsg",     1, irc_cmd_privmsg,     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 },
+#if 0
 	{ "oper",        2, irc_cmd_oper,        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 },
-	{ "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 },
 	{ "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 },
 	{ "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 },
@@ -610,4 +627,5 @@
 	{ "restart",     0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
 	{ "kill",        2, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
+#endif
 	{ NULL }
 };
@@ -628,17 +646,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 +675,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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ irc_im.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,268 @@
+  /********************************************************************\
+  * 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 */
+
+static const struct irc_user_funcs irc_user_im_funcs;
+
+static gboolean bee_irc_user_new( bee_t *bee, bee_user_t *bu )
+{
+	irc_user_t *iu;
+	char nick[MAX_NICK_LENGTH+1], *s;
+	
+	memset( nick, 0, MAX_NICK_LENGTH + 1 );
+	strcpy( nick, nick_get( bu->ic->acc, bu->handle ) );
+	
+	bu->ui_data = iu = irc_user_new( (irc_t*) bee->ui_data, 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( set_getbool( &bee->set, "private" ) )
+		iu->flags |= IRC_USER_PRIVATE;
+	
+	iu->f = &irc_user_im_funcs;
+	//iu->last_typing_notice = 0;
+	
+	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_channel_t *ic = irc->channels->data; /* For now, just pick the first channel. */
+	
+	if( ( bu->flags & BEE_USER_ONLINE ) != ( old->flags & BEE_USER_ONLINE ) )
+	{
+		if( bu->flags & BEE_USER_ONLINE )
+			irc_channel_add_user( ic, (irc_user_t*) bu->ui_data );
+		else
+			irc_channel_del_user( ic, (irc_user_t*) bu->ui_data );
+	}
+	
+	return TRUE;
+}
+
+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_channel_t *ic = irc->channels->data;
+	irc_user_t *iu = (irc_user_t *) bu->ui_data;
+	char *dst, *prefix = NULL;
+	char *wrapped, *ts = NULL;
+	
+	if( sent_at > 0 && set_getbool( &irc->b->set, "display_timestamps" ) )
+		ts = irc_format_timestamp( irc, sent_at );
+	
+	if( iu->flags & IRC_USER_PRIVATE )
+	{
+		dst = irc->user->nick;
+		prefix = ts;
+		ts = NULL;
+	}
+	else
+	{
+		dst = ic->name;
+		prefix = g_strdup_printf( "%s%s%s", irc->user->nick, set_getstr( &bee->set, "to_char" ), ts );
+	}
+	
+	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_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 );
+	}
+	
+	s = set_getstr( &bu->ic->acc->set, "nick_source" );
+	if( strcmp( s, "handle" ) != 0 )
+	{
+		char *name = g_strdup( bu->fullname );
+		
+		if( strcmp( s, "first_name" ) == 0 )
+		{
+			int i;
+			for( i = 0; name[i] && !isspace( name[i] ); i ++ ) {}
+			name[i] = '\0';
+		}
+		
+		imcb_buddy_nick_hint( bu->ic, bu->handle, name );
+		
+		g_free( name );
+	}
+	
+	return TRUE;
+}
+
+/* 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 );
+}
+
+gboolean bee_irc_ft_out_start( struct im_connection *ic, file_transfer_t *ft )
+{
+	return dccs_recv_start( ft );
+}
+
+void bee_irc_ft_close( struct im_connection *ic, file_transfer_t *ft )
+{
+	return dcc_close( ft );
+}
+
+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_user_new,
+	bee_irc_user_free,
+	bee_irc_user_fullname,
+	bee_irc_user_status,
+	bee_irc_user_msg,
+	bee_irc_user_typing,
+	
+	bee_irc_ft_in_start,
+	bee_irc_ft_out_start,
+	bee_irc_ft_close,
+	bee_irc_ft_finished,
+};
+
+
+/* IRC->IM calls */
+
+static gboolean bee_irc_user_privmsg( irc_user_t *iu, const char *msg )
+{
+	if( iu->bu )
+		return bee_user_msg( iu->irc->b, iu->bu, msg, 0 );
+	else
+		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,
+};
Index: irc_send.c
===================================================================
--- irc_send.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ irc_send.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,322 @@
+  /********************************************************************\
+  * 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;
+	irc_user_t *iu;
+	char text[1024];
+	va_list params;
+	
+	va_start( params, format );
+	g_vsnprintf( text, sizeof( text ), format, params );
+	va_end( params );
+	
+	if( irc->last_root_cmd &&
+	    irc_channel_name_ok( irc->last_root_cmd ) && 
+	    ( ic = irc_channel_by_name( irc, irc->last_root_cmd ) ) &&
+	    ic->flags & IRC_CHANNEL_JOINED )
+		irc_send_msg( irc->root, "PRIVMSG", irc->last_root_cmd, text, NULL );
+	else if( irc->last_root_cmd &&
+	         ( iu = irc_user_by_name( irc, irc->last_root_cmd ) ) &&
+	         iu->f == &irc_user_root_funcs )
+		irc_send_msg( iu, "PRIVMSG", irc->user->nick, text, NULL );
+	else
+	{
+		g_free( irc->last_root_cmd );
+		irc->last_root_cmd = NULL;
+		
+		irc_send_msg( irc->root, "PRIVMSG", irc->user->nick, text, NULL );
+	}
+	
+	/*return( irc_msgfrom( irc, u->nick, text ) );*/
+}
+
+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_names( irc_channel_t *ic )
+{
+	GSList *l;
+	char namelist[385] = "";
+	//char *ops = set_getstr( &ic->irc->b->set, "ops" );
+	
+	/* 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_user_t *iu = l->data;
+		
+		if( strlen( namelist ) + strlen( iu->nick ) > sizeof( namelist ) - 4 )
+		{
+			irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist );
+			*namelist = 0;
+		}
+		
+		/*
+		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, 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_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
+	{
+		irc_send_num( irc, 312, "%s %s :%s", iu->nick, irc->root->host, IRCD_INFO " " BITLBEE_VERSION );
+	}
+	
+	irc_send_num( irc, 318, "%s :End of /WHOIS list", iu->nick );
+}
+
+void irc_send_who( irc_t *irc, GSList *l, const char *channel )
+{
+	while( l )
+	{
+		irc_user_t *iu = l->data;
+		/* 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, '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 );
+}
Index: irc_user.c
===================================================================
--- irc_user.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ irc_user.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,180 @@
+  /********************************************************************\
+  * 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"
+
+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;
+	
+	iu->flags = set_getbool( &irc->b->set, "private" ) ? IRC_USER_PRIVATE : 0;
+	
+	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 )
+{
+	GSList *l;
+	
+	if( !iu )
+		return 0;
+	
+	irc->users = g_slist_remove( irc->users, iu );
+	g_hash_table_remove( irc->nick_user_hash, iu->key );
+	
+	for( l = irc->channels; l; l = l->next )
+		irc_channel_del_user( (irc_channel_t*) l->data, iu );
+	
+	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->sendbuf );
+	if( iu->sendbuf_timer ) b_event_remove( iu->sendbuf_timer );
+	g_free( iu->key );
+	
+	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;
+	char key[strlen(new)+1];
+	GSList *cl;
+	
+	strcpy( key, new );
+	if( iu == NULL || !nick_lc( key ) || irc_user_by_name( irc, new ) )
+		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 );
+	
+	return 1;
+}
+
+gint irc_user_cmp( gconstpointer a_, gconstpointer b_ )
+{
+	const irc_user_t *a = a_, *b = b_;
+	
+	return strcmp( a->key, b->key );
+}
+
+/* User-type dependent functions, for root/NickServ: */
+static gboolean root_privmsg( irc_user_t *iu, const char *msg )
+{
+	char cmd[strlen(msg)+1];
+	
+	g_free( iu->irc->last_root_cmd );
+	iu->irc->last_root_cmd = g_strdup( iu->nick );
+	
+	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_raw( iu, "PRIVMSG", iu->nick, msg );
+	
+	return TRUE;
+}
+
+const struct irc_user_funcs irc_user_self_funcs = {
+	self_privmsg,
+};
Index: irc_util.c
===================================================================
--- irc_util.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ irc_util.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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, msg.tm_mday,
+		                        msg.tm_hour, msg.tm_min, msg.tm_sec );
+}
Index: lib/Makefile
===================================================================
--- lib/Makefile	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ lib/Makefile	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -10,5 +10,5 @@
 
 # [SH] Program variables
-objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o
+objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o ftutil.o
 
 CFLAGS += -Wall
Index: lib/ftutil.c
===================================================================
--- lib/ftutil.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ lib/ftutil.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,134 @@
+/***************************************************************************\
+*                                                                           *
+*  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 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
+	{
+		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;
+	}
+
+	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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ lib/ftutil.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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 for_bitlbee_client, char **errptr );
Index: lib/misc.c
===================================================================
--- lib/misc.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ lib/misc.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -649,2 +649,50 @@
 	return ret;
 }
+
+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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ lib/misc.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -69,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: nick.c
===================================================================
--- nick.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ nick.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -78,5 +78,5 @@
 		
 		nick_strip( nick );
-		if( set_getbool( &acc->irc->set, "lcnicks" ) )
+		if( set_getbool( &acc->bee->set, "lcnicks" ) )
 			nick_lc( nick );
 	}
@@ -92,9 +92,10 @@
 void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] )
 {
+	irc_t *irc = (irc_t*) acc->bee->ui_data;
 	int inf_protection = 256;
 	
 	/* 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 ) || irc_user_by_name( irc, nick ) )
 	{
 		if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) )
@@ -112,17 +113,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", 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() );
Index: protocols/Makefile
===================================================================
--- protocols/Makefile	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/Makefile	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -10,5 +10,6 @@
 
 # [SH] Program variables
-objects = nogaim.o
+objects = account.o bee.o bee_ft.o bee_user.o nogaim.o
+
 
 # [SH] The next two lines should contain the directory name (in $(subdirs))
Index: protocols/account.c
===================================================================
--- protocols/account.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/account.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,360 @@
+  /********************************************************************\
+  * 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( bee_t *bee, struct prpl *prpl, char *user, char *pass )
+{
+	account_t *a;
+	set_t *s;
+	
+	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_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( bee_t *bee, 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 = 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 );
+}
+
+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->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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/account.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,72 @@
+  /********************************************************************\
+  * 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 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, char *id );
+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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/bee.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,94 @@
+  /********************************************************************\
+  * 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", NULL/*set_eval_account_reconnect_delay*/, b );
+	s = set_add( &b->set, "debug", "false", set_eval_bool, b );
+	s = set_add( &b->set, "password", NULL, NULL/*set_eval_password*/, b );
+	s->flags |= SET_NULL_OK;
+	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 );
+	
+	return b;
+}
+
+void bee_free( bee_t *b )
+{
+	account_t *acc = b->accounts;
+	
+	while( acc )
+	{
+		if( acc->ic )
+			imc_logout( acc->ic, FALSE );
+		else if( acc->reconnect )
+			cancel_auto_reconnect( acc );
+		
+		if( acc->ic == NULL )
+			account_del( b, acc );
+		else
+			/* Nasty hack, but account_del() doesn't work in this
+			   case and we don't want infinite loops, do we? ;-) */
+			acc = acc->next;
+	}
+	
+	while( b->set )
+		set_del( &b->set, b->set->key );
+	
+	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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/bee.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,103 @@
+  /********************************************************************\
+  * 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;
+
+typedef struct bee
+{
+	struct set *set;
+	
+	GSList *users;
+	struct account *accounts; /* TODO(wilmer): Use GSList here too? */
+	
+	const struct bee_ui_funcs *ui;
+	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_flags_t;
+
+typedef struct bee_user
+{
+	struct im_connection *ic;
+	char *handle;
+	char *fullname;
+	char *group;
+
+	bee_user_flags_t flags;
+	char *status;
+	char *status_msg;
+	
+	bee_t *bee;
+	void *ui_data;
+} bee_user_t;
+
+typedef struct bee_ui_funcs
+{
+	gboolean (*user_new)( bee_t *bee, struct bee_user *bu );
+	gboolean (*user_free)( bee_t *bee, struct bee_user *bu );
+	gboolean (*user_fullname)( bee_t *bee, bee_user_t *bu );
+	gboolean (*user_status)( bee_t *bee, struct bee_user *bu, struct bee_user *old );
+	gboolean (*user_msg)( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at );
+	gboolean (*user_typing)( bee_t *bee, bee_user_t *bu, guint32 flags );
+	
+	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 );
+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 );
+
+/* 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 );
+/* 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, guint32 flags, time_t sent_at );
+
+#endif /* __BEE_H__ */
Index: protocols/bee_ft.c
===================================================================
--- protocols/bee_ft.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/bee_ft.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/bee_user.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,206 @@
+  /********************************************************************\
+  * 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_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->handle = g_strdup( handle );
+	bee->users = g_slist_prepend( bee->users, bu );
+	
+	if( bee->ui->user_new )
+		bee->ui->user_new( bee, bu );
+	
+	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 );
+	
+	g_free( bu->handle );
+	g_free( bu->fullname );
+	g_free( bu->group );
+	g_free( bu->status );
+	g_free( bu->status_msg );
+	
+	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;
+}
+
+
+/* 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 );
+		}
+		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 );
+#if 0	
+	/* LISPy... */
+	if( ( set_getbool( &ic->bee->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->bee->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 );
+	}
+#endif
+}
+
+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 );
+		}
+	}
+	
+	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/chat.c
===================================================================
--- protocols/chat.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/chat.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,192 @@
+  /********************************************************************\
+  * 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: protocols/chat.h
===================================================================
--- protocols/chat.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/chat.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,51 @@
+  /********************************************************************\
+  * 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: protocols/ft.h
===================================================================
--- protocols/ft.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/ft.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,175 @@
+/********************************************************************\
+* 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;
+	
+	/*
+	 * 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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/jabber/Makefile	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -10,5 +10,5 @@
 
 # [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
Index: protocols/jabber/iq.c
===================================================================
--- protocols/jabber/iq.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/jabber/iq.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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;
@@ -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,21 @@
 	else if( strcmp( type, "set" ) == 0 )
 	{
-		if( !( c = xt_find_node( node->children, "query" ) ) ||
-		    !( s = xt_find_attr( c, "xmlns" ) ) )
+		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" ) ) )
 		{
 			imcb_log( ic, "Warning: Received incomplete IQ-%s packet", type );
 			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 +160,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;
 		}
@@ -379,5 +392,5 @@
 			if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) )
 			{
-				if( initial || imcb_find_buddy( ic, jid ) == NULL )
+				if( initial || bee_user_by_handle( ic->bee, ic, jid ) == NULL )
 					imcb_add_buddy( ic, jid, ( group && group->text_len ) ?
 					                           group->text : NULL );
@@ -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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/jabber/jabber.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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 );
@@ -264,4 +266,16 @@
 	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 );
@@ -544,4 +558,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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/jabber/jabber.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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 */
@@ -187,4 +237,16 @@
 int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name );
 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 );
Index: protocols/jabber/jabber_util.c
===================================================================
--- protocols/jabber/jabber_util.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/jabber/jabber_util.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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 ) )
Index: protocols/jabber/s5bytestream.c
===================================================================
--- protocols/jabber/s5bytestream.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/jabber/s5bytestream.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -0,0 +1,1154 @@
+/***************************************************************************\
+*                                                                           *
+*  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, GAIM_INPUT_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, GAIM_INPUT_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" );
+
+	bt->tf->watch_in = 0;
+	/* 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, GAIM_INPUT_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, GAIM_INPUT_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, GAIM_INPUT_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, 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, GAIM_INPUT_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, GAIM_INPUT_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 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/jabber/si.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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/invitation.c
===================================================================
--- protocols/msn/invitation.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/msn/invitation.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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, GAIM_INPUT_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, GAIM_INPUT_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, GAIM_INPUT_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, GAIM_INPUT_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, GAIM_INPUT_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, GAIM_INPUT_READ, msn_ftp_read, file );
+
+	return TRUE;
+}
Index: protocols/msn/invitation.h
===================================================================
--- protocols/msn/invitation.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
+++ protocols/msn/invitation.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/msn/msn.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -78,4 +78,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 );
@@ -327,4 +333,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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/msn/msn.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -69,4 +69,5 @@
 	int sb_failures;
 	time_t first_sb_failure;
+	GSList *filetransfers;
 	
 	const struct msn_away_state *away_state;
@@ -181,3 +182,6 @@
 int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m );
 
+/* 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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/msn/msn_util.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -96,6 +96,5 @@
 	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 );
+	imcb_ask_add( bla->ic, bla->handle, NULL );
 	
 	g_free( bla->handle );
Index: protocols/msn/sb.c
===================================================================
--- protocols/msn/sb.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/msn/sb.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -29,4 +29,5 @@
 #include "passport.h"
 #include "md5.h"
+#include "invitation.h"
 
 static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond );
@@ -168,5 +169,16 @@
 		
 		/* 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( 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 );
@@ -181,10 +193,4 @@
 				buf[i++] = text[j];
 			}
-		}
-		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 );
 		}
 		
@@ -685,62 +691,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 )
Index: protocols/nogaim.c
===================================================================
--- protocols/nogaim.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/nogaim.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -38,7 +38,4 @@
 #include "chat.h"
 
-static int remove_chat_buddy_silent( struct groupchat *b, const char *handle );
-static char *format_timestamp( irc_t *irc, time_t msg_ts );
-
 GSList *connections;
 
@@ -92,6 +89,4 @@
 }
 #endif
-
-/* nogaim.c */
 
 GList *protocols = NULL;
@@ -126,5 +121,4 @@
 }
 
-/* nogaim.c */
 void nogaim_init()
 {
@@ -162,6 +156,4 @@
 GSList *get_connections() { return connections; }
 
-/* multi.c */
-
 struct im_connection *imcb_new( account_t *acc )
 {
@@ -170,5 +162,5 @@
 	ic = g_new0( struct im_connection, 1 );
 	
-	ic->irc = acc->irc;
+	ic->bee = acc->bee;
 	ic->acc = acc;
 	acc->ic = ic;
@@ -184,5 +176,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 )
 		{
@@ -205,10 +197,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;
@@ -216,7 +208,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 );
@@ -269,8 +262,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
@@ -279,6 +268,4 @@
 		return;
 	
-	u = user_find( ic->irc, ic->irc->nick );
-	
 	imcb_log( ic, "Logged in" );
 	
@@ -293,4 +280,5 @@
 	ic->acc->auto_reconnect_delay = 0;
 	
+	/*
 	for( c = irc->chatrooms; c; c = c->next )
 	{
@@ -301,4 +289,5 @@
 			chat_join( irc, c, NULL );
 	}
+	*/
 }
 
@@ -308,5 +297,5 @@
 	
 	a->reconnect = 0;
-	account_on( a->irc, a );
+	account_on( a->bee, a );
 	
 	return( FALSE );	/* Only have to run the timeout once */
@@ -321,7 +310,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;
 	
@@ -343,20 +332,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( ic->irc, ic );
+	
+	for( a = bee->accounts; a; a = a->next )
 		if( a->ic == ic )
 			break;
@@ -366,5 +353,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 )
@@ -377,25 +364,18 @@
 }
 
-
-/* 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, 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" ) )
+	bee_user_t *bu;
+	bee_t *bee = ic->bee;
+	
+	if( bee_user_by_handle( bee, ic, handle ) )
+	{
+		if( set_getbool( &bee->set, "debug" ) )
 			imcb_log( ic, "User already exists, ignoring add request: %s", handle );
 		
@@ -408,91 +388,22 @@
 	}
 	
-	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 );
+	bu = bee_user_new( bee, ic, handle );
+	bu->group = g_strdup( group );
+}
+
+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 );
+		
+		if( bee->ui->user_fullname )
+			bee->ui->user_fullname( bee, bu );
 	}
 }
@@ -500,8 +411,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 ) );
 }
 
@@ -510,4 +418,5 @@
 void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick )
 {
+#if 0
 	user_t *u = user_findhandle( ic, handle );
 	char newnick[MAX_NICK_LENGTH+1], *orig_nick;
@@ -524,5 +433,5 @@
 		/* Some processing to make sure this string is a valid IRC nickname. */
 		nick_strip( newnick );
-		if( set_getbool( &ic->irc->set, "lcnicks" ) )
+		if( set_getbool( &ic->bee->set, "lcnicks" ) )
 			nick_lc( newnick );
 		
@@ -541,4 +450,5 @@
 		}
 	}
+#endif
 }
 
@@ -585,5 +495,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, data );
 }
 
@@ -610,5 +521,5 @@
 	
 	/* TODO: Make a setting for this! */
-	if( user_findhandle( ic, handle ) != NULL )
+	if( bee_user_by_handle( ic->bee, ic, handle ) != NULL )
 		return;
 	
@@ -617,186 +528,16 @@
 	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, *ts = NULL;
-	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 );
-	
-	if( set_getbool( &ic->irc->set, "display_timestamps" ) &&
-	    ( ts = format_timestamp( irc, sent_at ) ) )
-	{
-		char *new = g_strconcat( ts, msg, NULL );
-		g_free( ts );
-		ts = msg = new;
-	}
-	
-	wrapped = word_wrap( msg, 425 );
-	irc_msgfrom( irc, u->nick, wrapped );
-	g_free( wrapped );
-	g_free( ts );
-}
-
-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 );
-	}
+	query_add( (irc_t *) ic->bee->ui_data, ic, s,
+	           imcb_ask_add_cb_yes, imcb_ask_add_cb_no, data );
+}
+
+struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle )
+{
+	return bee_user_by_handle( ic->bee, ic, handle );
 }
 
 struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle )
 {
+#if 0
 	struct groupchat *c;
 	
@@ -816,12 +557,15 @@
 	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" ) )
+	if( set_getbool( &ic->bee->set, "debug" ) )
 		imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle );
 	
 	return c;
+#endif
+	return NULL;
 }
 
 void imcb_chat_name_hint( struct groupchat *c, const char *name )
 {
+#if 0
 	if( !c->joined )
 	{
@@ -849,13 +593,15 @@
 		}
 	}
+#endif
 }
 
 void imcb_chat_free( struct groupchat *c )
 {
+#if 0
 	struct im_connection *ic = c->ic;
 	struct groupchat *l;
 	GList *ir;
 	
-	if( set_getbool( &ic->irc->set, "debug" ) )
+	if( set_getbool( &ic->bee->set, "debug" ) )
 		imcb_log( ic, "You were removed from conversation %p", c );
 	
@@ -890,8 +636,10 @@
 		g_free( c );
 	}
+#endif
 }
 
 void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at )
 {
+#if 0
 	struct im_connection *ic = c->ic;
 	char *wrapped;
@@ -904,6 +652,6 @@
 	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" ) ) )
+	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 );
 	
@@ -922,8 +670,10 @@
 	}
 	g_free( wrapped );
+#endif
 }
 
 void imcb_chat_log( struct groupchat *c, char *format, ... )
 {
+#if 0
 	irc_t *irc = c->ic->irc;
 	va_list params;
@@ -940,8 +690,10 @@
 	
 	g_free( text );
+#endif
 }
 
 void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at )
 {
+#if 0
 	struct im_connection *ic = c->ic;
 	user_t *u = NULL;
@@ -954,6 +706,6 @@
 		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" ) ) )
+	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 );
 	
@@ -963,15 +715,14 @@
 	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 */
+#endif
+}
 
 void imcb_chat_add_buddy( struct groupchat *b, const char *handle )
 {
+#if 0
 	user_t *u = user_findhandle( b->ic, handle );
 	int me = 0;
 	
-	if( set_getbool( &b->ic->irc->set, "debug" ) )
+	if( set_getbool( &b->ic->bee->set, "debug" ) )
 		imcb_log( b->ic, "User %s added to conversation %p", handle, b );
 	
@@ -1000,4 +751,5 @@
 		b->in_room = g_list_append( b->in_room, g_strdup( handle ) );
 	}
+#endif
 }
 
@@ -1005,8 +757,9 @@
 void imcb_chat_remove_buddy( struct groupchat *b, const char *handle, const char *reason )
 {
+#if 0
 	user_t *u;
 	int me = 0;
 	
-	if( set_getbool( &b->ic->irc->set, "debug" ) )
+	if( set_getbool( &b->ic->bee->set, "debug" ) )
 		imcb_log( b->ic, "User %s removed from conversation %p (%s)", handle, b, reason ? reason : "" );
 	
@@ -1028,6 +781,8 @@
 	if( me || ( remove_chat_buddy_silent( b, handle ) && b->joined && u ) )
 		irc_part( b->ic->irc, u, b->channel );
-}
-
+#endif
+}
+
+#if 0
 static int remove_chat_buddy_silent( struct groupchat *b, const char *handle )
 {
@@ -1048,10 +803,11 @@
 	}
 	
-	return( 0 );
-}
+	return 0;
+}
+#endif
 
 
 /* Misc. BitlBee stuff which shouldn't really be here */
-
+#if 0
 char *set_eval_away_devoice( set_t *set, char *value )
 {
@@ -1066,5 +822,5 @@
 	/* Horror.... */
 	
-	if( st != set_getbool( &irc->set, "away_devoice" ) )
+	if( st != set_getbool( &irc->b->set, "away_devoice" ) )
 	{
 		char list[80] = "";
@@ -1108,113 +864,8 @@
 	return value;
 }
-
-char *set_eval_timezone( set_t *set, char *value )
-{
-	char *s;
-	
-	if( strcmp( value, "local" ) == 0 ||
-	    strcmp( value, "gmt" ) == 0 || strcmp( value, "utc" ) == 0 )
-		return value;
-	
-	/* Otherwise: +/- at the beginning optional, then one or more numbers,
-	   possibly followed by a colon and more numbers. Don't bother bound-
-	   checking them since users are free to shoot themselves in the foot. */
-	s = value;
-	if( *s == '+' || *s == '-' )
-		s ++;
-	
-	/* \d+ */
-	if( !isdigit( *s ) )
-		return SET_INVALID;
-	while( *s && isdigit( *s ) ) s ++;
-	
-	/* EOS? */
-	if( *s == '\0' )
-		return value;
-	
-	/* Otherwise, colon */
-	if( *s != ':' )
-		return SET_INVALID;
-	s ++;
-	
-	/* \d+ */
-	if( !isdigit( *s ) )
-		return SET_INVALID;
-	while( *s && isdigit( *s ) ) s ++;
-	
-	/* EOS */
-	return *s == '\0' ? value : SET_INVALID;
-}
-
-static char *format_timestamp( irc_t *irc, time_t msg_ts )
-{
-	time_t now_ts = time( NULL );
-	struct tm now, msg;
-	char *set;
-	
-	/* If the timestamp is <= 0 or less than a minute ago, discard it as
-	   it doesn't seem to add to much useful info and/or might be noise. */
-	if( msg_ts <= 0 || msg_ts > now_ts - 60 )
-		return NULL;
-	
-	set = set_getstr( &irc->set, "timezone" );
-	if( strcmp( set, "local" ) == 0 )
-	{
-		localtime_r( &now_ts, &now );
-		localtime_r( &msg_ts, &msg );
-	}
-	else
-	{
-		int hr, min = 0, sign = 60;
-		
-		if( set[0] == '-' )
-		{
-			sign *= -1;
-			set ++;
-		}
-		else if( set[0] == '+' )
-		{
-			set ++;
-		}
-		
-		if( sscanf( set, "%d:%d", &hr, &min ) >= 1 )
-		{
-			msg_ts += sign * ( hr * 60 + min );
-			now_ts += sign * ( hr * 60 + min );
-		}
-		
-		gmtime_r( &now_ts, &now );
-		gmtime_r( &msg_ts, &msg );
-	}
-	
-	if( msg.tm_year == now.tm_year && msg.tm_yday == now.tm_yday )
-		return g_strdup_printf( "\x02[\x02\x02\x02%02d:%02d:%02d\x02]\x02 ",
-		                        msg.tm_hour, msg.tm_min, msg.tm_sec );
-	else
-		return g_strdup_printf( "\x02[\x02\x02\x02%04d-%02d-%02d "
-		                        "%02d:%02d:%02d\x02]\x02 ",
-		                        msg.tm_year + 1900, msg.tm_mon, msg.tm_mday,
-		                        msg.tm_hour, msg.tm_min, msg.tm_sec );
-}
+#endif
 
 /* 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 )
-{
-	char *buf = NULL;
-	int st;
-	
-	if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
-	{
-		buf = escape_html( msg );
-		msg = buf;
-	}
-	
-	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 )
@@ -1245,5 +896,5 @@
 	
 	away = set_getstr( &ic->acc->set, "away" ) ?
-	     : set_getstr( &ic->irc->set, "away" );
+	     : set_getstr( &ic->bee->set, "away" );
 	if( away && *away )
 	{
@@ -1256,5 +907,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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/nogaim.h	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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                *
   \********************************************************************/
 
@@ -45,4 +45,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,5 +87,5 @@
 	
 	/* BitlBee */
-	irc_t *irc;
+	bee_t *bee;
 	
 	struct groupchat *groupchats;
@@ -228,4 +230,7 @@
 	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 );
 };
 
@@ -281,14 +286,6 @@
 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 );
 
@@ -316,5 +313,4 @@
 /* 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 );
 
@@ -325,5 +321,4 @@
 
 /* Misc. stuff */
-char *set_eval_timezone( set_t *set, char *value );
 char *set_eval_away_devoice( set_t *set, char *value );
 gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond );
Index: protocols/oscar/oscar.c
===================================================================
--- protocols/oscar/oscar.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/oscar/oscar.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -1190,6 +1190,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);
@@ -1952,4 +1951,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)
@@ -1957,4 +1957,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);
@@ -2094,5 +2095,5 @@
 		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;
 
Index: protocols/twitter/twitter_lib.c
===================================================================
--- protocols/twitter/twitter_lib.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ protocols/twitter/twitter_lib.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -103,5 +103,5 @@
 
 	// Check if the buddy is allready in the buddy list.
-	if (!imcb_find_buddy( ic, name ))
+	if (!bee_user_by_handle( ic->bee, ic, name ))
 	{
 		// The buddy is not in the list, add the buddy and set the status to logged in.
Index: query.c
===================================================================
--- query.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ query.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -64,5 +64,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 );
 	
@@ -179,5 +179,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: root_commands.c
===================================================================
--- root_commands.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ root_commands.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -26,5 +26,4 @@
 #define BITLBEE_CORE
 #include "commands.h"
-#include "crypting.h"
 #include "bitlbee.h"
 #include "help.h"
@@ -33,48 +32,7 @@
 #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 );
+void root_command_string( irc_t *irc, char *command )
+{
+	root_command( irc, split_command_parts( command ) );
 }
 
@@ -93,12 +51,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 );
 			
@@ -162,5 +126,5 @@
 		irc->status |= USTATUS_IDENTIFIED;
 		irc_umode_set( irc, "+R", 1 );
-		if( set_getbool( &irc->set, "auto_connect" ) )
+		if( set_getbool( &irc->b->set, "auto_connect" ) )
 			cmd_account( irc, account_on );
 		break;
@@ -202,5 +166,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 +178,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,4 +186,14 @@
 }
 
+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!" );
+}
+
 struct cmd_account_del_data
 {
@@ -233,5 +207,5 @@
 	account_t *a;
 	
-	for( a = cad->irc->accounts; a && a != cad->a; a = a->next );
+	for( a = cad->irc->b->accounts; a && a != cad->a; a = a->next );
 	
 	if( a == NULL )
@@ -245,5 +219,5 @@
 	else
 	{
-		account_del( cad->irc, a );
+		account_del( cad->irc->b, a );
 		irc_usermsg( cad->irc, "Account deleted" );
 	}
@@ -286,5 +260,5 @@
 		set_name = set_full;
 		
-		head = &irc->set;
+		head = &irc->b->set;
 	}
 	else 
@@ -357,5 +331,5 @@
 	account_t *a;
 	
-	if( ( a = account_get( irc, id ) ) )
+	if( ( a = account_get( irc->b, id ) ) )
 		return &a->set;
 	else
@@ -405,5 +379,5 @@
 		}
 
-		a = account_add( irc, prpl, cmd[3], cmd[4] );
+		a = account_add( irc->b, prpl, cmd[3], cmd[4] );
 		if( cmd[5] )
 		{
@@ -419,5 +393,5 @@
 		MIN_ARGS( 2 );
 
-		if( !( a = account_get( irc, cmd[2] ) ) )
+		if( !( a = account_get( irc->b, cmd[2] ) ) )
 		{
 			irc_usermsg( irc, "Invalid account" );
@@ -441,5 +415,5 @@
 			                       "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 );
+			//query_add( irc, NULL, msg, cmd_account_del_yes, cmd_account_del_no, cad );
 			g_free( msg );
 		}
@@ -452,5 +426,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;
@@ -475,5 +449,5 @@
 		if( cmd[2] )
 		{
-			if( ( a = account_get( irc, cmd[2] ) ) )
+			if( ( a = account_get( irc->b, cmd[2] ) ) )
 			{
 				if( a->ic )
@@ -484,5 +458,5 @@
 				else
 				{
-					account_on( irc, a );
+					account_on( irc->b, a );
 				}
 			}
@@ -495,10 +469,11 @@
 		else
 		{
-			if ( irc->accounts ) {
+			if ( irc->b->accounts )
+			{
 				irc_usermsg( irc, "Trying to get all accounts connected..." );
 			
-				for( a = irc->accounts; a; a = a->next )
+				for( a = irc->b->accounts; a; a = a->next )
 					if( !a->ic && a->auto_connect )
-						account_on( irc, a );
+						account_on( irc->b, a );
 			} 
 			else
@@ -514,17 +489,17 @@
 			irc_usermsg( irc, "Deactivating all active (re)connections..." );
 			
-			for( a = irc->accounts; a; a = a->next )
+			for( a = irc->b->accounts; a; a = a->next )
 			{
 				if( a->ic )
-					account_off( irc, a );
+					account_off( irc->b, a );
 				else if( a->reconnect )
 					cancel_auto_reconnect( a );
 			}
 		}
-		else if( ( a = account_get( irc, cmd[2] ) ) )
+		else if( ( a = account_get( irc->b, cmd[2] ) ) )
 		{
 			if( a->ic )
 			{
-				account_off( irc, a );
+				account_off( irc->b, a );
 			}
 			else if( a->reconnect )
@@ -569,5 +544,5 @@
 	}
 	
-	if( !( a = account_get( irc, cmd[1] ) ) )
+	if( !( a = account_get( irc->b, cmd[1] ) ) )
 	{
 		irc_usermsg( irc, "Invalid account" );
@@ -587,5 +562,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] );
@@ -601,10 +576,32 @@
 		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 );
+		/* Only for add -tmp. For regular adds, this callback will
+		   be called once the IM server confirms. */
+		bee_user_new( irc->b, a->ic, cmd[2] );
 	
 	irc_usermsg( irc, "Adding `%s' to your contact list", 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->ic->acc, bu->handle );
+	//TODO(wilmer): bee_user_free() and/or let the IM mod do it? irc_user_free( irc, cmd[1] );
+	
+	irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
+	g_free( s );
+	
+	return;
 }
 
@@ -616,14 +613,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 +645,42 @@
 static void cmd_rename( irc_t *irc, char **cmd )
 {
-	user_t *u;
-	
-	if( g_strcasecmp( cmd[1], irc->nick ) == 0 )
+	irc_user_t *iu;
+	
+	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, "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 ) )
+	else if( !nick_ok( cmd[2] ) )
+	{
+		irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] );
+	}
+	else if( irc_user_by_name( irc, cmd[2] ) )
 	{
 		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->ic->acc, iu->bu->handle, cmd[2] );
 		}
 		
@@ -704,4 +689,5 @@
 }
 
+#if 0
 char *set_eval_root_nick( set_t *set, char *new_nick )
 {
@@ -730,26 +716,4 @@
 	
 	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;
 }
 
@@ -872,4 +836,5 @@
 	}
 }
+#endif
 
 static void cmd_yesno( irc_t *irc, char **cmd )
@@ -916,14 +881,5 @@
 }
 
-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!" );
-}
-
+#if 0
 static void cmd_blist( irc_t *irc, char **cmd )
 {
@@ -991,32 +947,4 @@
 }
 
-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] );
-	}
-}
-
 static void cmd_qlist( irc_t *irc, char **cmd )
 {
@@ -1037,10 +965,4 @@
 		else
 			irc_usermsg( irc, "%d, BitlBee: %s", num, q->question );
-}
-
-static void cmd_join_chat( irc_t *irc, char **cmd )
-{
-	irc_usermsg( irc, "This command is now obsolete. "
-	                  "Please try the `chat' command instead." );
 }
 
@@ -1157,25 +1079,92 @@
 }
 
+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, "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, "Canceled by user" );
+			}
+			break;
+		}
+	}
+}
+#endif
+
+/* 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 },
+	{ "drop",           1, cmd_drop,           0 },
 	{ "help",           0, cmd_help,           0 }, 
 	{ "identify",       1, cmd_identify,       0 },
+	{ "info",           1, cmd_info,           0 },
+	{ "no",             0, cmd_yesno,          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 },
 	{ "yes",            0, cmd_yesno,          0 },
-	{ "no",             0, cmd_yesno,          0 },
+#if 0
+	{ "allow",          1, cmd_allow,          0 },
 	{ "blist",          0, cmd_blist,          0 },
-	{ "nick",           1, cmd_nick,           0 },
+	{ "block",          1, cmd_block,          0 },
+	{ "chat",           1, cmd_chat,           0 },
+	{ "ft",             0, cmd_transfer,       0 },
+	{ "join_chat",      2, cmd_join_chat,      0 },
 	{ "qlist",          0, cmd_qlist,          0 },
-	{ "join_chat",      2, cmd_join_chat,      0 },
-	{ "chat",           1, cmd_chat,           0 },
+	{ "transfer",       0, cmd_transfer,       0 },
+#endif
 	{ NULL }
 };
Index: set.c
===================================================================
--- set.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ set.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -225,4 +225,5 @@
 }
 
+/*
 char *set_eval_ops( set_t *set, char *value )
 {
@@ -246,2 +247,3 @@
 	return value;
 }
+*/
Index: storage.c
===================================================================
--- storage.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ storage.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ storage_xml.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -147,5 +147,5 @@
 		                         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 );
@@ -181,5 +181,5 @@
 				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 );
@@ -215,5 +215,5 @@
 		if( xd->current_account && handle && channel )
 		{
-			xd->current_chat = chat_add( xd->irc, xd->current_account, handle, channel );
+			//xd->current_chat = chat_add( xd->irc, xd->current_account, handle, channel );
 		}
 		else
@@ -353,5 +353,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 );
 }
 
@@ -396,5 +396,5 @@
 	md5_state_t md5_state;
 	
-	path2 = g_strdup( irc->nick );
+	path2 = g_strdup( irc->user->nick );
 	nick_lc( path2 );
 	g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, path2, ".xml" );
@@ -422,15 +422,15 @@
 	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;
@@ -470,4 +470,5 @@
 			goto write_error;
 		
+#if 0
 		for( c = irc->chatrooms; c; c = c->next )
 		{
@@ -488,4 +489,5 @@
 				goto write_error;
 		}
+#endif
 		
 		if( !xml_printf( fd, 1, "</account>\n" ) )
Index: er.c
===================================================================
--- user.c	(revision 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ 	(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 156bbd7b66cf29220c2ff6a86217c4dec5e33765)
+++ win32.c	(revision 21c87a78f1c01fe24610a5d05a65e52bd8eaa796)
@@ -27,5 +27,4 @@
 #include "bitlbee.h"
 #include "commands.h"
-#include "crypting.h"
 #include "protocols/nogaim.h"
 #include "help.h"
