Index: protocols/jabber/conference.c
===================================================================
--- protocols/jabber/conference.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/jabber/conference.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -37,4 +37,6 @@
 	xt_add_attr( node, "xmlns", XMLNS_MUC );
 	node = jabber_make_packet( "presence", NULL, roomjid, node );
+	if( password )
+		xt_add_child( node, xt_new_node( "password", password, NULL ) );
 	jabber_cache_add( ic, node, jabber_chat_join_failed );
 	
@@ -73,15 +75,14 @@
 	
 	room = xt_find_attr( orig, "to" );
-	if( ( bud = jabber_buddy_by_jid( ic, room, 0 ) ) )
-		jabber_chat_free( jabber_chat_by_jid( ic, bud->bare_jid ) );
-	
+	bud = jabber_buddy_by_jid( ic, room, 0 );
 	err = jabber_error_parse( xt_find_node( node->children, "error" ), XMLNS_STANZA_ERROR );
 	if( err )
 	{
-		imcb_error( ic, "Error joining groupchat %s: %s%s%s",
-		            bud->bare_jid, err->code, err->text ? ": " : "",
-		            err->text ? err->text : "" );
+		imcb_error( ic, "Error joining groupchat %s: %s%s%s", room, err->code,
+		            err->text ? ": " : "", err->text ? err->text : "" );
 		jabber_error_free( err );
 	}
+	if( bud )
+		jabber_chat_free( jabber_chat_by_jid( ic, bud->bare_jid ) );
 	
 	return XT_HANDLED;
@@ -123,4 +124,6 @@
 	struct jabber_chat *jc = c->data;
 	struct xt_node *node;
+	
+	jc->flags |= JCFLAG_MESSAGE_SENT;
 	
 	node = xt_new_node( "body", message, NULL );
@@ -296,20 +299,57 @@
 	struct xt_node *subject = xt_find_node( node->children, "subject" );
 	struct xt_node *body = xt_find_node( node->children, "body" );
-	struct groupchat *chat;
+	struct groupchat *chat = bud ? jabber_chat_by_jid( ic, bud->bare_jid ) : NULL;
+	struct jabber_chat *jc = chat ? chat->data : NULL;
 	char *s;
 	
