Index: bitlbee.c
===================================================================
--- bitlbee.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ bitlbee.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -70,4 +70,5 @@
 #ifdef IPV6
 	memset( &listen_addr6, 0, sizeof( listen_addr6 ) );
+	memset( &listen_addr, 0, sizeof( listen_addr ) );
 	listen_addr6.sin6_family = AF_INET6;
 	listen_addr6.sin6_port = htons( global.conf->port );
@@ -77,5 +78,4 @@
 		use_ipv6 = 0;
 #endif
-		memset( &listen_addr, 0, sizeof( listen_addr ) );
 		listen_addr.sin_family = AF_INET;
 		listen_addr.sin_port = htons( global.conf->port );
@@ -125,5 +125,5 @@
 			exit( 0 );
 		
-		chdir( "/" );
+//		chdir( "/" );
 		
 		/* Sometimes std* are already closed (for example when we're in a RESTARTed
Index: doc/user-guide/misc.xml
===================================================================
--- doc/user-guide/misc.xml	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ doc/user-guide/misc.xml	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -81,4 +81,8 @@
 
 <para>
+Some protocols (like Jabber) also support named groupchats. BitlBee now supports these too. You can use the <emphasis>join_chat</emphasis> command to join them. See <emphasis>help join_chat</emphasis> for more information.
+</para>
+
+<para>
 This is all you'll probably need to know. If you have any problems, please read <emphasis>help groupchats3</emphasis>.
 </para>
Index: irc.c
===================================================================
--- irc.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ irc.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -659,5 +659,5 @@
 		}
 	}
-	else if( ( c = chat_by_channel( channel ) ) )
+	else if( ( c = irc_chat_by_channel( irc, channel ) ) )
 	{
 		GList *l;
@@ -812,5 +812,5 @@
 	else
 	{
-		struct groupchat *c = chat_by_channel( channel );
+		struct groupchat *c = irc_chat_by_channel( irc, channel );
 		
 		if( c )
@@ -950,5 +950,5 @@
 	if( *nick == '#' || *nick == '&' )
 	{
-		if( !( c = chat_by_channel( nick ) ) )
+		if( !( c = irc_chat_by_channel( irc, nick ) ) )
 		{
 			irc_reply( irc, 403, "%s :Channel does not exist", nick );
@@ -1216,2 +1216,26 @@
 	return TRUE;
 }
+
+struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel )
+{
+	struct groupchat *c;
+	account_t *a;
+	
+	/* This finds the connection which has a conversation which belongs to this channel */
+	for( a = irc->accounts; a; a = a->next )
+	{
+		if( a->ic == NULL )
+			continue;
+		
+		c = a->ic->groupchats;
+		while( c )
+		{
+			if( c->channel && g_strcasecmp( c->channel, channel ) == 0 )
+				return c;
+			
+			c = c->next;
+		}
+	}
+	
+	return NULL;
+}
Index: irc.h
===================================================================
--- irc.h	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ irc.h	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -140,4 +140,5 @@
 
 void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags );
+struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel );
 
 #endif
Index: irc_commands.c
===================================================================
--- irc_commands.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ irc_commands.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -144,5 +144,5 @@
 		irc_join( irc, u, irc->channel );
 	}
