source: protocols/jabber/jabber.c @ f536a99

Last change on this file since f536a99 was 7125cb3, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-08-24T18:01:05Z

Added SET_INVALID, which set evaluators should now return instead of NULL
when the given value is not accepted. This to allow certain variables
actually be set to NULL (server, for example). This should fully close
#444.

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