-	if( bud == NULL )
-	{
+	if( bud == NULL || ( jc && ~jc->flags & JCFLAG_MESSAGE_SENT && bud == jc->me ) )
+	{
+		char *nick;
+		
+		if( body == NULL || body->text_len == 0 )
+			/* Meh. Empty messages aren't very interesting, no matter
+			   how much some servers love to send them. */
+			return;
+		
 		s = xt_find_attr( node, "from" ); /* pkt_message() already NULL-checked this one. */
-		if( strchr( s, '/' ) == NULL )
+		nick = strchr( s, '/' );
+		if( nick )
+		{
+			/* If this message included a resource/nick we don't know,
+			   we might still know the groupchat itself. */
+			*nick = 0;
+			chat = jabber_chat_by_jid( ic, s );
+			*nick = '/';
+			
+			nick ++;
+		}
+		else
+		{
+			/* message.c uses the EXACT_JID option, so bud should
+			   always be NULL here for bare JIDs. */
+			chat = jabber_chat_by_jid( ic, s );
+		}
+		
+		if( nick == NULL )
+		{
 			/* This is fine, the groupchat itself isn't in jd->buddies. */
-			imcb_log( ic, "System message from groupchat %s: %s", s, body? body->text : "NULL" );
+			if( chat )
+				imcb_chat_log( chat, "From conference server: %s", body->text );
+			else
+				imcb_log( ic, "System message from unknown groupchat %s: %s", s, body->text );
+		}
 		else
-			/* This, however, isn't fine! */
-			imcb_log( ic, "Groupchat message from unknown participant %s: %s", s, body ? body->text : "NULL" );
+		{
+			/* This can happen too, at least when receiving a backlog when
+			   just joining a channel. */
+			if( chat )
+				imcb_chat_log( chat, "Message from unknown participant %s: %s", nick, body->text );
+			else
+				imcb_log( ic, "Groupchat message from unknown JID %s: %s", s, body->text );
+		}
 		
 		return;
 	}
-	else if( ( chat = jabber_chat_by_jid( ic, bud->bare_jid ) ) == NULL )
+	else if( chat == NULL )
 	{
 		/* How could this happen?? We could do kill( self, 11 )
Index: protocols/jabber/io.c
===================================================================
--- protocols/jabber/io.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/jabber/io.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -249,4 +249,7 @@
 	struct im_connection *ic = data;
 	
+	if( g_slist_find( jabber_connections, ic ) == NULL )
+		return FALSE;
+	
 	if( source == -1 )
 	{
@@ -264,5 +267,10 @@
 {
 	struct im_connection *ic = data;
-	struct jabber_data *jd = ic->proto_data;
+	struct jabber_data *jd;
+	
+	if( g_slist_find( jabber_connections, ic ) == NULL )
+		return FALSE;
+	
+	jd = ic->proto_data;
 	
 	if( source == NULL )
Index: protocols/jabber/iq.c
===================================================================
--- protocols/jabber/iq.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/jabber/iq.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -92,5 +92,6 @@
 		else if( strcmp( s, XMLNS_DISCOVER ) == 0 )
 		{
-			const char *features[] = { XMLNS_VERSION,
+			const char *features[] = { XMLNS_DISCOVER,
+			                           XMLNS_VERSION,
 			                           XMLNS_TIME,
 			                           XMLNS_CHATSTATES,
Index: protocols/jabber/jabber.c
===================================================================
--- protocols/jabber/jabber.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/jabber/jabber.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -35,4 +35,6 @@
 #include "base64.h"
 
+GSList *jabber_connections;
+
 static void jabber_init( account_t *acc )
 {
@@ -70,4 +72,9 @@
 	struct ns_srv_reply *srv = NULL;
 	char *connect_to, *s;
+	
+	/* For now this is needed in the _connected() handlers if using
+	   GLib event handling, to make sure we're not handling events
+	   on dead connections. */
+	jabber_connections = g_slist_prepend( jabber_connections, ic );
 	
 	jd->ic = ic;
@@ -263,4 +270,6 @@
 	g_free( jd->username );
 	g_free( jd );
+	
+	jabber_connections = g_slist_remove( jabber_connections, ic );
 }
 
Index: protocols/jabber/jabber.h
===================================================================
--- protocols/jabber/jabber.h	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/jabber/jabber.h	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -29,4 +29,6 @@
 #include "xmltree.h"
 #include "bitlbee.h"
+
+extern GSList *jabber_connections;
 
 typedef enum
@@ -47,13 +49,19 @@
 typedef enum
 {
-	JBFLAG_PROBED_XEP85 = 1,	/* Set this when we sent our probe packet to make
+	JBFLAG_PROBED_XEP85 = 1,        /* Set this when we sent our probe packet to make
 	                                   sure it gets sent only once. */
-	JBFLAG_DOES_XEP85 = 2,		/* Set this when the resource seems to support
+	JBFLAG_DOES_XEP85 = 2,          /* Set this when the resource seems to support
 	                                   XEP85 (typing notification shite). */
-	JBFLAG_IS_CHATROOM = 4,		/* It's convenient to use this JID thingy for
+	JBFLAG_IS_CHATROOM = 4,         /* It's convenient to use this JID thingy for
 	                                   groupchat state info too. */
-	JBFLAG_IS_ANONYMOUS = 8,	/* For anonymous chatrooms, when we don't have
+	JBFLAG_IS_ANONYMOUS = 8,        /* For anonymous chatrooms, when we don't have
 	                                   have a real JID. */
 } jabber_buddy_flags_t;
+
+typedef enum
+{
+	JCFLAG_MESSAGE_SENT = 1,        /* Set this after sending the first message, so
+	                                   we can detect echoes/backlogs. */
+} jabber_chat_flags_t;
 
 struct jabber_data
@@ -93,4 +101,5 @@
 struct jabber_cache_entry
 {
+	time_t saved_at;
 	struct xt_node *node;
 	jabber_cache_event func;
@@ -138,4 +147,8 @@
 #define JABBER_PACKET_ID "BeeP"
 #define JABBER_CACHED_ID "BeeC"
+
+/* The number of seconds to keep cached packets before garbage collecting
+   them. This gc is done on every keepalive (every minute). */
+#define JABBER_CACHE_MAX_AGE 600
 
 /* RFC 392[01] stuff */
@@ -159,4 +172,5 @@
 #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 */
 
 /* iq.c */
Index: protocols/jabber/jabber_util.c
===================================================================
--- protocols/jabber/jabber_util.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/jabber/jabber_util.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -142,4 +142,5 @@
 	entry->node = node;
 	entry->func = func;
+	entry->saved_at = time( NULL );
 	g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry );
 }
