source: protocols/jabber/jabber.c @ ffdf2e7

Last change on this file since ffdf2e7 was ffdf2e7, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-08-10T11:18:09Z

When doing SRV lookups, return an array with all RRs instead of just the
first one. The first isn't always the best one and this is currently causing
GTalk issues when talk2.l.google.com (which is currently dead) is first.

  • Property mode set to 100644
File size: 16.1 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Main file                                                *
5*                                                                           *
6*  Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net>                   *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include <glib.h>
25#include <string.h>
26#include <unistd.h>
27#include <ctype.h>
28#include <stdio.h>
29
30#include "ssl_client.h"
31#include "xmltree.h"
32#include "bitlbee.h"
33#include "jabber.h"
34#include "md5.h"
35
36GSList *jabber_connections;
37
38/* First enty is the default */
39static const int jabber_port_list[] = {
40        5222,
41        5223,
42        5220,
43        5221,
44        5224,
45        5225,
46        5226,
47        5227,
48        5228,
49        5229,
50        80,
51        443,
52        0
53};
54
55static void jabber_init( account_t *acc )
56{
57        set_t *s;
58        char str[16];
59       
60        s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc );
61       
62        g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
63        s = set_add( &acc->set, "port", str, set_eval_int, acc );
64        s->flags |= ACC_SET_OFFLINE_ONLY;
65       
66        s = set_add( &acc->set, "priority", "0", set_eval_priority, acc );
67
68        s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc );
69       
70        s = set_add( &acc->set, "resource", "BitlBee", NULL, acc );
71        s->flags |= ACC_SET_OFFLINE_ONLY;
72       
73        s = set_add( &acc->set, "resource_select", "activity", NULL, acc );
74       
75        s = set_add( &acc->set, "server", NULL, set_eval_account, acc );
76        s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
77       
78        s = set_add( &acc->set, "ssl", "false", set_eval_bool, acc );
79        s->flags |= ACC_SET_OFFLINE_ONLY;
80       
81        s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );
82        s->flags |= ACC_SET_OFFLINE_ONLY;
83       
84        s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc );
85       
86        s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
87        s->flags |= ACC_SET_OFFLINE_ONLY;
88       
89        acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE;
90}
91
92static void jabber_generate_id_hash( struct jabber_data *jd );
93
94static void jabber_login( account_t *acc )
95{
96        struct im_connection *ic = imcb_new( acc );
97        struct jabber_data *jd = g_new0( struct jabber_data, 1 );
98        struct ns_srv_reply **srvl = NULL, *srv;
99        char *connect_to, *s;
100        int i;
101       
102        /* For now this is needed in the _connected() handlers if using
103           GLib event handling, to make sure we're not handling events
104           on dead connections. */
105        jabber_connections = g_slist_prepend( jabber_connections, ic );
106       
107        jd->ic = ic;
108        ic->proto_data = jd;
109       
110        jd->username = g_strdup( acc->user );
111        jd->server = strchr( jd->username, '@' );
112       
113        jd->fd = jd->r_inpa = jd->w_inpa = -1;
114       
115        if( jd->server == NULL )
116        {
117                imcb_error( ic, "Incomplete account name (format it like <username@jabberserver.name>)" );
118                imc_logout( ic, FALSE );
119                return;
120        }
121       
122        /* So don't think of free()ing jd->server.. :-) */
123        *jd->server = 0;
124        jd->server ++;
125       
126        if( ( s = strchr( jd->server, '/' ) ) )
127        {
128                *s = 0;
129                set_setstr( &acc->set, "resource", s + 1 );
130               
131                /* Also remove the /resource from the original variable so we
132                   won't have to do this again every time. */
133                s = strchr( acc->user, '/' );
134                *s = 0;
135        }
136       
137        /* This code isn't really pretty. Backwards compatibility never is... */
138        s = acc->server;
139        while( s )
140        {
141                static int had_port = 0;
142               
143                if( strncmp( s, "ssl", 3 ) == 0 )
144                {
145                        set_setstr( &acc->set, "ssl", "true" );
146                       
147                        /* Flush this part so that (if this was the first
148                           part of the server string) acc->server gets
149                           flushed. We don't want to have to do this another
150                           time. :-) */
151                        *s = 0;
152                        s ++;
153                       
154                        /* Only set this if the user didn't specify a custom
155                           port number already... */
156                        if( !had_port )
157                                set_setint( &acc->set, "port", 5223 );
158                }
159                else if( isdigit( *s ) )
160                {
161                        int i;
162                       
163                        /* The first character is a digit. It could be an
164                           IP address though. Only accept this as a port#
165                           if there are only digits. */
166                        for( i = 0; isdigit( s[i] ); i ++ );
167                       
168                        /* If the first non-digit character is a colon or
169                           the end of the string, save the port number
170                           where it should be. */
171                        if( s[i] == ':' || s[i] == 0 )
172                        {
173                                sscanf( s, "%d", &i );
174                                set_setint( &acc->set, "port", i );
175                               
176                                /* See above. */
177                                *s = 0;
178                                s ++;
179                        }
180                       
181                        had_port = 1;
182                }
183               
184                s = strchr( s, ':' );
185                if( s )
186                {
187                        *s = 0;
188                        s ++;
189                }
190        }
191       
192        jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free );
193        jd->buddies = g_hash_table_new( g_str_hash, g_str_equal );
194       
195        /* Figure out the hostname to connect to. */
196        if( acc->server && *acc->server )
197                connect_to = acc->server;
198        else if( ( srvl = srv_lookup( "xmpp-client", "tcp", jd->server ) ) ||
199                 ( srvl = srv_lookup( "jabber-client", "tcp", jd->server ) ) )
200        {
201                /* Find the lowest-priority one. These usually come
202                   back in random/shuffled order. Not looking at
203                   weights etc for now. */
204                srv = *srvl;
205                for( i = 1; srvl[i]; i ++ )
206                        if( srvl[i]->prio < srv->prio )
207                                srv = srvl[i];
208               
209                connect_to = srv->name;
210        }
211        else
212                connect_to = jd->server;
213       
214        imcb_log( ic, "Connecting" );
215       
216        for( i = 0; jabber_port_list[i] > 0; i ++ )
217                if( set_getint( &acc->set, "port" ) == jabber_port_list[i] )
218                        break;
219
220        if( jabber_port_list[i] == 0 )
221        {
222                imcb_log( ic, "Illegal port number" );
223                imc_logout( ic, FALSE );
224                return;
225        }
226       
227        /* For non-SSL connections we can try to use the port # from the SRV
228           reply, but let's not do that when using SSL, SSL usually runs on
229           non-standard ports... */
230        if( set_getbool( &acc->set, "ssl" ) )
231        {
232                jd->ssl = ssl_connect( connect_to, set_getint( &acc->set, "port" ), jabber_connected_ssl, ic );
233                jd->fd = jd->ssl ? ssl_getfd( jd->ssl ) : -1;
234        }
235        else
236        {
237                jd->fd = proxy_connect( connect_to, srv ? srv->port : set_getint( &acc->set, "port" ), jabber_connected_plain, ic );
238        }
239        srv_free( srvl );
240       
241        if( jd->fd == -1 )
242        {
243                imcb_error( ic, "Could not connect to server" );
244                imc_logout( ic, TRUE );
245               
246                return;
247        }
248       
249        if( set_getbool( &acc->set, "xmlconsole" ) )
250        {
251                jd->flags |= JFLAG_XMLCONSOLE;
252                /* Shouldn't really do this at this stage already, maybe. But
253                   I think this shouldn't break anything. */
254                imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
255        }
256       
257        jabber_generate_id_hash( jd );
258}
259
260/* This generates an unfinished md5_state_t variable. Every time we generate
261   an ID, we finish the state by adding a sequence number and take the hash. */
262static void jabber_generate_id_hash( struct jabber_data *jd )
263{
264        md5_byte_t binbuf[4];
265        char *s;
266       
267        md5_init( &jd->cached_id_prefix );
268        md5_append( &jd->cached_id_prefix, (unsigned char *) jd->username, strlen( jd->username ) );
269        md5_append( &jd->cached_id_prefix, (unsigned char *) jd->server, strlen( jd->server ) );
270        s = set_getstr( &jd->ic->acc->set, "resource" );
271        md5_append( &jd->cached_id_prefix, (unsigned char *) s, strlen( s ) );
272        random_bytes( binbuf, 4 );
273        md5_append( &jd->cached_id_prefix, binbuf, 4 );
274}
275
276static void jabber_logout( struct im_connection *ic )
277{
278        struct jabber_data *jd = ic->proto_data;
279       
280        while( jd->filetransfers )
281                imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" );
282
283        while( jd->streamhosts )
284        {
285                jabber_streamhost_t *sh = jd->streamhosts->data;
286                jd->streamhosts = g_slist_remove( jd->streamhosts, sh );
287                g_free( sh->jid );
288                g_free( sh->host );
289                g_free( sh );
290        }
291
292        if( jd->fd >= 0 )
293                jabber_end_stream( ic );
294       
295        while( ic->groupchats )
296                jabber_chat_free( ic->groupchats->data );
297       
298        if( jd->r_inpa >= 0 )
299                b_event_remove( jd->r_inpa );
300        if( jd->w_inpa >= 0 )
301                b_event_remove( jd->w_inpa );
302       
303        if( jd->ssl )
304                ssl_disconnect( jd->ssl );
305        if( jd->fd >= 0 )
306                closesocket( jd->fd );
307       
308        if( jd->tx_len )
309                g_free( jd->txq );
310       
311        if( jd->node_cache )
312                g_hash_table_destroy( jd->node_cache );
313       
314        jabber_buddy_remove_all( ic );
315       
316        xt_free( jd->xt );
317       
318        g_free( jd->away_message );
319        g_free( jd->username );
320        g_free( jd );
321       
322        jabber_connections = g_slist_remove( jabber_connections, ic );
323}
324
325static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
326{
327        struct jabber_data *jd = ic->proto_data;
328        struct jabber_buddy *bud;
329        struct xt_node *node;
330        char *s;
331        int st;
332       
333        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
334                return jabber_write( ic, message, strlen( message ) );
335       
336        if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
337                bud = jabber_buddy_by_ext_jid( ic, who, 0 );
338        else
339                bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK );
340       
341        node = xt_new_node( "body", message, NULL );
342        node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
343       
344        if( bud && ( jd->flags & JFLAG_WANT_TYPING ) &&
345            ( ( bud->flags & JBFLAG_DOES_XEP85 ) ||
346             !( bud->flags & JBFLAG_PROBED_XEP85 ) ) )
347        {
348                struct xt_node *act;
349               
350                /* If the user likes typing notification and if we don't know
351                   (and didn't probe before) if this resource supports XEP85,
352                   include a probe in this packet now. Also, if we know this
353                   buddy does support XEP85, we have to send this <active/>
354                   tag to tell that the user stopped typing (well, that's what
355                   we guess when s/he pressed Enter...). */
356                act = xt_new_node( "active", NULL, NULL );
357                xt_add_attr( act, "xmlns", XMLNS_CHATSTATES );
358                xt_add_child( node, act );
359               
360                /* Just make sure we do this only once. */
361                bud->flags |= JBFLAG_PROBED_XEP85;
362        }
363       
364        st = jabber_write_packet( ic, node );
365        xt_free_node( node );
366       
367        return st;
368}
369
370static GList *jabber_away_states( struct im_connection *ic )
371{
372        static GList *l = NULL;
373        int i;
374       
375        if( l == NULL )
376                for( i = 0; jabber_away_state_list[i].full_name; i ++ )
377                        l = g_list_append( l, (void*) jabber_away_state_list[i].full_name );
378       
379        return l;
380}
381
382static void jabber_get_info( struct im_connection *ic, char *who )
383{
384        struct jabber_buddy *bud;
385       
386        bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST );
387       
388        while( bud )
389        {
390                imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority );
391                if( bud->away_state )
392                        imcb_log( ic, "Away state: %s", bud->away_state->full_name );
393                imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" );
394               
395                bud = bud->next;
396        }
397       
398        jabber_get_vcard( ic, bud ? bud->full_jid : who );
399}
400
401static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message )
402{
403        struct jabber_data *jd = ic->proto_data;
404       
405        /* state_txt == NULL -> Not away.
406           Unknown state -> fall back to the first defined away state. */
407        if( state_txt == NULL )
408                jd->away_state = NULL;
409        else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL )
410                jd->away_state = jabber_away_state_list;
411       
412        g_free( jd->away_message );
413        jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL;
414       
415        presence_send_update( ic );
416}
417
418static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
419{
420        struct jabber_data *jd = ic->proto_data;
421       
422        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
423        {
424                jd->flags |= JFLAG_XMLCONSOLE;
425                imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
426                return;
427        }
428       
429        if( jabber_add_to_roster( ic, who, NULL, group ) )
430                presence_send_request( ic, who, "subscribe" );
431}
432
433static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
434{
435        struct jabber_data *jd = ic->proto_data;
436       
437        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
438        {
439                jd->flags &= ~JFLAG_XMLCONSOLE;
440                /* Not necessary for now. And for now the code isn't too
441                   happy if the buddy is completely gone right after calling
442                   this function already.
443                imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
444                */
445                return;
446        }
447       
448        /* We should always do this part. Clean up our administration a little bit. */
449        jabber_buddy_remove_bare( ic, who );
450       
451        if( jabber_remove_from_roster( ic, who ) )
452                presence_send_request( ic, who, "unsubscribe" );
453}
454
455static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
456{
457        if( strchr( room, '@' ) == NULL )
458                imcb_error( ic, "Invalid room name: %s", room );
459        else if( jabber_chat_by_jid( ic, room ) )
460                imcb_error( ic, "Already present in chat `%s'", room );
461        else
462                return jabber_chat_join( ic, room, nick, password );
463       
464        return NULL;
465}
466
467static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
468{
469        if( c && message )
470                jabber_chat_msg( c, message, flags );
471}
472
473static void jabber_chat_topic_( struct groupchat *c, char *topic )
474{
475        if( c && topic )
476                jabber_chat_topic( c, topic );
477}
478
479static void jabber_chat_leave_( struct groupchat *c )
480{
481        if( c )
482                jabber_chat_leave( c, NULL );
483}
484
485static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg )
486{
487        struct jabber_chat *jc = c->data;
488        gchar *msg_alt = NULL;
489
490        if( msg == NULL )
491                msg_alt = g_strdup_printf( "%s invited you to %s", c->ic->acc->user, jc->name );
492       
493        if( c && who )
494                jabber_chat_invite( c, who, msg ? msg : msg_alt );
495       
496        g_free( msg_alt );
497}
498
499static void jabber_keepalive( struct im_connection *ic )
500{
501        /* Just any whitespace character is enough as a keepalive for XMPP sessions. */
502        if( !jabber_write( ic, "\n", 1 ) )
503                return;
504       
505        /* This runs the garbage collection every minute, which means every packet
506           is in the cache for about a minute (which should be enough AFAIK). */
507        jabber_cache_clean( ic );
508}
509
510static int jabber_send_typing( struct im_connection *ic, char *who, int typing )
511{
512        struct jabber_data *jd = ic->proto_data;
513        struct jabber_buddy *bud;
514       
515        /* Enable typing notification related code from now. */
516        jd->flags |= JFLAG_WANT_TYPING;
517       
518        if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL )
519        {
520                /* Sending typing notifications to unknown buddies is
521                   unsupported for now. Shouldn't be a problem, I think. */
522                return 0;
523        }
524       
525        if( bud->flags & JBFLAG_DOES_XEP85 )
526        {
527                /* We're only allowed to send this stuff if we know the other
528                   side supports it. */
529               
530                struct xt_node *node;
531                char *type;
532                int st;
533               
534                if( typing & OPT_TYPING )
535                        type = "composing";
536                else if( typing & OPT_THINKING )
537                        type = "paused";
538                else
539                        type = "active";
540               
541                node = xt_new_node( type, NULL, NULL );
542                xt_add_attr( node, "xmlns", XMLNS_CHATSTATES );
543                node = jabber_make_packet( "message", "chat", bud->full_jid, node );
544               
545                st = jabber_write_packet( ic, node );
546                xt_free_node( node );
547               
548                return st;
549        }
550       
551        return 1;
552}
553
554void jabber_initmodule()
555{
556        struct prpl *ret = g_new0( struct prpl, 1 );
557       
558        ret->name = "jabber";
559        ret->login = jabber_login;
560        ret->init = jabber_init;
561        ret->logout = jabber_logout;
562        ret->buddy_msg = jabber_buddy_msg;
563        ret->away_states = jabber_away_states;
564        ret->set_away = jabber_set_away;
565//      ret->set_info = jabber_set_info;
566        ret->get_info = jabber_get_info;
567        ret->add_buddy = jabber_add_buddy;
568        ret->remove_buddy = jabber_remove_buddy;
569        ret->chat_msg = jabber_chat_msg_;
570        ret->chat_topic = jabber_chat_topic_;
571        ret->chat_invite = jabber_chat_invite_;
572        ret->chat_leave = jabber_chat_leave_;
573        ret->chat_join = jabber_chat_join_;
574        ret->keepalive = jabber_keepalive;
575        ret->send_typing = jabber_send_typing;
576        ret->handle_cmp = g_strcasecmp;
577        ret->transfer_request = jabber_si_transfer_request;
578
579        register_protocol( ret );
580}
Note: See TracBrowser for help on using the repository browser.