source: protocols/jabber/jabber.c @ 57b4525

Last change on this file since 57b4525 was 57b4525, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-07-22T18:29:25Z

Nothing useful yet, this just generates an auth URL. Things to do: Ability
to process the answer. This is hard because the GTalk server will time out
very quickly which means we lose our state/scope/etc. (And the ability to
even receive the answer, at least if I'd do this the same way the Twitter
module does it.)

And then, get the access token and use it, of course. :-)

  • Property mode set to 100644
File size: 17.4 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, "user_agent", "BitlBee", NULL, acc );
87       
88        s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
89        s->flags |= ACC_SET_OFFLINE_ONLY;
90       
91        acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE;
92}
93
94static void jabber_generate_id_hash( struct jabber_data *jd );
95
96static void jabber_login( account_t *acc )
97{
98        struct im_connection *ic = imcb_new( acc );
99        struct jabber_data *jd = g_new0( struct jabber_data, 1 );
100        struct ns_srv_reply **srvl = NULL, *srv = NULL;
101        char *connect_to, *s;
102        int i;
103       
104        /* For now this is needed in the _connected() handlers if using
105           GLib event handling, to make sure we're not handling events
106           on dead connections. */
107        jabber_connections = g_slist_prepend( jabber_connections, ic );
108       
109        jd->ic = ic;
110        ic->proto_data = jd;
111       
112        jd->username = g_strdup( acc->user );
113        jd->server = strchr( jd->username, '@' );
114       
115        jd->fd = jd->r_inpa = jd->w_inpa = -1;
116       
117        if( jd->server == NULL )
118        {
119                imcb_error( ic, "Incomplete account name (format it like <username@jabberserver.name>)" );
120                imc_logout( ic, FALSE );
121                return;
122        }
123       
124        /* So don't think of free()ing jd->server.. :-) */
125        *jd->server = 0;
126        jd->server ++;
127       
128        if( ( s = strchr( jd->server, '/' ) ) )
129        {
130                *s = 0;
131                set_setstr( &acc->set, "resource", s + 1 );
132               
133                /* Also remove the /resource from the original variable so we
134                   won't have to do this again every time. */
135                s = strchr( acc->user, '/' );
136                *s = 0;
137        }
138       
139        /* This code isn't really pretty. Backwards compatibility never is... */
140        s = acc->server;
141        while( s )
142        {
143                static int had_port = 0;
144               
145                if( strncmp( s, "ssl", 3 ) == 0 )
146                {
147                        set_setstr( &acc->set, "ssl", "true" );
148                       
149                        /* Flush this part so that (if this was the first
150                           part of the server string) acc->server gets
151                           flushed. We don't want to have to do this another
152                           time. :-) */
153                        *s = 0;
154                        s ++;
155                       
156                        /* Only set this if the user didn't specify a custom
157                           port number already... */
158                        if( !had_port )
159                                set_setint( &acc->set, "port", 5223 );
160                }
161                else if( isdigit( *s ) )
162                {
163                        int i;
164                       
165                        /* The first character is a digit. It could be an
166                           IP address though. Only accept this as a port#
167                           if there are only digits. */
168                        for( i = 0; isdigit( s[i] ); i ++ );
169                       
170                        /* If the first non-digit character is a colon or
171                           the end of the string, save the port number
172                           where it should be. */
173                        if( s[i] == ':' || s[i] == 0 )
174                        {
175                                sscanf( s, "%d", &i );
176                                set_setint( &acc->set, "port", i );
177                               
178                                /* See above. */
179                                *s = 0;
180                                s ++;
181                        }
182                       
183                        had_port = 1;
184                }
185               
186                s = strchr( s, ':' );
187                if( s )
188                {
189                        *s = 0;
190                        s ++;
191                }
192        }
193       
194        jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free );
195        jd->buddies = g_hash_table_new( g_str_hash, g_str_equal );
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" ), 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->away_message );
321        g_free( jd->username );
322        g_free( jd );
323       
324        jabber_connections = g_slist_remove( jabber_connections, ic );
325}
326
327static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
328{
329        struct jabber_data *jd = ic->proto_data;
330        struct jabber_buddy *bud;
331        struct xt_node *node;
332        char *s;
333        int st;
334       
335        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
336                return jabber_write( ic, message, strlen( message ) );
337       
338        if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
339                bud = jabber_buddy_by_ext_jid( ic, who, 0 );
340        else
341                bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK );
342       
343        node = xt_new_node( "body", message, NULL );
344        node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
345       
346        if( bud && ( jd->flags & JFLAG_WANT_TYPING ) &&
347            ( ( bud->flags & JBFLAG_DOES_XEP85 ) ||
348             !( bud->flags & JBFLAG_PROBED_XEP85 ) ) )
349        {
350                struct xt_node *act;
351               
352                /* If the user likes typing notification and if we don't know
353                   (and didn't probe before) if this resource supports XEP85,
354                   include a probe in this packet now. Also, if we know this
355                   buddy does support XEP85, we have to send this <active/>
356                   tag to tell that the user stopped typing (well, that's what
357                   we guess when s/he pressed Enter...). */
358                act = xt_new_node( "active", NULL, NULL );
359                xt_add_attr( act, "xmlns", XMLNS_CHATSTATES );
360                xt_add_child( node, act );
361               
362                /* Just make sure we do this only once. */
363                bud->flags |= JBFLAG_PROBED_XEP85;
364        }
365       
366        st = jabber_write_packet( ic, node );
367        xt_free_node( node );
368       
369        return st;
370}
371
372static GList *jabber_away_states( struct im_connection *ic )
373{
374        static GList *l = NULL;
375        int i;
376       
377        if( l == NULL )
378                for( i = 0; jabber_away_state_list[i].full_name; i ++ )
379                        l = g_list_append( l, (void*) jabber_away_state_list[i].full_name );
380       
381        return l;
382}
383
384static void jabber_get_info( struct im_connection *ic, char *who )
385{
386        struct jabber_buddy *bud;
387       
388        bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST );
389       
390        while( bud )
391        {
392                imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority );
393                if( bud->away_state )
394                        imcb_log( ic, "Away state: %s", bud->away_state->full_name );
395                imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" );
396               
397                bud = bud->next;
398        }
399       
400        jabber_get_vcard( ic, bud ? bud->full_jid : who );
401}
402
403static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message )
404{
405        struct jabber_data *jd = ic->proto_data;
406       
407        /* state_txt == NULL -> Not away.
408           Unknown state -> fall back to the first defined away state. */
409        if( state_txt == NULL )
410                jd->away_state = NULL;
411        else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL )
412                jd->away_state = jabber_away_state_list;
413       
414        g_free( jd->away_message );
415        jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL;
416       
417        presence_send_update( ic );
418}
419
420static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
421{
422        struct jabber_data *jd = ic->proto_data;
423       
424        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
425        {
426                jd->flags |= JFLAG_XMLCONSOLE;
427                imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
428                return;
429        }
430       
431        if( jabber_add_to_roster( ic, who, NULL, group ) )
432                presence_send_request( ic, who, "subscribe" );
433}
434
435static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
436{
437        struct jabber_data *jd = ic->proto_data;
438       
439        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
440        {
441                jd->flags &= ~JFLAG_XMLCONSOLE;
442                /* Not necessary for now. And for now the code isn't too
443                   happy if the buddy is completely gone right after calling
444                   this function already.
445                imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
446                */
447                return;
448        }
449       
450        /* We should always do this part. Clean up our administration a little bit. */
451        jabber_buddy_remove_bare( ic, who );
452       
453        if( jabber_remove_from_roster( ic, who ) )
454                presence_send_request( ic, who, "unsubscribe" );
455}
456
457static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
458{
459        if( strchr( room, '@' ) == NULL )
460                imcb_error( ic, "Invalid room name: %s", room );
461        else if( jabber_chat_by_jid( ic, room ) )
462                imcb_error( ic, "Already present in chat `%s'", room );
463        else
464                return jabber_chat_join( ic, room, nick, set_getstr( sets, "password" ) );
465       
466        return NULL;
467}
468
469static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
470{
471        if( c && message )
472                jabber_chat_msg( c, message, flags );
473}
474
475static void jabber_chat_topic_( struct groupchat *c, char *topic )
476{
477        if( c && topic )
478                jabber_chat_topic( c, topic );
479}
480
481static void jabber_chat_leave_( struct groupchat *c )
482{
483        if( c )
484                jabber_chat_leave( c, NULL );
485}
486
487static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg )
488{
489        struct jabber_chat *jc = c->data;
490        gchar *msg_alt = NULL;
491
492        if( msg == NULL )
493                msg_alt = g_strdup_printf( "%s invited you to %s", c->ic->acc->user, jc->name );
494       
495        if( c && who )
496                jabber_chat_invite( c, who, msg ? msg : msg_alt );
497       
498        g_free( msg_alt );
499}
500
501static void jabber_keepalive( struct im_connection *ic )
502{
503        /* Just any whitespace character is enough as a keepalive for XMPP sessions. */
504        if( !jabber_write( ic, "\n", 1 ) )
505                return;
506       
507        /* This runs the garbage collection every minute, which means every packet
508           is in the cache for about a minute (which should be enough AFAIK). */
509        jabber_cache_clean( ic );
510}
511
512static int jabber_send_typing( struct im_connection *ic, char *who, int typing )
513{
514        struct jabber_data *jd = ic->proto_data;
515        struct jabber_buddy *bud;
516       
517        /* Enable typing notification related code from now. */
518        jd->flags |= JFLAG_WANT_TYPING;
519       
520        if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL )
521        {
522                /* Sending typing notifications to unknown buddies is
523                   unsupported for now. Shouldn't be a problem, I think. */
524                return 0;
525        }
526       
527        if( bud->flags & JBFLAG_DOES_XEP85 )
528        {
529                /* We're only allowed to send this stuff if we know the other
530                   side supports it. */
531               
532                struct xt_node *node;
533                char *type;
534                int st;
535               
536                if( typing & OPT_TYPING )
537                        type = "composing";
538                else if( typing & OPT_THINKING )
539                        type = "paused";
540                else
541                        type = "active";
542               
543                node = xt_new_node( type, NULL, NULL );
544                xt_add_attr( node, "xmlns", XMLNS_CHATSTATES );
545                node = jabber_make_packet( "message", "chat", bud->full_jid, node );
546               
547                st = jabber_write_packet( ic, node );
548                xt_free_node( node );
549               
550                return st;
551        }
552       
553        return 1;
554}
555
556void jabber_chat_add_settings( account_t *acc, set_t **head )
557{
558        /* Meh. Stupid room passwords. Not trying to obfuscate/hide
559           them from the user for now. */
560        set_add( head, "password", NULL, NULL, NULL );
561}
562
563void jabber_chat_free_settings( account_t *acc, set_t **head )
564{
565        set_del( head, "password" );
566}
567
568GList *jabber_buddy_action_list( bee_user_t *bu )
569{
570        static GList *ret = NULL;
571       
572        if( ret == NULL )
573        {
574                static const struct buddy_action ba[2] = {
575                        { "VERSION", "Get client (version) information" },
576                };
577               
578                ret = g_list_prepend( ret, (void*) ba + 0 );
579        }
580       
581        return ret;
582}
583
584void *jabber_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data )
585{
586        if( g_strcasecmp( action, "VERSION" ) == 0 )
587        {
588                struct jabber_buddy *bud;
589               
590                if( ( bud = jabber_buddy_by_ext_jid( bu->ic, bu->handle, 0 ) ) == NULL )
591                        bud = jabber_buddy_by_jid( bu->ic, bu->handle, GET_BUDDY_FIRST );
592                for( ; bud; bud = bud->next )
593                        jabber_iq_version_send( bu->ic, bud, data );
594        }
595       
596        return NULL;
597}
598
599void jabber_initmodule()
600{
601        struct prpl *ret = g_new0( struct prpl, 1 );
602       
603        ret->name = "jabber";
604        ret->mms = 0;                        /* no limit */
605        ret->login = jabber_login;
606        ret->init = jabber_init;
607        ret->logout = jabber_logout;
608        ret->buddy_msg = jabber_buddy_msg;
609        ret->away_states = jabber_away_states;
610        ret->set_away = jabber_set_away;
611//      ret->set_info = jabber_set_info;
612        ret->get_info = jabber_get_info;
613        ret->add_buddy = jabber_add_buddy;
614        ret->remove_buddy = jabber_remove_buddy;
615        ret->chat_msg = jabber_chat_msg_;
616        ret->chat_topic = jabber_chat_topic_;
617        ret->chat_invite = jabber_chat_invite_;
618        ret->chat_leave = jabber_chat_leave_;
619        ret->chat_join = jabber_chat_join_;
620        ret->chat_add_settings = jabber_chat_add_settings;
621        ret->chat_free_settings = jabber_chat_free_settings;
622        ret->keepalive = jabber_keepalive;
623        ret->send_typing = jabber_send_typing;
624        ret->handle_cmp = g_strcasecmp;
625        ret->transfer_request = jabber_si_transfer_request;
626        ret->buddy_action_list = jabber_buddy_action_list;
627        ret->buddy_action = jabber_buddy_action;
628
629        register_protocol( ret );
630}
Note: See TracBrowser for help on using the repository browser.