@@ -163,20 +164,15 @@
 {
 	struct jabber_data *jd = ic->proto_data;
-	
-	g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, NULL );
-}
-
-gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer nullpointer )
+	time_t threshold = time( NULL ) - JABBER_CACHE_MAX_AGE;
+	
+	g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, &threshold );
+}
+
+gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer threshold_ )
 {
 	struct jabber_cache_entry *entry = entry_;
-	struct xt_node *node = entry->node;
-	
-	if( node->flags & XT_SEEN )
-		return TRUE;
-	else
-	{
-		node->flags |= XT_SEEN;
-		return FALSE;
-	}
+	time_t *threshold = threshold_;
+	
+	return entry->saved_at < *threshold;
 }
 
@@ -392,17 +388,24 @@
 		if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
 		{
+			/* Just return the first one for this bare JID. */
+			if( flags & GET_BUDDY_FIRST )
+			{
+				*s = '/';
+				g_free( jid );
+				return bud;
+			}
+			
 			/* Is this one of those no-resource buddies? */
 			if( bud->resource == NULL )
 			{
+				*s = '/';
 				g_free( jid );
 				return NULL;
 			}
-			else
-			{
-				/* See if there's an exact match. */
-				for( ; bud; bud = bud->next )
-					if( g_strcasecmp( bud->resource, s + 1 ) == 0 )
-						break;
-			}
+			
+			/* See if there's an exact match. */
+			for( ; bud; bud = bud->next )
+				if( g_strcasecmp( bud->resource, s + 1 ) == 0 )
+					break;
 		}
 		else
@@ -413,4 +416,6 @@
 			   is done to handle conferences properly. */
 			none_found = 1;
+			/* TODO(wilmer): Find out what I was thinking when I
+			   wrote this??? And then fix it. This makes me sad... */
 		}
 		
@@ -442,4 +447,7 @@
 		else if( ( bud->resource == NULL || bud->next == NULL ) )
 			/* No need for selection if there's only one option. */
+			return bud;
+		else if( flags & GET_BUDDY_FIRST )
+			/* Looks like the caller doesn't care about details. */
 			return bud;
 		
Index: protocols/jabber/presence.c
===================================================================
--- protocols/jabber/presence.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/jabber/presence.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -29,7 +29,7 @@
 	char *from = xt_find_attr( node, "from" );
 	char *type = xt_find_attr( node, "type" );	/* NULL should mean the person is online. */
-	struct xt_node *c;
-	struct jabber_buddy *bud;
-	int is_chat = 0, is_away = 0;
+	struct xt_node *c, *cap;
+	struct jabber_buddy *bud, *send_presence = NULL;
+	int is_chat = 0;
 	char *s;
 	
@@ -63,6 +63,4 @@
 		{
 			bud->away_state = (void*) jabber_away_state_by_code( c->text );
-			if( strcmp( c->text, "chat" ) != 0 )
-				is_away = OPT_AWAY;
 		}
 		else
@@ -79,10 +77,28 @@
 			bud->priority = 0;
 		
+		if( bud && ( cap = xt_find_node( node->children, "c" ) ) &&
+		    ( s = xt_find_attr( cap, "xmlns" ) ) && strcmp( s, XMLNS_CAPS ) == 0 )
+		{
+			/* This <presence> stanza includes an XEP-0115
+			   capabilities part. Not too interesting, but we can
+			   see if it has an ext= attribute. */
+			s = xt_find_attr( cap, "ext" );
+			if( s && ( strstr( s, "cstates" ) || strstr( s, "chatstate" ) ) )
+				bud->flags |= JBFLAG_DOES_XEP85;
+			
+			/* This field can contain more information like xhtml
+			   support, but we don't support that ourselves.
+			   Officially the ext= tag was deprecated, but enough
+			   clients do send it.
+			   
+			   (I'm aware that this is not the right way to use
+			   this field.) See for an explanation of ext=:
+			   http://www.xmpp.org/extensions/attic/xep-0115-1.3.html*/
+		}
+		
 		if( is_chat )
 			jabber_chat_pkt_presence( ic, bud, node );
-		else if( bud == jabber_buddy_by_jid( ic, bud->bare_jid, 0 ) )
-			imcb_buddy_status( ic, bud->bare_jid, OPT_LOGGED_IN | is_away,
-			                   ( is_away && bud->away_state ) ? bud->away_state->full_name : NULL,
-			                   bud->away_message );
+		else
+			send_presence = jabber_buddy_by_jid( ic, bud->bare_jid, 0 );
 	}
 	else if( strcmp( type, "unavailable" ) == 0 )
@@ -119,15 +135,5 @@
 			/* If another resource is still available, send its presence
 			   information. */