-	else if( ( c = chat_by_channel( cmd[1] ) ) )
+	else if( ( c = irc_chat_by_channel( irc, cmd[1] ) ) )
 	{
 		user_t *u = user_find( irc, irc->nick );
@@ -201,5 +201,5 @@
 {
 	char *nick = cmd[1], *channel = cmd[2];
-	struct groupchat *c = chat_by_channel( channel );
+	struct groupchat *c = irc_chat_by_channel( irc, channel );
 	user_t *u = user_find( irc, nick );
 	
@@ -287,5 +287,5 @@
 			u = u->next;
 		}
-	else if( ( c = chat_by_channel( channel ) ) )
+	else if( ( c = irc_chat_by_channel( irc, channel ) ) )
 		for( l = c->in_room; l; l = l->next )
 		{
Index: nick.c
===================================================================
--- nick.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ nick.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -57,5 +57,4 @@
 	static char nick[MAX_NICK_LENGTH+1];
 	char *store_handle, *found_nick;
-	int inf_protection = 256;
 	
 	memset( nick, 0, MAX_NICK_LENGTH + 1 );
@@ -82,4 +81,15 @@
 	}
 	g_free( store_handle );
+	
+	/* Make sure the nick doesn't collide with an existing one by adding
+	   underscores and that kind of stuff, if necessary. */
+	nick_dedupe( acc, handle, nick );
+	
+	return nick;
+}
+
+void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] )
+{
+	int inf_protection = 256;
 	
 	/* Now, find out if the nick is already in use at the moment, and make
@@ -120,6 +130,4 @@
 		}
 	}
-	
-	return nick;
 }
 
Index: nick.h
===================================================================
--- nick.h	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ nick.h	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -26,4 +26,5 @@
 void nick_set( account_t *acc, const char *handle, const char *nick );
 char *nick_get( account_t *acc, const char *handle );
+void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] );
 int nick_saved( account_t *acc, const char *handle );
 void nick_del( account_t *acc, const char *handle );
Index: protocols/jabber/Makefile
===================================================================
--- protocols/jabber/Makefile	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/Makefile	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -10,5 +10,5 @@
 
 # [SH] Program variables
-objects = io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o
+objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o
 
 CFLAGS += -Wall
Index: protocols/jabber/conference.c
===================================================================
--- protocols/jabber/conference.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
+++ protocols/jabber/conference.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -0,0 +1,246 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Jabber module - Conference rooms                                         *
+*                                                                           *
+*  Copyright 2007 Wilmer van der Gaast <wilmer@gaast.net>                   *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#include "jabber.h"
+
+struct groupchat *jabber_chat_join( struct im_connection *ic, char *room, char *nick, char *password )
+{
+	struct jabber_chat *jc;
+	struct xt_node *node;
+	struct groupchat *c;
+	char *roomjid;
+	
+	roomjid = g_strdup_printf( "%s/%s", room, nick );
+	node = xt_new_node( "x", NULL, NULL );
+	xt_add_attr( node, "xmlns", XMLNS_MUC );
+	node = jabber_make_packet( "presence", NULL, roomjid, node );
+	
+	if( !jabber_write_packet( ic, node ) )
+	{
+		g_free( roomjid );
+		xt_free_node( node );
+		return NULL;
+	}
+	xt_free_node( node );
+	
+	jc = g_new0( struct jabber_chat, 1 );
+	jc->name = jabber_normalize( room );
+	
+	if( ( jc->me = jabber_buddy_add( ic, roomjid ) ) == NULL )
+	{
+		g_free( roomjid );
+		g_free( jc->name );
+		g_free( jc );
+		return NULL;
+	}
+	
+	/* roomjid isn't normalized yet, and we need an original version
+	   of the nick to send a proper presence update. */
+	jc->my_full_jid = roomjid;
+	
+	c = imcb_chat_new( ic, room );
+	c->data = jc;
+	
+	return c;
+}
+
+void jabber_chat_free( struct groupchat *c )
+{
+	struct jabber_chat *jc = c->data;
+	
+	jabber_buddy_remove_bare( c->ic, jc->name );
+	
+	g_free( jc->my_full_jid );
+	g_free( jc->name );
+	g_free( jc );
+	
+	imcb_chat_free( c );
+}
+
+int jabber_chat_msg( struct groupchat *c, char *message, int flags )
+{
+	struct im_connection *ic = c->ic;
+	struct jabber_chat *jc = c->data;
+	struct xt_node *node;
+	
+	node = xt_new_node( "body", message, NULL );
+	node = jabber_make_packet( "message", "groupchat", jc->name, node );
+	
+	if( !jabber_write_packet( ic, node ) )
+	{
+		xt_free_node( node );
+		return 0;
+	}
+	xt_free_node( node );
+	
+	return 1;
+}
+
+int jabber_chat_leave( struct groupchat *c, const char *reason )
+{
+	struct im_connection *ic = c->ic;
+	struct jabber_chat *jc = c->data;
+	struct xt_node *node;
+	
+	node = xt_new_node( "x", NULL, NULL );
+	xt_add_attr( node, "xmlns", XMLNS_MUC );
+	node = jabber_make_packet( "presence", "unavailable", jc->my_full_jid, node );
+	
+	if( !jabber_write_packet( ic, node ) )
+	{
+		xt_free_node( node );
+		return 0;
+	}
+	xt_free_node( node );
+	
+	return 1;
+}
+
+/* Not really the same syntax as the normal pkt_ functions, but this isn't
+   called by the xmltree parser directly and this way I can add some extra
+   parameters so we won't have to repeat too many things done by the caller
+   already. */
+void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node )
+{
+	struct groupchat *chat;
+	struct xt_node *c;
+	char *type = xt_find_attr( node, "type" );
+	struct jabber_chat *jc;
+	char *s;
+	
+	if( ( chat = jabber_chat_by_name( ic, bud->bare_jid ) ) == NULL )
+	{
+		/* How could this happen?? We could do kill( self, 11 )
+		   now or just wait for the OS to do it. :-) */
+		return;
+	}
+	
+	jc = chat->data;
+	
+	if( type == NULL && !( bud->flags & JBFLAG_IS_CHATROOM ) )
+	{
+		bud->flags |= JBFLAG_IS_CHATROOM;
+		/* If this one wasn't set yet, this buddy just joined the chat.
+		   Slightly hackish way of finding out eh? ;-) */
+		
+		/* This is pretty messy... Here it sets ext_jid to the real
+		   JID of the participant. Works for non-anonymized channels.
+		   Might break if someone joins a chat twice, though. */
+		for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+			if( ( s = xt_find_attr( c, "xmlns" ) ) &&
+			    ( strcmp( s, XMLNS_MUC_USER ) == 0 ) )
+			{
+				c = xt_find_node( c->children, "item" );
+				if( ( s = xt_find_attr( c, "jid" ) ) )
+				{
+					/* Yay, found what we need. :-) */
+					bud->ext_jid = jabber_normalize( s );
+					break;
+				}
+			}
+		
+		/* Make up some other handle, if necessary. */
+		if( bud->ext_jid == NULL )
+		{
+			if( bud == jc->me )
+			{
+				bud->ext_jid = jabber_normalize( ic->acc->user );
+			}
+			else
+			{
+				int i;
+				
+				/* Don't want the nick to be at the end, so let's
+				   think of some slightly different notation to use
+				   for anonymous groupchat participants in BitlBee. */
+				bud->ext_jid = g_strdup_printf( "%s=%s", bud->resource, bud->bare_jid );
+				
+				/* And strip any unwanted characters. */
+				for( i = 0; bud->resource[i]; i ++ )
+					if( bud->ext_jid[i] == '=' || bud->ext_jid[i] == '@' )
+						bud->ext_jid[i] = '_';
+				
+				/* Some program-specific restrictions. */
+				imcb_clean_handle( ic, bud->ext_jid );
+			}
+			bud->flags |= JBFLAG_IS_ANONYMOUS;
+		}
+		
+		if( bud != jc->me )
+		{
+			imcb_add_buddy( ic, bud->ext_jid, NULL );
+			imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource );
+		}
+		
+		s = strchr( bud->ext_jid, '/' );
+		if( s ) *s = 0; /* Should NEVER be NULL, but who knows... */
+		imcb_chat_add_buddy( chat, bud->ext_jid );
+		if( s ) *s = '/';
+	}
+	else if( type ) /* type can only be NULL or "unavailable" in this function */
+	{
+		s = strchr( bud->ext_jid, '/' );
+		if( s ) *s = 0;
+		imcb_chat_remove_buddy( chat, bud->ext_jid, NULL );
+		if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS )
+			imcb_remove_buddy( ic, bud->ext_jid, NULL );
+		if( s ) *s = '/';
+		
+		if( bud == jc->me )
+			jabber_chat_free( chat );
+	}
+}
+
+void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node )
+{
+	struct xt_node *body = xt_find_node( node->children, "body" );
+	struct groupchat *chat;
+	char *s;
+	
+	if( bud == NULL )
+	{
+		s = xt_find_attr( node, "from" ); /* pkt_message() already NULL-checked this one. */
+		if( strchr( s, '/' ) == 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" );
+		else
+			/* This, however, isn't fine! */
+			imcb_log( ic, "Groupchat message from unknown participant %s: %s", s, body ? body->text : "NULL" );
+		
+		return;
+	}
+	else if( ( chat = jabber_chat_by_name( ic, bud->bare_jid ) ) == NULL )
+	{
+		/* How could this happen?? We could do kill( self, 11 )
+		   now or just wait for the OS to do it. :-) */
+		return;
+	}
+	
+	if( body && body->text_len > 0 )
+	{
+		s = strchr( bud->ext_jid, '/' );
+		if( s ) *s = 0;
+		imcb_chat_msg( chat, bud->ext_jid, body->text, 0, jabber_get_timestamp( node ) );
+		if( s ) *s = '/';
+	}
+}
Index: protocols/jabber/io.c
===================================================================
--- protocols/jabber/io.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/io.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -44,4 +44,13 @@
 	struct jabber_data *jd = ic->proto_data;
 	gboolean ret;
+	
+	if( jd->flags & JFLAG_XMLCONSOLE )
+	{
+		char *msg;
+		
+		msg = g_strdup_printf( "TX: %s", buf );
+		imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
+		g_free( msg );
+	}
 	
 	if( jd->tx_len == 0 )
@@ -427,32 +436,15 @@
 {
 	struct im_connection *ic = data;
-	struct xt_node *c;
-	char *s, *type = NULL, *text = NULL;
 	int allow_reconnect = TRUE;
-	
-	for( c = node->children; c; c = c->next )
-	{
-		if( !( s = xt_find_attr( c, "xmlns" ) ) ||
-		    strcmp( s, XMLNS_STREAM_ERROR ) != 0 )
-			continue;
-		
-		if( strcmp( c->name, "text" ) != 0 )
-		{
-			type = c->name;
-		}
-		/* Only use the text if it doesn't have an xml:lang attribute,
-		   if it's empty or if it's set to something English. */
-		else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
-		         !*s || strncmp( s, "en", 2 ) == 0 )
-		{
-			text = c->text;
-		}
-	}
+	struct jabber_error *err;
+	
+	err = jabber_error_parse( node, XMLNS_STREAM_ERROR );
 	
 	/* Tssk... */
-	if( type == NULL )
+	if( err->code == NULL )
 	{
 		imcb_error( ic, "Unknown stream error reported by server" );
 		imc_logout( ic, allow_reconnect );
+		jabber_error_free( err );
 		return XT_ABORT;
 	}
@@ -461,5 +453,5 @@
 	   should turn off auto-reconnect to make sure we won't get some nasty
 	   infinite loop! */
-	if( strcmp( type, "conflict" ) == 0 )
+	if( strcmp( err->code, "conflict" ) == 0 )
 	{
 		imcb_error( ic, "Account and resource used from a different location" );
@@ -468,7 +460,9 @@
 	else
 	{
-		imcb_error( ic, "Stream error: %s%s%s", type, text ? ": " : "", text ? text : "" );
-	}
-	
+		imcb_error( ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "",
+		            err->text ? err->text : "" );
+	}
+	
+	jabber_error_free( err );
 	imc_logout( ic, allow_reconnect );
 	
@@ -484,5 +478,25 @@
 }
 
