source: protocols/jabber/jabber.c @ 18c6d36

Last change on this file since 18c6d36 was 18c6d36, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-18T19:25:44Z

More generic OAuth support now. Should work well for all GTalk accounts now
and somewhat for MS Messenger. The fb part needs different parsing of the
authorize request, and possibly some other work.

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