-			if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) )
-			{
-				if( bud->away_state && ( *bud->away_state->code == 0 ||
-				    strcmp( bud->away_state->code, "chat" ) == 0 ) )
-					is_away = OPT_AWAY;
-				
-				imcb_buddy_status( ic, bud->bare_jid, OPT_LOGGED_IN | is_away,
-				                   ( is_away && bud->away_state ) ? bud->away_state->full_name : NULL,
-				                   bud->away_message );
-			}
-			else
+			if( ( send_presence = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL )
 			{
 				/* Otherwise, count him/her as offline now. */
@@ -177,4 +183,18 @@
 		} */
 	}
+
+	if( send_presence )
+	{
+		int is_away = 0;
+
+		if( send_presence->away_state && !( *send_presence->away_state->code == 0 ||
+		    strcmp( send_presence->away_state->code, "chat" ) == 0 ) )
+			is_away = OPT_AWAY;
+
+		imcb_buddy_status( ic, send_presence->bare_jid, OPT_LOGGED_IN | is_away,
+		                   ( is_away && send_presence->away_state ) ?
+		                   send_presence->away_state->full_name : NULL,
+		                   send_presence->away_message );
+	}
 	
 	return XT_HANDLED;
@@ -186,5 +206,5 @@
 {
 	struct jabber_data *jd = ic->proto_data;
-	struct xt_node *node;
+	struct xt_node *node, *cap;
 	char *show = jd->away_state->code;
 	char *status = jd->away_message;
@@ -199,4 +219,14 @@
 		xt_add_child( node, xt_new_node( "status", status, NULL ) );
 	
+	/* This makes the packet slightly bigger, but clients interested in
+	   capabilities can now cache the discovery info. This reduces the
+	   usual post-login iq-flood. See XEP-0115. At least libpurple and
+	   Trillian seem to do this right. */
+	cap = xt_new_node( "c", NULL, NULL );
+	xt_add_attr( cap, "xmlns", XMLNS_CAPS );
+	xt_add_attr( cap, "node", "http://bitlbee.org/xmpp/caps" );
+	xt_add_attr( cap, "ver", BITLBEE_VERSION ); /* The XEP wants this hashed, but nobody's doing that. */
+	xt_add_child( node, cap );
+	
 	st = jabber_write_packet( ic, node );
 	
Index: protocols/jabber/sasl.c
===================================================================
--- protocols/jabber/sasl.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/jabber/sasl.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -21,4 +21,6 @@
 *                                                                           *
 \***************************************************************************/
+
+#include <ctype.h>
 
 #include "jabber.h"
@@ -107,9 +109,14 @@
 }
 
-static char *sasl_get_part( char *data, char *field )
+/* Non-static function, but not mentioned in jabber.h because it's for internal
+   use, just that the unittest should be able to reach it... */
+char *sasl_get_part( char *data, char *field )
 {
 	int i, len;
 	
 	len = strlen( field );
+	
+	while( isspace( *data ) || *data == ',' )
+		data ++;
 	
 	if( g_strncasecmp( data, field, len ) == 0 && data[len] == '=' )
@@ -129,11 +136,17 @@
 			}
 			
-			/* If we got a comma, we got a new field. Check it. */
-			if( data[i] == ',' &&
-			    g_strncasecmp( data + i + 1, field, len ) == 0 &&
-			    data[i+len+1] == '=' )
+			/* If we got a comma, we got a new field. Check it,
+			   find the next key after it. */
+			if( data[i] == ',' )
 			{
-				i += len + 2;
-				break;
+				while( isspace( data[i] ) || data[i] == ',' )
+					i ++;
+				
+				if( g_strncasecmp( data + i, field, len ) == 0 &&
+				    data[i+len] == '=' )
+				{
+					i += len + 1;
+					break;
+				}
 			}
 		}
Index: protocols/msn/msn.h
===================================================================
--- protocols/msn/msn.h	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/msn/msn.h	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -29,9 +29,7 @@
 #define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r"
 
-#ifdef _WIN32
-#define debug 
+#ifdef DEBUG
+#define debug( text... ) imcb_log( ic, text );
 #else
-#define debug( text... ) irc_usermsg( IRC, text );
-#undef debug
 #define debug( text... )
 #endif
@@ -66,6 +64,8 @@
 	GSList *msgq;
 	GSList *switchboards;
+	int sb_failures;
+	time_t first_sb_failure;
+	
 	const struct msn_away_state *away_state;
-	
 	int buddycount;
 	int groupcount;
Index: protocols/msn/ns.c
===================================================================
--- protocols/msn/ns.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/msn/ns.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -584,5 +584,5 @@
 	else
 	{
-		debug( "Received unknown command from main server: %s", cmd[0] );
+		/* debug( "Received unknown command from main server: %s", cmd[0] ); */
 	}
 	