+static xt_status jabber_xmlconsole( struct xt_node *node, gpointer data )
+{
+	struct im_connection *ic = data;
+	struct jabber_data *jd = ic->proto_data;
+	
+	if( jd->flags & JFLAG_XMLCONSOLE )
+	{
+		char *msg, *pkt;
+		
+		pkt = xt_to_string( node );
+		msg = g_strdup_printf( "RX: %s", pkt );
+		imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
+		g_free( msg );
+		g_free( pkt );
+	}
+	
+	return XT_NEXT;
+}
+
 static const struct xt_handler_entry jabber_handlers[] = {
+	{ NULL,                 "stream:stream",        jabber_xmlconsole },
 	{ "stream:stream",      "<root>",               jabber_end_of_stream },
 	{ "message",            "stream:stream",        jabber_pkt_message },
Index: protocols/jabber/iq.c
===================================================================
--- protocols/jabber/iq.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/iq.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -99,4 +99,11 @@
 		else if( strcmp( s, XMLNS_DISCOVER ) == 0 )
 		{
+			const char *features[] = { XMLNS_VERSION,
+			                           XMLNS_TIME,
+			                           XMLNS_CHATSTATES,
+			                           XMLNS_MUC,
+			                           NULL };
+			const char **f;
+			
 			c = xt_new_node( "identity", NULL, NULL );
 			xt_add_attr( c, "category", "client" );
@@ -105,18 +112,10 @@
 			xt_add_child( reply, c );
 			
-			c = xt_new_node( "feature", NULL, NULL );
-			xt_add_attr( c, "var", XMLNS_VERSION );
-			xt_add_child( reply, c );
-			
-			c = xt_new_node( "feature", NULL, NULL );
-			xt_add_attr( c, "var", XMLNS_TIME );
-			xt_add_child( reply, c );
-			
-			c = xt_new_node( "feature", NULL, NULL );
-			xt_add_attr( c, "var", XMLNS_CHATSTATES );
-			xt_add_child( reply, c );
-			
-			/* Later this can be useful to announce things like
-			   MUC support. */
+			for( f = features; *f; f ++ )
+			{
+				c = xt_new_node( "feature", NULL, NULL );
+				xt_add_attr( c, "var", *f );
+				xt_add_child( reply, c );
+			}
 		}
 		else
@@ -373,13 +372,11 @@
 					                           group->text : NULL );
 				
-				imcb_rename_buddy( ic, jid, name );
+				if( name )
+					imcb_rename_buddy( ic, jid, name );
 			}
 			else if( strcmp( sub, "remove" ) == 0 )
 			{
-				/* Don't have any API call for this yet! So let's
-				   just try to handle this as well as we can. */
 				jabber_buddy_remove_bare( ic, jid );
-				imcb_buddy_status( ic, jid, 0, NULL, NULL );
-				/* FIXME! */
+				imcb_remove_buddy( ic, jid, NULL );
 			}
 		}
Index: protocols/jabber/jabber.c
===================================================================
--- protocols/jabber/jabber.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/jabber.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -197,4 +197,7 @@
 	jabber_end_stream( ic );
 	
+	while( ic->groupchats )
+		jabber_chat_free( ic->groupchats );
+	
 	if( jd->r_inpa >= 0 )
 		b_event_remove( jd->r_inpa );
@@ -224,7 +227,14 @@
 	struct jabber_buddy *bud;
 	struct xt_node *node;
+	char *s;
 	int st;
 	
-	bud = jabber_buddy_by_jid( ic, who, 0 );
+	if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+		return jabber_write( ic, message, strlen( message ) );
+	
+	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_name( ic, s + 1 ) )
+		bud = jabber_buddy_by_ext_jid( ic, who, 0 );
+	else
+		bud = jabber_buddy_by_jid( ic, who, 0 );
 	
 	node = xt_new_node( "body", message, NULL );
