diff options
| -rw-r--r-- | bitlbee.c | 4 | ||||
| -rw-r--r-- | irc.c | 30 | ||||
| -rw-r--r-- | irc.h | 1 | ||||
| -rw-r--r-- | irc_commands.c | 6 | ||||
| -rw-r--r-- | nick.c | 14 | ||||
| -rw-r--r-- | nick.h | 1 | ||||
| -rw-r--r-- | protocols/jabber/Makefile | 2 | ||||
| -rw-r--r-- | protocols/jabber/conference.c | 243 | ||||
| -rw-r--r-- | protocols/jabber/io.c | 29 | ||||
| -rw-r--r-- | protocols/jabber/iq.c | 35 | ||||
| -rw-r--r-- | protocols/jabber/jabber.c | 65 | ||||
| -rw-r--r-- | protocols/jabber/jabber.h | 57 | ||||
| -rw-r--r-- | protocols/jabber/jabber_util.c | 143 | ||||
| -rw-r--r-- | protocols/jabber/message.c | 25 | ||||
| -rw-r--r-- | protocols/jabber/presence.c | 59 | ||||
| -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 | 106 | ||||
| -rw-r--r-- | protocols/nogaim.h | 9 | ||||
| -rw-r--r-- | protocols/oscar/oscar.c | 3 | ||||
| -rw-r--r-- | protocols/yahoo/yahoo.c | 14 | ||||
| -rw-r--r-- | root_commands.c | 33 | ||||
| -rw-r--r-- | user.c | 1 | 
24 files changed, 747 insertions, 151 deletions
| @@ -69,6 +69,7 @@ int bitlbee_daemon_init()  #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 );  	if( ( i = inet_pton( AF_INET6, ipv6_wrap( global.conf->iface ), &listen_addr6.sin6_addr ) ) != 1 ) @@ -76,7 +77,6 @@ int bitlbee_daemon_init()  		/* Forget about IPv6 in this function. */  		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 );  		if( strcmp( global.conf->iface, "::" ) == 0 ) @@ -124,7 +124,7 @@ int bitlbee_daemon_init()  		else if( i != 0 )   			exit( 0 ); -		chdir( "/" ); +//		chdir( "/" );  		/* Sometimes std* are already closed (for example when we're in a RESTARTed  		   BitlBee process. So let's only close TTY-fds. */ @@ -657,7 +657,7 @@ void irc_names( irc_t *irc, char *channel )  			strcat( namelist, " " );  		}  	} -	else if( ( c = chat_by_channel( channel ) ) ) +	else if( ( c = irc_chat_by_channel( irc, channel ) ) )  	{  		GList *l; @@ -810,7 +810,7 @@ void irc_topic( irc_t *irc, char *channel )  	}  	else  	{ -		struct groupchat *c = chat_by_channel( channel ); +		struct groupchat *c = irc_chat_by_channel( irc, channel );  		if( c )  			irc_reply( irc, 332, "%s :BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", channel, c->title ); @@ -948,7 +948,7 @@ int irc_send( irc_t *irc, char *nick, char *s, int flags )  	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 );  			return( 0 ); @@ -1214,3 +1214,27 @@ static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )  	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; +} @@ -139,5 +139,6 @@ int irc_msgfrom( irc_t *irc, char *nick, char *msg );  int irc_noticefrom( irc_t *irc, char *nick, char *msg );  void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags ); +struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel );  #endif diff --git a/irc_commands.c b/irc_commands.c index 8d841aaa..266d9732 100644 --- a/irc_commands.c +++ b/irc_commands.c @@ -143,7 +143,7 @@ static void irc_cmd_part( irc_t *irc, char **cmd )  		irc_part( irc, u, irc->channel );  		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 ); @@ -200,7 +200,7 @@ static void irc_cmd_join( irc_t *irc, char **cmd )  static void irc_cmd_invite( irc_t *irc, char **cmd )  {  	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 );  	if( u && c && ( u->ic == c->ic ) ) @@ -286,7 +286,7 @@ static void irc_cmd_who( irc_t *irc, char **cmd )  				irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );  			u = u->next;  		} -	else if( ( c = chat_by_channel( channel ) ) ) +	else if( ( c = irc_chat_by_channel( irc, channel ) ) )  		for( l = c->in_room; l; l = l->next )  		{  			if( ( u = user_findhandle( c->ic, l->data ) ) ) @@ -56,7 +56,6 @@ char *nick_get( account_t *acc, const char *handle )  {  	static char nick[MAX_NICK_LENGTH+1];  	char *store_handle, *found_nick; -	int inf_protection = 256;  	memset( nick, 0, MAX_NICK_LENGTH + 1 ); @@ -82,6 +81,17 @@ char *nick_get( account_t *acc, const char *handle )  	}  	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  	   subtle changes to make it unique. */  	while( !nick_ok( nick ) || user_find( acc->irc, nick ) ) @@ -119,8 +129,6 @@ char *nick_get( account_t *acc, const char *handle )  			break;  		}  	} -	 -	return nick;  }  /* Just check if there is a nickname set for this buddy or if we'd have to @@ -25,6 +25,7 @@  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 );  void nick_strip( char *nick ); 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..f49dbd1c --- /dev/null +++ b/protocols/jabber/conference.c @@ -0,0 +1,243 @@ +/***************************************************************************\ +*                                                                           * +*  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] = '_'; +			} +			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..edde5a8d 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. */ @@ -483,7 +492,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..90c1e9f6 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,22 @@ 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_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 );  extern const struct jabber_away_state jabber_away_state_list[]; @@ -192,4 +217,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..5b91c5ed 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,109 @@ 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; +} diff --git a/protocols/jabber/message.c b/protocols/jabber/message.c index 19edbdfd..52ee3a53 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,7 +83,8 @@ 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 ); @@ -83,18 +92,18 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )  		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..e53978ea 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 = '/';  		} @@ -144,6 +176,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 +188,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 a70d6eca..22d82ecb 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -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. */ -		if( !u->online && !nick_saved( ic->acc, handle ) ) +		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 )  		{ -			/* 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 );  	} @@ -684,10 +706,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" ) ) @@ -707,10 +729,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 ); @@ -748,13 +773,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 ); @@ -803,6 +828,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; @@ -814,6 +840,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; @@ -823,9 +852,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 ) @@ -852,24 +880,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; diff --git a/protocols/nogaim.h b/protocols/nogaim.h index 59f9e870..74a63306 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,17 +91,15 @@ struct im_connection  	/* BitlBee */  	irc_t *irc; -	struct groupchat *conversations; +	struct groupchat *groupchats;  };  struct groupchat {  	struct im_connection *ic; -	/* stuff used just for chat */  	GList *in_room;  	GList *ignored; -	/* BitlBee */  	struct groupchat *next;  	char *channel;  	char *title; @@ -194,6 +193,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 */  G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ); @@ -207,8 +207,7 @@ G_MODULE_EXPORT struct groupchat *imcb_chat_new( struct im_connection *ic, char  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 );  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 ); diff --git a/root_commands.c b/root_commands.c index a7582936..0f9f776c 100644 --- a/root_commands.c +++ b/root_commands.c @@ -911,24 +911,49 @@ static void cmd_join_chat( irc_t *irc, char **cmd )  	chat = cmd[2];  	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  	{  		char *s; -		channel = g_strdup( chat ); +		channel = g_strdup_printf( "&%s", chat );  		if( ( s = strchr( channel, '@' ) ) )  			*s = 0;  	}  	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 ); +	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; +	} -	g_free( channel ); +	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 ); +	}  }  const command_t commands[] = { @@ -160,6 +160,7 @@ user_t *user_findhandle( struct im_connection *ic, char *handle )  	return NULL;  } +/* DO NOT PASS u->nick FOR oldnick !!! */  void user_rename( irc_t *irc, char *oldnick, char *newnick )  {  	user_t *u = user_find( irc, oldnick ); | 