Index: protocols/msn/sb.c
===================================================================
--- protocols/msn/sb.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/msn/sb.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -277,8 +277,49 @@
 {
 	struct msn_switchboard *sb = data;
+	struct im_connection *ic = sb->ic;
+	struct msn_data *md = ic->proto_data;
 	
 	if( msn_handler( sb->handler ) == -1 )
 	{
+		time_t now = time( NULL );
+		
+		if( now - md->first_sb_failure > 600 )
+		{
+			/* It's not really the first one, but the start of this "series".
+			   With this, the warning below will be shown only if this happens
+			   at least three times in ten minutes. This algorithm isn't
+			   perfect, but for this purpose it will do. */
+			md->first_sb_failure = now;
+			md->sb_failures = 0;
+		}
+		
 		debug( "Error: Switchboard died" );
+		if( ++ md->sb_failures >= 3 )
+			imcb_log( ic, "Warning: Many switchboard failures on MSN connection. "
+			              "There might be problems delivering your messages." );
+		
+		if( sb->msgq != NULL )
+		{
+			char buf[1024];
+			
+			if( md->msgq == NULL )
+			{
+				md->msgq = sb->msgq;
+			}
+			else
+			{
+				GSList *l;
+				
+				for( l = md->msgq; l->next; l = l->next );
+				l->next = sb->msgq;
+			}
+			sb->msgq = NULL;
+			
+			debug( "Moved queued messages back to the main queue, creating a new switchboard to retry." );
+			g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
+			if( !msn_write( ic, buf, strlen( buf ) ) )
+				return FALSE;
+		}
+		
 		msn_sb_destroy( sb );
 		
@@ -286,5 +327,7 @@
 	}
 	else
+	{
 		return TRUE;
+	}
 }
 
@@ -551,5 +594,5 @@
 	else
 	{
-		debug( "Received unknown command from switchboard server: %s", cmd[0] );
+		/* debug( "Received unknown command from switchboard server: %s", cmd[0] ); */
 	}
 	
Index: protocols/nogaim.c
===================================================================
--- protocols/nogaim.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/nogaim.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -625,5 +625,5 @@
 }
 
-void imcb_buddy_msg( struct im_connection *ic, char *handle, char *msg, u_int32_t flags, time_t sent_at )
+void imcb_buddy_msg( struct im_connection *ic, char *handle, char *msg, uint32_t flags, time_t sent_at )
 {
 	irc_t *irc = ic->irc;
@@ -676,5 +676,5 @@
 }
 
-void imcb_buddy_typing( struct im_connection *ic, char *handle, u_int32_t flags )
+void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags )
 {
 	user_t *u;
@@ -690,4 +690,29 @@
 		irc_privmsg( ic->irc, u, "PRIVMSG", ic->irc->nick, NULL, buf );
 	}
+}
+
+struct groupchat *imcb_chat_new( struct im_connection *ic, char *handle )
+{
+	struct groupchat *c;
+	
+	/* This one just creates the conversation structure, user won't see anything yet */
+	
+	if( ic->groupchats )
+	{
+		for( c = ic->groupchats; c->next; c = c->next );
+		c = c->next = g_new0( struct groupchat, 1 );
+	}
+	else
+		ic->groupchats = c = g_new0( struct groupchat, 1 );
+	
+	c->ic = ic;
+	c->title = g_strdup( handle );
+	c->channel = g_strdup_printf( "&chat_%03d", ic->irc->c_id++ );
+	c->topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title );
+	
+	if( set_getbool( &ic->irc->set, "debug" ) )
+		imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle );
+	
+	return c;
 }
 
@@ -728,9 +753,10 @@
 		g_free( c->channel );
 		g_free( c->title );
+		g_free( c->topic );
 		g_free( c );
 	}
 }
 
-void imcb_chat_msg( struct groupchat *c, char *who, char *msg, u_int32_t flags, time_t sent_at )
+void imcb_chat_msg( struct groupchat *c, char *who, char *msg, uint32_t flags, time_t sent_at )
 {
 	struct im_connection *ic = c->ic;
@@ -758,4 +784,22 @@
 	}
 	g_free( wrapped );
+}
+
+void imcb_chat_log( struct groupchat *c, char *format, ... )
+{
+	irc_t *irc = c->ic->irc;
+	va_list params;
+	char *text;
+	user_t *u;
+	
+	va_start( params, format );
+	text = g_strdup_vprintf( format, params );
+	va_end( params );
+	
+	u = user_find( irc, irc->mynick );
+	
+	irc_privmsg( irc, u, "PRIVMSG", c->channel, "System message: ", text );
+	
+	g_free( text );
 }
 