@@ -311,4 +321,13 @@
 static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
 {
+	struct jabber_data *jd = ic->proto_data;
+	
+	if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+	{
+		jd->flags |= JFLAG_XMLCONSOLE;
+		imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+		return;
+	}
+	
 	if( jabber_add_to_roster( ic, who, NULL ) )
 		presence_send_request( ic, who, "subscribe" );
@@ -317,4 +336,17 @@
 static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
 {
+	struct jabber_data *jd = ic->proto_data;
+	
+	if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+	{
+		jd->flags &= ~JFLAG_XMLCONSOLE;
+		/* Not necessary for now. And for now the code isn't too
+		   happy if the buddy is completely gone right after calling
+		   this function already.
+		imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+		*/
+		return;
+	}
+	
 	/* We should always do this part. Clean up our administration a little bit. */
 	jabber_buddy_remove_bare( ic, who );
@@ -322,4 +354,28 @@
 	if( jabber_remove_from_roster( ic, who ) )
 		presence_send_request( ic, who, "unsubscribe" );
+}
+
+static struct groupchat *jabber_chat_join_( struct im_connection *ic, char *room, char *nick, char *password )
+{
+	if( strchr( room, '@' ) == NULL )
+		imcb_error( ic, "Invalid room name: %s", room );
+	else if( jabber_chat_by_name( ic, room ) )
+		imcb_error( ic, "Already present in chat `%s'", room );
+	else
+		return jabber_chat_join( ic, room, nick, password );
+	
+	return NULL;
+}
+
+static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
+{
+	if( c && message )
+		jabber_chat_msg( c, message, flags );
+}
+
+static void jabber_chat_leave_( struct groupchat *c )
+{
+	if( c )
+		jabber_chat_leave( c, NULL );
 }
 
@@ -388,5 +444,4 @@
 	ret->buddy_msg = jabber_buddy_msg;
 	ret->away_states = jabber_away_states;
-//	ret->get_status_string = jabber_get_status_string;
 	ret->set_away = jabber_set_away;
 //	ret->set_info = jabber_set_info;
@@ -394,8 +449,8 @@
 	ret->add_buddy = jabber_add_buddy;
 	ret->remove_buddy = jabber_remove_buddy;
-//	ret->chat_msg = jabber_chat_msg;
+	ret->chat_msg = jabber_chat_msg_;
 //	ret->chat_invite = jabber_chat_invite;
-//	ret->chat_leave = jabber_chat_leave;
-//	ret->chat_open = jabber_chat_open;
+	ret->chat_leave = jabber_chat_leave_;
+	ret->chat_join = jabber_chat_join_;
 	ret->keepalive = jabber_keepalive;
 	ret->send_typing = jabber_send_typing;
Index: protocols/jabber/jabber.h
===================================================================
--- protocols/jabber/jabber.h	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/jabber.h	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -32,14 +32,15 @@
 typedef enum
 {
-	JFLAG_STREAM_STARTED = 1,	/* Set when we detected the beginning of the stream
+	JFLAG_STREAM_STARTED = 1,       /* Set when we detected the beginning of the stream
 	                                   and want to do auth. */
-	JFLAG_AUTHENTICATED = 2,	/* Set when we're successfully authenticatd. */
-	JFLAG_STREAM_RESTART = 4,	/* Set when we want to restart the stream (after
+	JFLAG_AUTHENTICATED = 2,        /* Set when we're successfully authenticatd. */
+	JFLAG_STREAM_RESTART = 4,       /* Set when we want to restart the stream (after
 	                                   SASL or TLS). */
-	JFLAG_WAIT_SESSION = 8,		/* Set if we sent a <session> tag and need a reply
+	JFLAG_WAIT_SESSION = 8,	        /* Set if we sent a <session> tag and need a reply
 	                                   before we continue. */
-	JFLAG_WAIT_BIND = 16,		/* ... for <bind> tag. */
-	JFLAG_WANT_TYPING = 32,		/* Set if we ever sent a typing notification, this
+	JFLAG_WAIT_BIND = 16,           /* ... for <bind> tag. */
+	JFLAG_WANT_TYPING = 32,         /* Set if we ever sent a typing notification, this
 	                                   activates all XEP-85 related code. */
+	JFLAG_XMLCONSOLE = 64,          /* If the user added an xmlconsole buddy. */
 } jabber_flags_t;
 
@@ -50,9 +51,9 @@
 	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
+	                                   groupchat state info too. */
+	JBFLAG_IS_ANONYMOUS = 8,	/* For anonymous chatrooms, when we don't have
+	                                   have a real JID. */
 } jabber_buddy_flags_t;
-
-#define JABBER_PORT_DEFAULT "5222"
-#define JABBER_PORT_MIN 5220
-#define JABBER_PORT_MAX 5229
 
 struct jabber_data
@@ -101,4 +102,7 @@
 	char *resource;
 	
+	char *ext_jid; /* The JID to use in BitlBee. The real JID if possible, */
+	               /* otherwise something similar to the conference JID. */
+	
 	int priority;
 	struct jabber_away_state *away_state;
@@ -110,4 +114,18 @@
 	struct jabber_buddy *next;
 };
+
+struct jabber_chat
+{
+	int flags;
+	char *name;
+	char *my_full_jid; /* Separate copy because of case sensitivity. */
+	struct jabber_buddy *me;
+};
+
+#define JABBER_XMLCONSOLE_HANDLE "xmlconsole"
+
+#define JABBER_PORT_DEFAULT "5222"
+#define JABBER_PORT_MIN 5220
+#define JABBER_PORT_MAX 5229
 
 /* Prefixes to use for packet IDs (mainly for IQ packets ATM). Usually the
@@ -132,6 +150,9 @@
 #define XMLNS_TIME         "jabber:iq:time"                     /* XEP-0090 */
 #define XMLNS_VCARD        "vcard-temp"                         /* XEP-0054 */
+#define XMLNS_DELAY        "jabber:x:delay"                     /* XEP-0091 */
 #define XMLNS_CHATSTATES   "http://jabber.org/protocol/chatstates"  /* 0085 */
 #define XMLNS_DISCOVER     "http://jabber.org/protocol/disco#info"  /* 0030 */
+#define XMLNS_MUC          "http://jabber.org/protocol/muc"     /* XEP-0045 */
+#define XMLNS_MUC_USER     "http://jabber.org/protocol/muc#user"/* XEP-0045 */
 
 /* iq.c */
@@ -164,16 +185,27 @@
 const struct jabber_away_state *jabber_away_state_by_name( char *name );
 void jabber_buddy_ask( struct im_connection *ic, char *handle );
-char *jabber_normalize( char *orig );
+char *jabber_normalize( const char *orig );
 
 typedef enum
 {
 	GET_BUDDY_CREAT = 1,	/* Try to create it, if necessary. */
-	GET_BUDDY_EXACT = 2,	/* Get an exact message (only makes sense with bare JIDs). */
+	GET_BUDDY_EXACT = 2,	/* Get an exact match (only makes sense with bare JIDs). */
+	GET_BUDDY_FIRST = 4,	/* No selection, simply get the first resource for this JID. */
 } get_buddy_flags_t;
+
+struct jabber_error
+{
+	char *code, *text, *type;
+};
 
 struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid );
 struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags );
+struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags );
 int jabber_buddy_remove( struct im_connection *ic, char *full_jid );
 int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid );
+struct groupchat *jabber_chat_by_name( struct im_connection *ic, const char *name );
+time_t jabber_get_timestamp( struct xt_node *xt );
+struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns );
+void jabber_error_free( struct jabber_error *err );
 
 extern const struct jabber_away_state jabber_away_state_list[];
@@ -193,3 +225,11 @@
 gboolean sasl_supported( struct im_connection *ic );
 
+/* conference.c */
+struct groupchat *jabber_chat_join( struct im_connection *ic, char *room, char *nick, char *password );
+void jabber_chat_free( struct groupchat *c );
+int jabber_chat_msg( struct groupchat *ic, char *message, int flags );
+int jabber_chat_leave( struct groupchat *c, const char *reason );
+void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node );
+void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node );
+
 #endif
Index: protocols/jabber/jabber_util.c
===================================================================
--- protocols/jabber/jabber_util.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/jabber_util.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -48,5 +48,5 @@
 		   call p_s_u() now to send the new prio setting, it would
 		   send the old setting because the set->value gets changed
-		   when the eval returns a non-NULL value.
+		   after the (this) eval returns a non-NULL value.
 		   
 		   So now I can choose between implementing post-set
