aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/jabber/jabber_util.c
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/jabber/jabber_util.c')
-rw-r--r--protocols/jabber/jabber_util.c532
1 files changed, 532 insertions, 0 deletions
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
new file mode 100644
index 00000000..3c0e71f4
--- /dev/null
+++ b/protocols/jabber/jabber_util.c
@@ -0,0 +1,532 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Misc. stuff *
+* *
+* 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"
+
+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 NULL;
+ }
+ else
+ return NULL;
+
+ /* 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
+ when the 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 )
+{
+ 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 );
+
+ /* 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 writing it! */
+void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func )
+{
+ struct jabber_data *jd = ic->proto_data;
+ char *id = g_strdup_printf( "%s%05x", JABBER_CACHED_ID, ( next_id++ ) & 0xfffff );
+ struct jabber_cache_entry *entry = g_new0( struct jabber_cache_entry, 1 );
+
+ xt_add_attr( node, "id", id );
+ g_free( id );
+
+ entry->node = node;
+ entry->func = func;
+ 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;
+
+ g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, NULL );
+}
+
+gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer nullpointer )
+{
+ struct jabber_cache_entry *entry = entry_;
+ struct xt_node *node = entry->node;
+
+ if( node->flags & XT_SEEN )
+ return TRUE;
+ else
+ {
+ node->flags |= XT_SEEN;
+ return FALSE;
+ }
+}
+
+const struct jabber_away_state jabber_away_state_list[] =
+{
+ { "away", "Away" },
+ { "chat", "Free for Chat" },
+ { "dnd", "Do not Disturb" },
+ { "xa", "Extended Away" },
+ { "", "Online" },
+ { "", NULL }
+};
+
+const struct jabber_away_state *jabber_away_state_by_code( char *code )
+{
+ int i;
+
+ 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;
+
+ 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( gpointer w, struct jabber_buddy_ask_data *bla )
+{
+ presence_send_request( bla->ic, bla->handle, "subscribed" );
+
+ if( imcb_find_buddy( bla->ic, bla->handle ) == NULL )
+ imcb_ask_add( bla->ic, bla->handle, NULL );
+
+ g_free( bla->handle );
+ g_free( bla );
+}
+
+static void jabber_buddy_ask_no( gpointer w, struct jabber_buddy_ask_data *bla )
+{
+ 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( char *orig )
+{
+ int len, i;
+ char *new;
+
+ len = strlen( orig );
+ new = g_new( char, len + 1 );
+ for( i = 0; i < len; i ++ )
+ new[i] = tolower( 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 ) ) )
+ {
+ /* 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( g_strcasecmp( 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->bare_jid = g_strdup( full_jid );
+ g_hash_table_insert( jd->buddies, new->bare_jid, new );
+ }
+
+ 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.. */
+ 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;
+ char *s, *jid;
+
+ jid = jabber_normalize( jid_ );
+
+ if( ( s = strchr( jid, '/' ) ) )
+ {
+ *s = 0;
+ if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
+ {
+ /* Is this one of those no-resource buddies? */
+ if( bud->resource == NULL )
+ {
+ g_free( jid );
+ return NULL;
+ }
+ else
+ {
+ /* See if there's an exact match. */
+ for( ; bud; bud = bud->next )
+ if( g_strcasecmp( bud->resource, s + 1 ) == 0 )
+ break;
+ }
+ }
+
+ if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid ) )
+ {
+ *s = '/';
+ bud = jabber_buddy_add( ic, jid );
+ }
+
+ g_free( jid );
+ return bud;
+ }
+ else
+ {
+ struct jabber_buddy *best_prio, *best_time;
+ char *set;
+
+ bud = g_hash_table_lookup( jd->buddies, jid );
+
+ g_free( jid );
+
+ if( bud == NULL )
+ /* No match. Create it now? */
+ return ( ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( 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;
+
+ best_prio = best_time = bud;
+ for( ; bud; bud = bud->next )
+ {
+ if( bud->priority > best_prio->priority )
+ best_prio = bud;
+ if( bud->last_act > best_time->last_act )
+ best_time = bud;
+ }
+
+ if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL )
+ return NULL;
+ else if( strcmp( set, "activity" ) == 0 )
+ return best_time;
+ else /* if( strcmp( set, "priority" ) == 0 ) */
+ return best_prio;
+ }
+}
+
+/* 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, *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 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 ) || g_strcasecmp( bud->resource, s + 1 ) == 0 ) )
+ {
+ g_hash_table_remove( jd->buddies, bud->bare_jid );
+ g_free( bud->bare_jid );
+ g_free( bud->full_jid );
+ g_free( bud->away_message );
+ g_free( bud );
+
+ g_free( full_jid );
+
+ return 1;
+ }
+ 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, prev = NULL; bi; bi = (prev=bi)->next )
+ if( g_strcasecmp( bi->resource, s + 1 ) == 0 )
+ break;
+
+ g_free( full_jid );
+
+ if( bi )
+ {
+ if( prev )
+ prev->next = bi->next;
+ else
+ /* The hash table should point at the second
+ item, because we're removing the first. */
+ g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
+
+ 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;
+ char *bare_jid;
+
+ if( strchr( bare_jid_, '/' ) )
+ return 0;
+
+ bare_jid = jabber_normalize( bare_jid_ );
+
+ if( ( bud = g_hash_table_lookup( jd->buddies, bare_jid ) ) )
+ {
+ /* Most important: Remove the hash reference. We don't know
+ this buddy anymore. */
+ g_hash_table_remove( jd->buddies, bud->bare_jid );
+
+ /* Deallocate the linked list of resources. */
+ while( bud )
+ {
+ next = bud->next;
+ g_free( bud->full_jid );
+ g_free( bud->away_message );
+ g_free( bud );
+ bud = next;
+ }
+
+ g_free( bare_jid );
+ return 1;
+ }
+ else
+ {
+ g_free( bare_jid );
+ return 0;
+ }
+}