@@ -781,29 +825,4 @@
 	if( c->joined && u )
 		irc_write( ic->irc, ":%s!%s@%s TOPIC %s :%s", u->nick, u->user, u->host, c->channel, topic );
-}
-
-struct groupchat *imcb_chat_new( struct im_connection *ic, char *handle )
-{
-	struct groupchat *c;
-	
-	/* This one just creates the conversation structure, user won't see anything yet */
-	
-	if( ic->groupchats )
-	{
-		for( c = ic->groupchats; c->next; c = c->next );
-		c = c->next = g_new0( struct groupchat, 1 );
-	}
-	else
-		ic->groupchats = c = g_new0( struct groupchat, 1 );
-	
-	c->ic = ic;
-	c->title = g_strdup( handle );
-	c->channel = g_strdup_printf( "&chat_%03d", ic->irc->c_id++ );
-	c->topic = g_strdup_printf( "%s :BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->channel, c->title );
-	
-	if( set_getbool( &ic->irc->set, "debug" ) )
-		imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle );
-	
-	return c;
 }
 
Index: protocols/nogaim.h
===================================================================
--- protocols/nogaim.h	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/nogaim.h	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -68,5 +68,5 @@
 {
 	account_t *acc;
-	u_int32_t flags;
+	uint32_t flags;
 	
 	/* each connection then can have its own protocol-specific data */
@@ -282,6 +282,6 @@
 /* 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, char *handle, char *msg, u_int32_t flags, time_t sent_at );
-G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, char *handle, u_int32_t flags );
+G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, 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 void imcb_clean_handle( struct im_connection *ic, char *handle );
 
@@ -299,5 +299,7 @@
 G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason );
 /* To tell BitlBee 'who' said 'msg' in 'c'. 'flags' and 'sent_at' can be 0. */
-G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, char *who, char *msg, u_int32_t flags, time_t sent_at );
+G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, char *who, char *msg, uint32_t flags, time_t sent_at );
+/* System messages specific to a groupchat, so they can be displayed in the right context. */
+G_MODULE_EXPORT void imcb_chat_log( struct groupchat *c, char *format, ... ) G_GNUC_PRINTF( 2, 3 );
 /* To tell BitlBee 'who' changed the topic of 'c' to 'topic'. */
 G_MODULE_EXPORT void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at );
Index: protocols/oscar/oscar.c
===================================================================
--- protocols/oscar/oscar.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/oscar/oscar.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -91,4 +91,6 @@
 	gboolean icq;
 	GSList *evilhack;
+	
+	GHashTable *ips;
 
 	struct {
@@ -356,7 +358,8 @@
 	struct oscar_data *odata = ic->proto_data = g_new0(struct oscar_data, 1);
 
-	if (!isdigit(acc->user[0])) {
+	if (isdigit(acc->user[0]))
+		odata->icq = TRUE;
+	else
 		ic->flags |= OPT_DOES_HTML;
-	}
 
 	sess = g_new0(aim_session_t, 1);
@@ -411,4 +414,6 @@
 		g_free(cr);
 	}
+	if (odata->ips)
+		g_hash_table_destroy(odata->ips);
 	if (odata->email)
 		g_free(odata->email);
@@ -987,4 +992,14 @@
 		signon = time(NULL) - info->sessionlen;
 
+	if (info->present & AIM_USERINFO_PRESENT_ICQIPADDR) {
+		uint32_t *uin = g_new0(uint32_t, 1);
+		
+		if (od->ips == NULL)
+			od->ips = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, NULL);
+		
+		if (sscanf(info->sn, "%d", uin) == 1)
+			g_hash_table_insert(od->ips, uin, (gpointer) (long) info->icqinfo.ipaddr);
+	}
+
 	tmp = g_strdup(normalize(ic->acc->user));
 	if (!strcmp(tmp, normalize(info->sn)))