@@ -129,5 +129,5 @@
 /* Cache a node/packet for later use. Mainly useful for IQ packets if you need
    them when you receive the response. Use this BEFORE sending the packet so
-   it'll get a new id= tag, and do NOT free() the packet after writing it! */
+   it'll get a new id= tag, and do NOT free() the packet after sending it! */
 void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func )
 {
@@ -252,5 +252,5 @@
 
 /* Returns a new string. Don't leak it! */
-char *jabber_normalize( char *orig )
+char *jabber_normalize( const char *orig )
 {
 	int len, i;
@@ -320,4 +320,6 @@
 	else
 	{
+		/* Keep in mind that full_jid currently isn't really
+		   a full JID... */
 		new->bare_jid = g_strdup( full_jid );
 		g_hash_table_insert( jd->buddies, new->bare_jid, new );
@@ -333,5 +335,6 @@
 	{
 		/* Let's waste some more bytes of RAM instead of to make
-		   memory management a total disaster here.. */
+		   memory management a total disaster here. And it saves
+		   me one g_free() call in this function. :-P */
 		new->full_jid = full_jid;
 	}
@@ -353,4 +356,6 @@
 	if( ( s = strchr( jid, '/' ) ) )
 	{
+		int none_found = 0;
+		
 		*s = 0;
 		if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
@@ -370,6 +375,14 @@
 			}
 		}
-		
-		if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid ) )
+		else
+		{
+			/* This hack is there to make sure that O_CREAT will
+			   work if there's already another resouce present
+			   for this JID, even if it's an unknown buddy. This
+			   is done to handle conferences properly. */
+			none_found = 1;
+		}
+		
+		if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && ( imcb_find_buddy( ic, jid ) || !none_found ) )
 		{
 			*s = '/';
@@ -418,4 +431,36 @@
 }
 
+/* I'm keeping a separate ext_jid attribute to save a JID that makes sense
+   to export to BitlBee. This is mainly for groupchats right now. It's
+   a bit of a hack, but I just think having the user nickname in the hostname
+   part of the hostmask doesn't look nice on IRC. Normally you can convert
+   a normal JID to ext_jid by swapping the part before and after the / and
+   replacing the / with a =. But there should be some stripping (@s are
+   allowed in Jabber nicks...). */
+struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
+{
+	struct jabber_buddy *bud;
+	char *s, *jid;
+	
+	jid = jabber_normalize( jid_ );
+	
+	if( ( s = strchr( jid, '=' ) ) == NULL )
+		return NULL;
+	
+	for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next )
+	{
+		/* Hmmm, could happen if not all people in the chat are anonymized? */
+		if( bud->ext_jid == NULL )
+			continue;
+		
+		if( strcmp( bud->ext_jid, jid ) == 0 )
+			break;
+	}
+	
+	g_free( jid );
+	
+	return bud;
+}
+
 /* Remove one specific full JID from our list. Use this when a buddy goes
    off-line (because (s)he can still be online from a different location.
@@ -441,4 +486,5 @@
 			g_hash_table_remove( jd->buddies, bud->bare_jid );
 			g_free( bud->bare_jid );
+			g_free( bud->ext_jid );
 			g_free( bud->full_jid );
 			g_free( bud->away_message );
@@ -473,4 +519,5 @@
 					g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
 				
+				g_free( bi->ext_jid );
 				g_free( bi->full_jid );
 				g_free( bi->away_message );
@@ -495,25 +542,31 @@
    specified bare JID. Use this when removing someone from the contact
    list, for example. */
-int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid_ )
+int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
 {
 	struct jabber_data *jd = ic->proto_data;
 	struct jabber_buddy *bud, *next;
-	char *bare_jid;
-	
-	if( strchr( bare_jid_, '/' ) )
+	
+	if( strchr( bare_jid, '/' ) )
 		return 0;
 	
-	bare_jid = jabber_normalize( bare_jid_ );
-	
-	if( ( bud = g_hash_table_lookup( jd->buddies, bare_jid ) ) )
+	if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) )
 	{
 		/* Most important: Remove the hash reference. We don't know
 		   this buddy anymore. */
 		g_hash_table_remove( jd->buddies, bud->bare_jid );
+		g_free( bud->bare_jid );
 		
 		/* Deallocate the linked list of resources. */
 		while( bud )
 		{
+			/* ext_jid && anonymous means that this buddy is
+			   specific to one groupchat (the one we're
+			   currently cleaning up) so it can be deleted
+			   completely. */
+			if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS )
+				imcb_remove_buddy( ic, bud->ext_jid, NULL );
+			
 			next = bud->next;
+			g_free( bud->ext_jid );
 			g_free( bud->full_jid );
 			g_free( bud->away_message );
@@ -522,11 +575,110 @@
 		}
 		
-		g_free( bare_jid );
 		return 1;
 	}
 	else
 	{
-		g_free( bare_jid );
 		return 0;
 	}
 }
+
+struct groupchat *jabber_chat_by_name( struct im_connection *ic, const char *name )
+{
+	char *normalized = jabber_normalize( name );
+	struct groupchat *ret;
+	struct jabber_chat *jc;
+	
+	for( ret = ic->groupchats; ret; ret = ret->next )
+	{
+		jc = ret->data;
+		if( strcmp( normalized, jc->name ) == 0 )
+			break;
+	}
+	g_free( normalized );
+	
+	return ret;
+}
+
+time_t jabber_get_timestamp( struct xt_node *xt )
+{
+	struct tm tp, utc;
+	struct xt_node *c;
+	time_t res, tres;
+	char *s = NULL;
+	
+	for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+	{
+		if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 )
+			break;
+	}
+	
+	if( !c || !( s = xt_find_attr( c, "stamp" ) ) )
+		return 0;
+	
+	memset( &tp, 0, sizeof( tp ) );
+	if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
+	                                        &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 )
+		return 0;
+	
+	tp.tm_year -= 1900;
+	tp.tm_mon --;
+	tp.tm_isdst = -1; /* GRRRRRRRRRRR */
+	
+	res = mktime( &tp );
+	/* Problem is, mktime() just gave us the GMT timestamp for the
+	   given local time... While the given time WAS NOT local. So
+	   we should fix this now.
+	
+	   Now I could choose between messing with environment variables
+	   (kludgy) or using timegm() (not portable)... Or doing the
+	   following, which I actually prefer... */
+	gmtime_r( &res, &utc );
+	utc.tm_isdst = -1; /* Once more: GRRRRRRRRRRRRRRRRRR!!! */
+	if( utc.tm_hour == tp.tm_hour && utc.tm_min == tp.tm_min )
+		/* Sweet! We're in UTC right now... */
+		return res;
+	
+	tres = mktime( &utc );
+	res += res - tres;
+	
+	/* Yes, this is a hack. And it will go wrong around DST changes.
+	   BUT this is more likely to be threadsafe than messing with
+	   environment variables, and possibly more portable... */
+	
+	return res;
+}
+
+struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
+{
+	struct jabber_error *err = g_new0( struct jabber_error, 1 );
+	struct xt_node *c;
+	char *s;
+	
+	err->type = xt_find_attr( node, "type" );
+	
+	for( c = node->children; c; c = c->next )
+	{
+		if( !( s = xt_find_attr( c, "xmlns" ) ) ||
+		    strcmp( s, xmlns ) != 0 )
+			continue;
+		
+		if( strcmp( c->name, "text" ) != 0 )
+		{
+			err->code = c->name;
+		}
+		/* Only use the text if it doesn't have an xml:lang attribute,
+		   if it's empty or if it's set to something English. */
+		else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
+		         !*s || strncmp( s, "en", 2 ) == 0 )
+		{
+			err->text = c->text;
+		}
+	}
+	
+	return err;
+}
+
+void jabber_error_free( struct jabber_error *err )
+{
+	g_free( err );
+}
Index: protocols/jabber/message.c
===================================================================
--- protocols/jabber/message.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/message.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -30,5 +30,11 @@
 	char *type = xt_find_attr( node, "type" );
 	struct xt_node *body = xt_find_node( node->children, "body" ), *c;
