source: protocols/jabber/jabber.c @ d6aa6dd

Last change on this file since d6aa6dd was 4eef271, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-06-23T00:13:46Z

Added user_agent setting to Jabber accounts so people can get around
ridiculous user agent restrictions on certain Jabber servers. Obviously
this is pretty simple to detect and break, but it works at least with
Openfire.

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