@@ -1051,10 +1066,12 @@
 		g_snprintf(tmp, BUF_LONG, "%s", args->msg);
 	} else {
-		int i;
+		aim_mpmsg_section_t *part;
 		
 		*tmp = 0;
-		for (i = 0; i < args->mpmsg.numparts; i ++) {
-			g_strlcat(tmp, (char*) args->mpmsg.parts[i].data, BUF_LONG);
-			g_strlcat(tmp, "\n", BUF_LONG);
+		for (part = args->mpmsg.parts; part; part = part->next) {
+			if (part->data) {
+				g_strlcat(tmp, (char*) part->data, BUF_LONG);
+				g_strlcat(tmp, "\n", BUF_LONG);
+			}
 		}
 	}
@@ -2219,85 +2236,92 @@
 static int gaim_icqinfo(aim_session_t *sess, aim_frame_t *fr, ...)
 {
-        struct im_connection *ic = sess->aux_data;
-        gchar who[16];
-        GString *str;
-        va_list ap;
-        struct aim_icq_info *info;
-
-        va_start(ap, fr);
-        info = va_arg(ap, struct aim_icq_info *);
-        va_end(ap);
-
-        if (!info->uin)
-                return 0;
-
-        str = g_string_sized_new(100);
-        g_snprintf(who, sizeof(who), "%u", info->uin);
-
-        g_string_sprintfa(str, "%s: %s - %s: %s", _("UIN"), who, _("Nick"), 
-				info->nick ? info->nick : "-");
-        info_string_append(str, "\n", _("First Name"), info->first);
-        info_string_append(str, "\n", _("Last Name"), info->last);
-		info_string_append(str, "\n", _("Email Address"), info->email);
-        if (info->numaddresses && info->email2) {
-                int i;
-                for (i = 0; i < info->numaddresses; i++) {
-					info_string_append(str, "\n", _("Email Address"), info->email2[i]);
-                }
-        }
-        info_string_append(str, "\n", _("Mobile Phone"), info->mobile);
-        if (info->gender != 0)
-        	info_string_append(str, "\n", _("Gender"), info->gender==1 ? _("Female") : _("Male"));
-        if (info->birthyear || info->birthmonth || info->birthday) {
-                char date[30];
-                struct tm tm;
-                tm.tm_mday = (int)info->birthday;
-                tm.tm_mon = (int)info->birthmonth-1;
-                tm.tm_year = (int)info->birthyear%100;
-                strftime(date, sizeof(date), "%Y-%m-%d", &tm);
-                info_string_append(str, "\n", _("Birthday"), date);
-        }
-        if (info->age) {
-                char age[5];
-                g_snprintf(age, sizeof(age), "%hhd", info->age);
-                info_string_append(str, "\n", _("Age"), age);
-        }
-		info_string_append(str, "\n", _("Personal Web Page"), info->personalwebpage);
-        if (info->info && info->info[0]) {
-                g_string_sprintfa(str, "\n%s:\n%s\n%s", _("Additional Information"), 
-						info->info, _("End of Additional Information"));
-        }
-        g_string_sprintfa(str, "\n");
-        if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) {
-                g_string_sprintfa(str, "%s:", _("Home Address"));
-                info_string_append(str, "\n", _("Address"), info->homeaddr);
-                info_string_append(str, "\n", _("City"), info->homecity);
-                info_string_append(str, "\n", _("State"), info->homestate); 
-				info_string_append(str, "\n", _("Zip Code"), info->homezip);
-                g_string_sprintfa(str, "\n");
-        }
-        if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) {
-                g_string_sprintfa(str, "%s:", _("Work Address"));
-                info_string_append(str, "\n", _("Address"), info->workaddr);
-                info_string_append(str, "\n", _("City"), info->workcity);
-                info_string_append(str, "\n", _("State"), info->workstate);
-				info_string_append(str, "\n", _("Zip Code"), info->workzip);
-                g_string_sprintfa(str, "\n");
-        }
-        if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) {
-                g_string_sprintfa(str, "%s:", _("Work Information"));
-                info_string_append(str, "\n", _("Company"), info->workcompany);
-                info_string_append(str, "\n", _("Division"), info->workdivision);
-                info_string_append(str, "\n", _("Position"), info->workposition);
-                if (info->workwebpage && info->workwebpage[0]) {
-                        info_string_append(str, "\n", _("Web Page"), info->workwebpage);
-                }
-                g_string_sprintfa(str, "\n");
-        }
-
-		imcb_log(ic, "%s\n%s", _("User Info"), str->str);
-        g_string_free(str, TRUE);
-
-        return 1;
+	struct im_connection *ic = sess->aux_data;
+	struct oscar_data *od = ic->proto_data;
+	gchar who[16];
+	GString *str;
+	va_list ap;
+	struct aim_icq_info *info;
+	uint32_t ip;
+
+	va_start(ap, fr);
+	info = va_arg(ap, struct aim_icq_info *);
+	va_end(ap);
+
+	if (!info->uin)
+		return 0;
+
+	str = g_string_sized_new(512);
+	g_snprintf(who, sizeof(who), "%u", info->uin);
+
+	g_string_printf(str, "%s: %s - %s: %s", _("UIN"), who, _("Nick"), 
+	info->nick ? info->nick : "-");
+	g_string_append_printf(str, "\n%s: %s", _("First Name"), info->first);
+	g_string_append_printf(str, "\n%s: %s", _("Last Name"), info->last);
+	g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email);
+	if (info->numaddresses && info->email2) {
+		int i;
+		for (i = 0; i < info->numaddresses; i++) {
+			g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email2[i]);
+		}
+	}
+	if ((ip = (long) g_hash_table_lookup(od->ips, &info->uin)) != 0) {
+		g_string_append_printf(str, "\n%s: %d.%d.%d.%d", _("Last used IP address"),
+		                       (ip >> 24), (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
+	}
+	g_string_append_printf(str, "\n%s: %s", _("Mobile Phone"), info->mobile);
+	if (info->gender != 0)
+		g_string_append_printf(str, "\n%s: %s", _("Gender"), info->gender==1 ? _("Female") : _("Male"));
+	if (info->birthyear || info->birthmonth || info->birthday) {
+		char date[30];
+		struct tm tm;
+		memset(&tm, 0, sizeof(struct tm));
+		tm.tm_mday = (int)info->birthday;
+		tm.tm_mon = (int)info->birthmonth-1;
+		tm.tm_year = (int)info->birthyear%100;
+		strftime(date, sizeof(date), "%Y-%m-%d", &tm);
+		g_string_append_printf(str, "\n%s: %s", _("Birthday"), date);
+	}
+	if (info->age) {
+		char age[5];
+		g_snprintf(age, sizeof(age), "%hhd", info->age);
+		g_string_append_printf(str, "\n%s: %s", _("Age"), age);
+	}
+	g_string_append_printf(str, "\n%s: %s", _("Personal Web Page"), info->personalwebpage);
+	if (info->info && info->info[0]) {
+		g_string_sprintfa(str, "\n%s:\n%s\n%s", _("Additional Information"), 
+		info->info, _("End of Additional Information"));
+	}
+	g_string_append_c(str, '\n');
+	if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) {
+		g_string_append_printf(str, "%s:", _("Home Address"));
+		g_string_append_printf(str, "\n%s: %s", _("Address"), info->homeaddr);
+		g_string_append_printf(str, "\n%s: %s", _("City"), info->homecity);
+		g_string_append_printf(str, "\n%s: %s", _("State"), info->homestate); 
+		g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->homezip);
+		g_string_append_c(str, '\n');
+	}
+	if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) {
+		g_string_append_printf(str, "%s:", _("Work Address"));
+		g_string_append_printf(str, "\n%s: %s", _("Address"), info->workaddr);
+		g_string_append_printf(str, "\n%s: %s", _("City"), info->workcity);
+		g_string_append_printf(str, "\n%s: %s", _("State"), info->workstate);
+		g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->workzip);
+		g_string_append_c(str, '\n');
+	}
+	if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) {
+		g_string_append_printf(str, "%s:", _("Work Information"));
+		g_string_append_printf(str, "\n%s: %s", _("Company"), info->workcompany);
+		g_string_append_printf(str, "\n%s: %s", _("Division"), info->workdivision);
+		g_string_append_printf(str, "\n%s: %s", _("Position"), info->workposition);
+		if (info->workwebpage && info->workwebpage[0]) {
+			g_string_append_printf(str, "\n%s: %s", _("Web Page"), info->workwebpage);
+		}
+		g_string_append_c(str, '\n');
+	}
+
+	imcb_log(ic, "%s\n%s", _("User Info"), str->str);
+	g_string_free(str, TRUE);
+
+	return 1;
 
 }
@@ -2433,5 +2457,5 @@
 		/* User has stopped typing */
 		imcb_buddy_typing(ic, sn, 0);
-	}        
+	}
 	
 	return 1;
Index: protocols/yahoo/yahoo.c
===================================================================
--- protocols/yahoo/yahoo.c	(revision fc5cf88448d4337d1a5fde418df1c6206a9b0ea2)
+++ protocols/yahoo/yahoo.c	(revision ca605509d0b49e6012d10ae5d1553ced007e6ce7)
@@ -624,8 +624,12 @@
 {
 	struct im_connection *ic = byahoo_get_ic_by_id( id );
-	char *m = byahoo_strip( msg );
-	
-	imcb_buddy_msg( ic, (char*) who, (char*) m, 0, 0 );
-	g_free( m );
+	char *m;
+	
+	if( msg )
+	{
+		m = byahoo_strip( msg );
+		imcb_buddy_msg( ic, (char*) who, (char*) m, 0, 0 );
+		g_free( m );
+	}
 }
 