+	struct jabber_buddy *bud = NULL;
 	char *s;
+	
+	if( !from )
+		return XT_HANDLED; /* Consider this packet corrupted. */
+	
+	bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT );
 	
 	if( type && strcmp( type, "error" ) == 0 )
@@ -36,17 +42,19 @@
 		/* Handle type=error packet. */
 	}
-	else if( type && strcmp( type, "groupchat" ) == 0 )
+	else if( type && from && strcmp( type, "groupchat" ) == 0 )
 	{
-		/* TODO! */
+		jabber_chat_pkt_message( ic, bud, node );
 	}
 	else /* "chat", "normal", "headline", no-type or whatever. Should all be pretty similar. */
 	{
-		struct jabber_buddy *bud = NULL;
 		GString *fullmsg = g_string_new( "" );
 		
 		if( ( s = strchr( from, '/' ) ) )
 		{
-			if( ( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) ) )
+			if( bud )
+			{
 				bud->last_act = time( NULL );
+				from = bud->ext_jid ? : bud->bare_jid;
+			}
 			else
 				*s = 0; /* We need to generate a bare JID now. */
@@ -76,13 +84,18 @@
 		
 		if( fullmsg->len > 0 )
-			imcb_buddy_msg( ic, bud ? bud->bare_jid : from, fullmsg->str, 0, 0 );
+			imcb_buddy_msg( ic, from, fullmsg->str,
+			                0, jabber_get_timestamp( node ) );
 		
 		g_string_free( fullmsg, TRUE );
 		
 		/* Handling of incoming typing notifications. */
-		if( xt_find_node( node->children, "composing" ) )
+		if( bud == NULL )
+		{
+			/* Can't handle these for unknown buddies. */
+		}
+		else if( xt_find_node( node->children, "composing" ) )
 		{
 			bud->flags |= JBFLAG_DOES_XEP85;
-			imcb_buddy_typing( ic, bud ? bud->bare_jid : from, OPT_TYPING );
+			imcb_buddy_typing( ic, from, OPT_TYPING );
 		}
 		/* No need to send a "stopped typing" signal when there's a message. */
@@ -90,10 +103,10 @@
 		{
 			bud->flags |= JBFLAG_DOES_XEP85;
-			imcb_buddy_typing( ic, bud ? bud->bare_jid : from, 0 );
+			imcb_buddy_typing( ic, from, 0 );
 		}
 		else if( xt_find_node( node->children, "paused" ) )
 		{
 			bud->flags |= JBFLAG_DOES_XEP85;
-			imcb_buddy_typing( ic, bud ? bud->bare_jid : from, OPT_THINKING );
+			imcb_buddy_typing( ic, from, OPT_THINKING );
 		}
 		
Index: protocols/jabber/presence.c
===================================================================
--- protocols/jabber/presence.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/presence.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -31,4 +31,5 @@
 	struct xt_node *c;
 	struct jabber_buddy *bud;
+	int is_chat = 0, is_away = 0;
 	char *s;
 	
@@ -36,8 +37,14 @@
 		return XT_HANDLED;
 	
+	if( ( s = strchr( from, '/' ) ) )
+	{
+		*s = 0;
+		if( jabber_chat_by_name( ic, from ) )
+			is_chat = 1;
+		*s = '/';
+	}
+	
 	if( type == NULL )
 	{
-		int is_away = 0;
-		
 		if( !( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT | GET_BUDDY_CREAT ) ) )
 		{
@@ -72,5 +79,7 @@
 			bud->priority = 0;
 		
-		if( bud == jabber_buddy_by_jid( ic, bud->bare_jid, 0 ) )
+		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,
@@ -79,5 +88,5 @@
 	else if( strcmp( type, "unavailable" ) == 0 )
 	{
-		if( jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) == NULL )
+		if( ( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) ) == NULL )
 		{
 			if( set_getbool( &ic->irc->set, "debug" ) )
@@ -86,14 +95,37 @@
 		}
 		
+		/* Handle this before we delete the JID. */
+		if( is_chat )
+		{
+			jabber_chat_pkt_presence( ic, bud, node );
+		}
+		
 		jabber_buddy_remove( ic, from );
 		
-		if( ( s = strchr( from, '/' ) ) )
+		if( is_chat )
+		{
+			/* Nothing else to do for now? */
+		}
+		else if( ( s = strchr( from, '/' ) ) )
 		{
 			*s = 0;
 		
-			/* Only count this as offline if there's no other resource
-			   available anymore. */
-			if( jabber_buddy_by_jid( ic, from, 0 ) == NULL )
+			/* 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
+			{
+				/* Otherwise, count him/her as offline now. */
 				imcb_buddy_status( ic, from, 0, NULL, NULL );
+			}
 			
 			*s = '/';
@@ -126,5 +158,15 @@
 	else if( strcmp( type, "error" ) == 0 )
 	{
-		/* What to do with it? */
+		struct jabber_error *err;
+		
+		if( ( c = xt_find_node( node->children, "error" ) ) )
+		{
+			err = jabber_error_parse( c, XMLNS_STANZA_ERROR );
+			imcb_error( ic, "Stanza (%s) error: %s%s%s", node->name,
+			            err->code, err->text ? ": " : "",
+			            err->text ? err->text : "" );
+			jabber_error_free( err );
+		}
+		/* What else to do with it? */
 	}
 	else
@@ -145,4 +187,5 @@
 	char *show = jd->away_state->code;
 	char *status = jd->away_message;
+	struct groupchat *c;
 	int st;
 	
@@ -156,4 +199,14 @@
 	st = jabber_write_packet( ic, node );
 	
+	/* Have to send this update to all groupchats too, the server won't
+	   do this automatically. */
+	for( c = ic->groupchats; c && st; c = c->next )
+	{
+		struct jabber_chat *jc = c->data;
+		
+		xt_add_attr( node, "to", jc->my_full_jid );
+		st = jabber_write_packet( ic, node );
+	}
+	
 	xt_free_node( node );
 	return st;
Index: protocols/jabber/xmltree.c
===================================================================
--- protocols/jabber/xmltree.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/xmltree.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -442,5 +442,5 @@
    list, not the node itself! The reason you have to do this by hand: So
    that you can also use this function as a find-next. */
-struct xt_node *xt_find_node( struct xt_node *node, char *name )
+struct xt_node *xt_find_node( struct xt_node *node, const char *name )
 {
 	while( node )
@@ -455,5 +455,5 @@
 }
 
-char *xt_find_attr( struct xt_node *node, char *key )
+char *xt_find_attr( struct xt_node *node, const char *key )
 {
 	int i;
@@ -524,5 +524,5 @@
 }
 
-void xt_add_attr( struct xt_node *node, char *key, char *value )
+void xt_add_attr( struct xt_node *node, const char *key, const char *value )
 {
 	int i;
@@ -551,5 +551,5 @@
 }
 
-int xt_remove_attr( struct xt_node *node, char *key )
+int xt_remove_attr( struct xt_node *node, const char *key )
 {
 	int i, last;
Index: protocols/jabber/xmltree.h
===================================================================
--- protocols/jabber/xmltree.h	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/jabber/xmltree.h	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -87,11 +87,11 @@
 void xt_free_node( struct xt_node *node );
 void xt_free( struct xt_parser *xt );
-struct xt_node *xt_find_node( struct xt_node *node, char *name );
-char *xt_find_attr( struct xt_node *node, char *key );
+struct xt_node *xt_find_node( struct xt_node *node, const char *name );
+char *xt_find_attr( struct xt_node *node, const char *key );
 
 struct xt_node *xt_new_node( char *name, char *text, struct xt_node *children );
 void xt_add_child( struct xt_node *parent, struct xt_node *child );
-void xt_add_attr( struct xt_node *node, char *key, char *value );
-int xt_remove_attr( struct xt_node *node, char *key );
+void xt_add_attr( struct xt_node *node, const char *key, const char *value );
+int xt_remove_attr( struct xt_node *node, const char *key );
 
 #endif
Index: protocols/msn/sb.c
===================================================================
--- protocols/msn/sb.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/msn/sb.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -232,5 +232,5 @@
 	if( sb->chat )
 	{
-		imcb_chat_removed( sb->chat );
+		imcb_chat_free( sb->chat );
 	}
 	
Index: protocols/nogaim.c
===================================================================
--- protocols/nogaim.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/nogaim.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -99,5 +99,4 @@
 }
 
- 
 struct prpl *find_protocol(const char *name)
 {
@@ -289,5 +288,5 @@
 {
 	irc_t *irc = ic->irc;
-	user_t *t, *u = irc->users;
+	user_t *t, *u;
 	account_t *a;
 	
@@ -306,4 +305,5 @@
 	b_event_remove( ic->inpa );
 	
+	u = irc->users;
 	while( u )
 	{
@@ -428,5 +428,4 @@
 {
 	user_t *u = user_findhandle( ic, handle );
-	char *s, newnick[MAX_NICK_LENGTH+1];
 	
 	if( !u || !realname ) return;
@@ -440,28 +439,51 @@
 		if( ( ic->flags & OPT_LOGGED_IN ) && set_getbool( &ic->irc->set, "display_namechanges" ) )
 			imcb_log( ic, "User `%s' changed name to `%s'", u->nick, u->realname );
-		
-		if( !u->online && !nick_saved( ic->acc, handle ) )
-		{
-			/* Detect numeric handles: */
-			for( s = u->user; isdigit( *s ); s++ );
+	}
+}
+
+void imcb_remove_buddy( struct im_connection *ic, char *handle, char *group )
+{
+	user_t *u;
+	
+	if( ( u = user_findhandle( ic, handle ) ) )
+		user_del( ic->irc, u->nick );
+}
+
+/* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM
+   modules to suggest a nickname for a handle. */
+void imcb_buddy_nick_hint( struct im_connection *ic, char *handle, char *nick )
+{
+	user_t *u = user_findhandle( ic, handle );
+	char newnick[MAX_NICK_LENGTH+1], *orig_nick;
+	
+	if( u && !u->online && !nick_saved( ic->acc, handle ) )
+	{
+		/* Only do this if the person isn't online yet (which should
+		   be the case if we just added it) and if the user hasn't
+		   assigned a nickname to this buddy already. */
+		
+		strncpy( newnick, nick, MAX_NICK_LENGTH );
+		newnick[MAX_NICK_LENGTH] = 0;
+		
+		/* Some processing to make sure this string is a valid IRC nickname. */
+		nick_strip( newnick );
+		if( set_getbool( &ic->irc->set, "lcnicks" ) )
+			nick_lc( newnick );
+		
+		if( strcmp( u->nick, newnick ) != 0 )
+		{
+			/* Only do this if newnick is different from the current one.
+			   If rejoining a channel, maybe we got this nick already
+			   (and dedupe would only add an underscore. */
+			nick_dedupe( ic->acc, handle, newnick );
 			
-			if( *s == 0 )
-			{
-				/* If we reached the end of the string, it only contained numbers.
-				   Seems to be an ICQ# then, so hopefully realname contains
-				   something more useful. */
-				strcpy( newnick, realname );
-				
-				/* Some processing to make sure this string is a valid IRC nickname. */
-				nick_strip( newnick );
-				if( set_getbool( &ic->irc->set, "lcnicks" ) )
-					nick_lc( newnick );
-				
-				u->nick = g_strdup( newnick );
-			}
-		}
-	}
-}
-
+			/* u->nick will be freed halfway the process, so it can't be
+			   passed as an argument. */
+			orig_nick = g_strdup( u->nick );
+			user_rename( ic->irc, orig_nick, newnick );
+			g_free( orig_nick );
+		}
+	}
+}
 
 /* prpl.c */
@@ -554,6 +576,6 @@
 		u->online = 0;
 		
-		/* Remove him/her from the conversations to prevent PART messages after he/she QUIT already */
-		for( c = ic->conversations; c; c = c->next )
+		/* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */
+		for( c = ic->groupchats; c; c = c->next )
 			remove_chat_buddy_silent( c, (char*) handle );
 	}
