diff options
Diffstat (limited to 'protocols/jabber')
| -rw-r--r-- | protocols/jabber/Makefile | 46 | ||||
| -rw-r--r-- | protocols/jabber/conference.c | 384 | ||||
| -rw-r--r-- | protocols/jabber/io.c | 549 | ||||
| -rw-r--r-- | protocols/jabber/iq.c | 870 | ||||
| -rw-r--r-- | protocols/jabber/jabber.c | 628 | ||||
| -rw-r--r-- | protocols/jabber/jabber.h | 330 | ||||
| -rw-r--r-- | protocols/jabber/jabber_util.c | 762 | ||||
| -rw-r--r-- | protocols/jabber/message.c | 149 | ||||
| -rw-r--r-- | protocols/jabber/presence.c | 258 | ||||
| -rw-r--r-- | protocols/jabber/s5bytestream.c | 1153 | ||||
| -rw-r--r-- | protocols/jabber/sasl.c | 348 | ||||
| -rw-r--r-- | protocols/jabber/si.c | 533 | 
12 files changed, 6010 insertions, 0 deletions
| diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile new file mode 100644 index 00000000..32946b18 --- /dev/null +++ b/protocols/jabber/Makefile @@ -0,0 +1,46 @@ +########################### +## Makefile for BitlBee  ## +##                       ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/jabber/ +endif + +# [SH] Program variables +objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o + +LFLAGS += -r + +# [SH] Phony targets +all: jabber_mod.o +check: all +lcov: check +gcov:  +	gcov *.c + +.PHONY: all clean distclean + +clean: +	rm -f *.o core + +distclean: clean +	rm -rf .depend + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c +	@echo '*' Compiling $< +	@$(CC) -c $(CFLAGS) $< -o $@ + +jabber_mod.o: $(objects) +	@echo '*' Linking jabber_mod.o +	@$(LD) $(LFLAGS) $(objects) -o jabber_mod.o + +-include .depend/*.d diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c new file mode 100644 index 00000000..0c2db0b3 --- /dev/null +++ b/protocols/jabber/conference.c @@ -0,0 +1,384 @@ +/***************************************************************************\ +*                                                                           * +*  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" + +static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +struct groupchat *jabber_chat_join( struct im_connection *ic, const char *room, const char *nick, const 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 ); +	if( password ) +		xt_add_child( node, xt_new_node( "password", password, NULL ) ); +	node = jabber_make_packet( "presence", NULL, roomjid, node ); +	jabber_cache_add( ic, node, jabber_chat_join_failed ); +	 +	if( !jabber_write_packet( ic, node ) ) +	{ +		g_free( roomjid ); +		return NULL; +	} +	 +	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; +} + +static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct jabber_error *err; +	struct jabber_buddy *bud; +	char *room; +	 +	room = xt_find_attr( orig, "to" ); +	bud = jabber_buddy_by_jid( ic, room, 0 ); +	err = jabber_error_parse( xt_find_node( node->children, "error" ), XMLNS_STANZA_ERROR ); +	if( err ) +	{ +		imcb_error( ic, "Error joining groupchat %s: %s%s%s", room, err->code, +		            err->text ? ": " : "", err->text ? err->text : "" ); +		jabber_error_free( err ); +	} +	if( bud ) +		jabber_chat_free( jabber_chat_by_jid( ic, bud->bare_jid ) ); +	 +	return XT_HANDLED; +} + +struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name ) +{ +	char *normalized = jabber_normalize( name ); +	GSList *l; +	struct groupchat *ret; +	struct jabber_chat *jc; +	 +	for( l = ic->groupchats; l; l = l->next ) +	{ +		ret = l->data; +		jc = ret->data; +		if( strcmp( normalized, jc->name ) == 0 ) +			break; +	} +	g_free( normalized ); +	 +	return l ? ret : NULL; +} + +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; +	 +	jc->flags |= JCFLAG_MESSAGE_SENT; +	 +	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_topic( struct groupchat *c, char *topic ) +{ +	struct im_connection *ic = c->ic; +	struct jabber_chat *jc = c->data; +	struct xt_node *node; +	 +	node = xt_new_node( "subject", topic, 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; +} + +void jabber_chat_invite( struct groupchat *c, char *who, char *message ) +{ +	struct xt_node *node; +	struct im_connection *ic = c->ic; +	struct jabber_chat *jc = c->data; + +	node = xt_new_node( "reason", message, NULL );  + +	node = xt_new_node( "invite", NULL, node ); +	xt_add_attr( node, "to", who );  + +	node = xt_new_node( "x", NULL, node );  +	xt_add_attr( node, "xmlns", XMLNS_MUC_USER );  +	 +	node = jabber_make_packet( "message", NULL, jc->name, node );  + +	jabber_write_packet( ic, node );  + +	xt_free_node( node ); +} + +/* 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_jid( 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 ) ) +			{ +				struct xt_node *item; +				 +				item = xt_find_node( c->children, "item" ); +				if( ( s = xt_find_attr( item, "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 && bud->flags & JBFLAG_IS_ANONYMOUS ) +		{ +			/* If JIDs are anonymized, add them to the local +			   list for the duration of this chat. */ +			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 */ +	{ +		if( ( bud->flags & JBFLAG_IS_CHATROOM ) && bud->ext_jid ) +		{ +			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 *subject = xt_find_node( node->children, "subject" ); +	struct xt_node *body = xt_find_node( node->children, "body" ); +	struct groupchat *chat = bud ? jabber_chat_by_jid( ic, bud->bare_jid ) : NULL; +	struct jabber_chat *jc = chat ? chat->data : NULL; +	char *s; +	 +	if( subject && chat ) +	{ +		s = bud ? strchr( bud->ext_jid, '/' ) : NULL; +		if( s ) *s = 0; +		imcb_chat_topic( chat, bud ? bud->ext_jid : NULL, subject->text_len > 0 ? +		                 subject->text : NULL, jabber_get_timestamp( node ) ); +		if( s ) *s = '/'; +	} +	 +	if( bud == NULL || ( jc && ~jc->flags & JCFLAG_MESSAGE_SENT && bud == jc->me ) ) +	{ +		char *nick; +		 +		if( body == NULL || body->text_len == 0 ) +			/* Meh. Empty messages aren't very interesting, no matter +			   how much some servers love to send them. */ +			return; +		 +		s = xt_find_attr( node, "from" ); /* pkt_message() already NULL-checked this one. */ +		nick = strchr( s, '/' ); +		if( nick ) +		{ +			/* If this message included a resource/nick we don't know, +			   we might still know the groupchat itself. */ +			*nick = 0; +			chat = jabber_chat_by_jid( ic, s ); +			*nick = '/'; +			 +			nick ++; +		} +		else +		{ +			/* message.c uses the EXACT_JID option, so bud should +			   always be NULL here for bare JIDs. */ +			chat = jabber_chat_by_jid( ic, s ); +		} +		 +		if( nick == NULL ) +		{ +			/* This is fine, the groupchat itself isn't in jd->buddies. */ +			if( chat ) +				imcb_chat_log( chat, "From conference server: %s", body->text ); +			else +				imcb_log( ic, "System message from unknown groupchat %s: %s", s, body->text ); +		} +		else +		{ +			/* This can happen too, at least when receiving a backlog when +			   just joining a channel. */ +			if( chat ) +				imcb_chat_log( chat, "Message from unknown participant %s: %s", nick, body->text ); +			else +				imcb_log( ic, "Groupchat message from unknown JID %s: %s", s, body->text ); +		} +		 +		return; +	} +	else if( chat == 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 new file mode 100644 index 00000000..ef7d5c13 --- /dev/null +++ b/protocols/jabber/io.c @@ -0,0 +1,549 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - I/O stuff (plain, SSL), queues, etc                      * +*                                                                           * +*  Copyright 2006 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" +#include "ssl_client.h" + +static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond ); +static gboolean jabber_write_queue( struct im_connection *ic ); + +int jabber_write_packet( struct im_connection *ic, struct xt_node *node ) +{ +	char *buf; +	int st; +	 +	buf = xt_to_string( node ); +	st = jabber_write( ic, buf, strlen( buf ) ); +	g_free( buf ); +	 +	return st; +} + +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 && !( ic->flags & OPT_LOGGING_OUT ) ) +	{ +		char *msg, *s; +		 +		msg = g_strdup_printf( "TX: %s", buf ); +		/* Don't include auth info in XML logs. */ +		if( strncmp( msg, "TX: <auth ", 10 ) == 0 && ( s = strchr( msg, '>' ) ) ) +		{ +			s++; +			while( *s && *s != '<' ) +				*(s++) = '*'; +		} +		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. */ +		jd->tx_len = len; +		jd->txq = g_memdup( buf, len ); +		 +		/* Try if we can write it immediately so we don't have to do +		   it via the event handler. If not, add the handler. (In +		   most cases it probably won't be necessary.) */ +		if( ( ret = jabber_write_queue( ic ) ) && jd->tx_len > 0 ) +			jd->w_inpa = b_input_add( jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic ); +	} +	else +	{ +		/* Just add it to the buffer if it's already filled. The +		   event handler is already set. */ +		jd->txq = g_renew( char, jd->txq, jd->tx_len + len ); +		memcpy( jd->txq + jd->tx_len, buf, len ); +		jd->tx_len += len; +		 +		/* The return value for write() doesn't necessarily mean +		   that everything got sent, it mainly means that the +		   connection (officially) still exists and can still +		   be accessed without hitting SIGSEGV. IOW: */ +		ret = TRUE; +	} +	 +	return ret; +} + +/* Splitting up in two separate functions: One to use as a callback and one +   to use in the function above to escape from having to wait for the event +   handler to call us, if possible. +    +   Two different functions are necessary because of the return values: The +   callback should only return TRUE if the write was successful AND if the +   buffer is not empty yet (ie. if the handler has to be called again when +   the socket is ready for more data). */ +static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond ) +{ +	struct jabber_data *jd = ((struct im_connection *)data)->proto_data; +	 +	return jd->fd != -1 && +	       jabber_write_queue( data ) && +	       jd->tx_len > 0; +} + +static gboolean jabber_write_queue( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	int st; +	 +	if( jd->ssl ) +		st = ssl_write( jd->ssl, jd->txq, jd->tx_len ); +	else +		st = write( jd->fd, jd->txq, jd->tx_len ); +	 +	if( st == jd->tx_len ) +	{ +		/* We wrote everything, clear the buffer. */ +		g_free( jd->txq ); +		jd->txq = NULL; +		jd->tx_len = 0; +		 +		return TRUE; +	} +	else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) ) +	{ +		/* Set fd to -1 to make sure we won't write to it anymore. */ +		closesocket( jd->fd );	/* Shouldn't be necessary after errors? */ +		jd->fd = -1; +		 +		imcb_error( ic, "Short write() to server" ); +		imc_logout( ic, TRUE ); +		return FALSE; +	} +	else if( st > 0 ) +	{ +		char *s; +		 +		s = g_memdup( jd->txq + st, jd->tx_len - st ); +		jd->tx_len -= st; +		g_free( jd->txq ); +		jd->txq = s; +		 +		return TRUE; +	} +	else +	{ +		/* Just in case we had EINPROGRESS/EAGAIN: */ +		 +		return TRUE; +	} +} + +static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition cond ) +{ +	struct im_connection *ic = data; +	struct jabber_data *jd = ic->proto_data; +	char buf[512]; +	int st; +	 +	if( jd->fd == -1 ) +		return FALSE; +	 +	if( jd->ssl ) +		st = ssl_read( jd->ssl, buf, sizeof( buf ) ); +	else +		st = read( jd->fd, buf, sizeof( buf ) ); +	 +	if( st > 0 ) +	{ +		/* Parse. */ +		if( xt_feed( jd->xt, buf, st ) < 0 ) +		{ +			imcb_error( ic, "XML stream error" ); +			imc_logout( ic, TRUE ); +			return FALSE; +		} +		 +		/* Execute all handlers. */ +		if( !xt_handle( jd->xt, NULL, 1 ) ) +		{ +			/* Don't do anything, the handlers should have +			   aborted the connection already. */ +			return FALSE; +		} +		 +		if( jd->flags & JFLAG_STREAM_RESTART ) +		{ +			jd->flags &= ~JFLAG_STREAM_RESTART; +			jabber_start_stream( ic ); +		} +		 +		/* Garbage collection. */ +		xt_cleanup( jd->xt, NULL, 1 ); +		 +		/* This is a bit hackish, unfortunately. Although xmltree +		   has nifty event handler stuff, it only calls handlers +		   when nodes are complete. Since the server should only +		   send an opening <stream:stream> tag, we have to check +		   this by hand. :-( */ +		if( !( jd->flags & JFLAG_STREAM_STARTED ) && jd->xt && jd->xt->root ) +		{ +			if( g_strcasecmp( jd->xt->root->name, "stream:stream" ) == 0 ) +			{ +				jd->flags |= JFLAG_STREAM_STARTED; +				 +				/* If there's no version attribute, assume +				   this is an old server that can't do SASL +				   authentication. */ +				if( !sasl_supported( ic ) ) +				{ +					/* If there's no version= tag, we suppose +					   this server does NOT implement: XMPP 1.0, +					   SASL and TLS. */ +					if( set_getbool( &ic->acc->set, "tls" ) ) +					{ +						imcb_error( ic, "TLS is turned on for this " +						          "account, but is not supported by this server" ); +						imc_logout( ic, FALSE ); +						return FALSE; +					} +					else +					{ +						return jabber_init_iq_auth( ic ); +					} +				} +			} +			else +			{ +				imcb_error( ic, "XML stream error" ); +				imc_logout( ic, TRUE ); +				return FALSE; +			} +		} +	} +	else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) ) +	{ +		closesocket( jd->fd ); +		jd->fd = -1; +		 +		imcb_error( ic, "Error while reading from server" ); +		imc_logout( ic, TRUE ); +		return FALSE; +	} +	 +	if( ssl_pending( jd->ssl ) ) +		/* OpenSSL empties the TCP buffers completely but may keep some +		   data in its internap buffers. select() won't see that, but +		   ssl_pending() does. */ +		return jabber_read_callback( data, fd, cond ); +	else +		return TRUE; +} + +gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond ) +{ +	struct im_connection *ic = data; +	 +	if( g_slist_find( jabber_connections, ic ) == NULL ) +		return FALSE; +	 +	if( source == -1 ) +	{ +		imcb_error( ic, "Could not connect to server" ); +		imc_logout( ic, TRUE ); +		return FALSE; +	} +	 +	imcb_log( ic, "Connected to server, logging in" ); +	 +	return jabber_start_stream( ic ); +} + +gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond ) +{ +	struct im_connection *ic = data; +	struct jabber_data *jd; +	 +	if( g_slist_find( jabber_connections, ic ) == NULL ) +		return FALSE; +	 +	jd = ic->proto_data; +	 +	if( source == NULL ) +	{ +		/* The SSL connection will be cleaned up by the SSL lib +		   already, set it to NULL here to prevent a double cleanup: */ +		jd->ssl = NULL; +		 +		imcb_error( ic, "Could not connect to server" ); +		imc_logout( ic, TRUE ); +		return FALSE; +	} +	 +	imcb_log( ic, "Connected to server, logging in" ); +	 +	return jabber_start_stream( ic ); +} + +static xt_status jabber_end_of_stream( struct xt_node *node, gpointer data ) +{ +	imc_logout( data, TRUE ); +	return XT_ABORT; +} + +static xt_status jabber_pkt_features( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = data; +	struct jabber_data *jd = ic->proto_data; +	struct xt_node *c, *reply; +	int trytls; +	 +	trytls = g_strcasecmp( set_getstr( &ic->acc->set, "tls" ), "try" ) == 0; +	c = xt_find_node( node->children, "starttls" ); +	if( c && !jd->ssl ) +	{ +		/* If the server advertises the STARTTLS feature and if we're +		   not in a secure connection already: */ +		 +		c = xt_find_node( c->children, "required" ); +		 +		if( c && ( !trytls && !set_getbool( &ic->acc->set, "tls" ) ) ) +		{ +			imcb_error( ic, "Server requires TLS connections, but TLS is turned off for this account" ); +			imc_logout( ic, FALSE ); +			 +			return XT_ABORT; +		} +		 +		/* Only run this if the tls setting is set to true or try: */ +		if( ( trytls || set_getbool( &ic->acc->set, "tls" ) ) ) +		{ +			reply = xt_new_node( "starttls", NULL, NULL ); +			xt_add_attr( reply, "xmlns", XMLNS_TLS ); +			if( !jabber_write_packet( ic, reply ) ) +			{ +				xt_free_node( reply ); +				return XT_ABORT; +			} +			xt_free_node( reply ); +			 +			return XT_HANDLED; +		} +	} +	else if( !c && !jd->ssl ) +	{ +		/* If the server does not advertise the STARTTLS feature and +		   we're not in a secure connection already: (Servers have a +		   habit of not advertising <starttls/> anymore when already +		   using SSL/TLS. */ +		 +		if( !trytls && set_getbool( &ic->acc->set, "tls" ) ) +		{ +			imcb_error( ic, "TLS is turned on for this account, but is not supported by this server" ); +			imc_logout( ic, FALSE ); +			 +			return XT_ABORT; +		} +	} +	 +	/* This one used to be in jabber_handlers[], but it has to be done +	   from here to make sure the TLS session will be initialized +	   properly before we attempt SASL authentication. */ +	if( ( c = xt_find_node( node->children, "mechanisms" ) ) ) +	{ +		if( sasl_pkt_mechanisms( c, data ) == XT_ABORT ) +			return XT_ABORT; +	} +	/* If the server *SEEMS* to support SASL authentication but doesn't +	   support it after all, we should try to do authentication the +	   other way. jabber.com doesn't seem to do SASL while it pretends +	   to be XMPP 1.0 compliant! */ +	else if( !( jd->flags & JFLAG_AUTHENTICATED ) && sasl_supported( ic ) ) +	{ +		if( !jabber_init_iq_auth( ic ) ) +			return XT_ABORT; +	} +	 +	if( ( c = xt_find_node( node->children, "bind" ) ) ) +		jd->flags |= JFLAG_WANT_BIND; +	 +	if( ( c = xt_find_node( node->children, "session" ) ) ) +		jd->flags |= JFLAG_WANT_SESSION; +	 +	if( jd->flags & JFLAG_AUTHENTICATED ) +		return jabber_pkt_bind_sess( ic, NULL, NULL ); +	 +	return XT_HANDLED; +} + +static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = data; +	struct jabber_data *jd = ic->proto_data; +	char *xmlns; +	 +	xmlns = xt_find_attr( node, "xmlns" ); +	 +	/* Just ignore it when it doesn't seem to be TLS-related (is that at +	   all possible??). */ +	if( !xmlns || strcmp( xmlns, XMLNS_TLS ) != 0 ) +		return XT_HANDLED; +	 +	/* We don't want event handlers to touch our TLS session while it's +	   still initializing! */ +	b_event_remove( jd->r_inpa ); +	if( jd->tx_len > 0 ) +	{ +		/* Actually the write queue should be empty here, but just +		   to be sure... */ +		b_event_remove( jd->w_inpa ); +		g_free( jd->txq ); +		jd->txq = NULL; +		jd->tx_len = 0; +	} +	jd->w_inpa = jd->r_inpa = 0; +	 +	imcb_log( ic, "Converting stream to TLS" ); +	 +	jd->flags |= JFLAG_STARTTLS_DONE; +	jd->ssl = ssl_starttls( jd->fd, jabber_connected_ssl, ic ); +	 +	return XT_HANDLED; +} + +static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = data; +	int allow_reconnect = TRUE; +	struct jabber_error *err; +	 +	err = jabber_error_parse( node, XMLNS_STREAM_ERROR ); +	 +	/* Tssk... */ +	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( 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", err->code, err->text ? ": " : "", +		            err->text ? err->text : "" ); +	} +	 +	jabber_error_free( err ); +	imc_logout( ic, allow_reconnect ); +	 +	return XT_ABORT; +} + +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 }, +	{ "iq",                 "stream:stream",        jabber_pkt_iq }, +	{ "stream:features",    "stream:stream",        jabber_pkt_features }, +	{ "stream:error",       "stream:stream",        jabber_pkt_stream_error }, +	{ "proceed",            "stream:stream",        jabber_pkt_proceed_tls }, +	{ "challenge",          "stream:stream",        sasl_pkt_challenge }, +	{ "success",            "stream:stream",        sasl_pkt_result }, +	{ "failure",            "stream:stream",        sasl_pkt_result }, +	{ NULL,                 NULL,                   NULL } +}; + +gboolean jabber_start_stream( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	int st; +	char *greet; +	 +	/* We'll start our stream now, so prepare everything to receive one +	   from the server too. */ +	xt_free( jd->xt );	/* In case we're RE-starting. */ +	jd->xt = xt_new( jabber_handlers, ic ); +	 +	if( jd->r_inpa <= 0 ) +		jd->r_inpa = b_input_add( jd->fd, B_EV_IO_READ, jabber_read_callback, ic ); +	 +	greet = g_strdup_printf( "%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" " +	                          "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">",  +	                          ( jd->flags & JFLAG_STARTTLS_DONE ) ? "" : "<?xml version='1.0' ?>", +	                          jd->server ); +	 +	st = jabber_write( ic, greet, strlen( greet ) ); +	 +	g_free( greet ); +	 +	return st; +} + +void jabber_end_stream( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	 +	/* Let's only do this if the queue is currently empty, otherwise it'd +	   take too long anyway. */ +	if( jd->tx_len == 0 ) +	{ +		char eos[] = "</stream:stream>"; +		struct xt_node *node; +		int st = 1; +		 +		if( ic->flags & OPT_LOGGED_IN ) +		{ +			node = jabber_make_packet( "presence", "unavailable", NULL, NULL ); +			st = jabber_write_packet( ic, node ); +			xt_free_node( node ); +		} +		 +		if( st ) +			jabber_write( ic, eos, strlen( eos ) ); +	} +} diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c new file mode 100644 index 00000000..0c5671d0 --- /dev/null +++ b/protocols/jabber/iq.c @@ -0,0 +1,870 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - IQ packets                                               * +*                                                                           * +*  Copyright 2006 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" +#include "sha1.h" + +static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = data; +	struct xt_node *c, *reply = NULL; +	char *type, *s; +	int st, pack = 1; +	 +	type = xt_find_attr( node, "type" ); +	 +	if( !type ) +	{ +		imcb_error( ic, "Received IQ packet without type." ); +		imc_logout( ic, TRUE ); +		return XT_ABORT; +	} +	 +	if( strcmp( type, "result" ) == 0 || strcmp( type, "error" ) == 0 ) +	{ +		return jabber_cache_handle_packet( ic, node ); +	} +	else if( strcmp( type, "get" ) == 0 ) +	{ +		if( !( ( c = xt_find_node( node->children, "query" ) ) || +		       ( c = xt_find_node( node->children, "ping" ) ) || +		       ( c = xt_find_node( node->children, "time" ) ) ) || +		    !( s = xt_find_attr( c, "xmlns" ) ) ) +		{ +			/* Sigh. Who decided to suddenly invent new elements +			   instead of just sticking with <query/>? */ +			return XT_HANDLED; +		} +		 +		reply = xt_new_node( "query", NULL, NULL ); +		xt_add_attr( reply, "xmlns", s ); +		 +		/* Of course this is a very essential query to support. ;-) */ +		if( strcmp( s, XMLNS_VERSION ) == 0 ) +		{ +			xt_add_child( reply, xt_new_node( "name", set_getstr( &ic->acc->set, "user_agent" ), NULL ) ); +			xt_add_child( reply, xt_new_node( "version", BITLBEE_VERSION, NULL ) ); +			xt_add_child( reply, xt_new_node( "os", ARCH, NULL ) ); +		} +		else if( strcmp( s, XMLNS_TIME_OLD ) == 0 ) +		{ +			time_t time_ep; +			char buf[1024]; +			 +			buf[sizeof(buf)-1] = 0; +			time_ep = time( NULL ); +			 +			strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%S", gmtime( &time_ep ) ); +			xt_add_child( reply, xt_new_node( "utc", buf, NULL ) ); +			 +			strftime( buf, sizeof( buf ) - 1, "%Z", localtime( &time_ep ) ); +			xt_add_child( reply, xt_new_node( "tz", buf, NULL ) ); +		} +		else if( strcmp( s, XMLNS_TIME ) == 0 ) +		{ +			time_t time_ep; +			char buf[1024]; +			 +			buf[sizeof(buf)-1] = 0; +			time_ep = time( NULL ); +			 +			xt_free_node( reply ); +			reply = xt_new_node( "time", NULL, NULL ); +			xt_add_attr( reply, "xmlns", XMLNS_TIME ); +			 +			strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%SZ", gmtime( &time_ep ) ); +			xt_add_child( reply, xt_new_node( "utc", buf, NULL ) ); +			 +			strftime( buf, sizeof( buf ) - 1, "%z", localtime( &time_ep ) ); +			if( strlen( buf ) >= 5 ) +			{ +				buf[6] = '\0'; +				buf[5] = buf[4]; +				buf[4] = buf[3]; +				buf[3] = ':'; +			} +			xt_add_child( reply, xt_new_node( "tzo", buf, NULL ) ); +		} +		else if( strcmp( s, XMLNS_PING ) == 0 ) +		{ +			xt_free_node( reply ); +			reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), NULL ); +			if( ( s = xt_find_attr( node, "id" ) ) ) +				xt_add_attr( reply, "id", s ); +			pack = 0; +		} +		else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 ) +		{ +			const char *features[] = { XMLNS_DISCO_INFO, +			                           XMLNS_VERSION, +			                           XMLNS_TIME_OLD, +			                           XMLNS_TIME, +			                           XMLNS_CHATSTATES, +			                           XMLNS_MUC, +			                           XMLNS_PING, +			                           XMLNS_SI, +			                           XMLNS_BYTESTREAMS, +			                           XMLNS_FILETRANSFER, +			                           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", set_getstr( &ic->acc->set, "user_agent" ) ); +			xt_add_child( reply, c ); +			 +			for( f = features; *f; f ++ ) +			{ +				c = xt_new_node( "feature", NULL, NULL ); +				xt_add_attr( c, "var", *f ); +				xt_add_child( reply, c ); +			} +		} +		else +		{ +			xt_free_node( reply ); +			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); +			pack = 0; +		} +	} +	else if( strcmp( type, "set" ) == 0 ) +	{ +		if( ( c = xt_find_node( node->children, "si" ) ) && +		    ( s = xt_find_attr( c, "xmlns" ) ) && +		    ( strcmp( s, XMLNS_SI ) == 0 ) ) +		{ +			return jabber_si_handle_request( ic, node, c ); +		} +		else if( !( c = xt_find_node( node->children, "query" ) ) || +		         !( s = xt_find_attr( c, "xmlns" ) ) ) +		{ +			return XT_HANDLED; +		} +		else if( strcmp( s, XMLNS_ROSTER ) == 0 ) +		{ +		/* This is a roster push. XMPP servers send this when someone +		   was added to (or removed from) the buddy list. AFAIK they're +		   sent even if we added this buddy in our own session. */ +			int bare_len = strlen( ic->acc->user ); +			 +			if( ( s = xt_find_attr( node, "from" ) ) == NULL || +			    ( strncmp( s, ic->acc->user, bare_len ) == 0 && +			      ( s[bare_len] == 0 || s[bare_len] == '/' ) ) ) +			{ +				jabber_parse_roster( ic, node, NULL ); +				 +				/* Should we generate a reply here? Don't think it's +				   very important... */ +			} +			else +			{ +				imcb_log( ic, "Warning: %s tried to fake a roster push!", s ? s : "(unknown)" ); +				 +				xt_free_node( reply ); +				reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL ); +				pack = 0; +			} +		} +		else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) +		{ +			/* Bytestream Request (stage 2 of file transfer) */ +			return jabber_bs_recv_request( ic, node, c ); +		} +		else +		{ +			xt_free_node( reply ); +			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); +			pack = 0; +		} +	} +	 +	/* If we recognized the xmlns and managed to generate a reply, +	   finish and send it. */ +	if( reply ) +	{ +		/* Normally we still have to pack it into an iq-result +		   packet, but for errors, for example, we don't. */ +		if( pack ) +		{ +			reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), reply ); +			if( ( s = xt_find_attr( node, "id" ) ) ) +				xt_add_attr( reply, "id", s ); +		} +		 +		st = jabber_write_packet( ic, reply ); +		xt_free_node( reply ); +		if( !st ) +			return XT_ABORT; +	} +	 +	return XT_HANDLED; +} + +static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +int jabber_init_iq_auth( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct xt_node *node; +	int st; +	 +	node = xt_new_node( "query", NULL, xt_new_node( "username", jd->username, NULL ) ); +	xt_add_attr( node, "xmlns", XMLNS_AUTH ); +	node = jabber_make_packet( "iq", "get", NULL, node ); +	 +	jabber_cache_add( ic, node, jabber_do_iq_auth ); +	st = jabber_write_packet( ic, node ); +	 +	return st; +} + +static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct xt_node *reply, *query; +	xt_status st; +	char *s; +	 +	if( !( query = xt_find_node( node->children, "query" ) ) ) +	{ +		imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" ); +		imc_logout( ic, FALSE ); +		return XT_HANDLED; +	} +	 +	/* Time to authenticate ourselves! */ +	reply = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( reply, "xmlns", XMLNS_AUTH ); +	xt_add_child( reply, xt_new_node( "username", jd->username, NULL ) ); +	xt_add_child( reply, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) ); +	 +	if( xt_find_node( query->children, "digest" ) && ( s = xt_find_attr( jd->xt->root, "id" ) ) ) +	{ +		/* We can do digest authentication, it seems, and of +		   course we prefer that. */ +		sha1_state_t sha; +		char hash_hex[41]; +		unsigned char hash[20]; +		int i; +		 +		sha1_init( &sha ); +		sha1_append( &sha, (unsigned char*) s, strlen( s ) ); +		sha1_append( &sha, (unsigned char*) ic->acc->pass, strlen( ic->acc->pass ) ); +		sha1_finish( &sha, hash ); +		 +		for( i = 0; i < 20; i ++ ) +			sprintf( hash_hex + i * 2, "%02x", hash[i] ); +		 +		xt_add_child( reply, xt_new_node( "digest", hash_hex, NULL ) ); +	} +	else if( xt_find_node( query->children, "password" ) ) +	{ +		/* We'll have to stick with plaintext. Let's hope we're using SSL/TLS... */ +		xt_add_child( reply, xt_new_node( "password", ic->acc->pass, NULL ) ); +	} +	else +	{ +		xt_free_node( reply ); +		 +		imcb_error( ic, "Can't find suitable authentication method" ); +		imc_logout( ic, FALSE ); +		return XT_ABORT; +	} +	 +	reply = jabber_make_packet( "iq", "set", NULL, reply ); +	jabber_cache_add( ic, reply, jabber_finish_iq_auth ); +	st = jabber_write_packet( ic, reply ); +	 +	return st ? XT_HANDLED : XT_ABORT; +} + +static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct jabber_data *jd = ic->proto_data; +	char *type; +	 +	if( !( type = xt_find_attr( node, "type" ) ) ) +	{ +		imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" ); +		imc_logout( ic, FALSE ); +		return XT_HANDLED; +	} +	 +	if( strcmp( type, "error" ) == 0 ) +	{ +		imcb_error( ic, "Authentication failure" ); +		imc_logout( ic, FALSE ); +		return XT_ABORT; +	} +	else if( strcmp( type, "result" ) == 0 ) +	{ +		/* This happens when we just successfully authenticated the +		   old (non-SASL) way. */ +		jd->flags |= JFLAG_AUTHENTICATED; +		if( !jabber_get_roster( ic ) ) +			return XT_ABORT; +	} +	 +	return XT_HANDLED; +} + +xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct xt_node *c, *reply = NULL; +	char *s; +	 +	if( node && ( c = xt_find_node( node->children, "bind" ) ) ) +	{ +		c = xt_find_node( c->children, "jid" ); +		if( c && c->text_len && ( s = strchr( c->text, '/' ) ) && +		    strcmp( s + 1, set_getstr( &ic->acc->set, "resource" ) ) != 0 ) +			imcb_log( ic, "Server changed session resource string to `%s'", s + 1 ); +	} +	 +	if( jd->flags & JFLAG_WANT_BIND ) +	{ +		reply = xt_new_node( "bind", NULL, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) ); +		xt_add_attr( reply, "xmlns", XMLNS_BIND ); +		jd->flags &= ~JFLAG_WANT_BIND; +	} +	else if( jd->flags & JFLAG_WANT_SESSION ) +	{ +		reply = xt_new_node( "session", NULL, NULL ); +		xt_add_attr( reply, "xmlns", XMLNS_SESSION ); +		jd->flags &= ~JFLAG_WANT_SESSION; +	} +	 +	if( reply != NULL ) +	{ +		reply = jabber_make_packet( "iq", "set", NULL, reply ); +		jabber_cache_add( ic, reply, jabber_pkt_bind_sess ); +		 +		if( !jabber_write_packet( ic, reply ) ) +			return XT_ABORT; +	} +	else if( ( jd->flags & ( JFLAG_WANT_BIND | JFLAG_WANT_SESSION ) ) == 0 ) +	{ +		if( !jabber_get_roster( ic ) ) +			return XT_ABORT; +	} +	 +	return XT_HANDLED; +} + +int jabber_get_roster( struct im_connection *ic ) +{ +	struct xt_node *node; +	int st; +	 +	imcb_log( ic, "Authenticated, requesting buddy list" ); +	 +	node = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( node, "xmlns", XMLNS_ROSTER ); +	node = jabber_make_packet( "iq", "get", NULL, node ); +	 +	jabber_cache_add( ic, node, jabber_parse_roster ); +	st = jabber_write_packet( ic, node ); +	 +	return st; +} + +static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *query, *c; +	int initial = ( orig != NULL ); +	 +	if( !( query = xt_find_node( node->children, "query" ) ) ) +	{ +		imcb_log( ic, "Warning: Received NULL roster packet" ); +		return XT_HANDLED; +	} +	 +	c = query->children; +	while( ( c = xt_find_node( c, "item" ) ) ) +	{ +		struct xt_node *group = xt_find_node( c->children, "group" ); +		char *jid = xt_find_attr( c, "jid" ); +		char *name = xt_find_attr( c, "name" ); +		char *sub = xt_find_attr( c, "subscription" ); +		 +		if( jid && sub ) +		{ +			if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) ) +			{ +				imcb_add_buddy( ic, jid, ( group && group->text_len ) ? +				                           group->text : NULL ); +				 +				if( name ) +					imcb_rename_buddy( ic, jid, name ); +			} +			else if( strcmp( sub, "remove" ) == 0 ) +			{ +				jabber_buddy_remove_bare( ic, jid ); +				imcb_remove_buddy( ic, jid, NULL ); +			} +		} +		 +		c = c->next; +	} +	 +	if( initial ) +		imcb_connected( ic ); +	 +	return XT_HANDLED; +} + +int jabber_get_vcard( struct im_connection *ic, char *bare_jid ) +{ +	struct xt_node *node; +	 +	if( strchr( bare_jid, '/' ) ) +		return 1;	/* This was an error, but return 0 should only be done if the connection died... */ +	 +	node = xt_new_node( "vCard", NULL, NULL ); +	xt_add_attr( node, "xmlns", XMLNS_VCARD ); +	node = jabber_make_packet( "iq", "get", bare_jid, node ); +	 +	jabber_cache_add( ic, node, jabber_iq_display_vcard ); +	return jabber_write_packet( ic, node ); +} + +static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *vc, *c, *sc; /* subchild, ic is already in use ;-) */ +	GString *reply; +	char *s; +	 +	if( ( s = xt_find_attr( node, "type" ) ) == NULL || +	    strcmp( s, "result" ) != 0 || +	    ( vc = xt_find_node( node->children, "vCard" ) ) == NULL ) +	{ +		s = xt_find_attr( orig, "to" ); /* If this returns NULL something's wrong.. */ +		imcb_log( ic, "Could not retrieve vCard of %s", s ? s : "(NULL)" ); +		return XT_HANDLED; +	} +	 +	s = xt_find_attr( orig, "to" ); +	reply = g_string_new( "vCard information for " ); +	reply = g_string_append( reply, s ? s : "(NULL)" ); +	reply = g_string_append( reply, ":\n" ); +	 +	/* I hate this format, I really do... */ +	 +	if( ( c = xt_find_node( vc->children, "FN" ) ) && c->text_len ) +		g_string_append_printf( reply, "Name: %s\n", c->text ); +	 +	if( ( c = xt_find_node( vc->children, "N" ) ) && c->children ) +	{ +		reply = g_string_append( reply, "Full name:" ); +		 +		if( ( sc = xt_find_node( c->children, "PREFIX" ) ) && sc->text_len ) +			g_string_append_printf( reply, " %s", sc->text ); +		if( ( sc = xt_find_node( c->children, "GIVEN" ) ) && sc->text_len ) +			g_string_append_printf( reply, " %s", sc->text ); +		if( ( sc = xt_find_node( c->children, "MIDDLE" ) ) && sc->text_len ) +			g_string_append_printf( reply, " %s", sc->text ); +		if( ( sc = xt_find_node( c->children, "FAMILY" ) ) && sc->text_len ) +			g_string_append_printf( reply, " %s", sc->text ); +		if( ( sc = xt_find_node( c->children, "SUFFIX" ) ) && sc->text_len ) +			g_string_append_printf( reply, " %s", sc->text ); +		 +		reply = g_string_append_c( reply, '\n' ); +	} +	 +	if( ( c = xt_find_node( vc->children, "NICKNAME" ) ) && c->text_len ) +		g_string_append_printf( reply, "Nickname: %s\n", c->text ); +	 +	if( ( c = xt_find_node( vc->children, "BDAY" ) ) && c->text_len ) +		g_string_append_printf( reply, "Date of birth: %s\n", c->text ); +	 +	/* Slightly alternative use of for... ;-) */ +	for( c = vc->children; ( c = xt_find_node( c, "EMAIL" ) ); c = c->next ) +	{ +		if( ( sc = xt_find_node( c->children, "USERID" ) ) == NULL || sc->text_len == 0 ) +			continue; +		 +		if( xt_find_node( c->children, "HOME" ) ) +			s = "Home"; +		else if( xt_find_node( c->children, "WORK" ) ) +			s = "Work"; +		else +			s = "Misc."; +		 +		g_string_append_printf( reply, "%s e-mail address: %s\n", s, sc->text ); +	} +	 +	if( ( c = xt_find_node( vc->children, "URL" ) ) && c->text_len ) +		g_string_append_printf( reply, "Homepage: %s\n", c->text ); +	 +	/* Slightly alternative use of for... ;-) */ +	for( c = vc->children; ( c = xt_find_node( c, "ADR" ) ); c = c->next ) +	{ +		if( xt_find_node( c->children, "HOME" ) ) +			s = "Home"; +		else if( xt_find_node( c->children, "WORK" ) ) +			s = "Work"; +		else +			s = "Misc."; +		 +		g_string_append_printf( reply, "%s address: ", s ); +		 +		if( ( sc = xt_find_node( c->children, "STREET" ) ) && sc->text_len ) +			g_string_append_printf( reply, "%s ", sc->text ); +		if( ( sc = xt_find_node( c->children, "EXTADR" ) ) && sc->text_len ) +			g_string_append_printf( reply, "%s, ", sc->text ); +		if( ( sc = xt_find_node( c->children, "PCODE" ) ) && sc->text_len ) +			g_string_append_printf( reply, "%s, ", sc->text ); +		if( ( sc = xt_find_node( c->children, "LOCALITY" ) ) && sc->text_len ) +			g_string_append_printf( reply, "%s, ", sc->text ); +		if( ( sc = xt_find_node( c->children, "REGION" ) ) && sc->text_len ) +			g_string_append_printf( reply, "%s, ", sc->text ); +		if( ( sc = xt_find_node( c->children, "CTRY" ) ) && sc->text_len ) +			g_string_append_printf( reply, "%s", sc->text ); +		 +		if( reply->str[reply->len-2] == ',' ) +			reply = g_string_truncate( reply, reply->len-2 ); +		 +		reply = g_string_append_c( reply, '\n' ); +	} +	 +	for( c = vc->children; ( c = xt_find_node( c, "TEL" ) ); c = c->next ) +	{ +		if( ( sc = xt_find_node( c->children, "NUMBER" ) ) == NULL || sc->text_len == 0 ) +			continue; +		 +		if( xt_find_node( c->children, "HOME" ) ) +			s = "Home"; +		else if( xt_find_node( c->children, "WORK" ) ) +			s = "Work"; +		else +			s = "Misc."; +		 +		g_string_append_printf( reply, "%s phone number: %s\n", s, sc->text ); +	} +	 +	if( ( c = xt_find_node( vc->children, "DESC" ) ) && c->text_len ) +		g_string_append_printf( reply, "Other information:\n%s", c->text ); +	 +	/* *sigh* */ +	 +	imcb_log( ic, "%s", reply->str ); +	g_string_free( reply, TRUE ); +	 +	return XT_HANDLED; +} + +static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ) +{ +	struct xt_node *node; +	int st; +	 +	/* Build the item entry */ +	node = xt_new_node( "item", NULL, NULL ); +	xt_add_attr( node, "jid", handle ); +	if( name ) +		xt_add_attr( node, "name", name ); +	if( group ) +		xt_add_child( node, xt_new_node( "group", group, NULL ) ); +	 +	/* And pack it into a roster-add packet */ +	node = xt_new_node( "query", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_ROSTER ); +	node = jabber_make_packet( "iq", "set", NULL, node ); +	jabber_cache_add( ic, node, jabber_add_to_roster_callback ); +	 +	st = jabber_write_packet( ic, node ); +	 +	return st; +} + +static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	char *s, *jid = NULL; +	struct xt_node *c; +	 +	if( ( c = xt_find_node( orig->children, "query" ) ) && +	    ( c = xt_find_node( c->children, "item" ) ) && +	    ( jid = xt_find_attr( c, "jid" ) ) && +	    ( s = xt_find_attr( node, "type" ) ) && +	    strcmp( s, "result" ) == 0 ) +	{ +		if( bee_user_by_handle( ic->bee, ic, jid ) == NULL ) +			imcb_add_buddy( ic, jid, NULL ); +	} +	else +	{ +		imcb_log( ic, "Error while adding `%s' to your contact list.", +		          jid ? jid : "(unknown handle)" ); +	} +	 +	return XT_HANDLED; +} + +int jabber_remove_from_roster( struct im_connection *ic, char *handle ) +{ +	struct xt_node *node; +	int st; +	 +	/* Build the item entry */ +	node = xt_new_node( "item", NULL, NULL ); +	xt_add_attr( node, "jid", handle ); +	xt_add_attr( node, "subscription", "remove" ); +	 +	/* And pack it into a roster-add packet */ +	node = xt_new_node( "query", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_ROSTER ); +	node = jabber_make_packet( "iq", "set", NULL, node ); +	 +	st = jabber_write_packet( ic, node ); +	 +	xt_free_node( node ); +	return st; +} + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) +{ +	struct xt_node *node, *query; +	struct jabber_buddy *bud; +	 +	if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL ) +	{ +		/* Who cares about the unknown... */ +		imcb_log( ic, "Couldn't find buddy: %s", bare_jid); +		return XT_HANDLED; +	} +	 +	if( bud->features ) /* been here already */ +		return XT_HANDLED; +	 +	node = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO ); +	 +	if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) ) +	{ +		imcb_log( ic, "WARNING: Couldn't generate feature query" ); +		xt_free_node( node ); +		return XT_HANDLED; +	} + +	jabber_cache_add( ic, query, jabber_iq_parse_features ); + +	return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; +} + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *c; +	struct jabber_buddy *bud; +	char *feature, *xmlns, *from; + +	if( !( from = xt_find_attr( node, "from" ) ) || +	    !( c = xt_find_node( node->children, "query" ) ) || +	    !( xmlns = xt_find_attr( c, "xmlns" ) ) || +	    !( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); +		return XT_HANDLED; +	} +	if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) +	{ +		/* Who cares about the unknown... */ +		imcb_log( ic, "Couldn't find buddy: %s", from ); +		return XT_HANDLED; +	} +	 +	c = c->children; +	while( ( c = xt_find_node( c, "feature" ) ) ) +	{ +		feature = xt_find_attr( c, "var" ); +		if( feature ) +			bud->features = g_slist_append( bud->features, g_strdup( feature ) ); +		c = c->next; +	} + +	return XT_HANDLED; +} + +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ) +{ +	struct xt_node *node, *query; +	struct jabber_data *jd = ic->proto_data; +	 +	node = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( node, "xmlns", xmlns ); +	 +	if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) ) +	{ +		imcb_log( ic, "WARNING: Couldn't generate server query" ); +		xt_free_node( node ); +	} + +	jd->have_streamhosts--; +	jabber_cache_add( ic, query, jabber_iq_parse_server_features ); + +	return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; +} + +/* + * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info + */ +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *c; +	struct jabber_data *jd = ic->proto_data; +	char *xmlns, *from; + +	if( !( c = xt_find_node( node->children, "query" ) ) || +	    !( from = xt_find_attr( node, "from" ) ) || +	    !( xmlns = xt_find_attr( c, "xmlns" ) ) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); +		return XT_HANDLED; +	} + +	jd->have_streamhosts++; + +	if( strcmp( xmlns, XMLNS_DISCO_ITEMS ) == 0 ) +	{ +		char *itemjid; + +		/* answer from server */ +	 +		c = c->children; +		while( ( c = xt_find_node( c, "item" ) ) ) +		{ +			itemjid = xt_find_attr( c, "jid" ); +			 +			if( itemjid ) +				jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO ); + +			c = c->next; +		} +	} +	else if( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) +	{ +		char *category, *type; + +		/* answer from potential proxy */ + +		c = c->children; +		while( ( c = xt_find_node( c, "identity" ) ) ) +		{ +			category = xt_find_attr( c, "category" ); +			type = xt_find_attr( c, "type" ); + +			if( type && ( strcmp( type, "bytestreams" ) == 0 ) && +			    category && ( strcmp( category, "proxy" ) == 0 ) ) +				jabber_iq_query_server( ic, from, XMLNS_BYTESTREAMS ); + +			c = c->next; +		} +	} +	else if( strcmp( xmlns, XMLNS_BYTESTREAMS ) == 0 ) +	{ +		char *host, *jid, *port_s; +		int port; + +		/* answer from proxy */ + +		if( ( c = xt_find_node( c->children, "streamhost" ) ) && +		    ( host = xt_find_attr( c, "host" ) ) && +		    ( port_s = xt_find_attr( c, "port" ) ) && +		    ( sscanf( port_s, "%d", &port ) == 1 ) && +		    ( jid = xt_find_attr( c, "jid" ) ) ) +		{ +			jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); +			 +			sh->jid = g_strdup( jid ); +			sh->host = g_strdup( host ); +			g_snprintf( sh->port, sizeof( sh->port ), "%u", port ); + +			imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port ); +			jd->streamhosts = g_slist_append( jd->streamhosts, sh ); +		} +	} + +	if( jd->have_streamhosts == 0 ) +		jd->have_streamhosts++; + +	return XT_HANDLED; +} + +static xt_status jabber_iq_version_response( struct im_connection *ic, +	struct xt_node *node, struct xt_node *orig ); + +void jabber_iq_version_send( struct im_connection *ic, struct jabber_buddy *bud, void *data ) +{ +	struct xt_node *node, *query; +	 +	node = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( node, "xmlns", XMLNS_VERSION ); +	query = jabber_make_packet( "iq", "get", bud->full_jid, node ); +	jabber_cache_add( ic, query, jabber_iq_version_response ); + +	jabber_write_packet( ic, query ); +} + +static xt_status jabber_iq_version_response( struct im_connection *ic, +	struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *query; +	GString *rets; +	char *s; +	char *ret[2] = {}; +	bee_user_t *bu; +	struct jabber_buddy *bud = NULL; +	 +	if( ( s = xt_find_attr( node, "from" ) ) && +	    ( bud = jabber_buddy_by_jid( ic, s, 0 ) ) && +	    ( query = xt_find_node( node->children, "query" ) ) && +	    ( bu = bee_user_by_handle( ic->bee, ic, bud->bare_jid ) ) ) +	{ +		rets = g_string_new( "Resource " ); +		g_string_append( rets, bud->resource ); +	} +	else +		return XT_HANDLED; +	 +	for( query = query->children; query; query = query->next ) +		if( query->text_len > 0 ) +			g_string_append_printf( rets, " %s: %s,", query->name, query->text ); +	 +	g_string_truncate( rets, rets->len - 1 ); +	ret[0] = rets->str; +	imcb_buddy_action_response( bu, "VERSION", ret, NULL ); +	g_string_free( rets, TRUE ); +	 +	return XT_HANDLED; +} diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c new file mode 100644 index 00000000..802158c1 --- /dev/null +++ b/protocols/jabber/jabber.c @@ -0,0 +1,628 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - Main file                                                * +*                                                                           * +*  Copyright 2006 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 <glib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <stdio.h> + +#include "ssl_client.h" +#include "xmltree.h" +#include "bitlbee.h" +#include "jabber.h" +#include "md5.h" + +GSList *jabber_connections; + +/* First enty is the default */ +static const int jabber_port_list[] = { +	5222, +	5223, +	5220, +	5221, +	5224, +	5225, +	5226, +	5227, +	5228, +	5229, +	80, +	443, +	0 +}; + +static void jabber_init( account_t *acc ) +{ +	set_t *s; +	char str[16]; +	 +	s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc ); +	 +	g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] ); +	s = set_add( &acc->set, "port", str, set_eval_int, acc ); +	s->flags |= ACC_SET_OFFLINE_ONLY; +	 +	s = set_add( &acc->set, "priority", "0", set_eval_priority, acc ); + +	s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc ); +	 +	s = set_add( &acc->set, "resource", "BitlBee", NULL, acc ); +	s->flags |= ACC_SET_OFFLINE_ONLY; +	 +	s = set_add( &acc->set, "resource_select", "activity", NULL, acc ); +	 +	s = set_add( &acc->set, "server", NULL, set_eval_account, acc ); +	s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK; +	 +	s = set_add( &acc->set, "ssl", "false", set_eval_bool, acc ); +	s->flags |= ACC_SET_OFFLINE_ONLY; +	 +	s = set_add( &acc->set, "tls", "try", set_eval_tls, acc ); +	s->flags |= ACC_SET_OFFLINE_ONLY; +	 +	s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc ); +	 +	s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc ); +	s->flags |= ACC_SET_OFFLINE_ONLY; +	 +	acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE; +} + +static void jabber_generate_id_hash( struct jabber_data *jd ); + +static void jabber_login( account_t *acc ) +{ +	struct im_connection *ic = imcb_new( acc ); +	struct jabber_data *jd = g_new0( struct jabber_data, 1 ); +	struct ns_srv_reply **srvl = NULL, *srv = NULL; +	char *connect_to, *s; +	int i; +	 +	/* For now this is needed in the _connected() handlers if using +	   GLib event handling, to make sure we're not handling events +	   on dead connections. */ +	jabber_connections = g_slist_prepend( jabber_connections, ic ); +	 +	jd->ic = ic; +	ic->proto_data = jd; +	 +	jd->username = g_strdup( acc->user ); +	jd->server = strchr( jd->username, '@' ); +	 +	jd->fd = jd->r_inpa = jd->w_inpa = -1; +	 +	if( jd->server == NULL ) +	{ +		imcb_error( ic, "Incomplete account name (format it like <username@jabberserver.name>)" ); +		imc_logout( ic, FALSE ); +		return; +	} +	 +	/* So don't think of free()ing jd->server.. :-) */ +	*jd->server = 0; +	jd->server ++; +	 +	if( ( s = strchr( jd->server, '/' ) ) ) +	{ +		*s = 0; +		set_setstr( &acc->set, "resource", s + 1 ); +		 +		/* Also remove the /resource from the original variable so we +		   won't have to do this again every time. */ +		s = strchr( acc->user, '/' ); +		*s = 0; +	} +	 +	/* This code isn't really pretty. Backwards compatibility never is... */ +	s = acc->server; +	while( s ) +	{ +		static int had_port = 0; +		 +		if( strncmp( s, "ssl", 3 ) == 0 ) +		{ +			set_setstr( &acc->set, "ssl", "true" ); +			 +			/* Flush this part so that (if this was the first +			   part of the server string) acc->server gets +			   flushed. We don't want to have to do this another +			   time. :-) */ +			*s = 0; +			s ++; +			 +			/* Only set this if the user didn't specify a custom +			   port number already... */ +			if( !had_port ) +				set_setint( &acc->set, "port", 5223 ); +		} +		else if( isdigit( *s ) ) +		{ +			int i; +			 +			/* The first character is a digit. It could be an +			   IP address though. Only accept this as a port# +			   if there are only digits. */ +			for( i = 0; isdigit( s[i] ); i ++ ); +			 +			/* If the first non-digit character is a colon or +			   the end of the string, save the port number +			   where it should be. */ +			if( s[i] == ':' || s[i] == 0 ) +			{ +				sscanf( s, "%d", &i ); +				set_setint( &acc->set, "port", i ); +				 +				/* See above. */ +				*s = 0; +				s ++; +			} +			 +			had_port = 1; +		} +		 +		s = strchr( s, ':' ); +		if( s ) +		{ +			*s = 0; +			s ++; +		} +	} +	 +	jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free ); +	jd->buddies = g_hash_table_new( g_str_hash, g_str_equal ); +	 +	/* Figure out the hostname to connect to. */ +	if( acc->server && *acc->server ) +		connect_to = acc->server; +	else if( ( srvl = srv_lookup( "xmpp-client", "tcp", jd->server ) ) || +	         ( srvl = srv_lookup( "jabber-client", "tcp", jd->server ) ) ) +	{ +		/* Find the lowest-priority one. These usually come +		   back in random/shuffled order. Not looking at +		   weights etc for now. */ +		srv = *srvl; +		for( i = 1; srvl[i]; i ++ ) +			if( srvl[i]->prio < srv->prio ) +				srv = srvl[i]; +		 +		connect_to = srv->name; +	} +	else +		connect_to = jd->server; +	 +	imcb_log( ic, "Connecting" ); +	 +	for( i = 0; jabber_port_list[i] > 0; i ++ ) +		if( set_getint( &acc->set, "port" ) == jabber_port_list[i] ) +			break; + +	if( jabber_port_list[i] == 0 ) +	{ +		imcb_log( ic, "Illegal port number" ); +		imc_logout( ic, FALSE ); +		return; +	} +	 +	/* For non-SSL connections we can try to use the port # from the SRV +	   reply, but let's not do that when using SSL, SSL usually runs on +	   non-standard ports... */ +	if( set_getbool( &acc->set, "ssl" ) ) +	{ +		jd->ssl = ssl_connect( connect_to, set_getint( &acc->set, "port" ), jabber_connected_ssl, ic ); +		jd->fd = jd->ssl ? ssl_getfd( jd->ssl ) : -1; +	} +	else +	{ +		jd->fd = proxy_connect( connect_to, srv ? srv->port : set_getint( &acc->set, "port" ), jabber_connected_plain, ic ); +	} +	srv_free( srvl ); +	 +	if( jd->fd == -1 ) +	{ +		imcb_error( ic, "Could not connect to server" ); +		imc_logout( ic, TRUE ); +		 +		return; +	} +	 +	if( set_getbool( &acc->set, "xmlconsole" ) ) +	{ +		jd->flags |= JFLAG_XMLCONSOLE; +		/* Shouldn't really do this at this stage already, maybe. But +		   I think this shouldn't break anything. */ +		imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL ); +	} +	 +	jabber_generate_id_hash( jd ); +} + +/* This generates an unfinished md5_state_t variable. Every time we generate +   an ID, we finish the state by adding a sequence number and take the hash. */ +static void jabber_generate_id_hash( struct jabber_data *jd ) +{ +	md5_byte_t binbuf[4]; +	char *s; +	 +	md5_init( &jd->cached_id_prefix ); +	md5_append( &jd->cached_id_prefix, (unsigned char *) jd->username, strlen( jd->username ) ); +	md5_append( &jd->cached_id_prefix, (unsigned char *) jd->server, strlen( jd->server ) ); +	s = set_getstr( &jd->ic->acc->set, "resource" ); +	md5_append( &jd->cached_id_prefix, (unsigned char *) s, strlen( s ) ); +	random_bytes( binbuf, 4 ); +	md5_append( &jd->cached_id_prefix, binbuf, 4 ); +} + +static void jabber_logout( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	 +	while( jd->filetransfers ) +		imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" ); + +	while( jd->streamhosts ) +	{ +		jabber_streamhost_t *sh = jd->streamhosts->data; +		jd->streamhosts = g_slist_remove( jd->streamhosts, sh ); +		g_free( sh->jid ); +		g_free( sh->host ); +		g_free( sh ); +	} + +	if( jd->fd >= 0 ) +		jabber_end_stream( ic ); +	 +	while( ic->groupchats ) +		jabber_chat_free( ic->groupchats->data ); +	 +	if( jd->r_inpa >= 0 ) +		b_event_remove( jd->r_inpa ); +	if( jd->w_inpa >= 0 ) +		b_event_remove( jd->w_inpa ); +	 +	if( jd->ssl ) +		ssl_disconnect( jd->ssl ); +	if( jd->fd >= 0 ) +		closesocket( jd->fd ); +	 +	if( jd->tx_len ) +		g_free( jd->txq ); +	 +	if( jd->node_cache ) +		g_hash_table_destroy( jd->node_cache ); +	 +	jabber_buddy_remove_all( ic ); +	 +	xt_free( jd->xt ); +	 +	g_free( jd->away_message ); +	g_free( jd->username ); +	g_free( jd ); +	 +	jabber_connections = g_slist_remove( jabber_connections, ic ); +} + +static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct jabber_buddy *bud; +	struct xt_node *node; +	char *s; +	int st; +	 +	if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) +		return jabber_write( ic, message, strlen( message ) ); +	 +	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) +		bud = jabber_buddy_by_ext_jid( ic, who, 0 ); +	else +		bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK ); +	 +	node = xt_new_node( "body", message, NULL ); +	node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node ); +	 +	if( bud && ( jd->flags & JFLAG_WANT_TYPING ) && +	    ( ( bud->flags & JBFLAG_DOES_XEP85 ) || +	     !( bud->flags & JBFLAG_PROBED_XEP85 ) ) ) +	{ +		struct xt_node *act; +		 +		/* If the user likes typing notification and if we don't know +		   (and didn't probe before) if this resource supports XEP85, +		   include a probe in this packet now. Also, if we know this +		   buddy does support XEP85, we have to send this <active/> +		   tag to tell that the user stopped typing (well, that's what +		   we guess when s/he pressed Enter...). */ +		act = xt_new_node( "active", NULL, NULL ); +		xt_add_attr( act, "xmlns", XMLNS_CHATSTATES ); +		xt_add_child( node, act ); +		 +		/* Just make sure we do this only once. */ +		bud->flags |= JBFLAG_PROBED_XEP85; +	} +	 +	st = jabber_write_packet( ic, node ); +	xt_free_node( node ); +	 +	return st; +} + +static GList *jabber_away_states( struct im_connection *ic ) +{ +	static GList *l = NULL; +	int i; +	 +	if( l == NULL ) +		for( i = 0; jabber_away_state_list[i].full_name; i ++ ) +			l = g_list_append( l, (void*) jabber_away_state_list[i].full_name ); +	 +	return l; +} + +static void jabber_get_info( struct im_connection *ic, char *who ) +{ +	struct jabber_buddy *bud; +	 +	bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST ); +	 +	while( bud ) +	{ +		imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority ); +		if( bud->away_state ) +			imcb_log( ic, "Away state: %s", bud->away_state->full_name ); +		imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" ); +		 +		bud = bud->next; +	} +	 +	jabber_get_vcard( ic, bud ? bud->full_jid : who ); +} + +static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message ) +{ +	struct jabber_data *jd = ic->proto_data; +	 +	/* state_txt == NULL -> Not away. +	   Unknown state -> fall back to the first defined away state. */ +	if( state_txt == NULL ) +		jd->away_state = NULL; +	else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL ) +		jd->away_state = jabber_away_state_list; +	 +	g_free( jd->away_message ); +	jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL; +	 +	presence_send_update( ic ); +} + +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, group ) ) +		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 ); +	 +	if( jabber_remove_from_roster( ic, who ) ) +		presence_send_request( ic, who, "unsubscribe" ); +} + +static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets ) +{ +	if( strchr( room, '@' ) == NULL ) +		imcb_error( ic, "Invalid room name: %s", room ); +	else if( jabber_chat_by_jid( ic, room ) ) +		imcb_error( ic, "Already present in chat `%s'", room ); +	else +		return jabber_chat_join( ic, room, nick, set_getstr( sets, "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_topic_( struct groupchat *c, char *topic ) +{ +	if( c && topic ) +		jabber_chat_topic( c, topic ); +} + +static void jabber_chat_leave_( struct groupchat *c ) +{ +	if( c ) +		jabber_chat_leave( c, NULL ); +} + +static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg ) +{ +	struct jabber_chat *jc = c->data; +	gchar *msg_alt = NULL; + +	if( msg == NULL ) +		msg_alt = g_strdup_printf( "%s invited you to %s", c->ic->acc->user, jc->name ); +	 +	if( c && who ) +		jabber_chat_invite( c, who, msg ? msg : msg_alt ); +	 +	g_free( msg_alt ); +} + +static void jabber_keepalive( struct im_connection *ic ) +{ +	/* Just any whitespace character is enough as a keepalive for XMPP sessions. */ +	if( !jabber_write( ic, "\n", 1 ) ) +		return; +	 +	/* This runs the garbage collection every minute, which means every packet +	   is in the cache for about a minute (which should be enough AFAIK). */ +	jabber_cache_clean( ic ); +} + +static int jabber_send_typing( struct im_connection *ic, char *who, int typing ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct jabber_buddy *bud; +	 +	/* Enable typing notification related code from now. */ +	jd->flags |= JFLAG_WANT_TYPING; +	 +	if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL ) +	{ +		/* Sending typing notifications to unknown buddies is +		   unsupported for now. Shouldn't be a problem, I think. */ +		return 0; +	} +	 +	if( bud->flags & JBFLAG_DOES_XEP85 ) +	{ +		/* We're only allowed to send this stuff if we know the other +		   side supports it. */ +		 +		struct xt_node *node; +		char *type; +		int st; +		 +		if( typing & OPT_TYPING ) +			type = "composing"; +		else if( typing & OPT_THINKING ) +			type = "paused"; +		else +			type = "active"; +		 +		node = xt_new_node( type, NULL, NULL ); +		xt_add_attr( node, "xmlns", XMLNS_CHATSTATES ); +		node = jabber_make_packet( "message", "chat", bud->full_jid, node ); +		 +		st = jabber_write_packet( ic, node ); +		xt_free_node( node ); +		 +		return st; +	} +	 +	return 1; +} + +void jabber_chat_add_settings( account_t *acc, set_t **head ) +{ +	/* Meh. Stupid room passwords. Not trying to obfuscate/hide +	   them from the user for now. */ +	set_add( head, "password", NULL, NULL, NULL ); +} + +void jabber_chat_free_settings( account_t *acc, set_t **head ) +{ +	set_del( head, "password" ); +} + +GList *jabber_buddy_action_list( bee_user_t *bu ) +{ +	static GList *ret = NULL; +	 +	if( ret == NULL ) +	{ +		static const struct buddy_action ba[2] = { +			{ "VERSION", "Get client (version) information" }, +		}; +		 +		ret = g_list_prepend( ret, (void*) ba + 0 ); +	} +	 +	return ret; +} + +void *jabber_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data ) +{ +	if( g_strcasecmp( action, "VERSION" ) == 0 ) +	{ +		struct jabber_buddy *bud; +		 +		if( ( bud = jabber_buddy_by_ext_jid( bu->ic, bu->handle, 0 ) ) == NULL ) +			bud = jabber_buddy_by_jid( bu->ic, bu->handle, GET_BUDDY_FIRST ); +		for( ; bud; bud = bud->next ) +			jabber_iq_version_send( bu->ic, bud, data ); +	} +	 +	return NULL; +} + +void jabber_initmodule() +{ +	struct prpl *ret = g_new0( struct prpl, 1 ); +	 +	ret->name = "jabber"; +	ret->mms = 0;                        /* no limit */ +	ret->login = jabber_login; +	ret->init = jabber_init; +	ret->logout = jabber_logout; +	ret->buddy_msg = jabber_buddy_msg; +	ret->away_states = jabber_away_states; +	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_topic = jabber_chat_topic_; +	ret->chat_invite = jabber_chat_invite_; +	ret->chat_leave = jabber_chat_leave_; +	ret->chat_join = jabber_chat_join_; +	ret->chat_add_settings = jabber_chat_add_settings; +	ret->chat_free_settings = jabber_chat_free_settings; +	ret->keepalive = jabber_keepalive; +	ret->send_typing = jabber_send_typing; +	ret->handle_cmp = g_strcasecmp; +	ret->transfer_request = jabber_si_transfer_request; +	ret->buddy_action_list = jabber_buddy_action_list; +	ret->buddy_action = jabber_buddy_action; + +	register_protocol( ret ); +} diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h new file mode 100644 index 00000000..adf9a291 --- /dev/null +++ b/protocols/jabber/jabber.h @@ -0,0 +1,330 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - Main file                                                * +*                                                                           * +*  Copyright 2006 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.              * +*                                                                           * +\***************************************************************************/ + +#ifndef _JABBER_H +#define _JABBER_H + +#include <glib.h> + +#include "bitlbee.h" +#include "md5.h" +#include "xmltree.h" + +extern GSList *jabber_connections; + +typedef enum +{ +	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 +	                                   SASL or TLS). */ +	JFLAG_WANT_SESSION = 8,	        /* Set if the server wants a <session/> tag +	                                   before we continue. */ +	JFLAG_WANT_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. */ +	JFLAG_STARTTLS_DONE = 128,      /* If a plaintext session was converted to TLS. */ +} jabber_flags_t; + +typedef enum +{ +	JBFLAG_PROBED_XEP85 = 1,        /* Set this when we sent our probe packet to make +	                                   sure it gets sent only once. */ +	JBFLAG_DOES_XEP85 = 2,          /* Set this when the resource seems to support +	                                   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. */ +	JBFLAG_HIDE_SUBJECT = 16,       /* Hide the subject field since we probably +	                                   showed it already. */ +} jabber_buddy_flags_t; + +/* Stores a streamhost's (a.k.a. proxy) data */ +typedef struct +{ +	char *jid; +	char *host; +	char port[6]; +} jabber_streamhost_t; + +typedef enum +{ +	JCFLAG_MESSAGE_SENT = 1,        /* Set this after sending the first message, so +	                                   we can detect echoes/backlogs. */ +} jabber_chat_flags_t; + +struct jabber_data +{ +	struct im_connection *ic; +	 +	int fd; +	void *ssl; +	char *txq; +	int tx_len; +	int r_inpa, w_inpa; +	 +	struct xt_parser *xt; +	jabber_flags_t flags; +	 +	char *username;		/* USERNAME@server */ +	char *server;		/* username@SERVER -=> server/domain, not hostname */ +	 +	/* After changing one of these two (or the priority setting), call +	   presence_send_update() to inform the server about the changes. */ +	const struct jabber_away_state *away_state; +	char *away_message; +	 +	md5_state_t cached_id_prefix; +	GHashTable *node_cache; +	GHashTable *buddies; + +	GSList *filetransfers; +	GSList *streamhosts; +	int have_streamhosts; +}; + +struct jabber_away_state +{ +	char code[5]; +	char *full_name; +}; + +typedef xt_status (*jabber_cache_event) ( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +struct jabber_cache_entry +{ +	time_t saved_at; +	struct xt_node *node; +	jabber_cache_event func; +}; + +/* Somewhat messy data structure: We have a hash table with the bare JID as +   the key and the head of a struct jabber_buddy list as the value. The head +   is always a bare JID. If the JID has other resources (often the case, +   except for some transports that don't support multiple resources), those +   follow. In that case, the bare JID at the beginning doesn't actually +   refer to a real session and should only be used for operations that +   support incomplete JIDs. */ +struct jabber_buddy +{ +	char *bare_jid; +	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; +	GSList *features; +	 +	time_t last_msg; +	jabber_buddy_flags_t flags; +	 +	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; +}; + +struct jabber_transfer +{ +	/* bitlbee's handle for this transfer */ +	file_transfer_t *ft; + +	/* the stream's private handle */ +	gpointer streamhandle; + +	/* timeout for discover queries */ +	gint disco_timeout; +	gint disco_timeout_fired; + +	struct im_connection *ic; + +	struct jabber_buddy *bud; + +	int watch_in; +	int watch_out; + +	char *ini_jid; +	char *tgt_jid; +	char *iq_id; +	char *sid; +	int accepted; + +	size_t bytesread, byteswritten; +	int fd; +	struct sockaddr_storage saddr; +}; + +#define JABBER_XMLCONSOLE_HANDLE "xmlconsole" + +/* 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 +   if we have to do call an event handler for the response packet. Also +   we'll append a hash to make sure we won't trigger on cached packets from +   other BitlBee users. :-) */ +#define JABBER_PACKET_ID "BeeP" +#define JABBER_CACHED_ID "BeeC" + +/* The number of seconds to keep cached packets before garbage collecting +   them. This gc is done on every keepalive (every minute). */ +#define JABBER_CACHE_MAX_AGE 600 + +/* RFC 392[01] stuff */ +#define XMLNS_TLS          "urn:ietf:params:xml:ns:xmpp-tls" +#define XMLNS_SASL         "urn:ietf:params:xml:ns:xmpp-sasl" +#define XMLNS_BIND         "urn:ietf:params:xml:ns:xmpp-bind" +#define XMLNS_SESSION      "urn:ietf:params:xml:ns:xmpp-session" +#define XMLNS_STANZA_ERROR "urn:ietf:params:xml:ns:xmpp-stanzas" +#define XMLNS_STREAM_ERROR "urn:ietf:params:xml:ns:xmpp-streams" +#define XMLNS_ROSTER       "jabber:iq:roster" + +/* Some supported extensions/legacy stuff */ +#define XMLNS_AUTH         "jabber:iq:auth"                                      /* XEP-0078 */ +#define XMLNS_VERSION      "jabber:iq:version"                                   /* XEP-0092 */ +#define XMLNS_TIME_OLD     "jabber:iq:time"                                      /* XEP-0090 */ +#define XMLNS_TIME         "urn:xmpp:time"                                       /* XEP-0202 */ +#define XMLNS_PING         "urn:xmpp:ping"                                       /* XEP-0199 */ +#define XMLNS_VCARD        "vcard-temp"                                          /* XEP-0054 */ +#define XMLNS_DELAY        "jabber:x:delay"                                      /* XEP-0091 */ +#define XMLNS_XDATA        "jabber:x:data"                                       /* XEP-0004 */ +#define XMLNS_CHATSTATES   "http://jabber.org/protocol/chatstates"               /* XEP-0085 */ +#define XMLNS_DISCO_INFO   "http://jabber.org/protocol/disco#info"               /* XEP-0030 */ +#define XMLNS_DISCO_ITEMS  "http://jabber.org/protocol/disco#items"              /* XEP-0030 */ +#define XMLNS_MUC          "http://jabber.org/protocol/muc"                      /* XEP-0045 */ +#define XMLNS_MUC_USER     "http://jabber.org/protocol/muc#user"                 /* XEP-0045 */ +#define XMLNS_CAPS         "http://jabber.org/protocol/caps"                     /* XEP-0115 */ +#define XMLNS_FEATURE      "http://jabber.org/protocol/feature-neg"              /* XEP-0020 */ +#define XMLNS_SI           "http://jabber.org/protocol/si"                       /* XEP-0095 */ +#define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */ +#define XMLNS_BYTESTREAMS  "http://jabber.org/protocol/bytestreams"              /* XEP-0065 */ +#define XMLNS_IBB          "http://jabber.org/protocol/ibb"                      /* XEP-0047 */ + +/* iq.c */ +xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); +int jabber_init_iq_auth( struct im_connection *ic ); +xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +int jabber_get_roster( struct im_connection *ic ); +int jabber_get_vcard( struct im_connection *ic, char *bare_jid ); +int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ); +int jabber_remove_from_roster( struct im_connection *ic, char *handle ); +xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ); +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ); +void jabber_iq_version_send( struct im_connection *ic, struct jabber_buddy *bud, void *data ); + +/* si.c */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode ); +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); +void jabber_si_free_transfer( file_transfer_t *ft); + +/* s5bytestream.c */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); +gboolean jabber_bs_send_start( struct jabber_transfer *tf ); +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ); + +/* message.c */ +xt_status jabber_pkt_message( struct xt_node *node, gpointer data ); + +/* presence.c */ +xt_status jabber_pkt_presence( struct xt_node *node, gpointer data ); +int presence_send_update( struct im_connection *ic ); +int presence_send_request( struct im_connection *ic, char *handle, char *request ); + +/* jabber_util.c */ +char *set_eval_priority( set_t *set, char *value ); +char *set_eval_tls( set_t *set, char *value ); +struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ); +struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ); +void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func ); +struct xt_node *jabber_cache_get( struct im_connection *ic, char *id ); +void jabber_cache_entry_free( gpointer entry ); +void jabber_cache_clean( struct im_connection *ic ); +xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node ); +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( const char *orig ); + +typedef enum +{ +	GET_BUDDY_CREAT = 1,	/* Try to create it, if necessary. */ +	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_BARE = 8,	/* Get the bare version of the JID (possibly inexistent). */ +	GET_BUDDY_BARE_OK = 16,	/* Allow returning a bare JID if that seems better. */ +} 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 ); +void jabber_buddy_remove_all( struct im_connection *ic ); +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[]; + +/* io.c */ +int jabber_write_packet( struct im_connection *ic, struct xt_node *node ); +int jabber_write( struct im_connection *ic, char *buf, int len ); +gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond ); +gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond ); +gboolean jabber_start_stream( struct im_connection *ic ); +void jabber_end_stream( struct im_connection *ic ); + +/* sasl.c */ +xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data ); +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, const char *room, const char *nick, const char *password ); +struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name ); +void jabber_chat_free( struct groupchat *c ); +int jabber_chat_msg( struct groupchat *ic, char *message, int flags ); +int jabber_chat_topic( struct groupchat *c, char *topic ); +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 ); +void jabber_chat_invite( struct groupchat *c, char *who, char *message ); + +#endif diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c new file mode 100644 index 00000000..e6b13659 --- /dev/null +++ b/protocols/jabber/jabber_util.c @@ -0,0 +1,762 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - Misc. stuff                                              * +*                                                                           * +*  Copyright 2006-2010 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" +#include "md5.h" +#include "base64.h" + +static unsigned int next_id = 1; + +char *set_eval_priority( set_t *set, char *value ) +{ +	account_t *acc = set->data; +	int i; +	 +	if( sscanf( value, "%d", &i ) == 1 ) +	{ +		/* Priority is a signed 8-bit integer, according to RFC 3921. */ +		if( i < -128 || i > 127 ) +			return SET_INVALID; +	} +	else +		return SET_INVALID; +	 +	/* Only run this stuff if the account is online ATM, +	   and if the setting seems to be acceptable. */ +	if( acc->ic ) +	{ +		/* Although set_eval functions usually are very nice and +		   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 +		   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: */ +		 +		g_free( set->value ); +		set->value = g_strdup( value ); +		 +		/* (Yes, sorry, I prefer the hack. :-P) */ +		 +		presence_send_update( acc->ic ); +	} +	 +	return value; +} + +char *set_eval_tls( set_t *set, char *value ) +{ +	if( g_strcasecmp( value, "try" ) == 0 ) +		return value; +	else +		return set_eval_bool( set, value ); +} + +struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ) +{ +	struct xt_node *node; +	 +	node = xt_new_node( name, NULL, children ); +	 +	if( type ) +		xt_add_attr( node, "type", type ); +	if( to ) +		xt_add_attr( node, "to", to ); +	 +	/* IQ packets should always have an ID, so let's generate one. It +	   might get overwritten by jabber_cache_add() if this packet has +	   to be saved until we receive a response. Cached packets get +	   slightly different IDs so we can recognize them. */ +	if( strcmp( name, "iq" ) == 0 ) +	{ +		char *id = g_strdup_printf( "%s%05x", JABBER_PACKET_ID, ( next_id++ ) & 0xfffff ); +		xt_add_attr( node, "id", id ); +		g_free( id ); +	} +	 +	return node; +} + +struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ) +{ +	struct xt_node *node, *c; +	char *to; +	 +	/* Create the "defined-condition" tag. */ +	c = xt_new_node( err_cond, NULL, NULL ); +	xt_add_attr( c, "xmlns", XMLNS_STANZA_ERROR ); +	 +	/* Put it in an <error> tag. */ +	c = xt_new_node( "error", NULL, c ); +	xt_add_attr( c, "type", err_type ); +	 +	/* Add the error code, if present */ +	if (err_code) +		xt_add_attr( c, "code", err_code ); +	 +	/* To make the actual error packet, we copy the original packet and +	   add our <error>/type="error" tag. Including the original packet +	   is recommended, so let's just do it. */ +	node = xt_dup( orig ); +	xt_add_child( node, c ); +	xt_add_attr( node, "type", "error" ); +	 +	/* Return to sender. */ +	if( ( to = xt_find_attr( node, "from" ) ) ) +	{ +		xt_add_attr( node, "to", to ); +		xt_remove_attr( node, "from" ); +	} +		 +	return node; +} + +/* 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 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; +	struct jabber_cache_entry *entry = g_new0( struct jabber_cache_entry, 1 ); +	md5_state_t id_hash; +	md5_byte_t id_sum[16]; +	char *id, *asc_hash; +	 +	next_id ++; +	 +	id_hash = jd->cached_id_prefix; +	md5_append( &id_hash, (md5_byte_t*) &next_id, sizeof( next_id ) ); +	md5_finish( &id_hash, id_sum ); +	asc_hash = base64_encode( id_sum, 12 ); +	 +	id = g_strdup_printf( "%s%s", JABBER_CACHED_ID, asc_hash ); +	xt_add_attr( node, "id", id ); +	g_free( id ); +	g_free( asc_hash ); +	 +	entry->node = node; +	entry->func = func; +	entry->saved_at = time( NULL ); +	g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry ); +} + +void jabber_cache_entry_free( gpointer data ) +{ +	struct jabber_cache_entry *entry = data; +	 +	xt_free_node( entry->node ); +	g_free( entry ); +} + +gboolean jabber_cache_clean_entry( gpointer key, gpointer entry, gpointer nullpointer ); + +/* This one should be called from time to time (from keepalive, in this case) +   to make sure things don't stay in the node cache forever. By marking nodes +   during the first run and deleting marked nodes during a next run, every +   node should be available in the cache for at least a minute (assuming the +   function is indeed called every minute). */ +void jabber_cache_clean( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	time_t threshold = time( NULL ) - JABBER_CACHE_MAX_AGE; +	 +	g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, &threshold ); +} + +gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer threshold_ ) +{ +	struct jabber_cache_entry *entry = entry_; +	time_t *threshold = threshold_; +	 +	return entry->saved_at < *threshold; +} + +xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct jabber_cache_entry *entry; +	char *s; +	 +	if( ( s = xt_find_attr( node, "id" ) ) == NULL || +	    strncmp( s, JABBER_CACHED_ID, strlen( JABBER_CACHED_ID ) ) != 0 ) +	{ +		/* Silently ignore it, without an ID (or a non-cache +		   ID) we don't know how to handle the packet and we +		   probably don't have to. */ +		return XT_HANDLED; +	} +	 +	entry = g_hash_table_lookup( jd->node_cache, s ); +	 +	if( entry == NULL ) +	{ +		/* +		There's no longer an easy way to see if we generated this +		one or someone else, and there's a ten-minute timeout anyway, +		so meh. +		 +		imcb_log( ic, "Warning: Received %s-%s packet with unknown/expired ID %s!", +		              node->name, xt_find_attr( node, "type" ) ? : "(no type)", s ); +		*/ +	} +	else if( entry->func ) +	{ +		return entry->func( ic, node, entry->node ); +	} +	 +	return XT_HANDLED; +} + +const struct jabber_away_state jabber_away_state_list[] = +{ +	{ "away",  "Away" }, +	{ "chat",  "Free for Chat" },   /* WTF actually uses this? */ +	{ "dnd",   "Do not Disturb" }, +	{ "xa",    "Extended Away" }, +	{ "",      NULL } +}; + +const struct jabber_away_state *jabber_away_state_by_code( char *code ) +{ +	int i; +	 +	if( code == NULL ) +		return NULL; +	 +	for( i = 0; jabber_away_state_list[i].full_name; i ++ ) +		if( g_strcasecmp( jabber_away_state_list[i].code, code ) == 0 ) +			return jabber_away_state_list + i; +	 +	return NULL; +} + +const struct jabber_away_state *jabber_away_state_by_name( char *name ) +{ +	int i; +	 +	if( name == NULL ) +		return NULL; +	 +	for( i = 0; jabber_away_state_list[i].full_name; i ++ ) +		if( g_strcasecmp( jabber_away_state_list[i].full_name, name ) == 0 ) +			return jabber_away_state_list + i; +	 +	return NULL; +} + +struct jabber_buddy_ask_data +{ +	struct im_connection *ic; +	char *handle; +	char *realname; +}; + +static void jabber_buddy_ask_yes( void *data ) +{ +	struct jabber_buddy_ask_data *bla = data; +	 +	presence_send_request( bla->ic, bla->handle, "subscribed" ); +	 +	imcb_ask_add( bla->ic, bla->handle, NULL ); +	 +	g_free( bla->handle ); +	g_free( bla ); +} + +static void jabber_buddy_ask_no( void *data ) +{ +	struct jabber_buddy_ask_data *bla = data; +	 +	presence_send_request( bla->ic, bla->handle, "subscribed" ); +	 +	g_free( bla->handle ); +	g_free( bla ); +} + +void jabber_buddy_ask( struct im_connection *ic, char *handle ) +{ +	struct jabber_buddy_ask_data *bla = g_new0( struct jabber_buddy_ask_data, 1 ); +	char *buf; +	 +	bla->ic = ic; +	bla->handle = g_strdup( handle ); +	 +	buf = g_strdup_printf( "The user %s wants to add you to his/her buddy list.", handle ); +	imcb_ask( ic, buf, bla, jabber_buddy_ask_yes, jabber_buddy_ask_no ); +	g_free( buf ); +} + +/* Returns a new string. Don't leak it! */ +char *jabber_normalize( const char *orig ) +{ +	int len, i; +	char *new; +	 +	len = strlen( orig ); +	new = g_new( char, len + 1 ); +	 +	/* So it turns out the /resource part is case sensitive. Yeah, and +	   it's Unicode but feck Unicode. :-P So stop once we see a slash. */ +	for( i = 0; i < len && orig[i] != '/' ; i ++ ) +		new[i] = tolower( orig[i] ); +	for( ; orig[i]; i ++ ) +		new[i] = orig[i]; +	 +	new[i] = 0; +	return new; +} + +/* Adds a buddy/resource to our list. Returns NULL if full_jid is not really a +   FULL jid or if we already have this buddy/resource. XXX: No, great, actually +   buddies from transports don't (usually) have resources. So we'll really have +   to deal with that properly. Set their ->resource property to NULL. Do *NOT* +   allow to mix this stuff, though... */ +struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_ ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct jabber_buddy *bud, *new, *bi; +	char *s, *full_jid; +	 +	full_jid = jabber_normalize( full_jid_ ); +	 +	if( ( s = strchr( full_jid, '/' ) ) ) +		*s = 0; +	 +	new = g_new0( struct jabber_buddy, 1 ); +	 +	if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) ) +	{ +		/* The first entry is always a bare JID. If there are more, we +		   should ignore the first one here. */ +		if( bud->next ) +			bud = bud->next; +		 +		/* If this is a transport buddy or whatever, it can't have more +		   than one instance, so this is always wrong: */ +		if( s == NULL || bud->resource == NULL ) +		{ +			if( s ) *s = '/'; +			g_free( new ); +			g_free( full_jid ); +			return NULL; +		} +		 +		new->bare_jid = bud->bare_jid; +		 +		/* We already have another resource for this buddy, add the +		   new one to the list. */ +		for( bi = bud; bi; bi = bi->next ) +		{ +			/* Check for dupes. */ +			if( strcmp( bi->resource, s + 1 ) == 0 ) +			{ +				*s = '/'; +				g_free( new ); +				g_free( full_jid ); +				return NULL; +			} +			/* Append the new item to the list. */ +			else if( bi->next == NULL ) +			{ +				bi->next = new; +				break; +			} +		} +	} +	else +	{ +		new->full_jid = new->bare_jid = g_strdup( full_jid ); +		g_hash_table_insert( jd->buddies, new->bare_jid, new ); +		 +		if( s ) +		{ +			new->next = g_new0( struct jabber_buddy, 1 ); +			new->next->bare_jid = new->bare_jid; +			new = new->next; +		} +	} +	 +	if( s ) +	{ +		*s = '/'; +		new->full_jid = full_jid; +		new->resource = strchr( new->full_jid, '/' ) + 1; +	} +	else +	{ +		/* Let's waste some more bytes of RAM instead of to make +		   memory management a total disaster here. And it saves +		   me one g_free() call in this function. :-P */ +		new->full_jid = full_jid; +	} +	 +	return new; +} + +/* Finds a buddy from our structures. Can find both full- and bare JIDs. When +   asked for a bare JID, it uses the "resource_select" setting to see which +   resource to pick. */ +struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct jabber_buddy *bud, *head; +	char *s, *jid; +	 +	jid = jabber_normalize( jid_ ); +	 +	if( ( s = strchr( jid, '/' ) ) ) +	{ +		int bare_exists = 0; +		 +		*s = 0; +		if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) ) +		{ +			bare_exists = 1; +			 +			if( bud->next ) +				bud = bud->next; +			 +			/* Just return the first one for this bare JID. */ +			if( flags & GET_BUDDY_FIRST ) +			{ +				*s = '/'; +				g_free( jid ); +				return bud; +			} +			 +			/* Is this one of those no-resource buddies? */ +			if( bud->resource == NULL ) +			{ +				*s = '/'; +				g_free( jid ); +				return NULL; +			} +			 +			/* See if there's an exact match. */ +			for( ; bud; bud = bud->next ) +				if( strcmp( bud->resource, s + 1 ) == 0 ) +					break; +		} +		 +		if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && +		    ( bare_exists || bee_user_by_handle( ic->bee, ic, jid ) ) ) +		{ +			*s = '/'; +			bud = jabber_buddy_add( ic, jid ); +		} +		 +		g_free( jid ); +		return bud; +	} +	else +	{ +		struct jabber_buddy *best_prio, *best_time; +		char *set; +		 +		head = g_hash_table_lookup( jd->buddies, jid ); +		bud = ( head && head->next ) ? head->next : head; +		 +		g_free( jid ); +		 +		if( bud == NULL ) +			/* No match. Create it now? */ +			return ( ( flags & GET_BUDDY_CREAT ) && +			         bee_user_by_handle( ic->bee, ic, jid_ ) ) ? +			           jabber_buddy_add( ic, jid_ ) : NULL; +		else if( bud->resource && ( flags & GET_BUDDY_EXACT ) ) +			/* We want an exact match, so in thise case there shouldn't be a /resource. */ +			return NULL; +		else if( bud->resource == NULL || bud->next == NULL ) +			/* No need for selection if there's only one option. */ +			return bud; +		else if( flags & GET_BUDDY_FIRST ) +			/* Looks like the caller doesn't care about details. */ +			return bud; +		else if( flags & GET_BUDDY_BARE ) +			return head; +		 +		best_prio = best_time = bud; +		for( ; bud; bud = bud->next ) +		{ +			if( bud->priority > best_prio->priority ) +				best_prio = bud; +			if( bud->last_msg > best_time->last_msg ) +				best_time = bud; +		} +		 +		if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL ) +			return NULL; +		else if( strcmp( set, "priority" ) == 0 ) +			return best_prio; +		else if( flags & GET_BUDDY_BARE_OK ) /* && strcmp( set, "activity" ) == 0 */ +		{ +			if( best_time->last_msg + set_getint( &ic->acc->set, "activity_timeout" ) >= time( NULL ) ) +				return best_time; +			else +				return head; +		} +		else +			return best_time; +	} +} + +/* 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... */ +int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct jabber_buddy *bud, *prev = NULL, *bi; +	char *s, *full_jid; +	 +	full_jid = jabber_normalize( full_jid_ ); +	 +	if( ( s = strchr( full_jid, '/' ) ) ) +		*s = 0; +	 +	if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) ) +	{ +		if( bud->next ) +			bud = (prev=bud)->next; +		 +		/* If there's only one item in the list (and if the resource +		   matches), removing it is simple. (And the hash reference +		   should be removed too!) */ +		if( bud->next == NULL && +		    ( ( s == NULL && bud->resource == NULL ) || +		      ( bud->resource && s && strcmp( bud->resource, s + 1 ) == 0 ) ) ) +		{ +			int st = jabber_buddy_remove_bare( ic, full_jid ); +			g_free( full_jid ); +			return st; +		} +		else if( s == NULL || bud->resource == NULL ) +		{ +			/* Tried to remove a bare JID while this JID does seem +			   to have resources... (Or the opposite.) *sigh* */ +			g_free( full_jid ); +			return 0; +		} +		else +		{ +			for( bi = bud; bi; bi = (prev=bi)->next ) +				if( strcmp( bi->resource, s + 1 ) == 0 ) +					break; +			 +			g_free( full_jid ); +			 +			if( bi ) +			{ +				if( prev ) +					prev->next = bi->next; +				else +					/* Don't think this should ever happen anymore. */ +					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 ); +				 +				return 1; +			} +			else +			{ +				return 0; +			} +		} +	} +	else +	{ +		g_free( full_jid ); +		return 0; +	} +} + +/* 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 ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct jabber_buddy *bud, *next; +	 +	if( strchr( bare_jid, '/' ) ) +		return 0; +	 +	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; +		} +		 +		return 1; +	} +	else +	{ +		return 0; +	} +} + +static gboolean jabber_buddy_remove_all_cb( gpointer key, gpointer value, gpointer data ) +{ +	struct jabber_buddy *bud, *next; +	 +	bud = value; +	while( bud ) +	{ +		next = bud->next; +		g_free( bud->ext_jid ); +		g_free( bud->full_jid ); +		g_free( bud->away_message ); +		g_free( bud ); +		bud = next; +	} +	 +	return TRUE; +} + +void jabber_buddy_remove_all( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	 +	g_hash_table_foreach_remove( jd->buddies, jabber_buddy_remove_all_cb, NULL ); +	g_hash_table_destroy( jd->buddies ); +} + +time_t jabber_get_timestamp( struct xt_node *xt ) +{ +	struct xt_node *c; +	char *s = NULL; +	struct tm tp; +	 +	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 --; +	 +	return mktime_utc( &tp ); +} + +struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns ) +{ +	struct jabber_error *err; +	struct xt_node *c; +	char *s; +	 +	if( node == NULL ) +		return NULL; +	 +	err = g_new0( struct jabber_error, 1 ); +	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 new file mode 100644 index 00000000..85c71c9d --- /dev/null +++ b/protocols/jabber/message.c @@ -0,0 +1,149 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - Handling of message(s) (tags), etc                       * +*                                                                           * +*  Copyright 2006 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" + +xt_status jabber_pkt_message( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = 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, *room = NULL, *reason = NULL; +	 +	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 && from && strcmp( type, "groupchat" ) == 0 ) +	{ +		jabber_chat_pkt_message( ic, bud, node ); +	} +	else /* "chat", "normal", "headline", no-type or whatever. Should all be pretty similar. */ +	{ +		GString *fullmsg = g_string_new( "" ); + +		for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next ) +		{ +			char *ns = xt_find_attr( c, "xmlns" ); +			struct xt_node *inv; +			 +			if( ns && strcmp( ns, XMLNS_MUC_USER ) == 0 && +			    ( inv = xt_find_node( c->children, "invite" ) ) ) +			{ +				/* This is an invitation. Set some vars which +				   will be passed to imcb_chat_invite() below. */ +				room = from; +				if( ( from = xt_find_attr( inv, "from" ) ) == NULL ) +					from = room; +				if( ( inv = xt_find_node( inv->children, "reason" ) ) && inv->text_len > 0 ) +					reason = inv->text; +			} +		} +		 +		if( ( s = strchr( from, '/' ) ) ) +		{ +			if( bud ) +			{ +				bud->last_msg = time( NULL ); +				from = bud->ext_jid ? bud->ext_jid : bud->bare_jid; +			} +			else +				*s = 0; /* We need to generate a bare JID now. */ +		} +		 +		if( type && strcmp( type, "headline" ) == 0 ) +		{ +			if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 ) +				g_string_append_printf( fullmsg, "Headline: %s\n", c->text ); +			 +			/* <x xmlns="jabber:x:oob"><url>http://....</url></x> can contain a URL, it seems. */ +			for( c = node->children; c; c = c->next ) +			{ +				struct xt_node *url; +				 +				if( ( url = xt_find_node( c->children, "url" ) ) && url->text_len > 0 ) +					g_string_append_printf( fullmsg, "URL: %s\n", url->text ); +			} +		} +		else if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 && +		         ( !bud || !( bud->flags & JBFLAG_HIDE_SUBJECT ) ) ) +		{ +			g_string_append_printf( fullmsg, "<< \002BitlBee\002 - Message with subject: %s >>\n", c->text ); +			if( bud ) +				bud->flags |= JBFLAG_HIDE_SUBJECT; +		} +		else if( bud && !c ) +		{ +			/* Yeah, possibly we're hiding changes to this field now. But nobody uses +			   this for anything useful anyway, except GMail when people reply to an +			   e-mail via chat, repeating the same subject all the time. I don't want +			   to have to remember full subject strings for everyone. */ +			bud->flags &= ~JBFLAG_HIDE_SUBJECT; +		} +		 +		if( body && body->text_len > 0 ) /* Could be just a typing notification. */ +			fullmsg = g_string_append( fullmsg, body->text ); +		 +		if( fullmsg->len > 0 ) +			imcb_buddy_msg( ic, from, fullmsg->str, +			                0, jabber_get_timestamp( node ) ); +		if( room ) +			imcb_chat_invite( ic, room, from, reason ); +		 +		g_string_free( fullmsg, TRUE ); +		 +		/* Handling of incoming typing notifications. */ +		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, 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, from, 0 ); +		} +		else if( xt_find_node( node->children, "paused" ) ) +		{ +			bud->flags |= JBFLAG_DOES_XEP85; +			imcb_buddy_typing( ic, from, OPT_THINKING ); +		} +		 +		if( s ) +			*s = '/'; /* And convert it back to a full JID. */ +	} +	 +	return XT_HANDLED; +} diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c new file mode 100644 index 00000000..2875d23e --- /dev/null +++ b/protocols/jabber/presence.c @@ -0,0 +1,258 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - Handling of presence (tags), etc                         * +*                                                                           * +*  Copyright 2006 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" + +xt_status jabber_pkt_presence( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = data; +	char *from = xt_find_attr( node, "from" ); +	char *type = xt_find_attr( node, "type" );	/* NULL should mean the person is online. */ +	struct xt_node *c, *cap; +	struct jabber_buddy *bud, *send_presence = NULL; +	int is_chat = 0; +	char *s; +	 +	if( !from ) +		return XT_HANDLED; +	 +	if( ( s = strchr( from, '/' ) ) ) +	{ +		*s = 0; +		if( jabber_chat_by_jid( ic, from ) ) +			is_chat = 1; +		*s = '/'; +	} +	 +	if( type == NULL ) +	{ +		if( !( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT | GET_BUDDY_CREAT ) ) ) +		{ +			/* +			imcb_log( ic, "Warning: Could not handle presence information from JID: %s", from ); +			*/ +			return XT_HANDLED; +		} +		 +		g_free( bud->away_message ); +		if( ( c = xt_find_node( node->children, "status" ) ) && c->text_len > 0 ) +			bud->away_message = g_strdup( c->text ); +		else +			bud->away_message = NULL; +		 +		if( ( c = xt_find_node( node->children, "show" ) ) && c->text_len > 0 ) +		{ +			bud->away_state = (void*) jabber_away_state_by_code( c->text ); +		} +		else +		{ +			bud->away_state = NULL; +		} +		 +		if( ( c = xt_find_node( node->children, "priority" ) ) && c->text_len > 0 ) +			bud->priority = atoi( c->text ); +		else +			bud->priority = 0; +		 +		if( bud && ( cap = xt_find_node( node->children, "c" ) ) && +		    ( s = xt_find_attr( cap, "xmlns" ) ) && strcmp( s, XMLNS_CAPS ) == 0 ) +		{ +			/* This <presence> stanza includes an XEP-0115 +			   capabilities part. Not too interesting, but we can +			   see if it has an ext= attribute. */ +			s = xt_find_attr( cap, "ext" ); +			if( s && ( strstr( s, "cstates" ) || strstr( s, "chatstate" ) ) ) +				bud->flags |= JBFLAG_DOES_XEP85; +			 +			/* This field can contain more information like xhtml +			   support, but we don't support that ourselves. +			   Officially the ext= tag was deprecated, but enough +			   clients do send it. +			    +			   (I'm aware that this is not the right way to use +			   this field.) See for an explanation of ext=: +			   http://www.xmpp.org/extensions/attic/xep-0115-1.3.html*/ +		} +		 +		if( is_chat ) +			jabber_chat_pkt_presence( ic, bud, node ); +		else +			send_presence = jabber_buddy_by_jid( ic, bud->bare_jid, 0 ); +	} +	else if( strcmp( type, "unavailable" ) == 0 ) +	{ +		if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) +		{ +			/* +			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 ); +		} +		 +		if( strchr( from, '/' ) == NULL ) +			/* Sometimes servers send a type="unavailable" from a +			   bare JID, which should mean that suddenly all +			   resources for this JID disappeared. */ +			jabber_buddy_remove_bare( ic, from ); +		else +			jabber_buddy_remove( ic, from ); +		 +		if( is_chat ) +		{ +			/* Nothing else to do for now? */ +		} +		else if( ( s = strchr( from, '/' ) ) ) +		{ +			*s = 0; +		 +			/* If another resource is still available, send its presence +			   information. */ +			if( ( send_presence = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) +			{ +				/* Otherwise, count him/her as offline now. */ +				imcb_buddy_status( ic, from, 0, NULL, NULL ); +			} +			 +			*s = '/'; +		} +		else +		{ +			imcb_buddy_status( ic, from, 0, NULL, NULL ); +		} +	} +	else if( strcmp( type, "subscribe" ) == 0 ) +	{ +		jabber_buddy_ask( ic, from ); +	} +	else if( strcmp( type, "subscribed" ) == 0 ) +	{ +		/* Not sure about this one, actually... */ +		imcb_log( ic, "%s just accepted your authorization request", from ); +	} +	else if( strcmp( type, "unsubscribe" ) == 0 || strcmp( type, "unsubscribed" ) == 0 ) +	{ +		/* Do nothing here. Plenty of control freaks or over-curious +		   souls get excited when they can see who still has them in +		   their buddy list and who finally removed them. Somehow I +		   got the impression that those are the people who get +		   removed from many buddy lists for "some" reason... +		    +		   If you're one of those people, this is your chance to write +		   your first line of code in C... */ +	} +	else if( strcmp( type, "error" ) == 0 ) +	{ +		return jabber_cache_handle_packet( ic, node ); +		 +		/* +		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 ); +		} */ +	} + +	if( send_presence ) +	{ +		int is_away = 0; + +		if( send_presence->away_state && +		    strcmp( send_presence->away_state->code, "chat" ) != 0 ) +			is_away = OPT_AWAY; + +		imcb_buddy_status( ic, send_presence->bare_jid, OPT_LOGGED_IN | is_away, +		                   is_away ? send_presence->away_state->full_name : NULL, +		                   send_presence->away_message ); +	} +	 +	return XT_HANDLED; +} + +/* Whenever presence information is updated, call this function to inform the +   server. */ +int presence_send_update( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	struct xt_node *node, *cap; +	GSList *l; +	int st; +	 +	node = jabber_make_packet( "presence", NULL, NULL, NULL ); +	xt_add_child( node, xt_new_node( "priority", set_getstr( &ic->acc->set, "priority" ), NULL ) ); +	if( jd->away_state ) +		xt_add_child( node, xt_new_node( "show", jd->away_state->code, NULL ) ); +	if( jd->away_message ) +		xt_add_child( node, xt_new_node( "status", jd->away_message, NULL ) ); +	 +	/* This makes the packet slightly bigger, but clients interested in +	   capabilities can now cache the discovery info. This reduces the +	   usual post-login iq-flood. See XEP-0115. At least libpurple and +	   Trillian seem to do this right. */ +	cap = xt_new_node( "c", NULL, NULL ); +	xt_add_attr( cap, "xmlns", XMLNS_CAPS ); +	xt_add_attr( cap, "node", "http://bitlbee.org/xmpp/caps" ); +	xt_add_attr( cap, "ver", BITLBEE_VERSION ); /* The XEP wants this hashed, but nobody's doing that. */ +	xt_add_child( node, cap ); +	 +	st = jabber_write_packet( ic, node ); +	 +	/* Have to send this update to all groupchats too, the server won't +	   do this automatically. */ +	for( l = ic->groupchats; l && st; l = l->next ) +	{ +		struct groupchat *c = l->data; +		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; +} + +/* Send a subscribe/unsubscribe request to a buddy. */ +int presence_send_request( struct im_connection *ic, char *handle, char *request ) +{ +	struct xt_node *node; +	int st; +	 +	node = jabber_make_packet( "presence", NULL, NULL, NULL ); +	xt_add_attr( node, "to", handle ); +	xt_add_attr( node, "type", request ); +	 +	st = jabber_write_packet( ic, node ); +	 +	xt_free_node( node ); +	return st; +} diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c new file mode 100644 index 00000000..3304d99e --- /dev/null +++ b/protocols/jabber/s5bytestream.c @@ -0,0 +1,1153 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - SOCKS5 Bytestreams ( XEP-0065 )                          * +*                                                                           * +*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         * +*                                                                           * +*  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" +#include "sha1.h" +#include "lib/ftutil.h" +#include <poll.h> + +struct bs_transfer { + +	struct jabber_transfer *tf; + +	jabber_streamhost_t *sh; +	GSList *streamhosts; + +	enum  +	{  +		BS_PHASE_CONNECT,  +		BS_PHASE_CONNECTED,  +		BS_PHASE_REQUEST,  +		BS_PHASE_REPLY +	} phase; + +	/* SHA1( SID + Initiator JID + Target JID) */ +	char *pseudoadr; + +	gint connect_timeout; +	 +	char peek_buf[64]; +	int peek_buf_len; +}; + +struct socks5_message +{ +	unsigned char ver; +	union +	{ +		unsigned char cmd; +		unsigned char rep; +	} cmdrep; +	unsigned char rsv; +	unsigned char atyp; +	unsigned char addrlen; +	unsigned char address[40]; +	in_port_t port; +} __attribute__ ((packed));  + +char *socks5_reply_code[] = { +	"succeeded", +	"general SOCKS server failure", +	"connection not allowed by ruleset", +	"Network unreachable", +	"Host unreachable", +	"Connection refused", +	"TTL expired", +	"Command not supported", +	"Address type not supported", +	"unassigned"}; + +/* connect() timeout in seconds. */ +#define JABBER_BS_CONTIMEOUT 15 +/* listen timeout */ +#define JABBER_BS_LISTEN_TIMEOUT  90 + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ +	if( (op) == -1 ) \ +		return jabber_bs_abort( bt , msg ": %s", strerror( errno ) ); + +gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ); +void jabber_bs_canceled( file_transfer_t *ft , char *reason ); +void jabber_bs_free_transfer( file_transfer_t *ft ); +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ); +gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ); + +void jabber_bs_recv_answer_request( struct bs_transfer *bt ); +gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_recv_write_request( file_transfer_t *ft ); +gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ); +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode ); + +gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error ); +gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ); +gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ); +static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +void jabber_bs_send_activate( struct bs_transfer *bt ); + +/* + * Frees a bs_transfer struct and calls the SI free function + */ +void jabber_bs_free_transfer( file_transfer_t *ft) { +	struct jabber_transfer *tf = ft->data; +	struct bs_transfer *bt = tf->streamhandle; +	jabber_streamhost_t *sh; + +	if ( bt->connect_timeout ) +	{ +		b_event_remove( bt->connect_timeout ); +		bt->connect_timeout = 0; +	} + +	if ( tf->watch_in ) +		b_event_remove( tf->watch_in ); +	 +	if( tf->watch_out ) +		b_event_remove( tf->watch_out ); +	 +	g_free( bt->pseudoadr ); + +	while( bt->streamhosts ) +	{ +		sh = bt->streamhosts->data; +		bt->streamhosts = g_slist_remove( bt->streamhosts, sh ); +		g_free( sh->jid ); +		g_free( sh->host ); +		g_free( sh ); +	} +	 +	g_free( bt ); + +	jabber_si_free_transfer( ft ); +} + +/* + * Checks if buflen data is available on the socket and + * writes it to buffer if that's the case. + */ +gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ) +{ +	int ret; +	int fd = bt->tf->fd; + +	if( buflen > sizeof( bt->peek_buf ) ) +		return jabber_bs_abort( bt, "BUG: %d > sizeof(peek_buf)", buflen ); + +	ASSERTSOCKOP( ret = recv( fd, bt->peek_buf + bt->peek_buf_len, +		buflen - bt->peek_buf_len, 0 ), "recv() on SOCKS5 connection" ); + +	if( ret == 0 ) +		return jabber_bs_abort( bt, "Remote end closed connection" ); +	 +	bt->peek_buf_len += ret; +	memcpy( buffer, bt->peek_buf, bt->peek_buf_len ); +	 +	if( bt->peek_buf_len == buflen ) +	{ +		/* If we have everything the caller wanted, reset the peek buffer. */ +		bt->peek_buf_len = 0; +		return buflen; +	} +	else +		return bt->peek_buf_len; +} + + +/*  + * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect(). + */ +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ) +{ +	struct bs_transfer *bt = data; + +	bt->connect_timeout = 0; + +	jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT ); + +	return FALSE; +} + +/*  + * Polls the socket, checks for errors and removes a connect timer + * if there is one. + */ +gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ) +{ +	struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR }; +	 +	if ( bt->connect_timeout ) +	{ +		b_event_remove( bt->connect_timeout ); +		bt->connect_timeout = 0; +	} + +	ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" ) + +	if( pfd.revents & POLLERR ) +	{ +		int sockerror; +		socklen_t errlen = sizeof( sockerror ); + +		if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) ) +			return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" ); + +		if ( bt->phase == BS_PHASE_CONNECTED ) +			return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) ); + +		return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) ); +	} + +	if( pfd.revents & POLLHUP ) +		return jabber_bs_abort( bt, "Remote end closed connection" ); +	 +	*revents = pfd.revents; +	 +	return TRUE; +} + +/* + * Used for receive and send path. + */ +gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ) +{ +	va_list params; +	va_start( params, format ); +	char error[128]; + +	if( vsnprintf( error, 128, format, params ) < 0 ) +		sprintf( error, "internal error parsing error string (BUG)" ); +	va_end( params ); +	if( bt->tf->ft->sending ) +		return jabber_bs_send_handshake_abort( bt, error ); +	else +		return jabber_bs_recv_handshake_abort( bt, error ); +} + +/* Bad luck */ +void jabber_bs_canceled( file_transfer_t *ft , char *reason ) +{ +	struct jabber_transfer *tf = ft->data; + +	imcb_log( tf->ic, "File transfer aborted: %s", reason ); +} + +/* + * Parses an incoming bytestream request and calls jabber_bs_handshake on success. + */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode) +{ +	char *sid, *ini_jid, *tgt_jid, *mode, *iq_id; +	struct jabber_data *jd = ic->proto_data; +	struct jabber_transfer *tf = NULL; +	GSList *tflist; +	struct bs_transfer *bt; +	GSList *shlist=NULL; +	struct xt_node *shnode; + +	sha1_state_t sha; +	char hash_hex[41]; +	unsigned char hash[20]; +	int i; +	 +	if( !(iq_id   = xt_find_attr( node, "id" ) ) || +	    !(ini_jid = xt_find_attr( node, "from" ) ) || +	    !(tgt_jid = xt_find_attr( node, "to" ) ) || +	    !(sid     = xt_find_attr( qnode, "sid" ) ) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete SI bytestream request"); +		return XT_HANDLED; +	} + +	if( ( mode = xt_find_attr( qnode, "mode" ) ) && +	      ( strcmp( mode, "tcp" ) != 0 ) )  +	{ +		imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) ); +		return XT_HANDLED; +	} + +	shnode = qnode->children; +	while( ( shnode = xt_find_node( shnode, "streamhost" ) ) ) +	{ +		char *jid, *host, *port_s; +		int port; +		if( ( jid = xt_find_attr( shnode, "jid" ) ) && +		    ( host = xt_find_attr( shnode, "host" ) ) && +		    ( port_s = xt_find_attr( shnode, "port" ) ) && +		    ( sscanf( port_s, "%d", &port ) == 1 ) ) +		{ +			jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); +			sh->jid = g_strdup(jid); +			sh->host = g_strdup(host); +			sprintf( sh->port, "%u", port ); +			shlist = g_slist_append( shlist, sh ); +		} +		shnode = shnode->next; +	} +	 +	if( !shlist ) +	{ +		imcb_log( ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries"); +		return XT_HANDLED; +	} + +	/* Let's see if we can find out what this bytestream should be for... */ + +	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) +	{ +		struct jabber_transfer *tft = tflist->data; +		if( ( strcmp( tft->sid, sid ) == 0 ) && +		    ( strcmp( tft->ini_jid, ini_jid ) == 0 ) && +		    ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) ) +		{ +		    	tf = tft; +			break; +		} +	} + +	if (!tf)  +	{ +		imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); +		return XT_HANDLED; +	} + +	/* iq_id and canceled can be reused since SI is done */ +	g_free( tf->iq_id ); +	tf->iq_id = g_strdup( iq_id ); + +	tf->ft->canceled = jabber_bs_canceled; + +	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ +	sha1_init( &sha ); +	sha1_append( &sha, (unsigned char*) sid, strlen( sid ) ); +	sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) ); +	sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) ); +	sha1_finish( &sha, hash ); +	 +	for( i = 0; i < 20; i ++ ) +		sprintf( hash_hex + i * 2, "%02x", hash[i] ); +		 +	bt = g_new0( struct bs_transfer, 1 ); +	bt->tf = tf; +	bt->streamhosts = shlist; +	bt->sh = shlist->data; +	bt->phase = BS_PHASE_CONNECT; +	bt->pseudoadr = g_strdup( hash_hex ); +	tf->streamhandle = bt; +	tf->ft->free = jabber_bs_free_transfer; + +	jabber_bs_recv_handshake( bt, -1, 0 );  + +	return XT_HANDLED; +} + +/* + * This is what a protocol handshake can look like in cooperative multitasking :) + * Might be confusing at first because it's called from different places and is recursing. + * (places being the event thread, bs_request, bs_handshake_abort, and itself) + * + * All in all, it turned out quite nice :) + */ +gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ) +{ + +	struct bs_transfer *bt = data; +	short revents; +	int gret; + +	if ( ( fd != -1 ) && !jabber_bs_poll( bt, fd, &revents ) ) +		return FALSE; +	 +	switch( bt->phase )  +	{ +	case BS_PHASE_CONNECT: +		{ +			struct addrinfo hints, *rp; + +			memset( &hints, 0, sizeof( struct addrinfo ) ); +			hints.ai_socktype = SOCK_STREAM; + +			if ( ( gret = getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) ) != 0 ) +				return jabber_bs_abort( bt, "getaddrinfo() failed: %s", gai_strerror( gret ) ); + +			ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); + +			sock_make_nonblocking( fd ); + +			imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, bt->sh->host, bt->sh->port ); + +			if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && +			    ( errno != EINPROGRESS ) ) +				return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) ); + +			freeaddrinfo( rp ); + +			bt->phase = BS_PHASE_CONNECTED; +			 +			bt->tf->watch_out = b_input_add( fd, B_EV_IO_WRITE, jabber_bs_recv_handshake, bt ); + +			/* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */ +			bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt ); + +			bt->tf->watch_in = 0; +			return FALSE; +		} +	case BS_PHASE_CONNECTED: +		{ +			struct { +				unsigned char ver; +				unsigned char nmethods; +				unsigned char method; +			} socks5_hello = { +				.ver = 5, +				.nmethods = 1, +				.method = 0x00 /* no auth */ +				/* one could also implement username/password. If you know +				 * a jabber client or proxy that actually does it, tell me. +				 */ +			}; +			 +			ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" ); + +			bt->phase = BS_PHASE_REQUEST; + +			bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_recv_handshake, bt ); + +			bt->tf->watch_out = 0; +			return FALSE; +		} +	case BS_PHASE_REQUEST: +		{ +			struct socks5_message socks5_connect =  +			{ +				.ver = 5, +				.cmdrep.cmd = 0x01, +				.rsv = 0, +				.atyp = 0x03, +				.addrlen = strlen( bt->pseudoadr ), +				.port = 0 +			}; +			int ret; +			char buf[2]; + +			/* If someone's trying to be funny and sends only one byte at a time we'll fail :) */ +			ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" ); + +			if( !( ret == 2 ) || +			    !( buf[0] == 5 ) || +			    !( buf[1] == 0 ) ) +				return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)", +									ret, buf[0], buf[1] ); + +			/* copy hash into connect message */ +			memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen ); + +			ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" ); + +			bt->phase = BS_PHASE_REPLY; + +			return TRUE; +		} +	case BS_PHASE_REPLY: +		{ +			struct socks5_message socks5_reply; +			int ret; + +			if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) ) +				return FALSE; + +			if ( ret < 5 ) /* header up to address length */ +				return TRUE; +			else if( ret < sizeof( struct socks5_message ) ) +			{ +				/* Either a buggy proxy or just one that doesnt regard +				 * the SHOULD in XEP-0065 saying the reply SHOULD +				 * contain the address. We'll take it, so make sure the +				 * next jabber_bs_peek starts with an empty buffer. */ +				bt->peek_buf_len = 0; +			} + +			if( !( socks5_reply.ver == 5 ) || +			    !( socks5_reply.cmdrep.rep == 0 ) ) { +			    	char errstr[128] = ""; +				if( ( socks5_reply.ver == 5 ) && ( socks5_reply.cmdrep.rep <  +				    ( sizeof( socks5_reply_code ) / sizeof( socks5_reply_code[0] ) ) ) ) { +					sprintf( errstr, "with \"%s\" ", socks5_reply_code[ socks5_reply.cmdrep.rep ] ); +				} +				return jabber_bs_abort( bt, "SOCKS5 CONNECT failed %s(reply: ver=%d, rep=%d, atyp=%d, addrlen=%d)",  +					errstr, +					socks5_reply.ver, +					socks5_reply.cmdrep.rep, +					socks5_reply.atyp, +					socks5_reply.addrlen); +			} +			 +			/* usually a proxy sends back the 40 bytes address but I encountered at least one (of jabber.cz)  +			 * that sends atyp=0 addrlen=0 and only 6 bytes (one less than one would expect). +			 * Therefore I removed the wait for more bytes. Since we don't care about what else the proxy +			 * is sending, it shouldnt matter */ + +			if( bt->tf->ft->sending ) +				jabber_bs_send_activate( bt ); +			else +				jabber_bs_recv_answer_request( bt ); + +			return FALSE; +		} +	default: +		/* BUG */ +		imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + +		bt->tf->watch_in = 0; +		return FALSE; +	} +} + +/* + * If the handshake failed we can try the next streamhost, if there is one. + * An intelligent sender would probably specify himself as the first streamhost and + * a proxy as the second (Kopete and PSI are examples here). That way, a (potentially)  + * slow proxy is only used if neccessary. This of course also means, that the timeout + * per streamhost should be kept short. If one or two firewalled adresses are specified, + * they have to timeout first before a proxy is tried. + */ +gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct xt_node *reply, *iqnode; +	GSList *shlist; + +	imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)",  +		  tf->ft->file_name,  +		  bt->sh->host, +		  bt->sh->port, +		  error ); + +	/* Alright, this streamhost failed, let's try the next... */ +	bt->phase = BS_PHASE_CONNECT; +	shlist = g_slist_find( bt->streamhosts, bt->sh ); +	if( shlist && shlist->next ) +	{ +		bt->sh = shlist->next->data; +		return jabber_bs_recv_handshake( bt, -1, 0 ); +	} + + +	/* out of stream hosts */ + +	iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL ); +	reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" ); +	xt_free_node( iqnode ); + +	xt_add_attr( reply, "id", tf->iq_id ); +		 +	if( !jabber_write_packet( tf->ic, reply ) ) +		imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" ); +	xt_free_node( reply ); + +	imcb_file_canceled( tf->ic, tf->ft, "couldn't connect to any streamhosts" ); + +	/* MUST always return FALSE! */ +	return FALSE; +} + +/*  + * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose. + * If he is the streamhost himself, he might already know that. However, if it's a proxy, + * the initiator will have to make a connection himself. + */ +void jabber_bs_recv_answer_request( struct bs_transfer *bt ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct xt_node *reply; + +	imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s",  +		  tf->ft->file_name,  +		  bt->sh->host, +		  bt->sh->port ); + +	tf->ft->data = tf; +	tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt ); +	tf->ft->write_request = jabber_bs_recv_write_request; + +	reply = xt_new_node( "streamhost-used", NULL, NULL ); +	xt_add_attr( reply, "jid", bt->sh->jid ); + +	reply = xt_new_node( "query", NULL, reply ); +	xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS ); + +	reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply ); + +	xt_add_attr( reply, "id", tf->iq_id ); +		 +	if( !jabber_write_packet( tf->ic, reply ) ) +		imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream response" ); +	xt_free_node( reply ); +} + +/*  + * This function is called from write_request directly. If no data is available, it will install itself + * as a watcher for input on fd and once that happens, deliver the data and unschedule itself again. + */ +gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ) +{ +	int ret; +	struct bs_transfer *bt = data; +	struct jabber_transfer *tf = bt->tf; + +	if( fd != -1 ) /* called via event thread */ +	{ +		tf->watch_in = 0; +		ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" ); +	} +	else +	{ +		/* called directly. There might not be any data available. */ +		if( ( ( ret = recv( tf->fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) ) == -1 ) && +		    ( errno != EAGAIN ) ) +		    return jabber_bs_abort( bt, "Receiving: %s", strerror( errno ) ); + +		if( ( ret == -1 ) && ( errno == EAGAIN ) ) +		{ +			tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt ); +			return FALSE; +		} +	} + +	/* shouldn't happen since we know the file size */ +	if( ret == 0 ) +		return jabber_bs_abort( bt, "Remote end closed connection" ); +	 +	tf->bytesread += ret; + +	if( tf->bytesread >= tf->ft->file_size ) +		imcb_file_finished( tf->ic, tf->ft ); + +	tf->ft->write( tf->ft, tf->ft->buffer, ret );	 + +	return FALSE; +} + +/*  + * imc callback that is invoked when it is ready to receive some data. + */ +gboolean jabber_bs_recv_write_request( file_transfer_t *ft ) +{ +	struct jabber_transfer *tf = ft->data; + +	if( tf->watch_in ) +	{ +		imcb_file_canceled( tf->ic, ft, "BUG in jabber file transfer: write_request called when already watching for input" ); +		return FALSE; +	} +	 +	jabber_bs_recv_read( tf->streamhandle, -1 , 0 ); + +	return TRUE; +} + +/*  + * Issues a write_request to imc. + * */ +gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond ) +{ +	struct bs_transfer *bt = data; + +	bt->tf->watch_out = 0; + +	bt->tf->ft->write_request( bt->tf->ft ); + +	return FALSE; +} + +/* + * This should only be called if we can write, so just do it. + * Add a write watch so we can write more during the next cycle (if possible). + */ +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ) +{ +	struct jabber_transfer *tf = ft->data; +	struct bs_transfer *bt = tf->streamhandle; +	int ret; + +	if( tf->watch_out ) +		return jabber_bs_abort( bt, "BUG: write() called while watching " ); +	 +	/* TODO: catch broken pipe */ +	ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" ); + +	tf->byteswritten += ret; +	 +	/* TODO: this should really not be fatal */ +	if( ret < len ) +		return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len ); + +	if( tf->byteswritten >= ft->file_size ) +		imcb_file_finished( tf->ic, ft ); +	else +		bt->tf->watch_out = b_input_add( tf->fd, B_EV_IO_WRITE, jabber_bs_send_can_write, bt ); +		 +	return TRUE; +} + +/* + * Handles the reply by the receiver containing the used streamhost. + */ +static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { +	struct jabber_transfer *tf = NULL; +	struct jabber_data *jd = ic->proto_data; +	struct bs_transfer *bt; +	GSList *tflist; +	struct xt_node *c; +	char *sid, *jid; + +	if( !( c = xt_find_node( node->children, "query" ) ) || +	    !( c = xt_find_node( c->children, "streamhost-used" ) ) || +	    !( jid = xt_find_attr( c, "jid" ) ) ) + +	{ +		imcb_log( ic, "WARNING: Received incomplete bytestream reply" ); +		return XT_HANDLED; +	} +	 +	if( !( c = xt_find_node( orig->children, "query" ) ) || +	    !( sid = xt_find_attr( c, "sid" ) ) ) +	{ +		imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" ); +		return XT_HANDLED; +	} + +	/* Let's see if we can find out what this bytestream should be for... */ + +	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) +	{ +		struct jabber_transfer *tft = tflist->data; +		if( ( strcmp( tft->sid, sid ) == 0 ) ) +		{ +		    	tf = tft; +			break; +		} +	} + +	if( !tf ) +	{ +		imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" ); +		return XT_HANDLED; +	} + +	bt = tf->streamhandle; + +	tf->accepted = TRUE; + +	if( strcmp( jid, tf->ini_jid ) == 0 ) +	{ +		/* we're streamhost and target */ +		if( bt->phase == BS_PHASE_REPLY ) +		{ +			/* handshake went through, let's start transferring */ +			tf->ft->write_request( tf->ft ); +		} +	} else +	{ +		/* using a proxy, abort listen */ + +		if( tf->watch_in ) +		{ +			b_event_remove( tf->watch_in ); +			tf->watch_in = 0; +		} +		 +		if( tf->fd != -1 ) { +			closesocket( tf->fd ); +			tf->fd = -1; +		} + +		if ( bt->connect_timeout ) +		{ +			b_event_remove( bt->connect_timeout ); +			bt->connect_timeout = 0; +		} + +		GSList *shlist; +		for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) ) +		{ +			jabber_streamhost_t *sh = shlist->data; +			if( strcmp( sh->jid, jid ) == 0 ) +			{ +				bt->sh = sh; +				jabber_bs_recv_handshake( bt, -1, 0 ); +				return XT_HANDLED; +			} +		} + +		imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid ); +	} + +	return XT_HANDLED; +} + +/*  + * Tell the proxy to activate the stream. Looks like this: + * + * <iq type=set> + * 	<query xmlns=bs sid=sid> + * 		<activate>tgt_jid</activate> + * 	</query> + * </iq> + */ +void jabber_bs_send_activate( struct bs_transfer *bt ) +{ +	struct xt_node *node; + +	node = xt_new_node( "activate", bt->tf->tgt_jid, NULL ); +	node = xt_new_node( "query", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS ); +	xt_add_attr( node, "sid", bt->tf->sid ); +	node = jabber_make_packet( "iq", "set", bt->sh->jid, node ); + +	jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate ); + +	jabber_write_packet( bt->tf->ic, node ); +} + +/* + * The proxy has activated the bytestream. + * We can finally start pushing some data out. + */ +static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	char *sid; +	GSList *tflist; +	struct jabber_transfer *tf = NULL; +	struct xt_node *query; +	struct jabber_data *jd = ic->proto_data; + +	query = xt_find_node( orig->children, "query" ); +	sid = xt_find_attr( query, "sid" ); + +	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) +	{ +		struct jabber_transfer *tft = tflist->data; +		if( ( strcmp( tft->sid, sid ) == 0 ) ) +		{ +		    	tf = tft; +			break; +		} +	} + +	if( !tf ) +	{ +		imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" ); +		return XT_HANDLED; +	} + +	imcb_log( tf->ic, "File %s: SOCKS5 handshake and activation successful! Transfer about to start...", tf->ft->file_name ); + +	/* handshake went through, let's start transferring */ +	tf->ft->write_request( tf->ft ); + +	return XT_HANDLED; +} + +jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *proxy ) +{ +	char *host, *port, *jid; +	jabber_streamhost_t *sh; + +	if( ( ( host = strchr( proxy, ',' ) ) == 0 ) || +	     ( ( port = strchr( host+1, ',' ) ) == 0 ) ) { +		imcb_log( ic, "Error parsing proxy setting: \"%s\" (ignored)", proxy ); +		return NULL; +	} +	 +	jid = proxy; +	*host++ = '\0'; +	*port++ = '\0'; + +	sh = g_new0( jabber_streamhost_t, 1 ); +	sh->jid = g_strdup( jid ); +	sh->host = g_strdup( host ); +	strcpy( sh->port, port ); + +	return sh; +} + +void jabber_si_set_proxies( struct bs_transfer *bt ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct jabber_data *jd = tf->ic->proto_data; +	char *proxysetting = g_strdup ( set_getstr( &tf->ic->acc->set, "proxy" ) ); +	char *proxy, *next, *errmsg = NULL; +	char port[6]; +	char host[HOST_NAME_MAX+1]; +	jabber_streamhost_t *sh, *sh2; +	GSList *streamhosts = jd->streamhosts; + +	proxy = proxysetting; +	while ( proxy && ( *proxy!='\0' ) ) { +		if( ( next = strchr( proxy, ';' ) ) ) +			*next++ = '\0';	 +		 +		if( strcmp( proxy, "<local>" ) == 0 ) { +			if( ( tf->fd = ft_listen( &tf->saddr, host, port, jd->fd, FALSE, &errmsg ) ) != -1 ) { +				sh = g_new0( jabber_streamhost_t, 1 ); +				sh->jid = g_strdup( tf->ini_jid ); +				sh->host = g_strdup( host ); +				strcpy( sh->port, port ); +				bt->streamhosts = g_slist_append( bt->streamhosts, sh ); + +				bt->tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_send_handshake, bt ); +				bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt ); +			} else { +				imcb_log( tf->ic, "Transferring file %s: couldn't listen locally(non fatal, check your ft_listen setting in bitlbee.conf): %s", +					  tf->ft->file_name, +					  errmsg ); +			} +		} else if( strcmp( proxy, "<auto>" ) == 0 ) { +			while ( streamhosts ) { +				sh = g_new0( jabber_streamhost_t, 1 ); +				sh2 = streamhosts->data; +				sh->jid = g_strdup( sh2->jid ); +				sh->host = g_strdup( sh2->host ); +				strcpy( sh->port, sh2->port ); +				bt->streamhosts = g_slist_append( bt->streamhosts, sh ); +				streamhosts = g_slist_next( streamhosts ); +			} +		} else if( ( sh = jabber_si_parse_proxy( tf->ic, proxy ) ) ) +			bt->streamhosts = g_slist_append( bt->streamhosts, sh ); +		proxy = next; +	} +} + +/* + * Starts a bytestream. + */ +gboolean jabber_bs_send_start( struct jabber_transfer *tf ) +{ +	struct bs_transfer *bt; +	sha1_state_t sha; +	char hash_hex[41]; +	unsigned char hash[20]; +	int i,ret; + +	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ +	sha1_init( &sha ); +	sha1_append( &sha, (unsigned char*) tf->sid, strlen( tf->sid ) ); +	sha1_append( &sha, (unsigned char*) tf->ini_jid, strlen( tf->ini_jid ) ); +	sha1_append( &sha, (unsigned char*) tf->tgt_jid, strlen( tf->tgt_jid ) ); +	sha1_finish( &sha, hash ); +	 +	for( i = 0; i < 20; i ++ ) +		sprintf( hash_hex + i * 2, "%02x", hash[i] ); +		 +	bt = g_new0( struct bs_transfer, 1 ); +	bt->tf = tf; +	bt->phase = BS_PHASE_CONNECT; +	bt->pseudoadr = g_strdup( hash_hex ); +	tf->streamhandle = bt; +	tf->ft->free = jabber_bs_free_transfer; +	tf->ft->canceled = jabber_bs_canceled; + +	jabber_si_set_proxies( bt ); + +	ret = jabber_bs_send_request( tf, bt->streamhosts); + +	return ret; +} + +gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ) +{ +	struct xt_node *shnode, *query, *iq; + +	query = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( query, "xmlns", XMLNS_BYTESTREAMS ); +	xt_add_attr( query, "sid", tf->sid ); +	xt_add_attr( query, "mode", "tcp" ); + +	while( streamhosts ) { +		jabber_streamhost_t *sh = streamhosts->data; +		shnode = xt_new_node( "streamhost", NULL, NULL ); +		xt_add_attr( shnode, "jid", sh->jid ); +		xt_add_attr( shnode, "host", sh->host ); +		xt_add_attr( shnode, "port", sh->port ); + +		xt_add_child( query, shnode ); + +		streamhosts = g_slist_next( streamhosts ); +	} + + +	iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query ); +	xt_add_attr( iq, "from", tf->ini_jid ); + +	jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply ); + +	if( !jabber_write_packet( tf->ic, iq ) ) +		imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream request" ); +	return TRUE; +} + +gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct jabber_data *jd = tf->ic->proto_data; + +	/* TODO: did the receiver get here somehow??? */ +	imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s",  +		  tf->ft->file_name,  +		  error ); + +	if( jd->streamhosts==NULL ) /* we're done here unless we have a proxy to try */ +		imcb_file_canceled( tf->ic, tf->ft, error ); + +	/* MUST always return FALSE! */ +	return FALSE; +} + +/* + * SOCKS5BYTESTREAM protocol for the sender + */ +gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ) +{ +	struct bs_transfer *bt = data; +	struct jabber_transfer *tf = bt->tf; +	short revents; + +	if ( !jabber_bs_poll( bt, fd, &revents ) ) +		return FALSE; +	 +	switch( bt->phase )  +	{ +	case BS_PHASE_CONNECT: +		{ +			struct sockaddr_storage clt_addr; +			socklen_t ssize = sizeof( clt_addr ); +			 +			/* Connect */ + +			ASSERTSOCKOP( tf->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + +			closesocket( fd ); +			fd = tf->fd; +			sock_make_nonblocking( fd ); +			 +			bt->phase = BS_PHASE_CONNECTED; + +			bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_send_handshake, bt ); +			return FALSE; +		} +	case BS_PHASE_CONNECTED: +		{ +			int ret, have_noauth=FALSE; +			struct { +				unsigned char ver; +				unsigned char method; +			} socks5_auth_reply = { .ver = 5, .method = 0 }; +			struct { +				unsigned char ver; +				unsigned char nmethods; +				unsigned char method; +			} socks5_hello; + +			if( !( ret = jabber_bs_peek( bt, &socks5_hello, sizeof( socks5_hello ) ) ) ) +				return FALSE; + +			if( ret < sizeof( socks5_hello ) ) +				return TRUE; + +			if( !( socks5_hello.ver == 5 ) || +			    !( socks5_hello.nmethods >= 1 ) || +			    !( socks5_hello.nmethods < 32 ) ) +				return jabber_bs_abort( bt, "Invalid auth request ver=%d nmethods=%d method=%d", socks5_hello.ver, socks5_hello.nmethods, socks5_hello.method ); + +			have_noauth = socks5_hello.method == 0; + +			if( socks5_hello.nmethods > 1 ) +			{ +				char mbuf[32]; +				int i; +				ASSERTSOCKOP( ret = recv( fd, mbuf, socks5_hello.nmethods - 1, 0 ) , "Receiving auth methods" ); +				if( ret < ( socks5_hello.nmethods - 1 ) ) +					return jabber_bs_abort( bt, "Partial auth request"); +				for( i = 0 ; !have_noauth && ( i < socks5_hello.nmethods - 1 ) ; i ++ ) +					if( mbuf[i] == 0 ) +						have_noauth = TRUE; +			} +			 +			if( !have_noauth ) +				return jabber_bs_abort( bt, "Auth request didn't include no authentication" ); + +			ASSERTSOCKOP( send( fd, &socks5_auth_reply, sizeof( socks5_auth_reply ) , 0 ), "Sending auth reply" ); + +			bt->phase = BS_PHASE_REQUEST; + +			return TRUE; +		} +	case BS_PHASE_REQUEST: +		{ +			struct socks5_message socks5_connect; +			int msgsize = sizeof( struct socks5_message ); +			int ret; + +			if( !( ret = jabber_bs_peek( bt, &socks5_connect, msgsize ) ) ) +				return FALSE; + +			if( ret < msgsize ) +				return TRUE; + +			if( !( socks5_connect.ver == 5) || +			    !( socks5_connect.cmdrep.cmd == 1 ) || +			    !( socks5_connect.atyp == 3 ) || +			    !(socks5_connect.addrlen == 40 ) ) +				return jabber_bs_abort( bt, "Invalid SOCKS5 Connect message (addrlen=%d, ver=%d, cmd=%d, atyp=%d)", socks5_connect.addrlen, socks5_connect.ver, socks5_connect.cmdrep.cmd, socks5_connect.atyp ); +			if( !( memcmp( socks5_connect.address, bt->pseudoadr, 40 ) == 0 ) ) +				return jabber_bs_abort( bt, "SOCKS5 Connect message contained wrong digest"); + +			socks5_connect.cmdrep.rep = 0; + +			ASSERTSOCKOP( send( fd, &socks5_connect, msgsize, 0 ), "Sending connect reply" ); + +			bt->phase = BS_PHASE_REPLY; + +			imcb_log( tf->ic, "File %s: SOCKS5 handshake successful! Transfer about to start...", tf->ft->file_name ); + +			if( tf->accepted ) +			{ +				/* streamhost-used message came already in(possible?), let's start sending */ +				tf->ft->write_request( tf->ft ); +			} + +			tf->watch_in = 0; +			return FALSE; + +		} +	default: +		/* BUG */ +		imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + +		bt->tf->watch_in = 0; +		return FALSE; +	} +} +#undef ASSERTSOCKOP diff --git a/protocols/jabber/sasl.c b/protocols/jabber/sasl.c new file mode 100644 index 00000000..53248ef3 --- /dev/null +++ b/protocols/jabber/sasl.c @@ -0,0 +1,348 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - SASL authentication                                      * +*                                                                           * +*  Copyright 2006 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 <ctype.h> + +#include "jabber.h" +#include "base64.h" + +xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = data; +	struct jabber_data *jd = ic->proto_data; +	struct xt_node *c, *reply; +	char *s; +	int sup_plain = 0, sup_digest = 0; +	 +	if( !sasl_supported( ic ) ) +	{ +		/* Should abort this now, since we should already be doing +		   IQ authentication. Strange things happen when you try +		   to do both... */ +		imcb_log( ic, "XMPP 1.0 non-compliant server seems to support SASL, please report this as a BitlBee bug!" ); +		return XT_HANDLED; +	} +	 +	s = xt_find_attr( node, "xmlns" ); +	if( !s || strcmp( s, XMLNS_SASL ) != 0 ) +	{ +		imcb_log( ic, "Stream error while authenticating" ); +		imc_logout( ic, FALSE ); +		return XT_ABORT; +	} +	 +	c = node->children; +	while( ( c = xt_find_node( c, "mechanism" ) ) ) +	{ +		if( c->text && g_strcasecmp( c->text, "PLAIN" ) == 0 ) +			sup_plain = 1; +		if( c->text && g_strcasecmp( c->text, "DIGEST-MD5" ) == 0 ) +			sup_digest = 1; +		 +		c = c->next; +	} +	 +	if( !sup_plain && !sup_digest ) +	{ +		imcb_error( ic, "No known SASL authentication schemes supported" ); +		imc_logout( ic, FALSE ); +		return XT_ABORT; +	} +	 +	reply = xt_new_node( "auth", NULL, NULL ); +	xt_add_attr( reply, "xmlns", XMLNS_SASL ); +	 +	if( sup_digest ) +	{ +		xt_add_attr( reply, "mechanism", "DIGEST-MD5" ); +		 +		/* The rest will be done later, when we receive a <challenge/>. */ +	} +	else if( sup_plain ) +	{ +		int len; +		 +		xt_add_attr( reply, "mechanism", "PLAIN" ); +		 +		/* With SASL PLAIN in XMPP, the text should be b64(\0user\0pass) */ +		len = strlen( jd->username ) + strlen( ic->acc->pass ) + 2; +		s = g_malloc( len + 1 ); +		s[0] = 0; +		strcpy( s + 1, jd->username ); +		strcpy( s + 2 + strlen( jd->username ), ic->acc->pass ); +		reply->text = base64_encode( (unsigned char *)s, len ); +		reply->text_len = strlen( reply->text ); +		g_free( s ); +	} +	 +	if( !jabber_write_packet( ic, reply ) ) +	{ +		xt_free_node( reply ); +		return XT_ABORT; +	} +	xt_free_node( reply ); +	 +	/* To prevent classic authentication from happening. */ +	jd->flags |= JFLAG_STREAM_STARTED; +	 +	return XT_HANDLED; +} + +/* Non-static function, but not mentioned in jabber.h because it's for internal +   use, just that the unittest should be able to reach it... */ +char *sasl_get_part( char *data, char *field ) +{ +	int i, len; +	 +	len = strlen( field ); +	 +	while( isspace( *data ) || *data == ',' ) +		data ++; +	 +	if( g_strncasecmp( data, field, len ) == 0 && data[len] == '=' ) +	{ +		i = strlen( field ) + 1; +	} +	else +	{ +		for( i = 0; data[i]; i ++ ) +		{ +			/* If we have a ", skip until it's closed again. */ +			if( data[i] == '"' ) +			{ +				i ++; +				while( data[i] != '"' || data[i-1] == '\\' ) +					i ++; +			} +			 +			/* If we got a comma, we got a new field. Check it, +			   find the next key after it. */ +			if( data[i] == ',' ) +			{ +				while( isspace( data[i] ) || data[i] == ',' ) +					i ++; +				 +				if( g_strncasecmp( data + i, field, len ) == 0 && +				    data[i+len] == '=' ) +				{ +					i += len + 1; +					break; +				} +			} +		} +	} +	 +	if( data[i] == '"' ) +	{ +		int j; +		char *ret; +		 +		i ++; +		len = 0; +		while( data[i+len] != '"' || data[i+len-1] == '\\' ) +			len ++; +		 +		ret = g_strndup( data + i, len ); +		for( i = j = 0; ret[i]; i ++ ) +		{ +			if( ret[i] == '\\' ) +			{ +				ret[j++] = ret[++i]; +			} +			else +			{ +				ret[j++] = ret[i]; +			} +		} +		ret[j] = 0; +		 +		return ret; +	} +	else if( data[i] ) +	{ +		len = 0; +		while( data[i+len] && data[i+len] != ',' ) +			len ++; +		 +		return g_strndup( data + i, len ); +	} +	else +	{ +		return NULL; +	} +} + +xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = data; +	struct jabber_data *jd = ic->proto_data; +	struct xt_node *reply = NULL; +	char *nonce = NULL, *realm = NULL, *cnonce = NULL; +	unsigned char cnonce_bin[30]; +	char *digest_uri = NULL; +	char *dec = NULL; +	char *s = NULL; +	xt_status ret = XT_ABORT; +	 +	if( node->text_len == 0 ) +		goto error; +	 +	dec = frombase64( node->text ); +	 +	if( !( s = sasl_get_part( dec, "rspauth" ) ) ) +	{ +		/* See RFC 2831 for for information. */ +		md5_state_t A1, A2, H; +		md5_byte_t A1r[16], A2r[16], Hr[16]; +		char A1h[33], A2h[33], Hh[33]; +		int i; +		 +		nonce = sasl_get_part( dec, "nonce" ); +		realm = sasl_get_part( dec, "realm" ); +		 +		if( !nonce ) +			goto error; +		 +		/* Jabber.Org considers the realm part optional and doesn't +		   specify one. Oh well, actually they're right, but still, +		   don't know if this is right... */ +		if( !realm ) +			realm = g_strdup( jd->server ); +		 +		random_bytes( cnonce_bin, sizeof( cnonce_bin ) ); +		cnonce = base64_encode( cnonce_bin, sizeof( cnonce_bin ) ); +		digest_uri = g_strdup_printf( "%s/%s", "xmpp", jd->server ); +		 +		/* Generate the MD5 hash of username:realm:password, +		   I decided to call it H. */ +		md5_init( &H ); +		s = g_strdup_printf( "%s:%s:%s", jd->username, realm, ic->acc->pass ); +		md5_append( &H, (unsigned char *) s, strlen( s ) ); +		g_free( s ); +		md5_finish( &H, Hr ); +		 +		/* Now generate the hex. MD5 hash of H:nonce:cnonce, called A1. */ +		md5_init( &A1 ); +		s = g_strdup_printf( ":%s:%s", nonce, cnonce ); +		md5_append( &A1, Hr, 16 ); +		md5_append( &A1, (unsigned char *) s, strlen( s ) ); +		g_free( s ); +		md5_finish( &A1, A1r ); +		for( i = 0; i < 16; i ++ ) +			sprintf( A1h + i * 2, "%02x", A1r[i] ); +		 +		/* A2... */ +		md5_init( &A2 ); +		s = g_strdup_printf( "%s:%s", "AUTHENTICATE", digest_uri ); +		md5_append( &A2, (unsigned char *) s, strlen( s ) ); +		g_free( s ); +		md5_finish( &A2, A2r ); +		for( i = 0; i < 16; i ++ ) +			sprintf( A2h + i * 2, "%02x", A2r[i] ); +		 +		/* Final result: A1:nonce:00000001:cnonce:auth:A2. Let's reuse H for it. */ +		md5_init( &H ); +		s = g_strdup_printf( "%s:%s:%s:%s:%s:%s", A1h, nonce, "00000001", cnonce, "auth", A2h ); +		md5_append( &H, (unsigned char *) s, strlen( s ) ); +		g_free( s ); +		md5_finish( &H, Hr ); +		for( i = 0; i < 16; i ++ ) +			sprintf( Hh + i * 2, "%02x", Hr[i] ); +		 +		/* Now build the SASL response string: */ +		g_free( dec ); +		dec = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\"," +		                       "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s", +		                       jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" ); +		s = tobase64( dec ); +	} +	else +	{ +		/* We found rspauth, but don't really care... */ +		g_free( s ); +		s = NULL; +	} +	 +	reply = xt_new_node( "response", s, NULL ); +	xt_add_attr( reply, "xmlns", XMLNS_SASL ); +	 +	if( !jabber_write_packet( ic, reply ) ) +		goto silent_error; +	 +	ret = XT_HANDLED; +	goto silent_error; + +error: +	imcb_error( ic, "Incorrect SASL challenge received" ); +	imc_logout( ic, FALSE ); + +silent_error: +	g_free( digest_uri ); +	g_free( cnonce ); +	g_free( nonce ); +	g_free( realm ); +	g_free( dec ); +	g_free( s ); +	xt_free_node( reply ); +	 +	return ret; +} + +xt_status sasl_pkt_result( struct xt_node *node, gpointer data ) +{ +	struct im_connection *ic = data; +	struct jabber_data *jd = ic->proto_data; +	char *s; +	 +	s = xt_find_attr( node, "xmlns" ); +	if( !s || strcmp( s, XMLNS_SASL ) != 0 ) +	{ +		imcb_log( ic, "Stream error while authenticating" ); +		imc_logout( ic, FALSE ); +		return XT_ABORT; +	} +	 +	if( strcmp( node->name, "success" ) == 0 ) +	{ +		imcb_log( ic, "Authentication finished" ); +		jd->flags |= JFLAG_AUTHENTICATED | JFLAG_STREAM_RESTART; +	} +	else if( strcmp( node->name, "failure" ) == 0 ) +	{ +		imcb_error( ic, "Authentication failure" ); +		imc_logout( ic, FALSE ); +		return XT_ABORT; +	} +	 +	return XT_HANDLED; +} + +/* This one is needed to judge if we'll do authentication using IQ or SASL. +   It's done by checking if the <stream:stream> from the server has a +   version attribute. I don't know if this is the right way though... */ +gboolean sasl_supported( struct im_connection *ic ) +{ +	struct jabber_data *jd = ic->proto_data; +	 +	return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0; +} diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c new file mode 100644 index 00000000..4b0e57c4 --- /dev/null +++ b/protocols/jabber/si.c @@ -0,0 +1,533 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - SI packets                                               * +*                                                                           * +*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         * +*                                                                           * +*  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" +#include "sha1.h" + +void jabber_si_answer_request( file_transfer_t *ft ); +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ); + +/* file_transfer free() callback */ +void jabber_si_free_transfer( file_transfer_t *ft) +{ +	struct jabber_transfer *tf = ft->data; +	struct jabber_data *jd = tf->ic->proto_data; + +	if ( tf->watch_in ) +		b_event_remove( tf->watch_in ); + +	jd->filetransfers = g_slist_remove( jd->filetransfers, tf ); + +	if( tf->fd != -1 ) +	{ +		closesocket( tf->fd ); +		tf->fd = -1; +	} + +	if( tf->disco_timeout ) +		b_event_remove( tf->disco_timeout ); +	 +	g_free( tf->ini_jid ); +	g_free( tf->tgt_jid ); +	g_free( tf->iq_id ); +	g_free( tf->sid ); +	g_free( tf ); +} + +/* file_transfer canceled() callback */ +void jabber_si_canceled( file_transfer_t *ft, char *reason ) +{ +	struct jabber_transfer *tf = ft->data; +	struct xt_node *reply, *iqnode; + +	if( tf->accepted ) +		return; +	 +	iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL ); +	xt_add_attr( iqnode, "id", tf->iq_id ); +	reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" ); +	xt_free_node( iqnode ); +	 +	if( !jabber_write_packet( tf->ic, reply ) ) +		imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); +	xt_free_node( reply ); + +} + +int jabber_si_check_features( struct jabber_transfer *tf, GSList *features ) { +	int foundft = FALSE, foundbt = FALSE, foundsi = FALSE; + +	while ( features ) +	{ +		if( !strcmp( features->data, XMLNS_FILETRANSFER ) ) +			foundft = TRUE; +		if( !strcmp( features->data, XMLNS_BYTESTREAMS ) ) +			foundbt = TRUE; +		if( !strcmp( features->data, XMLNS_SI ) ) +			foundsi = TRUE; + +		features = g_slist_next(features); +	} + +	if( !foundft ) +		imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature file transfers" ); +	else if( !foundbt ) +		imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature byte streams (required)" ); +	else if( !foundsi ) +		imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature stream initiation (required)" ); +		 +	return foundft && foundbt && foundsi; +} + +void jabber_si_transfer_start( struct jabber_transfer *tf ) { + +	if( !jabber_si_check_features( tf, tf->bud->features ) ) +		return; +		 +	/* send the request to our buddy */ +	jabber_si_send_request( tf->ic, tf->bud->full_jid, tf ); + +	/* and start the receive logic */ +	imcb_file_recv_start( tf->ic, tf->ft ); + +} + +gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond ) +{ +	struct jabber_transfer *tf = data; +	struct jabber_data *jd = tf->ic->proto_data; + +	tf->disco_timeout_fired++; + +	if( tf->bud->features && jd->have_streamhosts==1 ) { +		tf->disco_timeout = 0; +		jabber_si_transfer_start( tf ); +		return FALSE; +	} + +	/* 8 seconds should be enough for server and buddy to respond */ +	if ( tf->disco_timeout_fired < 16 ) +		return TRUE; +	 +	if( !tf->bud->features && jd->have_streamhosts!=1 ) +		imcb_log( tf->ic, "Couldn't get buddy's features nor discover all services of the server" ); +	else if( !tf->bud->features ) +		imcb_log( tf->ic, "Couldn't get buddy's features" ); +	else +		imcb_log( tf->ic, "Couldn't discover some of the server's services" ); +	 +	tf->disco_timeout = 0; +	jabber_si_transfer_start( tf ); +	return FALSE; +} + +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who )  +{ +	struct jabber_transfer *tf; +	struct jabber_data *jd = ic->proto_data; +	struct jabber_buddy *bud; +	char *server = jd->server, *s; + +	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) +		bud = jabber_buddy_by_ext_jid( ic, who, 0 ); +	else +		bud = jabber_buddy_by_jid( ic, who, 0 ); + +	if( bud == NULL ) +	{ +		imcb_file_canceled( ic, ft, "Couldn't find buddy (BUG?)" ); +		return; +	} +	 +	imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who ); + +	tf = g_new0( struct jabber_transfer, 1 ); + +	tf->ic = ic; +	tf->ft = ft; +	tf->fd = -1; +	tf->ft->data = tf; +	tf->ft->free = jabber_si_free_transfer; +	tf->bud = bud; +	ft->write = jabber_bs_send_write; + +	jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + +	/* query buddy's features and server's streaming proxies if neccessary */ + +	if( !tf->bud->features ) +		jabber_iq_query_features( ic, bud->full_jid ); + +	/* If <auto> is not set don't check for proxies */ +	if( ( jd->have_streamhosts!=1 ) && ( jd->streamhosts==NULL ) && +	    ( strstr( set_getstr( &ic->acc->set, "proxy" ), "<auto>" ) != NULL ) ) { +		jd->have_streamhosts = 0; +		jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS ); +	} else if ( jd->streamhosts!=NULL ) +		jd->have_streamhosts = 1; + +	/* if we had to do a query, wait for the result.  +	 * Otherwise fire away. */ +	if( !tf->bud->features || jd->have_streamhosts!=1 ) +		tf->disco_timeout = b_timeout_add( 500, jabber_si_waitfor_disco, tf ); +	else +		jabber_si_transfer_start( tf ); +} + +/* + * First function that gets called when a file transfer request comes in. + * A lot to parse. + * + * We choose a stream type from the options given by the initiator. + * Then we wait for imcb to call the accept or cancel callbacks. + */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode) +{ +	struct xt_node *c, *d, *reply; +	char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s; +	struct jabber_buddy *bud; +	int requestok = FALSE; +	char *name, *cmp; +	size_t size; +	struct jabber_transfer *tf; +	struct jabber_data *jd = ic->proto_data; +	file_transfer_t *ft; +	 +	/* All this means we expect something like this: ( I think ) +	 * <iq from=... to=... id=...> +	 * 	<si id=id xmlns=si profile=ft> +	 * 		<file xmlns=ft/> +	 * 		<feature xmlns=feature> +	 * 			<x xmlns=xdata type=submit> +	 * 				<field var=stream-method> +	 * +	 */ +	if( !( ini_jid 		= xt_find_attr(   node, "from" ) 			) || +	    !( tgt_jid 		= xt_find_attr(   node, "to" ) 				) || +	    !( iq_id 		= xt_find_attr(   node, "id" ) 				) || +	    !( sid 		= xt_find_attr( sinode, "id" ) 				) || +	    !( cmp              = xt_find_attr( sinode, "profile" )                     ) || +	    !( 0               == strcmp( cmp, XMLNS_FILETRANSFER )			) || +	    !( d 		= xt_find_node( sinode->children, "file" ) 		) || +	    !( cmp = xt_find_attr( d, "xmlns" )						) || +	    !( 0               == strcmp( cmp, XMLNS_FILETRANSFER )			) || +	    !( name 		= xt_find_attr( d, "name" ) 				) || +	    !( size_s           = xt_find_attr( d, "size" )                             ) || +	    !( 1               == sscanf( size_s, "%zd", &size )                        ) || +	    !( d 		= xt_find_node( sinode->children, "feature" ) 		) || +	    !( cmp              = xt_find_attr( d, "xmlns" )				) || +	    !( 0               == strcmp( cmp, XMLNS_FEATURE )				) || +	    !( d 		= xt_find_node( d->children, "x" ) 			) || +	    !( cmp              = xt_find_attr( d, "xmlns" )				) || +	    !( 0               == strcmp( cmp, XMLNS_XDATA )				) || +	    !( cmp              = xt_find_attr( d, "type" )				) || +	    !( 0               == strcmp( cmp, "form" )					) || +	    !( d 		= xt_find_node( d->children, "field" ) 			) || +	    !( cmp              = xt_find_attr( d, "var" )				) || +	    !( 0               == strcmp( cmp, "stream-method" )			) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" ); +	} +	else +	{ +		/* Check if we support one of the options */ + +		c = d->children; +		while( ( c = xt_find_node( c, "option" ) ) ) +			if( ( d = xt_find_node( c->children, "value" ) ) && +			    ( d->text != NULL ) && +			    ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) +			{ +				requestok = TRUE; +				break; +			} +			else +			{ +				c = c->next; +			} + +		if ( !requestok ) +			imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); +	} +	 +	if( requestok ) +	{ +		/* Figure out who the transfer should come frome... */ + +		ext_jid = ini_jid; +		if( ( s = strchr( ini_jid, '/' ) ) ) +		{ +			if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) ) +			{ +				bud->last_msg = time( NULL ); +				ext_jid = bud->ext_jid ? : bud->bare_jid; +			} +			else +				*s = 0; /* We need to generate a bare JID now. */ +		} + +		if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) ) +		{  +			imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid); +			requestok = FALSE; +		} + +		*s = '/'; +	} +	else +	{  +		reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL ); +		if (!jabber_write_packet( ic, reply )) +			imcb_log( ic, "WARNING: Error generating reply to file transfer request" ); +		xt_free_node( reply ); +		return XT_HANDLED; +	} + +	/* Request is fine. */ + +	tf = g_new0( struct jabber_transfer, 1 ); + +	tf->ini_jid = g_strdup( ini_jid ); +	tf->tgt_jid = g_strdup( tgt_jid ); +	tf->iq_id = g_strdup( iq_id ); +	tf->sid = g_strdup( sid ); +	tf->ic = ic; +	tf->ft = ft; +	tf->fd = -1; +	tf->ft->data = tf; +	tf->ft->accept = jabber_si_answer_request; +	tf->ft->free = jabber_si_free_transfer; +	tf->ft->canceled = jabber_si_canceled; + +	jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + +	return XT_HANDLED; +} + +/* + * imc called the accept callback which probably means that the user accepted this file transfer. + * We send our response to the initiator. + * In the next step, the initiator will send us a request for the given stream type. + * (currently that can only be a SOCKS5 bytestream) + */ +void jabber_si_answer_request( file_transfer_t *ft ) { +	struct jabber_transfer *tf = ft->data; +	struct xt_node *node, *sinode, *reply; + +	/* generate response, start with the SI tag */ +	sinode = xt_new_node( "si", NULL, NULL ); +	xt_add_attr( sinode, "xmlns", XMLNS_SI ); +	xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); +	xt_add_attr( sinode, "id", tf->sid ); + +	/* now the file tag */ +	node = xt_new_node( "file", NULL, NULL ); +	xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); + +	xt_add_child( sinode, node ); + +	/* and finally the feature tag */ +	node = xt_new_node( "field", NULL, NULL ); +	xt_add_attr( node, "var", "stream-method" ); +	xt_add_attr( node, "type", "list-single" ); + +	/* Currently all we can do. One could also implement in-band (IBB) */ +	xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) ); + +	node = xt_new_node( "x", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_XDATA ); +	xt_add_attr( node, "type", "submit" ); + +	node = xt_new_node( "feature", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + +	xt_add_child( sinode, node ); + +	reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode ); +	xt_add_attr( reply, "id", tf->iq_id ); +	 +	if( !jabber_write_packet( tf->ic, reply ) ) +		imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); +	else +		tf->accepted = TRUE; +	xt_free_node( reply ); +} + +static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *c, *d; +	char *ini_jid = NULL, *tgt_jid, *iq_id, *cmp; +	GSList *tflist; +	struct jabber_transfer *tf=NULL; +	struct jabber_data *jd = ic->proto_data; + +	if( !( tgt_jid = xt_find_attr( node, "from" ) ) || +	    !( ini_jid = xt_find_attr( node, "to" ) ) ) +	{ +		imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid ); +		return XT_HANDLED; +	} +	 +	/* All this means we expect something like this: ( I think ) +	 * <iq from=... to=... id=...> +	 * 	<si xmlns=si> +	 * 	[	<file xmlns=ft/>    ] <-- not neccessary +	 * 		<feature xmlns=feature> +	 * 			<x xmlns=xdata type=submit> +	 * 				<field var=stream-method> +	 * 					<value> +	 */ +	if( !( tgt_jid = xt_find_attr( node, "from" ) ) || +	    !( ini_jid = xt_find_attr( node, "to" ) ) || +	    !( iq_id   = xt_find_attr( node, "id" ) ) || +	    !( c = xt_find_node( node->children, "si" ) ) || +	    !( cmp = xt_find_attr( c, "xmlns" ) ) || +	    !( strcmp( cmp, XMLNS_SI ) == 0 ) || +	    !( d = xt_find_node( c->children, "feature" ) ) || +	    !( cmp = xt_find_attr( d, "xmlns" ) ) || +	    !( strcmp( cmp, XMLNS_FEATURE ) == 0 ) || +	    !( d = xt_find_node( d->children, "x" ) ) || +	    !( cmp = xt_find_attr( d, "xmlns" ) ) || +	    !( strcmp( cmp, XMLNS_XDATA ) == 0 ) || +	    !( cmp = xt_find_attr( d, "type" ) ) || +	    !( strcmp( cmp, "submit" ) == 0 ) || +	    !( d = xt_find_node( d->children, "field" ) ) || +	    !( cmp = xt_find_attr( d, "var" ) ) || +	    !( strcmp( cmp, "stream-method" ) == 0 ) || +	    !( d = xt_find_node( d->children, "value" ) ) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" ); +		return XT_HANDLED; +	} + +	if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) {  +		/* since we should only have advertised what we can do and the peer should +		 * only have chosen what we offered, this should never happen */ +		imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text ); +			 +		return XT_HANDLED; +	} +	 +	/* Let's see if we can find out what this bytestream should be for... */ + +	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) +	{ +		struct jabber_transfer *tft = tflist->data; +		if( ( strcmp( tft->iq_id, iq_id ) == 0 ) ) +		{ +		    	tf = tft; +			break; +		} +	} + +	if (!tf)  +	{ +		imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); +		return XT_HANDLED; +	} + +	tf->ini_jid = g_strdup( ini_jid ); +	tf->tgt_jid = g_strdup( tgt_jid ); + +	imcb_log( ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid ); + +	jabber_bs_send_start( tf ); + +	return XT_HANDLED; +} + +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ) +{ +	struct xt_node *node, *sinode; +	struct jabber_buddy *bud; + +	/* who knows how many bits the future holds :) */ +	char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ]; + +	const char *methods[] =  +	{  	 +		XMLNS_BYTESTREAMS, +		//XMLNS_IBB, +		NULL  +	}; +	const char **m; +	char *s; + +	/* Maybe we should hash this? */ +	tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id ); +	 +	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) +		bud = jabber_buddy_by_ext_jid( ic, who, 0 ); +	else +		bud = jabber_buddy_by_jid( ic, who, 0 ); + +	/* start with the SI tag */ +	sinode = xt_new_node( "si", NULL, NULL ); +	xt_add_attr( sinode, "xmlns", XMLNS_SI ); +	xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); +	xt_add_attr( sinode, "id", tf->sid ); + +/*	if( mimetype )  +		xt_add_attr( node, "mime-type", mimetype ); */ + +	/* now the file tag */ +/*	if( desc ) + 		node = xt_new_node( "desc", descr, NULL ); */ +	node = xt_new_node( "range", NULL, NULL ); + +	sprintf( filesizestr, "%zd", tf->ft->file_size ); +	node = xt_new_node( "file", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); +	xt_add_attr( node, "name", tf->ft->file_name ); +	xt_add_attr( node, "size", filesizestr ); +/*	if (hash) +		xt_add_attr( node, "hash", hash ); +	if (date) +		xt_add_attr( node, "date", date ); */ + +	xt_add_child( sinode, node ); + +	/* and finally the feature tag */ +	node = xt_new_node( "field", NULL, NULL ); +	xt_add_attr( node, "var", "stream-method" ); +	xt_add_attr( node, "type", "list-single" ); + +	for ( m = methods ; *m ; m ++ ) +		xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) ); + +	node = xt_new_node( "x", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_XDATA ); +	xt_add_attr( node, "type", "form" ); + +	node = xt_new_node( "feature", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + +	xt_add_child( sinode, node ); + +	/* and we are there... */ +	node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode ); +	jabber_cache_add( ic, node, jabber_si_handle_response ); +	tf->iq_id = g_strdup( xt_find_attr( node, "id" ) ); +	 +	return jabber_write_packet( ic, node ); +} | 
