aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/jabber
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/jabber')
-rw-r--r--protocols/jabber/conference.c66
-rw-r--r--protocols/jabber/io.c10
-rw-r--r--protocols/jabber/iq.c15
-rw-r--r--protocols/jabber/jabber.c11
-rw-r--r--protocols/jabber/jabber.h22
-rw-r--r--protocols/jabber/jabber_util.c44
-rw-r--r--protocols/jabber/presence.c76
7 files changed, 178 insertions, 66 deletions
diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c
index 074412ec..79fdd053 100644
--- a/protocols/jabber/conference.c
+++ b/protocols/jabber/conference.c
@@ -36,6 +36,8 @@ struct groupchat *jabber_chat_join( struct im_connection *ic, char *room, char *
node = xt_new_node( "x", NULL, NULL );
xt_add_attr( node, "xmlns", XMLNS_MUC );
node = jabber_make_packet( "presence", NULL, roomjid, node );
+ if( password )
+ xt_add_child( node, xt_new_node( "password", password, NULL ) );
jabber_cache_add( ic, node, jabber_chat_join_failed );
if( !jabber_write_packet( ic, node ) )
@@ -72,17 +74,16 @@ static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_no
char *room;
room = xt_find_attr( orig, "to" );
- if( ( bud = jabber_buddy_by_jid( ic, room, 0 ) ) )
- jabber_chat_free( jabber_chat_by_jid( ic, bud->bare_jid ) );
-
+ 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",
- bud->bare_jid, err->code, err->text ? ": " : "",
- err->text ? err->text : "" );
+ 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;
}
@@ -123,6 +124,8 @@ int jabber_chat_msg( struct groupchat *c, char *message, int flags )
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 );
@@ -295,22 +298,59 @@ void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud
{
struct xt_node *subject = xt_find_node( node->children, "subject" );
struct xt_node *body = xt_find_node( node->children, "body" );
- struct groupchat *chat;
+ struct groupchat *chat = bud ? jabber_chat_by_jid( ic, bud->bare_jid ) : NULL;
+ struct jabber_chat *jc = chat ? chat->data : NULL;
char *s;
- if( bud == NULL )
+ 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. */
- if( strchr( s, '/' ) == NULL )
+ 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. */
- imcb_log( ic, "System message from groupchat %s: %s", s, body? body->text : "NULL" );
+ 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, however, isn't fine! */
- imcb_log( ic, "Groupchat message from unknown participant %s: %s", s, body ? body->text : "NULL" );
+ {
+ /* 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 = jabber_chat_by_jid( ic, bud->bare_jid ) ) == NULL )
+ else if( chat == NULL )
{
/* How could this happen?? We could do kill( self, 11 )
now or just wait for the OS to do it. :-) */
diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c
index 29561b86..86c216ef 100644
--- a/protocols/jabber/io.c
+++ b/protocols/jabber/io.c
@@ -248,6 +248,9 @@ gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition c
{
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" );
@@ -263,7 +266,12 @@ gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition c
gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond )
{
struct im_connection *ic = data;
- struct jabber_data *jd = ic->proto_data;
+ struct jabber_data *jd;
+
+ if( g_slist_find( jabber_connections, ic ) == NULL )
+ return FALSE;
+
+ jd = ic->proto_data;
if( source == NULL )
{
diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c
index 2f0959b0..d45b7625 100644
--- a/protocols/jabber/iq.c
+++ b/protocols/jabber/iq.c
@@ -53,7 +53,7 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
( c = xt_find_node( node->children, "ping" ) ) ) || /* O_o WHAT is wrong with just <query/> ????? */
!( s = xt_find_attr( c, "xmlns" ) ) )
{
- imcb_log( ic, "WARNING: Received incomplete IQ-%s packet", type );
+ imcb_log( ic, "Warning: Received incomplete IQ-%s packet", type );
return XT_HANDLED;
}
@@ -91,7 +91,8 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
}
else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 )
{
- const char *features[] = { XMLNS_VERSION,
+ const char *features[] = { XMLNS_DISCO_INFO,
+ XMLNS_VERSION,
XMLNS_TIME,
XMLNS_CHATSTATES,
XMLNS_MUC,
@@ -131,7 +132,7 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
} else if( !( c = xt_find_node( node->children, "query" ) ) ||
!( s = xt_find_attr( c, "xmlns" ) ) )
{
- imcb_log( ic, "WARNING: Received incomplete IQ-%s packet", type );
+ imcb_log( ic, "Warning: Received incomplete IQ-%s packet", type );
return XT_HANDLED;
} else if( strcmp( s, XMLNS_ROSTER ) == 0 )
{
@@ -151,7 +152,7 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
}
else
{
- imcb_log( ic, "WARNING: %s tried to fake a roster push!", s ? s : "(unknown)" );
+ 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 );
@@ -219,7 +220,7 @@ static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *no
if( !( query = xt_find_node( node->children, "query" ) ) )
{
- imcb_log( ic, "WARNING: Received incomplete IQ packet while authenticating" );
+ imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" );
imc_logout( ic, FALSE );
return XT_HANDLED;
}
@@ -277,7 +278,7 @@ static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node
if( !( type = xt_find_attr( node, "type" ) ) )
{
- imcb_log( ic, "WARNING: Received incomplete IQ packet while authenticating" );
+ imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" );
imc_logout( ic, FALSE );
return XT_HANDLED;
}
@@ -353,7 +354,7 @@ static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *
if( !( query = xt_find_node( node->children, "query" ) ) )
{
- imcb_log( ic, "WARNING: Received NULL roster packet" );
+ imcb_log( ic, "Warning: Received NULL roster packet" );
return XT_HANDLED;
}
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c
index d028655a..07d760a5 100644
--- a/protocols/jabber/jabber.c
+++ b/protocols/jabber/jabber.c
@@ -34,6 +34,8 @@
#include "md5.h"
#include "base64.h"
+GSList *jabber_connections;
+
static void jabber_init( account_t *acc )
{
set_t *s;
@@ -70,6 +72,11 @@ static void jabber_login( account_t *acc )
struct ns_srv_reply *srv = NULL;
char *connect_to, *s;
+ /* 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;
@@ -196,6 +203,8 @@ static void jabber_login( account_t *acc )
{
imcb_error( ic, "Could not connect to server" );
imc_logout( ic, TRUE );
+
+ return;
}
if( set_getbool( &acc->set, "xmlconsole" ) )
@@ -260,6 +269,8 @@ static void jabber_logout( struct im_connection *ic )
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 )
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
index 3251b49b..fc315710 100644
--- a/protocols/jabber/jabber.h
+++ b/protocols/jabber/jabber.h
@@ -29,6 +29,8 @@
#include "xmltree.h"
#include "bitlbee.h"
+extern GSList *jabber_connections;
+
typedef enum
{
JFLAG_STREAM_STARTED = 1, /* Set when we detected the beginning of the stream
@@ -46,13 +48,13 @@ typedef enum
typedef enum
{
- JBFLAG_PROBED_XEP85 = 1, /* Set this when we sent our probe packet to make
+ 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
+ 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
+ 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
+ JBFLAG_IS_ANONYMOUS = 8, /* For anonymous chatrooms, when we don't have
have a real JID. */
} jabber_buddy_flags_t;
@@ -64,6 +66,12 @@ typedef struct
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;
@@ -104,6 +112,7 @@ typedef xt_status (*jabber_cache_event) ( struct im_connection *ic, struct xt_no
struct jabber_cache_entry
{
+ time_t saved_at;
struct xt_node *node;
jabber_cache_event func;
};
@@ -175,6 +184,10 @@ struct jabber_transfer
#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"
@@ -197,6 +210,7 @@ struct jabber_transfer
#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 */
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
index 7350eaf4..c8470b57 100644
--- a/protocols/jabber/jabber_util.c
+++ b/protocols/jabber/jabber_util.c
@@ -145,6 +145,7 @@ void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_ca
entry->node = node;
entry->func = func;
+ entry->saved_at = time( NULL );
g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry );
}
@@ -166,22 +167,17 @@ gboolean jabber_cache_clean_entry( gpointer key, gpointer entry, gpointer nullpo
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, NULL );
+ g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, &threshold );
}
-gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer nullpointer )
+gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer threshold_ )
{
struct jabber_cache_entry *entry = entry_;
- struct xt_node *node = entry->node;
+ time_t *threshold = threshold_;
- if( node->flags & XT_SEEN )
- return TRUE;
- else
- {
- node->flags |= XT_SEEN;
- return FALSE;
- }
+ return entry->saved_at < *threshold;
}
xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node )
@@ -203,7 +199,7 @@ xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *
if( entry == NULL )
{
- imcb_log( ic, "WARNING: Received %s-%s packet with unknown/expired ID %s!",
+ 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 )
@@ -402,19 +398,26 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
*s = 0;
if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
{
+ /* 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;
}
- else
- {
- /* See if there's an exact match. */
- for( ; bud; bud = bud->next )
- if( g_strcasecmp( bud->resource, s + 1 ) == 0 )
- break;
- }
+
+ /* See if there's an exact match. */
+ for( ; bud; bud = bud->next )
+ if( g_strcasecmp( bud->resource, s + 1 ) == 0 )
+ break;
}
else
{
@@ -423,6 +426,8 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
for this JID, even if it's an unknown buddy. This
is done to handle conferences properly. */
none_found = 1;
+ /* TODO(wilmer): Find out what I was thinking when I
+ wrote this??? And then fix it. This makes me sad... */
}
if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && ( imcb_find_buddy( ic, jid ) || !none_found ) )
@@ -453,6 +458,9 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
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;
best_prio = best_time = bud;
for( ; bud; bud = bud->next )
diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c
index c3d7dced..6fc360b7 100644
--- a/protocols/jabber/presence.c
+++ b/protocols/jabber/presence.c
@@ -28,9 +28,9 @@ 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;
- struct jabber_buddy *bud;
- int is_chat = 0, is_away = 0;
+ struct xt_node *c, *cap;
+ struct jabber_buddy *bud, *send_presence = NULL;
+ int is_chat = 0;
char *s;
if( !from )
@@ -49,7 +49,7 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
if( !( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT | GET_BUDDY_CREAT ) ) )
{
if( set_getbool( &ic->irc->set, "debug" ) )
- imcb_log( ic, "WARNING: Could not handle presence information from JID: %s", from );
+ imcb_log( ic, "Warning: Could not handle presence information from JID: %s", from );
return XT_HANDLED;
}
@@ -62,8 +62,6 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
if( ( c = xt_find_node( node->children, "show" ) ) && c->text_len > 0 )
{
bud->away_state = (void*) jabber_away_state_by_code( c->text );
- if( strcmp( c->text, "chat" ) != 0 )
- is_away = OPT_AWAY;
}
else
{
@@ -78,19 +76,37 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
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 if( bud == jabber_buddy_by_jid( ic, bud->bare_jid, 0 ) )
- imcb_buddy_status( ic, bud->bare_jid, OPT_LOGGED_IN | is_away,
- ( is_away && bud->away_state ) ? bud->away_state->full_name : NULL,
- bud->away_message );
+ else
+ 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 )
{
if( set_getbool( &ic->irc->set, "debug" ) )
- imcb_log( ic, "WARNING: Received presence information from unknown JID: %s", from );
+ imcb_log( ic, "Warning: Received presence information from unknown JID: %s", from );
return XT_HANDLED;
}
@@ -118,17 +134,7 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
/* If another resource is still available, send its presence
information. */
- if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) )
- {
- if( bud->away_state && ( *bud->away_state->code == 0 ||
- strcmp( bud->away_state->code, "chat" ) == 0 ) )
- is_away = OPT_AWAY;
-
- imcb_buddy_status( ic, bud->bare_jid, OPT_LOGGED_IN | is_away,
- ( is_away && bud->away_state ) ? bud->away_state->full_name : NULL,
- bud->away_message );
- }
- else
+ 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 );
@@ -176,6 +182,20 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
jabber_error_free( err );
} */
}
+
+ if( send_presence )
+ {
+ int is_away = 0;
+
+ if( send_presence->away_state && !( *send_presence->away_state->code == 0 ||
+ 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 ) ?
+ send_presence->away_state->full_name : NULL,
+ send_presence->away_message );
+ }
return XT_HANDLED;
}
@@ -185,7 +205,7 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
int presence_send_update( struct im_connection *ic )
{
struct jabber_data *jd = ic->proto_data;
- struct xt_node *node;
+ struct xt_node *node, *cap;
char *show = jd->away_state->code;
char *status = jd->away_message;
struct groupchat *c;
@@ -198,6 +218,16 @@ int presence_send_update( struct im_connection *ic )
if( status )
xt_add_child( node, xt_new_node( "status", status, 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