@@ -670,8 +692,8 @@
 }
 
-void imcb_chat_removed( struct groupchat *c )
+void imcb_chat_free( struct groupchat *c )
 {
 	struct im_connection *ic = c->ic;
-	struct groupchat *l = NULL;
+	struct groupchat *l;
 	GList *ir;
 	
@@ -693,8 +715,11 @@
 		}
 		
+		/* Find the previous chat in the linked list. */
+		for( l = ic->groupchats; l && l->next != c; l = l->next );
+		
 		if( l )
 			l->next = c->next;
 		else
-			ic->conversations = c->next;
+			ic->groupchats = c->next;
 		
 		for( ir = c->in_room; ir; ir = ir->next )
@@ -741,11 +766,11 @@
 	/* This one just creates the conversation structure, user won't see anything yet */
 	
-	if( ic->conversations )
-	{
-		for( c = ic->conversations; c->next; c = c->next );
+	if( ic->groupchats )
+	{
+		for( c = ic->groupchats; c->next; c = c->next );
 		c = c->next = g_new0( struct groupchat, 1 );
 	}
 	else
-		ic->conversations = c = g_new0( struct groupchat, 1 );
+		ic->groupchats = c = g_new0( struct groupchat, 1 );
 	
 	c->ic = ic;
@@ -796,4 +821,5 @@
 }
 
+/* This function is one BIG hack... :-( EREWRITE */
 void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason )
 {
@@ -807,4 +833,7 @@
 	if( g_strcasecmp( handle, b->ic->acc->user ) == 0 )
 	{
+		if( b->joined == 0 )
+			return;
+		
 		u = user_find( b->ic->irc, b->ic->irc->nick );
 		b->joined = 0;
@@ -816,7 +845,6 @@
 	}
 	
-	if( remove_chat_buddy_silent( b, handle ) )
-		if( ( b->joined || me ) && u )
-			irc_part( b->ic->irc, u, b->channel );
+	if( me || ( remove_chat_buddy_silent( b, handle ) && b->joined && u ) )
+		irc_part( b->ic->irc, u, b->channel );
 }
 
@@ -844,22 +872,4 @@
 
 /* Misc. BitlBee stuff which shouldn't really be here */
