diff options
Diffstat (limited to 'protocols')
| -rw-r--r-- | protocols/jabber/Makefile | 2 | ||||
| -rw-r--r-- | protocols/jabber/conference.c | 246 | ||||
| -rw-r--r-- | protocols/jabber/io.c | 60 | ||||
| -rw-r--r-- | protocols/jabber/iq.c | 35 | ||||
| -rw-r--r-- | protocols/jabber/jabber.c | 65 | ||||
| -rw-r--r-- | protocols/jabber/jabber.h | 64 | ||||
| -rw-r--r-- | protocols/jabber/jabber_util.c | 178 | ||||
| -rw-r--r-- | protocols/jabber/message.c | 31 | ||||
| -rw-r--r-- | protocols/jabber/presence.c | 71 | ||||
| -rw-r--r-- | protocols/jabber/xmltree.c | 8 | ||||
| -rw-r--r-- | protocols/jabber/xmltree.h | 8 | ||||
| -rw-r--r-- | protocols/msn/sb.c | 2 | ||||
| -rw-r--r-- | protocols/nogaim.c | 142 | ||||
| -rw-r--r-- | protocols/nogaim.h | 9 | ||||
| -rw-r--r-- | protocols/oscar/oscar.c | 3 | ||||
| -rw-r--r-- | protocols/yahoo/yahoo.c | 14 | 
16 files changed, 776 insertions, 162 deletions
| diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile index e81e6c5a..e042f812 100644 --- a/protocols/jabber/Makefile +++ b/protocols/jabber/Makefile @@ -9,7 +9,7 @@  -include ../../Makefile.settings  # [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  LFLAGS += -r diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c new file mode 100644 index 00000000..3fc9ee70 --- /dev/null +++ b/protocols/jabber/conference.c @@ -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 = '/'; +	} +} diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c index 67deb3a6..cf71ff87 100644 --- a/protocols/jabber/io.c +++ b/protocols/jabber/io.c @@ -44,6 +44,15 @@ int jabber_write( struct im_connection *ic, char *buf, int len )  	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 )  	{  		/* If the queue is empty, allocate a new buffer. */ @@ -426,50 +435,35 @@ static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )  static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data )  {  	struct im_connection *ic = data; -	struct xt_node *c; -	char *s, *type = NULL, *text = NULL;  	int allow_reconnect = TRUE; +	struct jabber_error *err; -	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; -		} -	} +	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;  	}  	/* We know that this is a fatal error. If it's a "conflict" error, we  	   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" );  		allow_reconnect = FALSE;  	}  	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 );  	return XT_ABORT; @@ -483,7 +477,27 @@ static xt_status jabber_pkt_misc( struct xt_node *node, gpointer data )  	return XT_HANDLED;  } +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 },  	{ "presence",           "stream:stream",        jabber_pkt_presence }, diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 2aa9d432..4738817a 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -98,26 +98,25 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )  		}  		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" );  			xt_add_attr( c, "type", "pc" );  			xt_add_attr( c, "name", "BitlBee" );  			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  		{ @@ -372,15 +371,13 @@ static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *  					imcb_add_buddy( ic, jid, ( group && group->text_len ) ?  					                           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 );  			}  		} diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index edad5dbd..9176cd68 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -196,6 +196,9 @@ static void jabber_logout( struct im_connection *ic )  	jabber_end_stream( ic ); +	while( ic->groupchats ) +		jabber_chat_free( ic->groupchats ); +	  	if( jd->r_inpa >= 0 )  		b_event_remove( jd->r_inpa );  	if( jd->w_inpa >= 0 ) @@ -223,9 +226,16 @@ static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message,  	struct jabber_data *jd = ic->proto_data;  	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 );  	node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node ); @@ -310,12 +320,34 @@ static void jabber_set_away( struct im_connection *ic, char *state_txt, char *me  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" );  }  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 ); @@ -323,6 +355,30 @@ static void jabber_remove_buddy( struct im_connection *ic, char *who, char *grou  		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 ); +} +  static void jabber_keepalive( struct im_connection *ic )  {  	/* Just any whitespace character is enough as a keepalive for XMPP sessions. */ @@ -387,16 +443,15 @@ void jabber_initmodule()  	ret->logout = jabber_logout;  	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;  	ret->get_info = jabber_get_info;  	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;  	ret->handle_cmp = g_strcasecmp; diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index 42f57ae1..7af7f98e 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -31,16 +31,17 @@  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;  typedef enum @@ -49,12 +50,12 @@ typedef enum  	                                   sure it gets sent only once. */  	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  {  	struct im_connection *ic; @@ -100,6 +101,9 @@ struct jabber_buddy  	char *full_jid;  	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;  	char *away_message; @@ -110,6 +114,20 @@ struct jabber_buddy  	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     first one should be used, but when storing a packet in the cache, a     "special" kind of ID is assigned to make it easier later to figure out @@ -131,8 +149,11 @@ struct jabber_buddy  #define XMLNS_VERSION      "jabber:iq:version"                  /* XEP-0092 */  #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 */  xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); @@ -163,18 +184,29 @@ void jabber_cache_clean( struct im_connection *ic );  const struct jabber_away_state *jabber_away_state_by_code( char *code );  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[]; @@ -192,4 +224,12 @@ xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data );  xt_status sasl_pkt_result( struct xt_node *node, gpointer data );  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 diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c index 3c0e71f4..56491c4f 100644 --- a/protocols/jabber/jabber_util.c +++ b/protocols/jabber/jabber_util.c @@ -47,7 +47,7 @@ char *set_eval_priority( set_t *set, char *value )  		   convenient, they have one disadvantage: If I would just  		   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  		   functions next to evals, or just do this little hack: */ @@ -128,7 +128,7 @@ struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond,  /* 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 )  {  	struct jabber_data *jd = ic->proto_data; @@ -251,7 +251,7 @@ void jabber_buddy_ask( struct im_connection *ic, char *handle )  }  /* Returns a new string. Don't leak it! */ -char *jabber_normalize( char *orig ) +char *jabber_normalize( const char *orig )  {  	int len, i;  	char *new; @@ -319,6 +319,8 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_  	}  	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 );  	} @@ -332,7 +334,8 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_  	else  	{  		/* 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;  	} @@ -352,6 +355,8 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,  	if( ( s = strchr( jid, '/' ) ) )  	{ +		int none_found = 0; +		  		*s = 0;  		if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )  		{ @@ -369,8 +374,16 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,  						break;  			}  		} +		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 ) ) +		if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && ( imcb_find_buddy( ic, jid ) || !none_found ) )  		{  			*s = '/';  			bud = jabber_buddy_add( ic, jid ); @@ -417,6 +430,38 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,  	}  } +/* 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.     XXX: See above, we should accept bare JIDs too... */ @@ -440,6 +485,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )  		{  			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 );  			g_free( bud ); @@ -472,6 +518,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )  					   item, because we're removing the first. */  					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 );  				g_free( bi ); @@ -494,39 +541,144 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )  /* Remove a buddy completely; removes all resources that belong to the     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 );  			g_free( bud );  			bud = next;  		} -		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 ); +} diff --git a/protocols/jabber/message.c b/protocols/jabber/message.c index 19edbdfd..fab62a91 100644 --- a/protocols/jabber/message.c +++ b/protocols/jabber/message.c @@ -29,25 +29,33 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )  	char *from = xt_find_attr( node, "from" );  	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 )  	{  		/* 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. */  		} @@ -75,26 +83,31 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )  			fullmsg = g_string_append( fullmsg, body->text );  		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. */  		else if( xt_find_node( node->children, "active" ) && ( body == NULL ) )  		{  			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 );  		}  		if( s ) diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c index ef92740a..cbfcedae 100644 --- a/protocols/jabber/presence.c +++ b/protocols/jabber/presence.c @@ -30,15 +30,22 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )  	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;  	char *s;  	if( !from )  		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 ) ) )  		{  			if( set_getbool( &ic->irc->set, "debug" ) ) @@ -71,30 +78,55 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )  		else  			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,  			                   bud->away_message );  	}  	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" ) )  				imcb_log( ic, "WARNING: Received presence information from unknown JID: %s", from );  			return XT_HANDLED;  		} +		/* 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 = '/';  		} @@ -125,7 +157,17 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )  	}  	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  	{ @@ -144,6 +186,7 @@ int presence_send_update( struct im_connection *ic )  	struct xt_node *node;  	char *show = jd->away_state->code;  	char *status = jd->away_message; +	struct groupchat *c;  	int st;  	node = jabber_make_packet( "presence", NULL, NULL, NULL ); @@ -155,6 +198,16 @@ int presence_send_update( struct im_connection *ic )  	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;  } diff --git a/protocols/jabber/xmltree.c b/protocols/jabber/xmltree.c index 7a165a1e..b1edd55d 100644 --- a/protocols/jabber/xmltree.c +++ b/protocols/jabber/xmltree.c @@ -441,7 +441,7 @@ void xt_free( struct xt_parser *xt )  /* To find a node's child with a specific name, pass the node's children     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 )  	{ @@ -454,7 +454,7 @@ struct xt_node *xt_find_node( struct xt_node *node, char *name )  	return node;  } -char *xt_find_attr( struct xt_node *node, char *key ) +char *xt_find_attr( struct xt_node *node, const char *key )  {  	int i; @@ -523,7 +523,7 @@ void xt_add_child( struct xt_node *parent, struct xt_node *child )  	}  } -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; @@ -550,7 +550,7 @@ void xt_add_attr( struct xt_node *node, char *key, char *value )  	node->attr[i].value = g_strdup( value );  } -int xt_remove_attr( struct xt_node *node, char *key ) +int xt_remove_attr( struct xt_node *node, const char *key )  {  	int i, last; diff --git a/protocols/jabber/xmltree.h b/protocols/jabber/xmltree.h index 70850c1d..b8b61641 100644 --- a/protocols/jabber/xmltree.h +++ b/protocols/jabber/xmltree.h @@ -86,12 +86,12 @@ void xt_print( struct xt_node *node );  struct xt_node *xt_dup( struct xt_node *node );  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 diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c index 1693cb95..cb9e2cab 100644 --- a/protocols/msn/sb.c +++ b/protocols/msn/sb.c @@ -231,7 +231,7 @@ void msn_sb_destroy( struct msn_switchboard *sb )  	if( sb->chat )  	{ -		imcb_chat_removed( sb->chat ); +		imcb_chat_free( sb->chat );  	}  	if( sb->handler ) diff --git a/protocols/nogaim.c b/protocols/nogaim.c index d90870ad..3307b0f5 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -98,7 +98,6 @@ void register_protocol (struct prpl *p)  	protocols = g_list_append(protocols, p);  } -   struct prpl *find_protocol(const char *name)  {  	GList *gl; @@ -288,7 +287,7 @@ void cancel_auto_reconnect( account_t *a )  void imc_logout( struct im_connection *ic, int allow_reconnect )  {  	irc_t *irc = ic->irc; -	user_t *t, *u = irc->users; +	user_t *t, *u;  	account_t *a;  	/* Nested calls might happen sometimes, this is probably the best @@ -305,6 +304,7 @@ void imc_logout( struct im_connection *ic, int allow_reconnect )  	ic->acc->prpl->logout( ic );  	b_event_remove( ic->inpa ); +	u = irc->users;  	while( u )  	{  		if( u->ic == ic ) @@ -427,7 +427,6 @@ struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle )  void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname )  {  	user_t *u = user_findhandle( ic, handle ); -	char *s, newnick[MAX_NICK_LENGTH+1];  	if( !u || !realname ) return; @@ -439,31 +438,54 @@ void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname )  		if( ( ic->flags & OPT_LOGGED_IN ) && set_getbool( &ic->irc->set, "display_namechanges" ) )  			imcb_log( ic, "User `%s' changed name to `%s'", u->nick, u->realname ); +	} +} + +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; -		if( !u->online && !nick_saved( ic->acc, handle ) ) +		/* 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 )  		{ -			/* Detect numeric handles: */ -			for( s = u->user; isdigit( *s ); s++ ); +			/* 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 */  struct show_got_added_data @@ -553,8 +575,8 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,  		irc_kill( ic->irc, u );  		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 );  	} @@ -669,10 +691,10 @@ void imcb_buddy_typing( struct im_connection *ic, char *handle, u_int32_t flags  	}  } -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;  	if( set_getbool( &ic->irc->set, "debug" ) ) @@ -692,10 +714,13 @@ void imcb_chat_removed( struct groupchat *c )  			/* irc_part( ic->irc, u, c->channel ); */  		} +		/* Find the previous chat in the linked list. */ +		for( l = ic->groupchats; l && l->next != c; l = l->next ); +		  		if( l )  			l->next = c->next;  		else -			ic->conversations = c->next; +			ic->groupchats = c->next;  		for( ir = c->in_room; ir; ir = ir->next )  			g_free( ir->data ); @@ -740,13 +765,13 @@ struct groupchat *imcb_chat_new( struct im_connection *ic, char *handle )  	/* This one just creates the conversation structure, user won't see anything yet */ -	if( ic->conversations ) +	if( ic->groupchats )  	{ -		for( c = ic->conversations; c->next; c = c->next ); +		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;  	c->title = g_strdup( handle ); @@ -795,6 +820,7 @@ void imcb_chat_add_buddy( struct groupchat *b, char *handle )  	}  } +/* This function is one BIG hack... :-( EREWRITE */  void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason )  {  	user_t *u; @@ -806,6 +832,9 @@ void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason )  	/* It might be yourself! */  	if( g_strcasecmp( handle, b->ic->acc->user ) == 0 )  	{ +		if( b->joined == 0 ) +			return; +		  		u = user_find( b->ic->irc, b->ic->irc->nick );  		b->joined = 0;  		me = 1; @@ -815,9 +844,8 @@ void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason )  		u = user_findhandle( b->ic, handle );  	} -	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 );  }  static int remove_chat_buddy_silent( struct groupchat *b, char *handle ) @@ -844,24 +872,6 @@ static int remove_chat_buddy_silent( struct groupchat *b, char *handle )  /* 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 )  {  	irc_t *irc = set->data; @@ -1094,3 +1104,35 @@ void imc_rem_block( struct im_connection *ic, char *handle )  	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 ); +} diff --git a/protocols/nogaim.h b/protocols/nogaim.h index ff4cdb96..5bf6d922 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -60,6 +60,7 @@  #define OPT_LOGGING_OUT 0x00000002  #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! */ @@ -90,7 +91,7 @@ struct im_connection  	/* BitlBee */  	irc_t *irc; -	struct groupchat *conversations; +	struct groupchat *groupchats;  };  struct groupchat { @@ -103,7 +104,6 @@ struct groupchat {  	GList *in_room;  	GList *ignored; -	/* BitlBee */  	struct groupchat *next;  	char *channel;  	/* The title variable contains the ID you gave when you created the @@ -264,6 +264,7 @@ G_MODULE_EXPORT void imcb_add_buddy( struct im_connection *ic, char *handle, cha  G_MODULE_EXPORT void imcb_remove_buddy( struct im_connection *ic, char *handle, char *group );  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 */  /* To manipulate the status of a handle. @@ -275,6 +276,7 @@ G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *ha  /* 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_clean_handle( struct im_connection *ic, char *handle );  /* Groupchats */  G_MODULE_EXPORT void imcb_chat_invited( struct im_connection *ic, char *handle, char *who, char *msg, GList *data ); @@ -290,8 +292,7 @@ G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *b, char *handle );  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_removed( struct groupchat *c ); -struct groupchat *chat_by_channel( char *channel ); +G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c );  /* Actions, or whatever. */  int imc_set_away( struct im_connection *ic, char *away ); diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c index 426dd4a2..4d18e832 100644 --- a/protocols/oscar/oscar.c +++ b/protocols/oscar/oscar.c @@ -2021,6 +2021,7 @@ static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) {  					imcb_add_buddy(ic, curitem->name, NULL);  					if (realname) { +						imcb_buddy_nick_hint(ic, curitem->name, realname);  						imcb_rename_buddy(ic, curitem->name, realname);  						g_free(realname);  					} @@ -2513,7 +2514,7 @@ void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc)  	struct oscar_data *od = (struct oscar_data *)ic->proto_data;  	/* Notify the conversation window that we've left the chat */ -	imcb_chat_removed(cc->cnv); +	imcb_chat_free(cc->cnv);  	/* Destroy the chat_connection */  	od->oscar_chats = g_slist_remove(od->oscar_chats, cc); diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c index 69fc29bb..28a72877 100644 --- a/protocols/yahoo/yahoo.c +++ b/protocols/yahoo/yahoo.c @@ -144,8 +144,8 @@ static void byahoo_logout( struct im_connection *ic )  	struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data;  	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 )  	{ @@ -317,7 +317,7 @@ static void byahoo_chat_leave( struct groupchat *c )  	struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data;  	yahoo_conference_logoff( yd->y2_id, NULL, c->data, c->title ); -	imcb_chat_removed( c ); +	imcb_chat_free( c );  }  static struct groupchat *byahoo_chat_with( struct im_connection *ic, char *who ) @@ -797,7 +797,7 @@ static void byahoo_accept_conf( gpointer w, struct byahoo_conf_invitation *inv )  static void byahoo_reject_conf( gpointer w, struct byahoo_conf_invitation *inv )  {  	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 );  } @@ -840,7 +840,7 @@ void ext_yahoo_conf_userjoin( int id, const char *ignored, const char *who, cons  	struct im_connection *ic = byahoo_get_ic_by_id( id );  	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 )  		imcb_chat_add_buddy( c, (char*) who ); @@ -852,7 +852,7 @@ void ext_yahoo_conf_userleave( int id, const char *ignored, const char *who, con  	struct im_connection *ic = byahoo_get_ic_by_id( id );  	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 )  		imcb_chat_remove_buddy( c, (char*) who, "" ); @@ -864,7 +864,7 @@ void ext_yahoo_conf_message( int id, const char *ignored, const char *who, const  	char *m = byahoo_strip( msg );  	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 )  		imcb_chat_msg( c, (char*) who, (char*) m, 0, 0 ); | 
