Index: protocols/msn/Makefile
===================================================================
--- protocols/msn/Makefile	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/Makefile	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,39 @@
+###########################
+## Makefile for BitlBee  ##
+##                       ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+
+# [SH] Program variables
+objects = msn.o msn_util.o ns.o passport.o sb.o tables.o
+
+CFLAGS += -Wall
+LFLAGS += -r
+
+# [SH] Phony targets
+all: msnn.o
+	
+.PHONY: all clean distclean
+
+clean:
+	rm -f *.o core
+
+distclean: clean
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: %.c
+	@echo '*' Compiling $<
+	@$(CC) -c $(CFLAGS) $< -o $@
+
+msnn.o: $(objects)
+	@echo '*' Linking msnn.o
+	@$(LD) $(LFLAGS) $(objects) -o msnn.o
+	
+
Index: protocols/msn/msn.c
===================================================================
--- protocols/msn/msn.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/msn.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,402 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* MSN module - Main file; functions to be called from BitlBee          */
+
+/*
+  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 "nogaim.h"
+#include "msn.h"
+
+static struct prpl *my_protocol = NULL;
+
+static void msn_login( struct aim_user *acct )
+{
+	struct gaim_connection *gc = new_gaim_conn( acct );
+	struct msn_data *md = g_new0( struct msn_data, 1 );
+	
+	set_login_progress( gc, 1, "Connecting" );
+	
+	gc->proto_data = md;
+	md->fd = -1;
+	
+	if( strchr( acct->username, '@' ) == NULL )
+	{
+		hide_login_progress( gc, "Invalid account name" );
+		signoff( gc );
+		return;
+	}
+	
+	md->fd = proxy_connect( "messenger.hotmail.com", 1863, msn_ns_connected, gc );
+	if( md->fd < 0 )
+	{
+		hide_login_progress( gc, "Could not connect to server" );
+		signoff( gc );
+	}
+	else
+	{
+		md->gc = gc;
+		md->away_state = msn_away_state_list;
+		
+		msn_connections = g_slist_append( msn_connections, gc );
+	}
+}
+
+static void msn_close( struct gaim_connection *gc )
+{
+	struct msn_data *md = gc->proto_data;
+	GSList *l;
+	
+	if( md->fd >= 0 )
+		closesocket( md->fd );
+	
+	if( md->handler )
+	{
+		if( md->handler->rxq ) g_free( md->handler->rxq );
+		if( md->handler->cmd_text ) g_free( md->handler->cmd_text );
+		g_free( md->handler );
+	}
+	
+	while( md->switchboards )
+		msn_sb_destroy( md->switchboards->data );
+	
+	if( md->msgq )
+	{
+		struct msn_message *m;
+		
+		for( l = md->msgq; l; l = l->next )
+		{
+			m = l->data;
+			g_free( m->who );
+			g_free( m->text );
+			g_free( m );
+		}
+		g_slist_free( md->msgq );
+		
+		serv_got_crap( gc, "Warning: Closing down MSN connection with unsent message(s), you'll have to resend them." );
+	}
+	
+	for( l = gc->permit; l; l = l->next )
+		g_free( l->data );
+	g_slist_free( gc->permit );
+	
+	for( l = gc->deny; l; l = l->next )
+		g_free( l->data );
+	g_slist_free( gc->deny );
+	
+	g_free( md );
+	
+	msn_connections = g_slist_remove( msn_connections, gc );
+}
+
+static int msn_send_im( struct gaim_connection *gc, char *who, char *message, int len, int away )
+{
+	struct msn_switchboard *sb;
+	struct msn_data *md = gc->proto_data;
+	
+	if( ( sb = msn_sb_by_handle( gc, who ) ) )
+	{
+		return( msn_sb_sendmessage( sb, message ) );
+	}
+	else
+	{
+		struct msn_message *m;
+		char buf[1024];
+		
+		/* Create a message. We have to arrange a usable switchboard, and send the message later. */
+		m = g_new0( struct msn_message, 1 );
+		m->who = g_strdup( who );
+		m->text = g_strdup( message );
+		
+		/* FIXME: *CHECK* the reliability of using spare sb's! */
+		if( ( sb = msn_sb_spare( gc ) ) )
+		{
+			debug( "Trying to use a spare switchboard to message %s", who );
+			
+			sb->who = g_strdup( who );
+			g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, who );
+			if( msn_sb_write( sb, buf, strlen( buf ) ) )
+			{
+				/* He/She should join the switchboard soon, let's queue the message. */
+				sb->msgq = g_slist_append( sb->msgq, m );
+				return( 1 );
+			}
+		}
+		
+		debug( "Creating a new switchboard to message %s", who );
+		
+		/* If we reach this line, there was no spare switchboard, so let's make one. */
+		g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
+		if( !msn_write( gc, buf, strlen( buf ) ) )
+		{
+			g_free( m->who );
+			g_free( m->text );
+			g_free( m );
+			
+			return( 0 );
+		}
+		
+		/* And queue the message to md. We'll pick it up when the switchboard comes up. */
+		md->msgq = g_slist_append( md->msgq, m );
+		
+		/* FIXME: If the switchboard creation fails, the message will not be sent. */
+		
+		return( 1 );
+	}
+	
+	return( 0 );
+}
+
+static GList *msn_away_states( struct gaim_connection *gc )
+{
+	GList *l = NULL;
+	int i;
+	
+	for( i = 0; msn_away_state_list[i].number > -1; i ++ )
+		l = g_list_append( l, msn_away_state_list[i].name );
+	
+	return( l );
+}
+
+static char *msn_get_status_string( struct gaim_connection *gc, int number )
+{
+	struct msn_away_state *st = msn_away_state_by_number( number );
+	
+	if( st )
+		return( st->name );
+	else
+		return( "" );
+}
+
+static void msn_set_away( struct gaim_connection *gc, char *state, char *message )
+{
+	char buf[1024];
+	struct msn_data *md = gc->proto_data;
+	struct msn_away_state *st;
+	
+	if( strcmp( state, GAIM_AWAY_CUSTOM ) == 0 )
+		st = msn_away_state_by_name( "Away" );
+	else
+		st = msn_away_state_by_name( state );
+	
+	if( !st ) st = msn_away_state_list;
+	md->away_state = st;
+	
+	g_snprintf( buf, sizeof( buf ), "CHG %d %s\r\n", ++md->trId, st->code );
+	msn_write( gc, buf, strlen( buf ) );
+}
+
+static void msn_set_info( struct gaim_connection *gc, char *info )
+{
+	int i;
+	char buf[1024], *fn, *s;
+	struct msn_data *md = gc->proto_data;
+	
+	if( strlen( info ) > 129 )
+	{
+		do_error_dialog( gc, "Maximum name length exceeded", "MSN" );
+		return;
+	}
+	
+	/* Of course we could use http_encode() here, but when we encode
+	   every character, the server is less likely to complain about the
+	   chosen name. However, the MSN server doesn't seem to like escaped
+	   non-ASCII chars, so we keep those unescaped. */
+	s = fn = g_new0( char, strlen( info ) * 3 + 1 );
+	for( i = 0; info[i]; i ++ )
+		if( info[i] & 128 )
+		{
+			*s = info[i];
+			s ++;
+		}
+		else
+		{
+			g_snprintf( s, 4, "%%%02X", info[i] );
+			s += 3;
+		}
+	
+	g_snprintf( buf, sizeof( buf ), "REA %d %s %s\r\n", ++md->trId, gc->username, fn );
+	msn_write( gc, buf, strlen( buf ) );
+	g_free( fn );
+}
+
+static void msn_get_info(struct gaim_connection *gc, char *who) 
+{
+	/* Just make an URL and let the user fetch the info */
+	serv_got_crap( gc, "%s\n%s: %s%s", _("User Info"), _("For now, fetch yourself"), PROFILE_URL, who );
+}
+
+static void msn_add_buddy( struct gaim_connection *gc, char *who )
+{
+	msn_buddy_list_add( gc, "FL", who, who );
+}
+
+static void msn_remove_buddy( struct gaim_connection *gc, char *who, char *group )
+{
+	msn_buddy_list_remove( gc, "FL", who );
+}
+
+static int msn_chat_send( struct gaim_connection *gc, int id, char *message )
+{
+	struct msn_switchboard *sb = msn_sb_by_id( gc, id );
+	
+	if( sb )
+		return( msn_sb_sendmessage( sb, message ) );
+	else
+		return( 0 );
+}
+
+static void msn_chat_invite( struct gaim_connection *gc, int id, char *msg, char *who )
+{
+	struct msn_switchboard *sb = msn_sb_by_id( gc, id );
+	char buf[1024];
+	
+	if( sb )
+	{
+		g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, who );
+		msn_sb_write( sb, buf, strlen( buf ) );
+	}
+}
+
+static void msn_chat_leave( struct gaim_connection *gc, int id )
+{
+	struct msn_switchboard *sb = msn_sb_by_id( gc, id );
+	
+	if( sb )
+		msn_sb_write( sb, "OUT\r\n", 5 );
+}
+
+static int msn_chat_open( struct gaim_connection *gc, char *who )
+{
+	struct msn_switchboard *sb;
+	struct msn_data *md = gc->proto_data;
+	char buf[1024];
+	
+	if( ( sb = msn_sb_by_handle( gc, who ) ) )
+	{
+		debug( "Converting existing switchboard to %s to a groupchat", who );
+		msn_sb_to_chat( sb );
+		return( 1 );
+	}
+	else
+	{
+		struct msn_message *m;
+		
+		if( ( sb = msn_sb_spare( gc ) ) )
+		{
+			debug( "Trying to reuse an existing switchboard as a groupchat with %s", who );
+			g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, who );
+			if( msn_sb_write( sb, buf, strlen( buf ) ) )
+			{
+				msn_sb_to_chat( sb );
+				return( 1 );
+			}
+		}
+		
+		/* If the stuff above failed for some reason: */
+		debug( "Creating a new switchboard to groupchat with %s", who );
+		
+		/* Request a new switchboard. */
+		g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
+		if( !msn_write( gc, buf, strlen( buf ) ) )
+			return( 0 );
+		
+		/* Create a magic message. This is quite hackish, but who cares? :-P */
+		m = g_new0( struct msn_message, 1 );
+		m->who = g_strdup( who );
+		m->text = g_strdup( GROUPCHAT_SWITCHBOARD_MESSAGE );
+		
+		/* Queue the magic message and cross your fingers. */
+		md->msgq = g_slist_append( md->msgq, m );
+		
+		return( 1 );
+	}
+	
+	return( 0 );
+}
+
+static void msn_keepalive( struct gaim_connection *gc )
+{
+	msn_write( gc, "PNG\r\n", strlen( "PNG\r\n" ) );
+}
+
+static void msn_add_permit( struct gaim_connection *gc, char *who )
+{
+	msn_buddy_list_add( gc, "AL", who, who );
+}
+
+static void msn_rem_permit( struct gaim_connection *gc, char *who )
+{
+	msn_buddy_list_remove( gc, "AL", who );
+}
+
+static void msn_add_deny( struct gaim_connection *gc, char *who )
+{
+	struct msn_switchboard *sb;
+	
+	msn_buddy_list_add( gc, "BL", who, who );
+	
+	/* If there's still a conversation with this person, close it. */
+	if( ( sb = msn_sb_by_handle( gc, who ) ) )
+	{
+		msn_sb_destroy( sb );
+	}
+}
+
+static void msn_rem_deny( struct gaim_connection *gc, char *who )
+{
+	msn_buddy_list_remove( gc, "BL", who );
+}
+
+static int msn_send_typing( struct gaim_connection *gc, char *who, int typing )
+{
+	if( typing )
+		return( msn_send_im( gc, who, TYPING_NOTIFICATION_MESSAGE, strlen( TYPING_NOTIFICATION_MESSAGE ), 0 ) );
+	else
+		return( 1 );
+}
+
+void msn_init(struct prpl *ret)
+{
+	ret->protocol = PROTO_MSN;
+	ret->login = msn_login;
+	ret->close = msn_close;
+	ret->send_im = msn_send_im;
+	ret->away_states = msn_away_states;
+	ret->get_status_string = msn_get_status_string;
+	ret->set_away = msn_set_away;
+	ret->set_info = msn_set_info;
+	ret->get_info = msn_get_info;
+	ret->add_buddy = msn_add_buddy;
+	ret->remove_buddy = msn_remove_buddy;
+	ret->chat_send = msn_chat_send;
+	ret->chat_invite = msn_chat_invite;
+	ret->chat_leave = msn_chat_leave;
+	ret->chat_open = msn_chat_open;
+	ret->keepalive = msn_keepalive;
+	ret->add_permit = msn_add_permit;
+	ret->rem_permit = msn_rem_permit;
+	ret->add_deny = msn_add_deny;
+	ret->rem_deny = msn_rem_deny;
+	ret->send_typing = msn_send_typing;
+
+	my_protocol = ret;
+}
Index: protocols/msn/msn.h
===================================================================
--- protocols/msn/msn.h	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/msn.h	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,170 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* MSN module                                                           */
+
+/*
+  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
+*/
+
+/* Some hackish magicstrings to make special-purpose messages/switchboards.
+ */
+#define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\r\r\r"
+#define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r"
+
+#ifdef _WIN32
+#define debug 
+#else
+#define debug( text... ) irc_usermsg( IRC, text );
+#undef debug
+#define debug( text... )
+#endif
+
+#define QRY_NAME "msmsgs@msnmsgr.com"
+#define QRY_CODE "Q1P7W2E4J9R8U3S5"
+
+#define MSN_SB_NEW         -24062002
+
+#define MSN_MESSAGE_HEADERS "MIME-Version: 1.0\r\n" \
+                            "Content-Type: text/plain; charset=UTF-8\r\n" \
+                            "User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \
+                            "X-MMS-IM-Format: FN=MS%20Shell%20Dlg; EF=; CO=0; CS=0; PF=0\r\n" \
+                            "\r\n"
+
+#define MSN_TYPING_HEADERS "MIME-Version: 1.0\r\n" \
+                           "Content-Type: text/x-msmsgscontrol\r\n" \
+                           "TypingUser: %s\r\n" \
+                           "\r\n\r\n"
+
+#define PROFILE_URL "http://members.msn.com/"
+
+struct msn_data
+{
+	struct gaim_connection *gc;
+	
+	int fd;
+	struct msn_handler_data *handler;
+	
+	int trId;
+	
+	GSList *msgq;
+	GSList *switchboards;
+	int buddycount;
+	struct msn_away_state *away_state;
+};
+
+struct msn_switchboard
+{
+	struct gaim_connection *gc;
+	
+	int fd;
+	gint inp;
+	struct msn_handler_data *handler;
+	
+	int trId;
+	int ready;
+	
+	int session;
+	char *key;
+	
+	GSList *msgq;
+	char *who;
+	struct conversation *chat;
+};
+
+struct msn_away_state
+{
+	int number;
+	char code[4];
+	char name[16];
+};
+
+struct msn_status_code
+{
+	int number;
+	char *text;
+	int flags;
+};
+
+struct msn_message
+{
+	char *who;
+	char *text;
+};
+
+struct msn_handler_data
+{
+	int fd;
+	int rxlen;
+	char *rxq;
+	
+	int msglen;
+	char *cmd_text;
+	
+	gpointer data;
+	
+	int (*exec_command) ( gpointer data, char **cmd, int count );
+	int (*exec_message) ( gpointer data, char *msg, int msglen, char **cmd, int count );
+};
+
+/* Bitfield values for msn_status_code.flags */
+#define STATUS_FATAL            1
+#define STATUS_SB_FATAL         2
+
+int msn_chat_id;
+extern struct msn_away_state msn_away_state_list[];
+extern struct msn_status_code msn_status_code_list[];
+
+/* Keep a list of all the active connections. We need these lists because
+   "connected" callbacks might be called when the connection they belong too
+   is down already (for example, when an impatient user disabled the
+   connection), the callback should check whether it's still listed here
+   before doing *anything* else. */
+GSList *msn_connections;
+GSList *msn_switchboards;
+
+/* ns.c */
+void msn_ns_connected( gpointer data, gint source, GaimInputCondition cond );
+
+/* msn_util.c */
+int msn_write( struct gaim_connection *gc, char *s, int len );
+int msn_logged_in( struct gaim_connection *gc );
+int msn_buddy_list_add( struct gaim_connection *gc, char *list, char *who, char *realname );
+int msn_buddy_list_remove( struct gaim_connection *gc, char *list, char *who );
+void msn_buddy_ask( struct gaim_connection *gc, char *handle, char *realname );
+char *msn_findheader( char *text, char *header, int len );
+char **msn_linesplit( char *line );
+int msn_handler( struct msn_handler_data *h );
+
+/* tables.c */
+struct msn_away_state *msn_away_state_by_number( int number );
+struct msn_away_state *msn_away_state_by_code( char *code );
+struct msn_away_state *msn_away_state_by_name( char *name );
+struct msn_status_code *msn_status_by_number( int number );
+
+/* sb.c */
+int msn_sb_write( struct msn_switchboard *sb, char *s, int len );
+struct msn_switchboard *msn_sb_create( struct gaim_connection *gc, char *host, int port, char *key, int session );
+struct msn_switchboard *msn_sb_by_handle( struct gaim_connection *gc, char *handle );
+struct msn_switchboard *msn_sb_by_id( struct gaim_connection *gc, int id );
+struct msn_switchboard *msn_sb_spare( struct gaim_connection *gc );
+int msn_sb_sendmessage( struct msn_switchboard *sb, char *text );
+void msn_sb_to_chat( struct msn_switchboard *sb );
+void msn_sb_destroy( struct msn_switchboard *sb );
+void msn_sb_connected( gpointer data, gint source, GaimInputCondition cond );
Index: protocols/msn/msn_util.c
===================================================================
--- protocols/msn/msn_util.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/msn_util.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,359 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* MSN module - Miscellaneous utilities                                 */
+
+/*
+  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 "nogaim.h"
+#include "msn.h"
+#include <ctype.h>
+
+int msn_write( struct gaim_connection *gc, char *s, int len )
+{
+	struct msn_data *md = gc->proto_data;
+	int st;
+	
+	st = write( md->fd, s, len );
+	if( st != len )
+	{
+		hide_login_progress_error( gc, "Short write() to main server" );
+		signoff( gc );
+		return( 0 );
+	}
+	
+	return( 1 );
+}
+
+int msn_logged_in( struct gaim_connection *gc )
+{
+	struct msn_data *md = gc->proto_data;
+	char buf[1024];
+	
+	account_online( gc );
+	
+	/* account_online() sets an away state if there is any, so only
+	   execute this code if we're not away. */
+	if( md->away_state == msn_away_state_list )
+	{
+		g_snprintf( buf, sizeof( buf ), "CHG %d %s %d\r\n", ++md->trId, md->away_state->code, 0 );
+		return( msn_write( gc, buf, strlen( buf ) ) );
+	}
+	
+	return( 0 );
+}
+
+int msn_buddy_list_add( struct gaim_connection *gc, char *list, char *who, char *realname_ )
+{
+	struct msn_data *md = gc->proto_data;
+	GSList *l, **lp = NULL;
+	char buf[1024], *realname;
+	
+	if( strcmp( list, "AL" ) == 0 )
+		lp = &gc->permit;
+	else if( strcmp( list, "BL" ) == 0 )
+		lp = &gc->deny;
+	
+	if( lp )
+		for( l = *lp; l; l = l->next )
+			if( g_strcasecmp( l->data, who ) == 0 )
+				return( 1 );
+	
+	realname = g_new0( char, strlen( realname_ ) * 3 + 1 );
+	strcpy( realname, realname_ );
+	http_encode( realname );
+	
+	g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s\r\n", ++md->trId, list, who, realname );
+	if( msn_write( gc, buf, strlen( buf ) ) )
+	{
+		g_free( realname );
+		
+		if( lp )
+			*lp = g_slist_append( *lp, g_strdup( who ) );
+		
+		return( 1 );
+	}
+	
+	g_free( realname );
+	
+	return( 0 );
+}
+
+int msn_buddy_list_remove( struct gaim_connection *gc, char *list, char *who )
+{
+	struct msn_data *md = gc->proto_data;
+	GSList *l = NULL, **lp = NULL;
+	char buf[1024];
+	
+	if( strcmp( list, "AL" ) == 0 )
+		lp = &gc->permit;
+	else if( strcmp( list, "BL" ) == 0 )
+		lp = &gc->deny;
+	
+	if( lp )
+	{
+		for( l = *lp; l; l = l->next )
+			if( g_strcasecmp( l->data, who ) == 0 )
+				break;
+		
+		if( !l )
+			return( 1 );
+	}
+	
+	g_snprintf( buf, sizeof( buf ), "REM %d %s %s\r\n", ++md->trId, list, who );
+	if( msn_write( gc, buf, strlen( buf ) ) )
+	{
+		if( lp )
+			*lp = g_slist_remove( *lp, l->data );
+		
+		return( 1 );
+	}
+	
+	return( 0 );
+}
+
+struct msn_buddy_ask_data
+{
+	struct gaim_connection *gc;
+	char *handle;
+	char *realname;
+};
+
+static void msn_buddy_ask_yes( gpointer w, struct msn_buddy_ask_data *bla )
+{
+	msn_buddy_list_add( bla->gc, "AL", bla->handle, bla->realname );
+	
+	g_free( bla->handle );
+	g_free( bla->realname );
+	g_free( bla );
+}
+
+static void msn_buddy_ask_no( gpointer w, struct msn_buddy_ask_data *bla )
+{
+	msn_buddy_list_add( bla->gc, "BL", bla->handle, bla->realname );
+	
+	g_free( bla->handle );
+	g_free( bla->realname );
+	g_free( bla );
+}
+
+void msn_buddy_ask( struct gaim_connection *gc, char *handle, char *realname )
+{
+	struct msn_buddy_ask_data *bla = g_new0( struct msn_buddy_ask_data, 1 );
+	char buf[1024];
+	
+	bla->gc = gc;
+	bla->handle = g_strdup( handle );
+	bla->realname = g_strdup( realname );
+	
+	g_snprintf( buf, sizeof( buf ),
+	            "The user %s (%s) wants to add you to his/her buddy list. Do you want to allow this?",
+	            handle, realname );
+	do_ask_dialog( gc, buf, bla, msn_buddy_ask_yes, msn_buddy_ask_no );
+}
+
+char *msn_findheader( char *text, char *header, int len )
+{
+	int hlen = strlen( header ), i;
+	char *ret;
+	
+	if( len == 0 )
+		len = strlen( text );
+	
+	i = 0;
+	while( ( i + hlen ) < len )
+	{
+		/* Maybe this is a bit over-commented, but I just hate this part... */
+		if( g_strncasecmp( text + i, header, hlen ) == 0 )
+		{
+			/* Skip to the (probable) end of the header */
+			i += hlen;
+			
+			/* Find the first non-[: \t] character */
+			while( i < len && ( text[i] == ':' || text[i] == ' ' || text[i] == '\t' ) ) i ++;
+			
+			/* Make sure we're still inside the string */
+			if( i >= len ) return( NULL );
+			
+			/* Save the position */
+			ret = text + i;
+			
+			/* Search for the end of this line */
+			while( i < len && text[i] != '\r' && text[i] != '\n' ) i ++;
+			
+			/* Make sure we're still inside the string */
+			if( i >= len ) return( NULL );
+			
+			/* Copy the found data */
+			return( g_strndup( ret, text + i - ret ) );
+		}
+		
+		/* This wasn't the header we were looking for, skip to the next line. */
+		while( i < len && ( text[i] != '\r' && text[i] != '\n' ) ) i ++;
+		while( i < len && ( text[i] == '\r' || text[i] == '\n' ) ) i ++;
+		
+		/* End of headers? */
+		if( strncmp( text + i - 2, "\n\n", 2 ) == 0 ||
+		    strncmp( text + i - 4, "\r\n\r\n", 4 ) == 0 ||
+		    strncmp( text + i - 2, "\r\r", 2 ) == 0 )
+		{
+			break;
+		}
+	}
+	
+	return( NULL );
+}
+
+/* *NOT* thread-safe, but that's not a problem for now... */
+char **msn_linesplit( char *line )
+{
+	static char **ret = NULL;
+	static int size = 3;
+	int i, n = 0;
+	
+	if( ret == NULL )
+		ret = g_new0( char*, size );
+	
+	for( i = 0; line[i] && line[i] == ' '; i ++ );
+	if( line[i] )
+	{
+		ret[n++] = line + i;
+		for( i ++; line[i]; i ++ )
+		{
+			if( line[i] == ' ' )
+				line[i] = 0;
+			else if( line[i] != ' ' && !line[i-1] )
+				ret[n++] = line + i;
+			
+			if( n >= size )
+				ret = g_renew( char*, ret, size += 2 );
+		}
+	}
+	ret[n] = NULL;
+	
+	return( ret );
+}
+
+/* This one handles input from a MSN Messenger server. Both the NS and SB servers usually give
+   commands, but sometimes they give additional data (payload). This function tries to handle
+   this all in a nice way and send all data to the right places. */
+
+/* Return values: -1: Read error, abort connection.
+                   0: Command reported error; Abort *immediately*. (The connection does not exist anymore)
+                   1: OK */
+
+int msn_handler( struct msn_handler_data *h )
+{
+	int st;
+	
+	h->rxq = g_renew( char, h->rxq, h->rxlen + 1024 );
+	st = read( h->fd, h->rxq + h->rxlen, 1024 );
+	h->rxlen += st;
+	
+	if( st <= 0 )
+		return( -1 );
+	
+	while( st )
+	{
+		int i;
+		
+		if( h->msglen == 0 )
+		{
+			for( i = 0; i < h->rxlen; i ++ )
+			{
+				if( h->rxq[i] == '\r' || h->rxq[i] == '\n' )
+				{
+					char *cmd_text, **cmd;
+					int count;
+					
+					cmd_text = g_strndup( h->rxq, i );
+					cmd = msn_linesplit( cmd_text );
+					for( count = 0; cmd[count]; count ++ );
+					st = h->exec_command( h->data, cmd, count );
+					g_free( cmd_text );
+					
+					/* If the connection broke, don't continue. We don't even exist anymore. */
+					if( !st )
+						return( 0 );
+					
+					if( h->msglen )
+						h->cmd_text = g_strndup( h->rxq, i );
+					
+					/* Skip to the next non-emptyline */
+					while( i < h->rxlen && ( h->rxq[i] == '\r' || h->rxq[i] == '\n' ) ) i ++;
+					
+					break;
+				}
+			}
+			
+			/* If we reached the end of the buffer, there's still an incomplete command there.
+			   Return and wait for more data. */
+			if( i == h->rxlen && h->rxq[i-1] != '\r' && h->rxq[i-1] != '\n' )
+				break;
+		}
+		else
+		{
+			char *msg, **cmd;
+			int count;
+			
+			/* Do we have the complete message already? */
+			if( h->msglen > h->rxlen )
+				break;
+			
+			msg = g_strndup( h->rxq, h->msglen );
+			cmd = msn_linesplit( h->cmd_text );
+			for( count = 0; cmd[count]; count ++ );
+			
+			st = h->exec_message( h->data, msg, h->msglen, cmd, count );
+			g_free( msg );
+			g_free( h->cmd_text );
+			h->cmd_text = NULL;
+			
+			if( !st )
+				return( 0 );
+			
+			i = h->msglen;
+			h->msglen = 0;
+		}
+		
+		/* More data after this block? */
+		if( i < h->rxlen )
+		{
+			char *tmp;
+			
+			tmp = g_memdup( h->rxq + i, h->rxlen - i );
+			g_free( h->rxq );
+			h->rxq = tmp;
+			h->rxlen -= i;
+			i = 0;
+		}
+		else
+		/* If not, reset the rx queue and get lost. */
+		{
+			g_free( h->rxq );
+			h->rxq = g_new0( char, 1 );
+			h->rxlen = 0;
+			return( 1 );
+		}
+	}
+	
+	return( 1 );
+}
Index: protocols/msn/ns.c
===================================================================
--- protocols/msn/ns.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/ns.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,661 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* MSN module - Notification server callbacks                           */
+
+/*
+  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 <ctype.h>
+#include "nogaim.h"
+#include "msn.h"
+#include "passport.h"
+#include "md5.h"
+
+static void msn_ns_callback( gpointer data, gint source, GaimInputCondition cond );
+static int msn_ns_command( gpointer data, char **cmd, int num_parts );
+static int msn_ns_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts );
+
+static void msn_auth_got_passport_id( struct passport_reply *rep );
+
+void msn_ns_connected( gpointer data, gint source, GaimInputCondition cond )
+{
+	struct gaim_connection *gc = data;
+	struct msn_data *md;
+	char s[1024];
+	
+	if( !g_slist_find( msn_connections, gc ) )
+		return;
+	
+	if( source == -1 )
+	{
+		hide_login_progress( gc, "Could not connect to server" );
+		signoff( gc );
+		return;
+	}
+	
+	md = gc->proto_data;
+	
+	if( !md->handler )
+	{
+		md->handler = g_new0( struct msn_handler_data, 1 );
+		md->handler->data = gc;
+		md->handler->exec_command = msn_ns_command;
+		md->handler->exec_message = msn_ns_message;
+	}
+	else
+	{
+		if( md->handler->rxq )
+			g_free( md->handler->rxq );
+		
+		md->handler->rxlen = 0;
+	}
+	
+	md->handler->fd = md->fd;
+	md->handler->rxq = g_new0( char, 1 );
+	
+	g_snprintf( s, sizeof( s ), "VER %d MSNP8 CVR0\r\n", ++md->trId );
+	if( msn_write( gc, s, strlen( s ) ) )
+	{
+		gc->inpa = gaim_input_add( md->fd, GAIM_INPUT_READ, msn_ns_callback, gc );
+		set_login_progress( gc, 1, "Connected to server, waiting for reply" );
+	}
+}
+
+void msn_ns_callback( gpointer data, gint source, GaimInputCondition cond )
+{
+	struct gaim_connection *gc = data;
+	struct msn_data *md = gc->proto_data;
+	
+	if( msn_handler( md->handler ) == -1 ) /* Don't do this on ret == 0, it's already done then. */
+	{
+		hide_login_progress( gc, "Error while reading from server" );
+		signoff( gc );
+	}
+}
+
+static int msn_ns_command( gpointer data, char **cmd, int num_parts )
+{
+	struct gaim_connection *gc = data;
+	struct msn_data *md = gc->proto_data;
+	char buf[1024];
+	
+	if( num_parts == 0 )
+	{
+		/* Hrrm... Empty command...? Ignore? */
+		return( 1 );
+	}
+	
+	if( strcmp( cmd[0], "VER" ) == 0 )
+	{
+		if( cmd[2] && strncmp( cmd[2], "MSNP8", 5 ) != 0 )
+		{
+			hide_login_progress( gc, "Unsupported protocol" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		g_snprintf( buf, sizeof( buf ), "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s\r\n",
+		                                ++md->trId, gc->username );
+		return( msn_write( gc, buf, strlen( buf ) ) );
+	}
+	else if( strcmp( cmd[0], "CVR" ) == 0 )
+	{
+		/* We don't give a damn about the information we just received */
+		g_snprintf( buf, sizeof( buf ), "USR %d TWN I %s\r\n", ++md->trId, gc->username );
+		return( msn_write( gc, buf, strlen( buf ) ) );
+	}
+	else if( strcmp( cmd[0], "XFR" ) == 0 )
+	{
+		char *server;
+		int port;
+		
+		if( num_parts == 6 && strcmp( cmd[2], "NS" ) == 0 )
+		{
+			gaim_input_remove( gc->inpa );
+			gc->inpa = 0;
+			closesocket( md->fd );
+			
+			server = strchr( cmd[3], ':' );
+			if( !server )
+			{
+				hide_login_progress_error( gc, "Syntax error" );
+				signoff( gc );
+				return( 0 );
+			}
+			*server = 0;
+			port = atoi( server + 1 );
+			server = cmd[3];
+			
+			set_login_progress( gc, 1, "Transferring to other server" );
+			
+			md->fd = proxy_connect( server, port, msn_ns_connected, gc );
+		}
+		else if( num_parts == 6 && strcmp( cmd[2], "SB" ) == 0 )
+		{
+			struct msn_switchboard *sb;
+			
+			server = strchr( cmd[3], ':' );
+			if( !server )
+			{
+				hide_login_progress_error( gc, "Syntax error" );
+				signoff( gc );
+				return( 0 );
+			}
+			*server = 0;
+			port = atoi( server + 1 );
+			server = cmd[3];
+			
+			if( strcmp( cmd[4], "CKI" ) != 0 )
+			{
+				hide_login_progress_error( gc, "Unknown authentication method for switchboard" );
+				signoff( gc );
+				return( 0 );
+			}
+			
+			debug( "Connecting to a new switchboard with key %s", cmd[5] );
+			sb = msn_sb_create( gc, server, port, cmd[5], MSN_SB_NEW );
+			
+			if( md->msgq )
+			{
+				struct msn_message *m = md->msgq->data;
+				GSList *l;
+				
+				sb->who = g_strdup( m->who );
+				
+				/* Move all the messages to the first user in the message
+				   queue to the switchboard message queue. */
+				l = md->msgq;
+				while( l )
+				{
+					m = l->data;
+					l = l->next;
+					if( strcmp( m->who, sb->who ) == 0 )
+					{
+						sb->msgq = g_slist_append( sb->msgq, m );
+						md->msgq = g_slist_remove( md->msgq, m );
+					}
+				}
+			}
+		}
+		else
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+	}
+	else if( strcmp( cmd[0], "USR" ) == 0 )
+	{
+		if( num_parts == 5 && strcmp( cmd[2], "TWN" ) == 0 && strcmp( cmd[3], "S" ) == 0 )
+		{
+			/* Time for some Passport black magic... */
+			if( !passport_get_id( gc, gc->username, gc->password, cmd[4], msn_auth_got_passport_id ) )
+			{
+				hide_login_progress_error( gc, "Error while contacting Passport server" );
+				signoff( gc );
+				return( 0 );
+			}
+		}
+		else if( num_parts == 7 && strcmp( cmd[2], "OK" ) == 0 )
+		{
+			http_decode( cmd[4] );
+			
+			strncpy( gc->displayname, cmd[4], sizeof( gc->displayname ) );
+			gc->displayname[sizeof(gc->displayname)-1] = 0;
+			
+			set_login_progress( gc, 1, "Authenticated, getting buddy list" );
+			
+			g_snprintf( buf, sizeof( buf ), "SYN %d 0\r\n", ++md->trId );
+			return( msn_write( gc, buf, strlen( buf ) ) );
+		}
+		else
+		{
+			hide_login_progress( gc, "Unknown authentication type" );
+			signoff( gc );
+			return( 0 );
+		}
+	}
+	else if( strcmp( cmd[0], "MSG" ) == 0 )
+	{
+		if( num_parts != 4 )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		md->handler->msglen = atoi( cmd[3] );
+		
+		if( md->handler->msglen <= 0 )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+	}
+	else if( strcmp( cmd[0], "SYN" ) == 0 )
+	{
+		if( num_parts == 5 )
+		{
+			md->buddycount = atoi( cmd[3] );
+			
+			if( !*cmd[3] || md->buddycount == 0 )
+				msn_logged_in( gc );
+		}
+		else
+		{
+			/* Hrrm... This SYN reply doesn't really look like something we expected.
+			   Let's assume everything is okay. */
+			
+			msn_logged_in( gc );
+		}
+	}
+	else if( strcmp( cmd[0], "GTC" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "BLP" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "PRP" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "LSG" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "LST" ) == 0 )
+	{
+		int list;
+		
+		if( num_parts != 4 && num_parts != 5 )
+		{
+			hide_login_progress( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		http_decode( cmd[2] );
+		list = atoi( cmd[3] );
+		
+		if( list & 1 ) /* FL */
+		{
+			add_buddy( gc, NULL, cmd[1], cmd[2] );
+		}
+		if( list & 2 ) /* AL */
+		{
+			gc->permit = g_slist_append( gc->permit, g_strdup( cmd[1] ) );
+		}
+		if( list & 4 ) /* BL */
+		{
+			gc->deny = g_slist_append( gc->deny, g_strdup( cmd[1] ) );
+		}
+		if( list & 8 ) /* RL */
+		{
+			if( ( list & 6 ) == 0 )
+				msn_buddy_ask( gc, cmd[1], cmd[2] );
+		}
+		
+		if( --md->buddycount == 0 )
+		{
+			if( gc->flags & OPT_LOGGED_IN )
+			{
+				serv_got_crap( gc, "Successfully transferred to different server" );
+				g_snprintf( buf, sizeof( buf ), "CHG %d %s %d\r\n", ++md->trId, md->away_state->code, 0 );
+				return( msn_write( gc, buf, strlen( buf ) ) );
+			}
+			else
+			{
+				msn_logged_in( gc );
+			}
+		}
+	}
+	else if( strcmp( cmd[0], "BPR" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "CHG" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "CHL" ) == 0 )
+	{
+		md5_state_t state;
+		md5_byte_t digest[16];
+		int i;
+		
+		if( num_parts != 3 )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		md5_init( &state );
+		md5_append( &state, (const md5_byte_t *) cmd[2], strlen( cmd[2] ) );
+		md5_append( &state, (const md5_byte_t *) QRY_CODE, strlen( QRY_CODE ) );
+		md5_finish( &state, digest );
+		
+		g_snprintf( buf, sizeof( buf ), "QRY %d %s %d\r\n", ++md->trId, QRY_NAME, 32 );
+		for( i = 0; i < 16; i ++ )
+			g_snprintf( buf + strlen( buf ), 3, "%02x", digest[i] );
+		
+		return( msn_write( gc, buf, strlen( buf ) ) );
+	}
+	else if( strcmp( cmd[0], "QRY" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "QNG" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "ILN" ) == 0 )
+	{
+		struct msn_away_state *st;
+		
+		if( num_parts != 6 )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		http_decode( cmd[4] );
+		serv_buddy_rename( gc, cmd[3], cmd[4] );
+		
+		st = msn_away_state_by_code( cmd[2] );
+		if( !st )
+		{
+			/* FIXME: Warn/Bomb about unknown away state? */
+			st = msn_away_state_list;
+		}
+		
+		serv_got_update( gc, cmd[3], 1, 0, 0, 0, st->number, 0 );
+	}
+	else if( strcmp( cmd[0], "FLN" ) == 0 )
+	{
+		if( cmd[1] )
+			serv_got_update( gc, cmd[1], 0, 0, 0, 0, 0, 0 );
+	}
+	else if( strcmp( cmd[0], "NLN" ) == 0 )
+	{
+		struct msn_away_state *st;
+		
+		if( num_parts != 5 )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		http_decode( cmd[3] );
+		serv_buddy_rename( gc, cmd[2], cmd[3] );
+		
+		st = msn_away_state_by_code( cmd[1] );
+		if( !st )
+		{
+			/* FIXME: Warn/Bomb about unknown away state? */
+			st = msn_away_state_list;
+		}
+		
+		serv_got_update( gc, cmd[2], 1, 0, 0, 0, st->number, 0 );
+	}
+	else if( strcmp( cmd[0], "RNG" ) == 0 )
+	{
+		struct msn_switchboard *sb;
+		char *server;
+		int session, port;
+		
+		if( num_parts != 7 )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		session = atoi( cmd[1] );
+		
+		server = strchr( cmd[2], ':' );
+		if( !server )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+		*server = 0;
+		port = atoi( server + 1 );
+		server = cmd[2];
+		
+		if( strcmp( cmd[3], "CKI" ) != 0 )
+		{
+			hide_login_progress_error( gc, "Unknown authentication method for switchboard" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		debug( "Got a call from %s (session %d). Key = %s", cmd[5], session, cmd[4] );
+		
+		sb = msn_sb_create( gc, server, port, cmd[4], session );
+		sb->who = g_strdup( cmd[5] );
+	}
+	else if( strcmp( cmd[0], "ADD" ) == 0 )
+	{
+		if( num_parts == 6 && strcmp( cmd[2], "RL" ) == 0 )
+		{
+			GSList *l;
+			
+			http_decode( cmd[5] );
+			
+			if( strchr( cmd[4], '@' ) == NULL )
+			{
+				hide_login_progress_error( gc, "Syntax error" );
+				signoff( gc );
+				return( 0 );
+			}
+			
+			/* We got added by someone. If we don't have this person in permit/deny yet, inform the user. */
+			for( l = gc->permit; l; l = l->next )
+				if( g_strcasecmp( l->data, cmd[4] ) == 0 )
+					return( 1 );
+			
+			for( l = gc->deny; l; l = l->next )
+				if( g_strcasecmp( l->data, cmd[4] ) == 0 )
+					return( 1 );
+			
+			msn_buddy_ask( gc, cmd[4], cmd[5] );
+		}
+	}
+	else if( strcmp( cmd[0], "REM" ) == 0 )
+	{
+	}
+	else if( strcmp( cmd[0], "OUT" ) == 0 )
+	{
+		if( cmd[1] && strcmp( cmd[1], "OTH" ) == 0 )
+		{
+			hide_login_progress_error( gc, "Someone else logged in with your account" );
+			gc->wants_to_die = 1;
+		}
+		else if( cmd[1] && strcmp( cmd[1], "SSD" ) == 0 )
+		{
+			hide_login_progress_error( gc, "Terminating session because of server shutdown" );
+		}
+		else
+		{
+			hide_login_progress_error( gc, "Session terminated by remote server (reason unknown)" );
+		}
+		
+		signoff( gc );
+		return( 0 );
+	}
+	else if( strcmp( cmd[0], "REA" ) == 0 )
+	{
+		if( num_parts != 5 )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+		
+		if( g_strcasecmp( cmd[3], gc->username ) == 0 )
+		{
+			http_decode( cmd[4] );
+			strncpy( gc->displayname, cmd[4], sizeof( gc->displayname ) );
+			gc->displayname[sizeof(gc->displayname)-1] = 0;
+		}
+		else
+		{
+			/* This is not supposed to happen, but let's handle it anyway... */
+			http_decode( cmd[4] );
+			serv_buddy_rename( gc, cmd[3], cmd[4] );
+		}
+	}
+	else if( strcmp( cmd[0], "IPG" ) == 0 )
+	{
+		do_error_dialog( gc, "Received IPG command, we don't handle them yet.", "MSN" );
+		
+		md->handler->msglen = atoi( cmd[1] );
+		
+		if( md->handler->msglen <= 0 )
+		{
+			hide_login_progress_error( gc, "Syntax error" );
+			signoff( gc );
+			return( 0 );
+		}
+	}
+	else if( isdigit( cmd[0][0] ) )
+	{
+		int num = atoi( cmd[0] );
+		struct msn_status_code *err = msn_status_by_number( num );
+		
+		g_snprintf( buf, sizeof( buf ), "Error reported by MSN server: %s", err->text );
+		do_error_dialog( gc, buf, "MSN" );
+		
+		if( err->flags & STATUS_FATAL )
+		{
+			signoff( gc );
+			return( 0 );
+		}
+	}
+	else
+	{
+		debug( "Received unknown command from main server: %s", cmd[0] );
+	}
+	
+	return( 1 );
+}
+
+static int msn_ns_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts )
+{
+	struct gaim_connection *gc = data;
+	char *body;
+	int blen = 0;
+	
+	if( !num_parts )
+		return( 1 );
+	
+	if( ( body = strstr( msg, "\r\n\r\n" ) ) )
+	{
+		body += 4;
+		blen = msglen - ( body - msg );
+	}
+	
+	if( strcmp( cmd[0], "MSG" ) == 0 )
+	{
+		if( g_strcasecmp( cmd[1], "Hotmail" ) == 0 )
+		{
+			char *ct = msn_findheader( msg, "Content-Type:", msglen );
+			
+			if( !ct )
+				return( 1 );
+			
+			if( g_strncasecmp( ct, "application/x-msmsgssystemmessage", 33 ) == 0 )
+			{
+				char *mtype;
+				char *arg1;
+				
+				if( !body )
+					return( 1 );
+				
+				mtype = msn_findheader( body, "Type:", blen );
+				arg1 = msn_findheader( body, "Arg1:", blen );
+				
+				if( mtype && strcmp( mtype, "1" ) == 0 )
+				{
+					if( arg1 )
+						serv_got_crap( gc, "The server is going down for maintenance in %s minutes.", arg1 );
+				}
+				
+				if( arg1 ) g_free( arg1 );
+				if( mtype ) g_free( mtype );
+			}
+			else if( g_strncasecmp( ct, "text/x-msmsgsprofile", 20 ) == 0 )
+			{
+				/* We don't care about this profile for now... */
+			}
+			else if( g_strncasecmp( ct, "text/x-msmsgsinitialemailnotification", 37 ) == 0 )
+			{
+				char *inbox = msn_findheader( body, "Inbox-Unread:", blen );
+				char *folders = msn_findheader( body, "Folders-Unread:", blen );
+				
+				if( inbox && folders )
+				{
+					serv_got_crap( gc, "INBOX contains %s new messages, plus %s messages in other folders.", inbox, folders );
+				}
+			}
+			else if( g_strncasecmp( ct, "text/x-msmsgsemailnotification", 30 ) == 0 )
+			{
+				char *from = msn_findheader( body, "From-Addr:", blen );
+				char *fromname = msn_findheader( body, "From:", blen );
+				
+				if( from && fromname )
+				{
+					serv_got_crap( gc, "Received an e-mail message from %s <%s>.", fromname, from );
+				}
+			}
+			else if( g_strncasecmp( ct, "text/x-msmsgsactivemailnotification", 35 ) == 0 )
+			{
+				/* Sorry, but this one really is *USELESS* */
+			}
+			else
+			{
+				debug( "Can't handle %s packet from notification server", ct );
+			}
+			
+			g_free( ct );
+		}
+	}
+	
+	return( 1 );
+}
+
+static void msn_auth_got_passport_id( struct passport_reply *rep )
+{
+	struct gaim_connection *gc = rep->data;
+	struct msn_data *md = gc->proto_data;
+	char *key = rep->result;
+	char buf[1024];
+	
+	if( key == NULL )
+	{
+		hide_login_progress( gc, "Error during Passport authentication" );
+		signoff( gc );
+	}
+	else
+	{
+		g_snprintf( buf, sizeof( buf ), "USR %d TWN S %s\r\n", ++md->trId, key );
+		msn_write( gc, buf, strlen( buf ) );
+	}
+}
Index: protocols/msn/passport.c
===================================================================
--- protocols/msn/passport.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/passport.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,313 @@
+/* passport.c
+ *
+ * Functions to login to microsoft passport service for Messenger
+ * Copyright (C) 2004 Wouter Paesen <wouter@blue-gate.be>
+ * Copyright (C) 2004 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 version 2                   
+ * as published by the Free Software Foundation                                     
+ *                                                                                   
+ * This program is distributed in the hope that is will be useful,                  
+ * bit WITHOU 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA          
+ *
+ */
+
+#include "ssl_client.h"
+#include "passport.h"
+#include "msn.h"
+#include "bitlbee.h"
+#include <ctype.h>
+#include <errno.h>
+
+#define MSN_BUF_LEN 8192
+
+static char *prd_cached = NULL;
+
+static char *passport_create_header( char *reply, char *email, char *pwd );
+static int passport_retrieve_dalogin( gpointer data, gpointer func, char *header );
+static void passport_retrieve_dalogin_connected( gpointer data, void *ssl, GaimInputCondition cond );
+static int passport_get_id_from( gpointer data, gpointer func, char *header_i, char *url );
+static void passport_get_id_connected( gpointer data, void *ssl, GaimInputCondition cond );
+static void destroy_reply( struct passport_reply *rep );
+
+
+int passport_get_id( gpointer data, char *username, char *password, char *cookie, gpointer func )
+{
+	char *header = passport_create_header( cookie, username, password );
+	
+	if( prd_cached )
+	{
+		int st;
+		
+		st = passport_get_id_from( data, func, header, prd_cached );
+		g_free( header );
+		return( st );
+	}
+	else
+	{
+		return( passport_retrieve_dalogin( data, func, header ) );
+	}
+}
+
+
+static char *passport_create_header( char *reply, char *email, char *pwd )
+{
+	char *buffer = g_new0( char, 2048 );
+	char *currenttoken;
+	char *email_enc, *pwd_enc;
+	
+	email_enc = g_new0( char, strlen( email ) * 3 + 1 );
+	strcpy( email_enc, email );
+	http_encode( email_enc );
+	
+	pwd_enc = g_new0( char, strlen( pwd ) * 3 + 1 );
+	strcpy( pwd_enc, pwd );
+	http_encode( pwd_enc );
+	
+	currenttoken = strstr( reply, "lc=" );
+	if( currenttoken == NULL )
+		return( NULL );
+	
+	g_snprintf( buffer, 2048,
+	            "Authorization: Passport1.4 OrgVerb=GET,"
+	            "OrgURL=http%%3A%%2F%%2Fmessenger%%2Emsn%%2Ecom,"
+	            "sign-in=%s,pwd=%s,%s", email_enc, pwd_enc,
+	            currenttoken );
+	
+	g_free( email_enc );
+	g_free( pwd_enc );
+	
+	return( buffer );
+}
+
+
+static int passport_retrieve_dalogin( gpointer data, gpointer func, char *header )
+{
+	struct passport_reply *rep = g_new0( struct passport_reply, 1 );
+	void *ssl;
+	
+	rep->data = data;
+	rep->func = func;
+	rep->header = header;
+	
+	ssl = ssl_connect( "nexus.passport.com", 443, passport_retrieve_dalogin_connected, rep );
+	
+	if( !ssl )
+		destroy_reply( rep );
+	
+	return( ssl != NULL );
+}
+
+#define PPR_BUFFERSIZE 2048
+#define PPR_REQUEST "GET /rdr/pprdr.asp HTTP/1.0\r\n\r\n"
+static void passport_retrieve_dalogin_connected( gpointer data, void *ssl, GaimInputCondition cond )
+{
+	int ret;
+	char buffer[PPR_BUFFERSIZE+1];
+	struct passport_reply *rep = data;
+	
+	if( !g_slist_find( msn_connections, rep->data ) )
+	{
+		if( ssl ) ssl_disconnect( ssl );
+		destroy_reply( rep );
+		return;
+	}
+	
+	if( !ssl )
+	{
+		rep->func( rep );
+		destroy_reply( rep );
+		return;
+	}
+	
+	ssl_write( ssl, PPR_REQUEST, strlen( PPR_REQUEST ) );
+	
+	if( ( ret = ssl_read( ssl, buffer, PPR_BUFFERSIZE ) ) <= 0 )
+	{
+		goto failure;
+	}
+
+	{
+		char *dalogin = strstr( buffer, "DALogin=" );
+		char *urlend;
+		
+		if( !dalogin )
+			goto failure;
+		
+		dalogin += strlen( "DALogin=" );
+		urlend = strchr( dalogin, ',' );
+		if( urlend )
+			*urlend = 0;
+		
+		/* strip the http(s):// part from the url */
+		urlend = strstr( urlend, "://" );
+		if( urlend )
+			dalogin = urlend + strlen( "://" );
+		
+		if( prd_cached == NULL )
+			prd_cached = g_strdup( dalogin );
+	}
+	
+	if( passport_get_id_from( rep->data, rep->func, rep->header, prd_cached ) )
+	{
+		ssl_disconnect( ssl );
+		destroy_reply( rep );
+		return;
+	}
+	
+failure:	
+	ssl_disconnect( ssl );
+	rep->func( rep );
+	destroy_reply( rep );
+}
+
+
+static int passport_get_id_from( gpointer data, gpointer func, char *header_i, char *url )
+{
+	struct passport_reply *rep = g_new0( struct passport_reply, 1 );
+	char server[512], *dummy;
+	void *ssl;
+	
+	rep->data = data;
+	rep->func = func;
+	rep->redirects = 4;
+	
+	strncpy( server, url, 512 );
+	dummy = strchr( server, '/' );
+	if( dummy )
+		*dummy = 0;
+	
+	ssl = ssl_connect( server, 443, passport_get_id_connected, rep );
+	
+	if( ssl )
+	{
+		rep->header = g_strdup( header_i );
+		rep->url = g_strdup( url );
+	}
+	else
+	{
+		destroy_reply( rep );
+	}
+	
+	return( ssl != NULL );
+}
+
+#define PPG_BUFFERSIZE 4096
+static void passport_get_id_connected( gpointer data, void *ssl, GaimInputCondition cond )
+{
+	struct passport_reply *rep = data;
+	char server[512], buffer[PPG_BUFFERSIZE+1], *dummy;
+	int ret;
+	
+	if( !g_slist_find( msn_connections, rep->data ) )
+	{
+		if( ssl ) ssl_disconnect( ssl );
+		destroy_reply( rep );
+		return;
+	}
+	
+	if( !ssl )
+	{
+		rep->func( rep );
+		destroy_reply( rep );
+		return;
+	}
+	
+	memset( buffer, 0, PPG_BUFFERSIZE + 1 );
+	
+	strncpy( server, rep->url, 512 );
+	dummy = strchr( server, '/' );
+	if( dummy == NULL )
+		goto end;
+	
+	g_snprintf( buffer, PPG_BUFFERSIZE - 1, "GET %s HTTP/1.0\r\n"
+	            "%s\r\n\r\n", dummy, rep->header );
+	
+	ssl_write( ssl, buffer, strlen( buffer ) );
+	memset( buffer, 0, PPG_BUFFERSIZE + 1 );
+	
+	{
+		char *buffer2 = buffer;
+		
+		while( ( ( ret = ssl_read( ssl, buffer2, 512 ) ) > 0 ) &&
+		       ( buffer + PPG_BUFFERSIZE - buffer2 - ret - 512 >= 0 ) )
+		{
+			buffer2 += ret;
+		}
+	}
+	
+	if( *buffer == 0 )
+		goto end;
+	
+	if( ( dummy = strstr( buffer, "Location:" ) ) )
+	{
+		char *urlend;
+		
+		rep->redirects --;
+		if( rep->redirects == 0 )
+			goto end;
+		
+		dummy += strlen( "Location:" );
+		while( isspace( *dummy ) ) dummy ++;
+		urlend = dummy;
+		while( !isspace( *urlend ) ) urlend ++;
+		*urlend = 0;
+		if( ( urlend = strstr( dummy, "://" ) ) )
+			dummy = urlend + strlen( "://" );
+		
+		g_free( rep->url );
+		rep->url = g_strdup( dummy );
+		
+		strncpy( server, dummy, sizeof( server ) - 1 );
+		dummy = strchr( server, '/' );
+		if( dummy ) *dummy = 0;
+		
+		ssl_disconnect( ssl );
+		
+		if( ssl_connect( server, 443, passport_get_id_connected, rep ) )
+		{
+			return;
+		}
+		else
+		{
+			rep->func( rep );
+			destroy_reply( rep );
+			return;
+		}
+	}
+	else if( strstr( buffer, "200 OK" ) )
+	{
+		if( ( dummy = strstr( buffer, "from-PP='" ) ) )
+		{
+			char *responseend;
+			
+			dummy += strlen( "from-PP='" );
+			responseend = strchr( dummy, '\'' );
+			if( responseend )
+				*responseend = 0;
+			
+			rep->result = g_strdup( dummy );
+		}
+	}
+	
+end:
+	ssl_disconnect( ssl );
+	rep->func( rep );
+	destroy_reply( rep );
+}
+
+
+static void destroy_reply( struct passport_reply *rep )
+{
+	if( rep->result ) g_free( rep->result );
+	if( rep->url ) g_free( rep->url );
+	if( rep->header ) g_free( rep->header );
+	if( rep ) g_free( rep );
+}
Index: protocols/msn/passport.h
===================================================================
--- protocols/msn/passport.h	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/passport.h	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,47 @@
+#ifndef __PASSPORT_H__
+#define __PASSPORT_H__
+/* passport.h
+ *
+ * Functions to login to Microsoft Passport Service for Messenger
+ * Copyright (C) 2004 Wouter Paesen <wouter@blue-gate.be>,
+ *                    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 version 2                   
+ * as published by the Free Software Foundation                                     
+ *                                                                                   
+ * This program is distributed in the hope that is will be useful,                  
+ * bit WITHOU 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA          
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#endif
+#include "nogaim.h"
+
+struct passport_reply
+{
+	void *data;
+	char *result;
+	void (*func)( struct passport_reply * );
+	char *url;
+	char *header;
+	int redirects;
+};
+
+int passport_get_id( gpointer data, char *username, char *password, char *cookie, gpointer func );
+
+#endif /* __PASSPORT_H__ */
Index: protocols/msn/sb.c
===================================================================
--- protocols/msn/sb.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/sb.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,659 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* MSN module - Switchboard server callbacks and utilities              */
+
+/*
+  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 <ctype.h>
+#include "nogaim.h"
+#include "msn.h"
+#include "passport.h"
+#include "md5.h"
+
+static void msn_sb_callback( gpointer data, gint source, GaimInputCondition cond );
+static int msn_sb_command( gpointer data, char **cmd, int num_parts );
+static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts );
+
+int msn_sb_write( struct msn_switchboard *sb, char *s, int len )
+{
+	int st;
+	
+	st = write( sb->fd, s, len );
+	if( st != len )
+	{
+		msn_sb_destroy( sb );
+		return( 0 );
+	}
+	
+	return( 1 );
+}
+
+struct msn_switchboard *msn_sb_create( struct gaim_connection *gc, char *host, int port, char *key, int session )
+{
+	struct msn_data *md = gc->proto_data;
+	struct msn_switchboard *sb = g_new0( struct msn_switchboard, 1 );
+	
+	sb->fd = proxy_connect( host, port, msn_sb_connected, sb );
+	if( sb->fd < 0 )
+	{
+		g_free( sb );
+		return( NULL );
+	}
+	
+	sb->gc = gc;
+	sb->key = g_strdup( key );
+	sb->session = session;
+	
+	msn_switchboards = g_slist_append( msn_switchboards, sb );
+	md->switchboards = g_slist_append( md->switchboards, sb );
+	
+	return( sb );
+}
+
+struct msn_switchboard *msn_sb_by_handle( struct gaim_connection *gc, char *handle )
+{
+	struct msn_data *md = gc->proto_data;
+	struct msn_switchboard *sb;
+	GSList *l;
+	
+	for( l = md->switchboards; l; l = l->next )
+	{
+		sb = l->data;
+		if( sb->who && strcmp( sb->who, handle ) == 0 )
+			return( sb );
+	}
+	
+	return( NULL );
+}
+
+struct msn_switchboard *msn_sb_by_id( struct gaim_connection *gc, int id )
+{
+	struct msn_data *md = gc->proto_data;
+	struct msn_switchboard *sb;
+	GSList *l;
+	
+	for( l = md->switchboards; l; l = l->next )
+	{
+		sb = l->data;
+		if( sb->chat && sb->chat->id == id )
+			return( sb );
+	}
+	
+	return( NULL );
+}
+
+struct msn_switchboard *msn_sb_spare( struct gaim_connection *gc )
+{
+	struct msn_data *md = gc->proto_data;
+	struct msn_switchboard *sb;
+	GSList *l;
+	
+	for( l = md->switchboards; l; l = l->next )
+	{
+		sb = l->data;
+		if( !sb->who && !sb->chat )
+			return( sb );
+	}
+	
+	return( NULL );
+}
+
+int msn_sb_sendmessage( struct msn_switchboard *sb, char *text )
+{
+	if( sb->ready )
+	{
+		char cmd[1024], *buf;
+		int i, j;
+		
+		if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) != 0 )
+		{
+			buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 );
+			i = strlen( MSN_MESSAGE_HEADERS );
+			
+			strcpy( buf, MSN_MESSAGE_HEADERS );
+			for( j = 0; text[j]; j ++ )
+			{
+				if( text[j] == '\n' )
+					buf[i++] = '\r';
+				
+				buf[i++] = text[j];
+			}
+		}
+		else
+		{
+			i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->gc->username );
+			buf = g_new0( char, strlen( MSN_TYPING_HEADERS ) + strlen( sb->gc->username ) );
+			i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->gc->username );
+		}
+		
+		g_snprintf( cmd, sizeof( cmd ), "MSG %d N %d\r\n", ++sb->trId, i );
+		if( msn_sb_write( sb, cmd, strlen( cmd ) ) && msn_sb_write( sb, buf, i ) )
+		{
+			g_free( buf );
+			return( 1 );
+		}
+		else
+		{
+			g_free( buf );
+			return( 0 );
+		}
+	}
+	else if( sb->who )
+	{
+		struct msn_message *m = g_new0( struct msn_message, 1 );
+		
+		m->who = g_strdup( "" );
+		m->text = g_strdup( text );
+		sb->msgq = g_slist_append( sb->msgq, m );
+		
+		return( 1 );
+	}
+	else
+	{
+		return( 0 );
+	}
+}
+
+void msn_sb_to_chat( struct msn_switchboard *sb )
+{
+	struct gaim_connection *gc = sb->gc;
+	char buf[1024];
+	
+	/* Create the groupchat structure. */
+	g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session );
+	sb->chat = serv_got_joined_chat( gc, ++msn_chat_id, buf );
+	
+	/* Populate the channel. */
+	if( sb->who ) add_chat_buddy( sb->chat, sb->who );
+	add_chat_buddy( sb->chat, gc->username );
+	
+	/* And make sure the switchboard doesn't look like a regular chat anymore. */
+	if( sb->who )
+	{
+		g_free( sb->who );
+		sb->who = NULL;
+	}
+}
+
+void msn_sb_destroy( struct msn_switchboard *sb )
+{
+	struct gaim_connection *gc = sb->gc;
+	struct msn_data *md = gc->proto_data;
+	
+	debug( "Destroying switchboard: %s", sb->who ? sb->who : sb->key ? sb->key : "" );
+	
+	if( sb->key ) g_free( sb->key );
+	if( sb->who ) g_free( sb->who );
+	
+	if( sb->msgq )
+	{
+		struct msn_message *m;
+		GSList *l;
+		
+		for( l = sb->msgq; l; l = l->next )
+		{
+			m = l->data;
+			g_free( m->who );
+			g_free( m->text );
+			g_free( m );
+		}
+		g_slist_free( sb->msgq );
+		
+		serv_got_crap( gc, "Warning: Closing down MSN switchboard connection with unsent message(s), you'll have to resend them." );
+	}
+	
+	if( sb->chat )
+	{
+		serv_got_chat_left( gc, sb->chat->id );
+	}
+	
+	if( sb->handler )
+	{
+		if( sb->handler->rxq ) g_free( sb->handler->rxq );
+		if( sb->handler->cmd_text ) g_free( sb->handler->cmd_text );
+		g_free( sb->handler );
+	}
+	
+	if( sb->inp ) gaim_input_remove( sb->inp );
+	closesocket( sb->fd );
+	
+	msn_switchboards = g_slist_remove( msn_switchboards, sb );
+	md->switchboards = g_slist_remove( md->switchboards, sb );
+	g_free( sb );
+}
+
+void msn_sb_connected( gpointer data, gint source, GaimInputCondition cond )
+{
+	struct msn_switchboard *sb = data;
+	struct gaim_connection *gc;
+	struct msn_data *md;
+	char buf[1024];
+	
+	/* Are we still alive? */
+	if( !g_slist_find( msn_switchboards, sb ) )
+		return;
+	
+	gc = sb->gc;
+	md = gc->proto_data;
+	
+	if( source != sb->fd )
+	{
+		debug( "ERROR %d while connecting to switchboard server", 1 );
+		msn_sb_destroy( sb );
+		return;
+	}
+	
+	/* Prepare the callback */
+	sb->handler = g_new0( struct msn_handler_data, 1 );
+	sb->handler->fd = sb->fd;
+	sb->handler->rxq = g_new0( char, 1 );
+	sb->handler->data = sb;
+	sb->handler->exec_command = msn_sb_command;
+	sb->handler->exec_message = msn_sb_message;
+	
+	if( sb->session == MSN_SB_NEW )
+		g_snprintf( buf, sizeof( buf ), "USR %d %s %s\r\n", ++sb->trId, gc->username, sb->key );
+	else
+		g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, gc->username, sb->key, sb->session );
+	
+	if( msn_sb_write( sb, buf, strlen( buf ) ) )
+		sb->inp = gaim_input_add( sb->fd, GAIM_INPUT_READ, msn_sb_callback, sb );
+	else
+		debug( "ERROR %d while connecting to switchboard server", 2 );
+}
+
+static void msn_sb_callback( gpointer data, gint source, GaimInputCondition cond )
+{
+	struct msn_switchboard *sb = data;
+	
+	if( msn_handler( sb->handler ) == -1 )
+	{
+		debug( "ERROR: Switchboard died" );
+		msn_sb_destroy( sb );
+	}
+}
+
+static int msn_sb_command( gpointer data, char **cmd, int num_parts )
+{
+	struct msn_switchboard *sb = data;
+	struct gaim_connection *gc = sb->gc;
+	char buf[1024];
+	
+	if( !num_parts )
+	{
+		/* Hrrm... Empty command...? Ignore? */
+		return( 1 );
+	}
+	
+	if( strcmp( cmd[0], "XFR" ) == 0 )
+	{
+		hide_login_progress_error( gc, "Received an XFR from a switchboard server, unable to comply! This is likely to be a bug, please report it!" );
+		signoff( gc );
+		return( 0 );
+	}
+	else if( strcmp( cmd[0], "USR" ) == 0 )
+	{
+		if( num_parts != 5 )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		if( strcmp( cmd[2], "OK" ) != 0 )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		if( sb->who )
+		{
+			g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, sb->who );
+			return( msn_sb_write( sb, buf, strlen( buf ) ) );
+		}
+		else
+		{
+			debug( "Just created a switchboard, but I don't know what to do with it." );
+		}
+	}
+	else if( strcmp( cmd[0], "IRO" ) == 0 )
+	{
+		int num, tot;
+		
+		if( num_parts != 6 )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		num = atoi( cmd[2] );
+		tot = atoi( cmd[3] );
+		
+		if( tot <= 0 )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		else if( tot > 1 )
+		{
+			char buf[1024];
+			
+			if( num == 1 )
+			{
+				g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session );
+				sb->chat = serv_got_joined_chat( gc, ++msn_chat_id, buf );
+				
+				g_free( sb->who );
+				sb->who = NULL;
+			}
+			
+			add_chat_buddy( sb->chat, cmd[4] );
+			
+			if( num == tot )
+			{
+				add_chat_buddy( sb->chat, gc->username );
+			}
+		}
+	}
+	else if( strcmp( cmd[0], "ANS" ) == 0 )
+	{
+		if( num_parts != 3 )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		if( strcmp( cmd[2], "OK" ) != 0 )
+		{
+			debug( "Switchboard server sent a negative ANS reply" );
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		sb->ready = 1;
+	}
+	else if( strcmp( cmd[0], "CAL" ) == 0 )
+	{
+		if( num_parts != 4 || !isdigit( cmd[3][0] ) )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		sb->session = atoi( cmd[3] );
+	}
+	else if( strcmp( cmd[0], "JOI" ) == 0 )
+	{
+		if( num_parts != 3 )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		if( sb->who && g_strcasecmp( cmd[1], sb->who ) == 0 )
+		{
+			/* The user we wanted to talk to is finally there, let's send the queued messages then. */
+			struct msn_message *m;
+			GSList *l;
+			int st = 1;
+			
+			debug( "%s arrived in the switchboard session, now sending queued message(s)", cmd[1] );
+			
+			/* Without this, sendmessage() will put everything back on the queue... */
+			sb->ready = 1;
+			
+			while( ( l = sb->msgq ) )
+			{
+				m = l->data;
+				if( st )
+				{
+					/* This hack is meant to convert a regular new chat into a groupchat */
+					if( strcmp( m->text, GROUPCHAT_SWITCHBOARD_MESSAGE ) == 0 )
+						msn_sb_to_chat( sb );
+					else
+						st = msn_sb_sendmessage( sb, m->text );
+				}
+				g_free( m->text );
+				g_free( m->who );
+				g_free( m );
+				
+				sb->msgq = g_slist_remove( sb->msgq, m );
+			}
+			
+			return( st );
+		}
+		else if( sb->who )
+		{
+			debug( "Converting chat with %s to a groupchat because %s joined the session.", sb->who, cmd[1] );
+			
+			/* This SB is a one-to-one chat right now, but someone else is joining. */
+			msn_sb_to_chat( sb );
+			
+			add_chat_buddy( sb->chat, cmd[1] );
+		}
+		else if( sb->chat )
+		{
+			add_chat_buddy( sb->chat, cmd[1] );
+			sb->ready = 1;
+		}
+		else
+		{
+			/* PANIC! */
+		}
+	}
+	else if( strcmp( cmd[0], "MSG" ) == 0 )
+	{
+		if( num_parts != 4 )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		sb->handler->msglen = atoi( cmd[3] );
+		
+		if( sb->handler->msglen <= 0 )
+		{
+			debug( "Received a corrupted message on the switchboard, the switchboard will be closed" );
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+	}
+	else if( strcmp( cmd[0], "BYE" ) == 0 )
+	{
+		if( num_parts < 2 )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		
+		/* if( cmd[2] && *cmd[2] == '1' ) -=> Chat is being cleaned up because of idleness */
+		
+		if( sb->who )
+		{
+			/* This is a single-person chat, and the other person is leaving. */
+			g_free( sb->who );
+			sb->who = NULL;
+			sb->ready = 0;
+			
+			debug( "Person %s left the one-to-one switchboard connection. Keeping it around as a spare...", cmd[1] );
+			
+			/* We could clean up the switchboard now, but keeping it around
+			   as a spare for a next conversation sounds more sane to me.
+			   The server will clean it up when it's idle for too long. */
+		}
+		else if( sb->chat )
+		{
+			remove_chat_buddy( sb->chat, cmd[1], "" );
+		}
+		else
+		{
+			/* PANIC! */
+		}
+	}
+	else if( isdigit( cmd[0][0] ) )
+	{
+		int num = atoi( cmd[0] );
+		struct msn_status_code *err = msn_status_by_number( num );
+		
+		g_snprintf( buf, sizeof( buf ), "Error reported by switchboard server: %s", err->text );
+		do_error_dialog( gc, buf, "MSN" );
+		
+		if( err->flags & STATUS_SB_FATAL )
+		{
+			msn_sb_destroy( sb );
+			return( 0 );
+		}
+		else if( err->flags & STATUS_FATAL )
+		{
+			signoff( gc );
+			return( 0 );
+		}
+	}
+	else
+	{
+		debug( "Received unknown command from switchboard server: %s", cmd[0] );
+	}
+	
+	return( 1 );
+}
+
+static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts )
+{
+	struct msn_switchboard *sb = data;
+	struct gaim_connection *gc = sb->gc;
+	char *body;
+	int blen = 0;
+	
+	if( !num_parts )
+		return( 1 );
+	
+	if( ( body = strstr( msg, "\r\n\r\n" ) ) )
+	{
+		body += 4;
+		blen = msglen - ( body - msg );
+	}
+	
+	if( strcmp( cmd[0], "MSG" ) == 0 )
+	{
+		char *ct = msn_findheader( msg, "Content-Type:", msglen );
+		
+		if( !ct )
+			return( 1 );
+		
+		if( g_strncasecmp( ct, "text/plain", 10 ) == 0 )
+		{
+			g_free( ct );
+			
+			if( !body )
+				return( 1 );
+			
+			if( sb->who )
+			{
+				serv_got_im( gc, cmd[1], body, 0, 0, blen );
+			}
+			else if( sb->chat )
+			{
+				serv_got_chat_in( gc, sb->chat->id, cmd[1], 0, body, 0 );
+			}
+			else
+			{
+				/* PANIC! */
+			}
+		}
+		else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 )
+		{
+			char *itype = msn_findheader( body, "Application-GUID:", blen );
+			char buf[1024];
+			
+			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 )
+			{
+				serv_got_im( gc, cmd[1], buf, 0, 0, strlen( buf ) );
+			}
+			else if( sb->chat )
+			{
+				serv_got_chat_in( gc, sb->chat->id, cmd[1], 0, buf, 0 );
+			}
+			else
+			{
+				/* PANIC! */
+			}
+		}
+		else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 )
+		{
+			char *who = msn_findheader( msg, "TypingUser:", msglen );
+			
+			if( who )
+			{
+				serv_got_typing( gc, who, 5 );
+				g_free( who );
+			}
+			
+			g_free( ct );
+		}
+		else
+		{
+			g_free( ct );
+		}
+	}
+	
+	return( 1 );
+}
Index: protocols/msn/tables.c
===================================================================
--- protocols/msn/tables.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
+++ protocols/msn/tables.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -0,0 +1,166 @@
+  /********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* MSN module - Some tables with useful data                            */
+
+/*
+  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 "nogaim.h"
+#include "msn.h"
+
+struct msn_away_state msn_away_state_list[] =
+{
+	{  0, "NLN", "Available" },
+	{  1, "BSY", "Busy" },
+	{  3, "IDL", "Idle" },
+	{  5, "BRB", "Be Right Back" },
+	{  7, "AWY", "Away" },
+	{  9, "PHN", "On the Phone" },
+	{ 11, "LUN", "Out to Lunch" },
+	{ 13, "HDN", "Hidden" },
+	{ -1, "",    "" }
+};
+
+struct msn_away_state *msn_away_state_by_number( int number )
+{
+	int i;
+	
+	for( i = 0; msn_away_state_list[i].number > -1; i ++ )
+		if( msn_away_state_list[i].number == number )
+			return( msn_away_state_list + i );
+	
+	return( NULL );
+}
+
+struct msn_away_state *msn_away_state_by_code( char *code )
+{
+	int i;
+	
+	for( i = 0; msn_away_state_list[i].number > -1; i ++ )
+		if( g_strcasecmp( msn_away_state_list[i].code, code ) == 0 )
+			return( msn_away_state_list + i );
+	
+	return( NULL );
+}
+
+struct msn_away_state *msn_away_state_by_name( char *name )
+{
+	int i;
+	
+	for( i = 0; msn_away_state_list[i].number > -1; i ++ )
+		if( g_strcasecmp( msn_away_state_list[i].name, name ) == 0 )
+			return( msn_away_state_list + i );
+	
+	return( NULL );
+}
+
+struct msn_status_code msn_status_code_list[] =
+{
+	{ 200, "Invalid syntax",                                        0 },
+	{ 201, "Invalid parameter",                                     0 },
+	{ 205, "Invalid (non-existent) handle",                         0 },
+	{ 206, "Domain name missing",                                   0 },
+	{ 207, "Already logged in",                                     0 },
+	{ 208, "Invalid handle",                                        0 },
+	{ 209, "Forbidden nickname",                                    0 },
+	{ 210, "Buddy list too long",                                   0 },
+	{ 215, "Handle is already in list",                             0 },
+	{ 216, "Handle is not in list",                                 0 },
+	{ 217, "Person is off-line or non-existent",                    0 },
+	{ 218, "Already in that mode",                                  0 },
+	{ 219, "Handle is already in opposite list",                    0 },
+	{ 223, "Too many groups",                                       0 },
+	{ 224, "Invalid group or already in list",                      0 },
+	{ 225, "Handle is not in that group",                           0 },
+	{ 229, "Group name too long",                                   0 },
+	{ 230, "Cannot remove that group",                              0 },
+	{ 231, "Invalid group",                                         0 },
+	{ 280, "Switchboard failed",                                    STATUS_SB_FATAL },
+	{ 281, "Transfer to switchboard failed",                        0 },
+	
+	{ 300, "Required field missing",                                0 },
+	{ 302, "Not logged in",                                         0 },
+	
+	{ 500, "Internal server error/Account banned",                  STATUS_FATAL },
+	{ 501, "Database server error",                                 STATUS_FATAL },
+	{ 502, "Command disabled",                                      0 },
+	{ 510, "File operation failed",                                 STATUS_FATAL },
+	{ 520, "Memory allocation failed",                              STATUS_FATAL },
+	{ 540, "Challenge response invalid",                            STATUS_FATAL },
+	
+	{ 600, "Server is busy",                                        STATUS_FATAL },
+	{ 601, "Server is unavailable",                                 STATUS_FATAL },
+	{ 602, "Peer nameserver is down",                               STATUS_FATAL },
+	{ 603, "Database connection failed",                            STATUS_FATAL },
+	{ 604, "Server is going down",                                  STATUS_FATAL },
+	{ 605, "Server is unavailable",                                 STATUS_FATAL },
+	
+	{ 700, "Could not create connection",                           STATUS_FATAL },
+	{ 710, "Invalid CVR parameters",                                STATUS_FATAL },
+	{ 711, "Write is blocking",                                     STATUS_FATAL },
+	{ 712, "Session is overloaded",                                 STATUS_FATAL },
+	{ 713, "Calling too rapidly",                                   0 },
+	{ 714, "Too many sessions",                                     STATUS_FATAL },
+	{ 715, "Not expected/Invalid argument/action",                  0 },
+	{ 717, "Bad friend file",                                       STATUS_FATAL },
+	{ 731, "Not expected/Invalid argument",                         0 },
+	
+	{ 800, "Changing too rapidly",                                  0 },
+	
+	{ 910, "Server is busy",                                        STATUS_FATAL },
+	{ 911, "Authentication failed",                                 STATUS_FATAL },
+	{ 912, "Server is busy",                                        STATUS_FATAL },
+	{ 913, "Not allowed when hiding",                               0 },
+	{ 914, "Server is unavailable",                                 STATUS_FATAL },
+	{ 915, "Server is unavailable",                                 STATUS_FATAL },
+	{ 916, "Server is unavailable",                                 STATUS_FATAL },
+	{ 917, "Authentication failed",                                 STATUS_FATAL },
+	{ 918, "Server is busy",                                        STATUS_FATAL },
+	{ 919, "Server is busy",                                        STATUS_FATAL },
+	{ 920, "Not accepting new principals",                          0 }, /* When a sb is full? */
+	{ 922, "Server is busy",                                        STATUS_FATAL },
+	{ 923, "Kids Passport without parental consent",                STATUS_FATAL },
+	{ 924, "Passport account not yet verified",                     STATUS_FATAL },
+	{ 928, "Bad ticket",                                            STATUS_FATAL },
+	{  -1, NULL,                                                    0 }
+};
+
+struct msn_status_code *msn_status_by_number( int number )
+{
+	static struct msn_status_code *unknown = NULL;
+	int i;
+	
+	for( i = 0; msn_status_code_list[i].number >= 0; i ++ )
+		if( msn_status_code_list[i].number == number )
+			return( msn_status_code_list + i );
+	
+	if( unknown == NULL )
+	{
+		unknown = g_new0( struct msn_status_code, 1 );
+		unknown->text = g_new0( char, 128 );
+	}
+	
+	unknown->number = number;
+	unknown->flags = 0;
+	g_snprintf( unknown->text, 128, "Unknown error (%d)", number );
+	
+	return( unknown );
+}