-
-struct groupchat *chat_by_channel( char *channel )
-{
-	struct im_connection *ic;
-	struct groupchat *c;
-	GSList *l;
-	
-	/* This finds the connection which has a conversation which belongs to this channel */
-	for( l = connections; l; l = l->next )
-	{
-		ic = l->data;
-		for( c = ic->conversations; c && g_strcasecmp( c->channel, channel ) != 0; c = c->next );
-		if( c )
-			return c;
-	}
-	
-	return NULL;
-}
 
 char *set_eval_away_devoice( set_t *set, char *value )
@@ -1095,2 +1105,34 @@
 	ic->acc->prpl->rem_deny( ic, handle );
 }
+
+void imcb_clean_handle( struct im_connection *ic, char *handle )
+{
+	/* Accepts a handle and does whatever is necessary to make it
+	   BitlBee-friendly. Currently this means removing everything
+	   outside 33-127 (ASCII printable excl spaces), @ (only one
+	   is allowed) and ! and : */
+	char out[strlen(handle)+1];
+	int s, d;
+	
+	s = d = 0;
+	while( handle[s] )
+	{
+		if( handle[s] > ' ' && handle[s] != '!' && handle[s] != ':' &&
+		    ( handle[s] & 0x80 ) == 0 )
+		{
+			if( handle[s] == '@' )
+			{
+				/* See if we got an @ already? */
+				out[d] = 0;
+				if( strchr( out, '@' ) )
+					continue;
+			}
+			
+			out[d++] = handle[s];
+		}
+		s ++;
+	}
+	out[d] = handle[s];
+	
+	strcpy( handle, out );
+}
Index: protocols/nogaim.h
===================================================================
--- protocols/nogaim.h	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/nogaim.h	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -61,4 +61,5 @@
 #define OPT_AWAY        0x00000004
 #define OPT_DOES_HTML   0x00000010
+#define OPT_LOCALBUDDY  0x00000020 /* For nicks local to one groupchat */
 #define OPT_TYPING      0x00000100 /* Some pieces of code make assumptions */
 #define OPT_THINKING    0x00000200 /* about these values... Stupid me! */
@@ -91,5 +92,5 @@
 	irc_t *irc;
 	
-	struct groupchat *conversations;
+	struct groupchat *groupchats;
 };
 
@@ -104,5 +105,4 @@
 	GList *ignored;
 	
-	/* BitlBee */
 	struct groupchat *next;
 	char *channel;
@@ -265,4 +265,5 @@
 G_MODULE_EXPORT struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle );
 G_MODULE_EXPORT void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname );
+G_MODULE_EXPORT void imcb_buddy_nick_hint( struct im_connection *ic, char *handle, char *nick );
 
 /* Buddy activity */
@@ -276,4 +277,5 @@
 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_clean_handle( struct im_connection *ic, char *handle );
 
 /* Groupchats */
@@ -291,6 +293,5 @@
 /* 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_removed( struct groupchat *c );
-struct groupchat *chat_by_channel( char *channel );
+G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c );
 
 /* Actions, or whatever. */
Index: protocols/oscar/oscar.c
===================================================================
--- protocols/oscar/oscar.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/oscar/oscar.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -2022,4 +2022,5 @@
 					
 					if (realname) {
+						imcb_buddy_nick_hint(ic, curitem->name, realname);
 						imcb_rename_buddy(ic, curitem->name, realname);
 						g_free(realname);
@@ -2514,5 +2515,5 @@
 
 	/* Notify the conversation window that we've left the chat */
-	imcb_chat_removed(cc->cnv);
+	imcb_chat_free(cc->cnv);
 
 	/* Destroy the chat_connection */
Index: protocols/yahoo/yahoo.c
===================================================================
--- protocols/yahoo/yahoo.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ protocols/yahoo/yahoo.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -145,6 +145,6 @@
 	GSList *l;
 	
-	while( ic->conversations )
-		imcb_chat_removed( ic->conversations );
+	while( ic->groupchats )
+		imcb_chat_free( ic->groupchats );
 	
 	for( l = yd->buddygroups; l; l = l->next )
@@ -318,5 +318,5 @@
 	
 	yahoo_conference_logoff( yd->y2_id, NULL, c->data, c->title );
-	imcb_chat_removed( c );
+	imcb_chat_free( c );
 }
 
@@ -798,5 +798,5 @@
 {
 	yahoo_conference_decline( inv->yid, NULL, inv->members, inv->name, "User rejected groupchat" );
-	imcb_chat_removed( inv->c );
+	imcb_chat_free( inv->c );
 	g_free( inv->name );
 	g_free( inv );
@@ -841,5 +841,5 @@
 	struct groupchat *c;
 	
-	for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next );
+	for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
 	
 	if( c )
@@ -853,5 +853,5 @@
 	struct groupchat *c;
 	
-	for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next );
+	for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
 	
 	if( c )
@@ -865,5 +865,5 @@
 	struct groupchat *c;
 	
-	for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next );
+	for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next );
 	
 	if( c )
Index: root_commands.c
===================================================================
--- root_commands.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ root_commands.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -912,5 +912,8 @@
 	if( cmd[3] )
 	{
-		channel = g_strdup( cmd[3] );
+		if( cmd[3][0] != '#' && cmd[3][0] != '&' )
+			channel = g_strdup_printf( "&%s", cmd[3] );
+		else
+			channel = g_strdup( cmd[3] );
 	}
 	else
@@ -918,5 +921,5 @@
 		char *s;
 		
-		channel = g_strdup( chat );
+		channel = g_strdup_printf( "&%s", chat );
 		if( ( s = strchr( channel, '@' ) ) )
 			*s = 0;
@@ -924,10 +927,32 @@
 	if( cmd[3] && cmd[4] )
 		nick = cmd[4];
+	else
+		nick = irc->nick;
 	if( cmd[3] && cmd[4] && cmd[5] )
 		password = cmd[5];
 	
-	c = a->prpl->chat_join( ic, chat, nick, password );
-	
-	g_free( channel );
+	if( !nick_ok( channel + 1 ) )
+	{
+		irc_usermsg( irc, "Invalid channel name: %s", channel );
+		g_free( channel );
+		return;
+	}
+	else if( g_strcasecmp( channel, irc->channel ) == 0 || irc_chat_by_channel( irc, channel ) )
+	{
+		irc_usermsg( irc, "Channel already exists: %s", channel );
+		g_free( channel );
+		return;
+	}
+	
+	if( ( c = a->prpl->chat_join( ic, chat, nick, password ) ) )
+	{
+		g_free( c->channel );
+		c->channel = channel;
+	}
+	else
+	{
+		irc_usermsg( irc, "Tried to join chat, not sure if this was successful" );
+		g_free( channel );
+	}
 }
 
Index: user.c
===================================================================
--- user.c	(revision d444c09e6c7ac6fc3c1686af0e63c09805d8cd00)
+++ user.c	(revision eda54e40d04c83028d84e91c895a550c1929b436)
@@ -161,4 +161,5 @@
 }
 
+/* DO NOT PASS u->nick FOR oldnick !!! */
 void user_rename( irc_t *irc, char *oldnick, char *newnick )
 {
