source: protocols/jabber/jabber.c @ 0e788f5

Last change on this file since 0e788f5 was 0e788f5, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-02-21T19:15:59Z

I'm still bored on a long flight. Wrote a script to automatically update
my copyright mentions since some were getting pretty stale. Left files not
touched since before 2012 alone so that this change doesn't touch almost
EVERY source file.

  • Property mode set to 100644
File size: 18.5 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Main file                                                *
5*                                                                           *
6*  Copyright 2006-2013 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 "oauth.h"
35#include "md5.h"
36
37GSList *jabber_connections;
38
39/* First enty is the default */
40static const int jabber_port_list[] = {
41        5222,
42        5223,
43        5220,
44        5221,
45        5224,
46        5225,
47        5226,
48        5227,
49        5228,
50        5229,
51        80,
52        443,
53        0
54};
55
56static void jabber_init( account_t *acc )
57{
58        set_t *s;
59        char str[16];
60       
61        s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc );
62       
63        s = set_add( &acc->set, "oauth", "false", set_eval_oauth, acc );
64
65        g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
66        s = set_add( &acc->set, "port", str, set_eval_int, acc );
67        s->flags |= ACC_SET_OFFLINE_ONLY;
68       
69        s = set_add( &acc->set, "priority", "0", set_eval_priority, acc );
70
71        s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc );
72       
73        s = set_add( &acc->set, "resource", "BitlBee", NULL, acc );
74        s->flags |= ACC_SET_OFFLINE_ONLY;
75       
76        s = set_add( &acc->set, "resource_select", "activity", NULL, acc );
77       
78        s = set_add( &acc->set, "sasl", "true", set_eval_bool, acc );
79        s->flags |= ACC_SET_OFFLINE_ONLY | SET_HIDDEN_DEFAULT;
80       
81        s = set_add( &acc->set, "server", NULL, set_eval_account, acc );
82        s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
83       
84        s = set_add( &acc->set, "ssl", "false", set_eval_bool, acc );
85        s->flags |= ACC_SET_OFFLINE_ONLY;
86       
87        s = set_add( &acc->set, "tls", "true", set_eval_tls, acc );
88        s->flags |= ACC_SET_OFFLINE_ONLY;
89       
90        s = set_add( &acc->set, "tls_verify", "true", set_eval_bool, acc );
91        s->flags |= ACC_SET_OFFLINE_ONLY;
92
93        s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc );
94       
95        s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
96        s->flags |= ACC_SET_OFFLINE_ONLY;
97       
98        acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE |
99                      ACC_FLAG_HANDLE_DOMAINS;
100}
101
102static void jabber_generate_id_hash( struct jabber_data *jd );
103
104static void jabber_login( account_t *acc )
105{
106        struct im_connection *ic = imcb_new( acc );
107        struct jabber_data *jd = g_new0( struct jabber_data, 1 );
108        char *s;
109       
110        /* For now this is needed in the _connected() handlers if using
111           GLib event handling, to make sure we're not handling events
112           on dead connections. */
113        jabber_connections = g_slist_prepend( jabber_connections, ic );
114       
115        jd->ic = ic;
116        ic->proto_data = jd;
117       
118        jabber_set_me( ic, acc->user );
119       
120        jd->fd = jd->r_inpa = jd->w_inpa = -1;
121       
122        if( jd->server == NULL )
123        {
124                imcb_error( ic, "Incomplete account name (format it like <username@jabberserver.name>)" );
125                imc_logout( ic, FALSE );
126                return;
127        }
128       
129        if( ( s = strchr( jd->server, '/' ) ) )
130        {
131                *s = 0;
132                set_setstr( &acc->set, "resource", s + 1 );
133               
134                /* Also remove the /resource from the original variable so we
135                   won't have to do this again every time. */
136                s = strchr( acc->user, '/' );
137                *s = 0;
138        }
139       
140        jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free );
141        jd->buddies = g_hash_table_new( g_str_hash, g_str_equal );
142       
143        if( set_getbool( &acc->set, "oauth" ) )
144        {
145                GSList *p_in = NULL;
146                const char *tok;
147               
148                jd->fd = jd->r_inpa = jd->w_inpa = -1;
149               
150                if( strstr( jd->server, ".live.com" ) )
151                        jd->oauth2_service = &oauth2_service_mslive;
152                else if( strstr( jd->server, ".facebook.com" ) )
153                        jd->oauth2_service = &oauth2_service_facebook;
154                else
155                        jd->oauth2_service = &oauth2_service_google;
156               
157                oauth_params_parse( &p_in, ic->acc->pass );
158               
159                /* First see if we have a refresh token, in which case any
160                   access token we *might* have has probably expired already
161                   anyway. */
162                if( ( tok = oauth_params_get( &p_in, "refresh_token" ) ) )
163                {
164                        sasl_oauth2_refresh( ic, tok );
165                }
166                /* If we don't have a refresh token, let's hope the access
167                   token is still usable. */
168                else if( ( tok = oauth_params_get( &p_in, "access_token" ) ) )
169                {
170                        jd->oauth2_access_token = g_strdup( tok );
171                        jabber_connect( ic );
172                }
173                /* If we don't have any, start the OAuth process now. Don't
174                   even open an XMPP connection yet. */
175                else
176                {
177                        sasl_oauth2_init( ic );
178                        ic->flags |= OPT_SLOW_LOGIN;
179                }
180               
181                oauth_params_free( &p_in );
182        }
183        else
184                jabber_connect( ic );
185}
186
187/* Separate this from jabber_login() so we can do OAuth first if necessary.
188   Putting this in io.c would probably be more correct. */
189void jabber_connect( struct im_connection *ic )
190{
191        account_t *acc = ic->acc;
192        struct jabber_data *jd = ic->proto_data;
193        int i;
194        char *connect_to;
195        struct ns_srv_reply **srvl = NULL, *srv = NULL;
196       
197        /* Figure out the hostname to connect to. */
198        if( acc->server && *acc->server )
199                connect_to = acc->server;
200        else if( ( srvl = srv_lookup( "xmpp-client", "tcp", jd->server ) ) ||
201                 ( srvl = srv_lookup( "jabber-client", "tcp", jd->server ) ) )
202        {
203                /* Find the lowest-priority one. These usually come
204                   back in random/shuffled order. Not looking at
205                   weights etc for now. */
206                srv = *srvl;
207                for( i = 1; srvl[i]; i ++ )
208                        if( srvl[i]->prio < srv->prio )
209                                srv = srvl[i];
210               
211                connect_to = srv->name;
212        }
213        else
214                connect_to = jd->server;
215       
216        imcb_log( ic, "Connecting" );
217       
218        for( i = 0; jabber_port_list[i] > 0; i ++ )
219                if( set_getint( &acc->set, "port" ) == jabber_port_list[i] )
220                        break;
221
222        if( jabber_port_list[i] == 0 )
223        {
224                imcb_log( ic, "Illegal port number" );
225                imc_logout( ic, FALSE );
226                return;
227        }
228       
229        /* For non-SSL connections we can try to use the port # from the SRV
230           reply, but let's not do that when using SSL, SSL usually runs on
231           non-standard ports... */
232        if( set_getbool( &acc->set, "ssl" ) )
233        {
234                jd->ssl = ssl_connect( connect_to, set_getint( &acc->set, "port" ), set_getbool( &acc->set, "tls_verify" ), jabber_connected_ssl, ic );
235                jd->fd = jd->ssl ? ssl_getfd( jd->ssl ) : -1;
236        }
237        else
238        {
239                jd->fd = proxy_connect( connect_to, srv ? srv->port : set_getint( &acc->set, "port" ), jabber_connected_plain, ic );
240        }
241        srv_free( srvl );
242       
243        if( jd->fd == -1 )
244        {
245                imcb_error( ic, "Could not connect to server" );
246                imc_logout( ic, TRUE );
247               
248                return;
249        }
250       
251        if( set_getbool( &acc->set, "xmlconsole" ) )
252        {
253                jd->flags |= JFLAG_XMLCONSOLE;
254                /* Shouldn't really do this at this stage already, maybe. But
255                   I think this shouldn't break anything. */
256                imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
257        }
258       
259        jabber_generate_id_hash( jd );
260}
261
262/* This generates an unfinished md5_state_t variable. Every time we generate
263   an ID, we finish the state by adding a sequence number and take the hash. */
264static void jabber_generate_id_hash( struct jabber_data *jd )
265{
266        md5_byte_t binbuf[4];
267        char *s;
268       
269        md5_init( &jd->cached_id_prefix );
270        md5_append( &jd->cached_id_prefix, (unsigned char *) jd->username, strlen( jd->username ) );
271        md5_append( &jd->cached_id_prefix, (unsigned char *) jd->server, strlen( jd->server ) );
272        s = set_getstr( &jd->ic->acc->set, "resource" );
273        md5_append( &jd->cached_id_prefix, (unsigned char *) s, strlen( s ) );
274        random_bytes( binbuf, 4 );
275        md5_append( &jd->cached_id_prefix, binbuf, 4 );
276}
277
278static void jabber_logout( struct im_connection *ic )
279{
280        struct jabber_data *jd = ic->proto_data;
281       
282        while( jd->filetransfers )
283                imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" );
284
285        while( jd->streamhosts )
286        {
287                jabber_streamhost_t *sh = jd->streamhosts->data;
288                jd->streamhosts = g_slist_remove( jd->streamhosts, sh );
289                g_free( sh->jid );
290                g_free( sh->host );
291                g_free( sh );
292        }
293
294        if( jd->fd >= 0 )
295                jabber_end_stream( ic );
296       
297        while( ic->groupchats )
298                jabber_chat_free( ic->groupchats->data );
299       
300        if( jd->r_inpa >= 0 )
301                b_event_remove( jd->r_inpa );
302        if( jd->w_inpa >= 0 )
303                b_event_remove( jd->w_inpa );
304       
305        if( jd->ssl )
306                ssl_disconnect( jd->ssl );
307        if( jd->fd >= 0 )
308                closesocket( jd->fd );
309       
310        if( jd->tx_len )
311                g_free( jd->txq );
312       
313        if( jd->node_cache )
314                g_hash_table_destroy( jd->node_cache );
315       
316        jabber_buddy_remove_all( ic );
317       
318        xt_free( jd->xt );
319       
320        g_free( jd->oauth2_access_token );
321        g_free( jd->away_message );
322        g_free( jd->username );
323        g_free( jd->me );
324        g_free( jd );
325       
326        jabber_connections = g_slist_remove( jabber_connections, ic );
327}
328
329static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
330{
331        struct jabber_data *jd = ic->proto_data;
332        struct jabber_buddy *bud;
333        struct xt_node *node;
334        char *s;
335        int st;
336       
337        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
338                return jabber_write( ic, message, strlen( message ) );
339       
340        if( g_strcasecmp( who, JABBER_OAUTH_HANDLE ) == 0 &&
341            !( jd->flags & OPT_LOGGED_IN ) && jd->fd == -1 )
342        {
343                if( sasl_oauth2_get_refresh_token( ic, message ) )
344                {
345                        return 1;
346                }
347                else
348                {
349                        imcb_error( ic, "OAuth failure" );
350                        imc_logout( ic, TRUE );
351                        return 0;
352                }
353        }
354       
355        if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
356                bud = jabber_buddy_by_ext_jid( ic, who, 0 );
357        else
358                bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK );
359       
360        node = xt_new_node( "body", message, NULL );
361        node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
362       
363        if( bud && ( jd->flags & JFLAG_WANT_TYPING ) &&
364            ( ( bud->flags & JBFLAG_DOES_XEP85 ) ||
365             !( bud->flags & JBFLAG_PROBED_XEP85 ) ) )
366        {
367                struct xt_node *act;
368               
369                /* If the user likes typing notification and if we don't know
370                   (and didn't probe before) if this resource supports XEP85,
371                   include a probe in this packet now. Also, if we know this
372                   buddy does support XEP85, we have to send this <active/>
373                   tag to tell that the user stopped typing (well, that's what
374                   we guess when s/he pressed Enter...). */
375                act = xt_new_node( "active", NULL, NULL );
376                xt_add_attr( act, "xmlns", XMLNS_CHATSTATES );
377                xt_add_child( node, act );
378               
379                /* Just make sure we do this only once. */
380                bud->flags |= JBFLAG_PROBED_XEP85;
381        }
382       
383        st = jabber_write_packet( ic, node );
384        xt_free_node( node );
385       
386        return st;
387}
388
389static GList *jabber_away_states( struct im_connection *ic )
390{
391        static GList *l = NULL;
392        int i;
393       
394        if( l == NULL )
395                for( i = 0; jabber_away_state_list[i].full_name; i ++ )
396                        l = g_list_append( l, (void*) jabber_away_state_list[i].full_name );
397       
398        return l;
399}
400
401static void jabber_get_info( struct im_connection *ic, char *who )
402{
403        struct jabber_buddy *bud;
404       
405        bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST );
406       
407        while( bud )
408        {
409                imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority );
410                if( bud->away_state )
411                        imcb_log( ic, "Away state: %s", bud->away_state->full_name );
412                imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" );
413               
414                bud = bud->next;
415        }
416       
417        jabber_get_vcard( ic, bud ? bud->full_jid : who );
418}
419
420static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message )
421{
422        struct jabber_data *jd = ic->proto_data;
423       
424        /* state_txt == NULL -> Not away.
425           Unknown state -> fall back to the first defined away state. */
426        if( state_txt == NULL )
427                jd->away_state = NULL;
428        else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL )
429                jd->away_state = jabber_away_state_list;
430       
431        g_free( jd->away_message );
432        jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL;
433       
434        presence_send_update( ic );
435}
436
437static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
438{
439        struct jabber_data *jd = ic->proto_data;
440       
441        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
442        {
443                jd->flags |= JFLAG_XMLCONSOLE;
444                imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
445                return;
446        }
447       
448        if( jabber_add_to_roster( ic, who, NULL, group ) )
449                presence_send_request( ic, who, "subscribe" );
450}
451
452static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
453{
454        struct jabber_data *jd = ic->proto_data;
455       
456        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
457        {
458                jd->flags &= ~JFLAG_XMLCONSOLE;
459                /* Not necessary for now. And for now the code isn't too
460                   happy if the buddy is completely gone right after calling
461                   this function already.
462                imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
463                */
464                return;
465        }
466       
467        /* We should always do this part. Clean up our administration a little bit. */
468        jabber_buddy_remove_bare( ic, who );
469       
470        if( jabber_remove_from_roster( ic, who ) )
471                presence_send_request( ic, who, "unsubscribe" );
472}
473
474static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
475{
476        struct jabber_data *jd = ic->proto_data;
477       
478        if( strchr( room, '@' ) == NULL )
479                imcb_error( ic, "%s is not a valid Jabber room name. Maybe you mean %s@conference.%s?",
480                            room, room, jd->server );
481        else if( jabber_chat_by_jid( ic, room ) )
482                imcb_error( ic, "Already present in chat `%s'", room );
483        else
484                return jabber_chat_join( ic, room, nick, set_getstr( sets, "password" ) );
485       
486        return NULL;
487}
488
489static struct groupchat *jabber_chat_with_( struct im_connection *ic, char *who )
490{
491        return jabber_chat_with( ic, who );
492}
493
494static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
495{
496        if( c && message )
497                jabber_chat_msg( c, message, flags );
498}
499
500static void jabber_chat_topic_( struct groupchat *c, char *topic )
501{
502        if( c && topic )
503                jabber_chat_topic( c, topic );
504}
505
506static void jabber_chat_leave_( struct groupchat *c )
507{
508        if( c )
509                jabber_chat_leave( c, NULL );
510}
511
512static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg )
513{
514        struct jabber_data *jd = c->ic->proto_data;
515        struct jabber_chat *jc = c->data;
516        gchar *msg_alt = NULL;
517
518        if( msg == NULL )
519                msg_alt = g_strdup_printf( "%s invited you to %s", jd->me, jc->name );
520       
521        if( c && who )
522                jabber_chat_invite( c, who, msg ? msg : msg_alt );
523       
524        g_free( msg_alt );
525}
526
527static void jabber_keepalive( struct im_connection *ic )
528{
529        /* Just any whitespace character is enough as a keepalive for XMPP sessions. */
530        if( !jabber_write( ic, "\n", 1 ) )
531                return;
532       
533        /* This runs the garbage collection every minute, which means every packet
534           is in the cache for about a minute (which should be enough AFAIK). */
535        jabber_cache_clean( ic );
536}
537
538static int jabber_send_typing( struct im_connection *ic, char *who, int typing )
539{
540        struct jabber_data *jd = ic->proto_data;
541        struct jabber_buddy *bud;
542       
543        /* Enable typing notification related code from now. */
544        jd->flags |= JFLAG_WANT_TYPING;
545       
546        if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL )
547        {
548                /* Sending typing notifications to unknown buddies is
549                   unsupported for now. Shouldn't be a problem, I think. */
550                return 0;
551        }
552       
553        if( bud->flags & JBFLAG_DOES_XEP85 )
554        {
555                /* We're only allowed to send this stuff if we know the other
556                   side supports it. */
557               
558                struct xt_node *node;
559                char *type;
560                int st;
561               
562                if( typing & OPT_TYPING )
563                        type = "composing";
564                else if( typing & OPT_THINKING )
565                        type = "paused";
566                else
567                        type = "active";
568               
569                node = xt_new_node( type, NULL, NULL );
570                xt_add_attr( node, "xmlns", XMLNS_CHATSTATES );
571                node = jabber_make_packet( "message", "chat", bud->full_jid, node );
572               
573                st = jabber_write_packet( ic, node );
574                xt_free_node( node );
575               
576                return st;
577        }
578       
579        return 1;
580}
581
582void jabber_chat_add_settings( account_t *acc, set_t **head )
583{
584        /* Meh. Stupid room passwords. Not trying to obfuscate/hide
585           them from the user for now. */
586        set_add( head, "password", NULL, NULL, NULL );
587}
588
589void jabber_chat_free_settings( account_t *acc, set_t **head )
590{
591        set_del( head, "password" );
592}
593
594GList *jabber_buddy_action_list( bee_user_t *bu )
595{
596        static GList *ret = NULL;
597       
598        if( ret == NULL )
599        {
600                static const struct buddy_action ba[2] = {
601                        { "VERSION", "Get client (version) information" },
602                };
603               
604                ret = g_list_prepend( ret, (void*) ba + 0 );
605        }
606       
607        return ret;
608}
609
610void *jabber_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data )
611{
612        if( g_strcasecmp( action, "VERSION" ) == 0 )
613        {
614                struct jabber_buddy *bud;
615               
616                if( ( bud = jabber_buddy_by_ext_jid( bu->ic, bu->handle, 0 ) ) == NULL )
617                        bud = jabber_buddy_by_jid( bu->ic, bu->handle, GET_BUDDY_FIRST );
618                for( ; bud; bud = bud->next )
619                        jabber_iq_version_send( bu->ic, bud, data );
620        }
621       
622        return NULL;
623}
624
625void jabber_initmodule()
626{
627        struct prpl *ret = g_new0( struct prpl, 1 );
628       
629        ret->name = "jabber";
630        ret->mms = 0;                        /* no limit */
631        ret->login = jabber_login;
632        ret->init = jabber_init;
633        ret->logout = jabber_logout;
634        ret->buddy_msg = jabber_buddy_msg;
635        ret->away_states = jabber_away_states;
636        ret->set_away = jabber_set_away;
637//      ret->set_info = jabber_set_info;
638        ret->get_info = jabber_get_info;
639        ret->add_buddy = jabber_add_buddy;
640        ret->remove_buddy = jabber_remove_buddy;
641        ret->chat_msg = jabber_chat_msg_;
642        ret->chat_topic = jabber_chat_topic_;
643        ret->chat_invite = jabber_chat_invite_;
644        ret->chat_leave = jabber_chat_leave_;
645        ret->chat_join = jabber_chat_join_;
646        ret->chat_with = jabber_chat_with_;
647        ret->chat_add_settings = jabber_chat_add_settings;
648        ret->chat_free_settings = jabber_chat_free_settings;
649        ret->keepalive = jabber_keepalive;
650        ret->send_typing = jabber_send_typing;
651        ret->handle_cmp = g_strcasecmp;
652        ret->transfer_request = jabber_si_transfer_request;
653        ret->buddy_action_list = jabber_buddy_action_list;
654        ret->buddy_action = jabber_buddy_action;
655
656        register_protocol( ret );
657}
Note: See TracBrowser for help on using the repository browser.