source: protocols/jabber/jabber.c @ 8240840

Last change on this file since 8240840 was 17a6ee9, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-11T14:37:06Z

Including DCC stuff again, with a wonderful extra layer of abstraction.
Some hooks are missing so sending files doesn't work yet. Receiving also
still seems to have some issues. On the plus side, at least the MSN/Jabber
modules work again.

  • Property mode set to 100644
File size: 15.7 KB
RevLine 
[f06894d]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
[21167d2]30#include "ssl_client.h"
[f06894d]31#include "xmltree.h"
32#include "bitlbee.h"
33#include "jabber.h"
[608f8cf]34#include "md5.h"
[f06894d]35
[b5c8a34]36GSList *jabber_connections;
37
[7f69740]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
[0da65d5]55static void jabber_init( account_t *acc )
[f06894d]56{
57        set_t *s;
[7f69740]58        char str[16];
[f06894d]59       
[76c85b4c]60        s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc );
61       
[7f69740]62        g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
63        s = set_add( &acc->set, "port", str, set_eval_int, acc );
[f06894d]64        s->flags |= ACC_SET_OFFLINE_ONLY;
65       
[ebe7b36]66        s = set_add( &acc->set, "priority", "0", set_eval_priority, acc );
[1c3008a]67
68        s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc );
[f06894d]69       
[ebe7b36]70        s = set_add( &acc->set, "resource", "BitlBee", NULL, acc );
71        s->flags |= ACC_SET_OFFLINE_ONLY;
[f06894d]72       
[76c85b4c]73        s = set_add( &acc->set, "resource_select", "activity", NULL, acc );
[a21a8ac]74       
[f06894d]75        s = set_add( &acc->set, "server", NULL, set_eval_account, acc );
[7125cb3]76        s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
[f06894d]77       
78        s = set_add( &acc->set, "ssl", "false", set_eval_bool, acc );
79        s->flags |= ACC_SET_OFFLINE_ONLY;
80       
81        s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );
82        s->flags |= ACC_SET_OFFLINE_ONLY;
[a6df0b5]83       
84        s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
85        s->flags |= ACC_SET_OFFLINE_ONLY;
[840bba8]86       
87        acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE;
[f06894d]88}
89
[608f8cf]90static void jabber_generate_id_hash( struct jabber_data *jd );
91
[f06894d]92static void jabber_login( account_t *acc )
93{
[84b045d]94        struct im_connection *ic = imcb_new( acc );
[21167d2]95        struct jabber_data *jd = g_new0( struct jabber_data, 1 );
[36e9f62]96        struct ns_srv_reply *srv = NULL;
[3b3cd693]97        char *connect_to, *s;
[7f69740]98        int i;
[21167d2]99       
[b5c8a34]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       
[0da65d5]105        jd->ic = ic;
106        ic->proto_data = jd;
[21167d2]107       
108        jd->username = g_strdup( acc->user );
109        jd->server = strchr( jd->username, '@' );
110       
[de03374]111        jd->fd = jd->r_inpa = jd->w_inpa = -1;
112       
[21167d2]113        if( jd->server == NULL )
114        {
[84b045d]115                imcb_error( ic, "Incomplete account name (format it like <username@jabberserver.name>)" );
[c2fb3809]116                imc_logout( ic, FALSE );
[21167d2]117                return;
118        }
119       
120        /* So don't think of free()ing jd->server.. :-) */
121        *jd->server = 0;
122        jd->server ++;
123       
[3b3cd693]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       
[038d17f]190        jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free );
[6a1128d]191        jd->buddies = g_hash_table_new( g_str_hash, g_str_equal );
[fe7a554]192       
[36e9f62]193        /* Figure out the hostname to connect to. */
[3b3cd693]194        if( acc->server && *acc->server )
[36e9f62]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       
[84b045d]202        imcb_log( ic, "Connecting" );
[35f6677]203       
[7f69740]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 )
[0f4c1bb5]209        {
[7f69740]210                imcb_log( ic, "Illegal port number" );
[c2fb3809]211                imc_logout( ic, FALSE );
[0f4c1bb5]212                return;
213        }
214       
[36e9f62]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... */
[21167d2]218        if( set_getbool( &acc->set, "ssl" ) )
219        {
[0da65d5]220                jd->ssl = ssl_connect( connect_to, set_getint( &acc->set, "port" ), jabber_connected_ssl, ic );
[3b3cd693]221                jd->fd = jd->ssl ? ssl_getfd( jd->ssl ) : -1;
[21167d2]222        }
223        else
224        {
[0da65d5]225                jd->fd = proxy_connect( connect_to, srv ? srv->port : set_getint( &acc->set, "port" ), jabber_connected_plain, ic );
[21167d2]226        }
[36e9f62]227        g_free( srv );
[35f6677]228       
229        if( jd->fd == -1 )
230        {
[84b045d]231                imcb_error( ic, "Could not connect to server" );
[c2fb3809]232                imc_logout( ic, TRUE );
[fb4ebcc5]233               
234                return;
[35f6677]235        }
[a6df0b5]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        }
[608f8cf]244       
245        jabber_generate_id_hash( jd );
246}
247
[89d736a]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. */
[608f8cf]250static void jabber_generate_id_hash( struct jabber_data *jd )
251{
[89d736a]252        md5_byte_t binbuf[4];
[608f8cf]253        char *s;
254       
[89d736a]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 ) );
[608f8cf]258        s = set_getstr( &jd->ic->acc->set, "resource" );
[89d736a]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 );
[f06894d]262}
263
[0da65d5]264static void jabber_logout( struct im_connection *ic )
[f06894d]265{
[0da65d5]266        struct jabber_data *jd = ic->proto_data;
[21167d2]267       
[4ac647d]268        while( jd->filetransfers )
[17a6ee9]269                imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" );
[4ac647d]270
271        while( jd->streamhosts )
272        {
273                jabber_streamhost_t *sh = jd->streamhosts->data;
274                jd->streamhosts = g_slist_remove( jd->streamhosts, sh );
275                g_free( sh->jid );
276                g_free( sh->host );
277                g_free( sh );
278        }
279
[de03374]280        if( jd->fd >= 0 )
281                jabber_end_stream( ic );
[4a0614e]282       
[c377417]283        while( ic->groupchats )
[9da0bbf]284                jabber_chat_free( ic->groupchats );
[c377417]285       
[21167d2]286        if( jd->r_inpa >= 0 )
287                b_event_remove( jd->r_inpa );
288        if( jd->w_inpa >= 0 )
289                b_event_remove( jd->w_inpa );
290       
291        if( jd->ssl )
292                ssl_disconnect( jd->ssl );
293        if( jd->fd >= 0 )
294                closesocket( jd->fd );
295       
[fe7a554]296        if( jd->tx_len )
297                g_free( jd->txq );
298       
[de03374]299        if( jd->node_cache )
300                g_hash_table_destroy( jd->node_cache );
[038d17f]301       
[70f6aab8]302        xt_free( jd->xt );
303       
[5e202b0]304        g_free( jd->away_message );
[21167d2]305        g_free( jd->username );
306        g_free( jd );
[b5c8a34]307       
308        jabber_connections = g_slist_remove( jabber_connections, ic );
[f06894d]309}
310
[f6c963b]311static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
[f06894d]312{
[0da65d5]313        struct jabber_data *jd = ic->proto_data;
[a21a8ac]314        struct jabber_buddy *bud;
[cc2cb2d]315        struct xt_node *node;
[b9f8b87]316        char *s;
[4a0614e]317        int st;
318       
[bb95d43]319        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
320                return jabber_write( ic, message, strlen( message ) );
321       
[5bd21df]322        if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
[b9f8b87]323                bud = jabber_buddy_by_ext_jid( ic, who, 0 );
324        else
[76c85b4c]325                bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK );
[a214954]326       
[4a0614e]327        node = xt_new_node( "body", message, NULL );
[788a1af]328        node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
[a21a8ac]329       
[0d3f30f]330        if( bud && ( jd->flags & JFLAG_WANT_TYPING ) &&
[788a1af]331            ( ( bud->flags & JBFLAG_DOES_XEP85 ) ||
332             !( bud->flags & JBFLAG_PROBED_XEP85 ) ) )
[a21a8ac]333        {
334                struct xt_node *act;
335               
336                /* If the user likes typing notification and if we don't know
[788a1af]337                   (and didn't probe before) if this resource supports XEP85,
[abbd8ed]338                   include a probe in this packet now. Also, if we know this
339                   buddy does support XEP85, we have to send this <active/>
340                   tag to tell that the user stopped typing (well, that's what
341                   we guess when s/he pressed Enter...). */
[a21a8ac]342                act = xt_new_node( "active", NULL, NULL );
[47d3ac4]343                xt_add_attr( act, "xmlns", XMLNS_CHATSTATES );
[a21a8ac]344                xt_add_child( node, act );
345               
346                /* Just make sure we do this only once. */
[788a1af]347                bud->flags |= JBFLAG_PROBED_XEP85;
[a21a8ac]348        }
349       
[0da65d5]350        st = jabber_write_packet( ic, node );
[4a0614e]351        xt_free_node( node );
352       
353        return st;
[f06894d]354}
355
[0da65d5]356static GList *jabber_away_states( struct im_connection *ic )
[dd788bb]357{
[5e202b0]358        static GList *l = NULL;
359        int i;
[dd788bb]360       
[5e202b0]361        if( l == NULL )
362                for( i = 0; jabber_away_state_list[i].full_name; i ++ )
363                        l = g_list_append( l, (void*) jabber_away_state_list[i].full_name );
[dd788bb]364       
[5e202b0]365        return l;
[dd788bb]366}
367
[0da65d5]368static void jabber_get_info( struct im_connection *ic, char *who )
[038d17f]369{
[6a1128d]370        struct jabber_buddy *bud;
[038d17f]371       
[76c85b4c]372        bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST );
[7e83adca]373       
[6a1128d]374        while( bud )
375        {
[63770b4]376                imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority );
377                if( bud->away_state )
378                        imcb_log( ic, "Away state: %s", bud->away_state->full_name );
379                imcb_log( ic, "Status message: %s", bud->away_message ? : "(none)" );
380               
[6a1128d]381                bud = bud->next;
382        }
[1991be6]383       
[0da65d5]384        jabber_get_vcard( ic, bud ? bud->full_jid : who );
[038d17f]385}
386
[0da65d5]387static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message )
[dd788bb]388{
[0da65d5]389        struct jabber_data *jd = ic->proto_data;
[5e202b0]390       
[840bba8]391        /* state_txt == NULL -> Not away.
392           Unknown state -> fall back to the first defined away state. */
393        jd->away_state = state_txt ? jabber_away_state_by_name( state_txt )
394                         ? : jabber_away_state_list : NULL;
395       
[5e202b0]396        g_free( jd->away_message );
397        jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL;
398       
[0da65d5]399        presence_send_update( ic );
[deff040]400}
401
[0da65d5]402static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
[cfbb3a6]403{
[bb95d43]404        struct jabber_data *jd = ic->proto_data;
405       
406        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
407        {
408                jd->flags |= JFLAG_XMLCONSOLE;
409                imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
410                return;
411        }
412       
[0da65d5]413        if( jabber_add_to_roster( ic, who, NULL ) )
414                presence_send_request( ic, who, "subscribe" );
[cfbb3a6]415}
416
[0da65d5]417static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
[cfbb3a6]418{
[bb95d43]419        struct jabber_data *jd = ic->proto_data;
420       
421        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
422        {
423                jd->flags &= ~JFLAG_XMLCONSOLE;
[a3d5766]424                /* Not necessary for now. And for now the code isn't too
425                   happy if the buddy is completely gone right after calling
426                   this function already.
[998b103]427                imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
[a3d5766]428                */
[bb95d43]429                return;
430        }
431       
[788a1af]432        /* We should always do this part. Clean up our administration a little bit. */
[0da65d5]433        jabber_buddy_remove_bare( ic, who );
[788a1af]434       
[0da65d5]435        if( jabber_remove_from_roster( ic, who ) )
436                presence_send_request( ic, who, "unsubscribe" );
[cfbb3a6]437}
438
[94acdd0]439static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password )
[e35d1a1]440{
441        if( strchr( room, '@' ) == NULL )
442                imcb_error( ic, "Invalid room name: %s", room );
[5bd21df]443        else if( jabber_chat_by_jid( ic, room ) )
[e35d1a1]444                imcb_error( ic, "Already present in chat `%s'", room );
445        else
446                return jabber_chat_join( ic, room, nick, password );
447       
448        return NULL;
449}
450
[43671b9]451static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
452{
453        if( c && message )
454                jabber_chat_msg( c, message, flags );
455}
456
[ef5c185]457static void jabber_chat_topic_( struct groupchat *c, char *topic )
458{
459        if( c && topic )
460                jabber_chat_topic( c, topic );
461}
462
[e35d1a1]463static void jabber_chat_leave_( struct groupchat *c )
464{
465        if( c )
466                jabber_chat_leave( c, NULL );
467}
468
[c058ff9]469static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg )
470{
471        struct jabber_chat *jc = c->data;
472        gchar *msg_alt = NULL;
473
474        if( msg == NULL )
475                msg_alt = g_strdup_printf( "%s invited you to %s", c->ic->acc->user, jc->name );
476       
477        if( c && who )
478                jabber_chat_invite( c, who, msg ? msg : msg_alt );
479       
480        g_free( msg_alt );
481}
482
[0da65d5]483static void jabber_keepalive( struct im_connection *ic )
[deff040]484{
485        /* Just any whitespace character is enough as a keepalive for XMPP sessions. */
[0da65d5]486        jabber_write( ic, "\n", 1 );
[a214954]487       
[038d17f]488        /* This runs the garbage collection every minute, which means every packet
489           is in the cache for about a minute (which should be enough AFAIK). */
[0da65d5]490        jabber_cache_clean( ic );
[dd788bb]491}
492
[0da65d5]493static int jabber_send_typing( struct im_connection *ic, char *who, int typing )
[a21a8ac]494{
[0da65d5]495        struct jabber_data *jd = ic->proto_data;
[a21a8ac]496        struct jabber_buddy *bud;
497       
498        /* Enable typing notification related code from now. */
499        jd->flags |= JFLAG_WANT_TYPING;
500       
[0da65d5]501        if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL )
[788a1af]502        {
503                /* Sending typing notifications to unknown buddies is
504                   unsupported for now. Shouldn't be a problem, I think. */
505                return 0;
506        }
507       
508        if( bud->flags & JBFLAG_DOES_XEP85 )
[a21a8ac]509        {
510                /* We're only allowed to send this stuff if we know the other
511                   side supports it. */
512               
513                struct xt_node *node;
514                char *type;
515                int st;
516               
[df1fb67]517                if( typing & OPT_TYPING )
[a21a8ac]518                        type = "composing";
[df1fb67]519                else if( typing & OPT_THINKING )
520                        type = "paused";
521                else
522                        type = "active";
[a21a8ac]523               
524                node = xt_new_node( type, NULL, NULL );
[47d3ac4]525                xt_add_attr( node, "xmlns", XMLNS_CHATSTATES );
[a21a8ac]526                node = jabber_make_packet( "message", "chat", bud->full_jid, node );
527               
[0da65d5]528                st = jabber_write_packet( ic, node );
[a21a8ac]529                xt_free_node( node );
530               
531                return st;
532        }
533       
534        return 1;
535}
536
[0da65d5]537void jabber_initmodule()
[f06894d]538{
[deff040]539        struct prpl *ret = g_new0( struct prpl, 1 );
[f06894d]540       
541        ret->name = "jabber";
542        ret->login = jabber_login;
[0da65d5]543        ret->init = jabber_init;
544        ret->logout = jabber_logout;
[f6c963b]545        ret->buddy_msg = jabber_buddy_msg;
[dd788bb]546        ret->away_states = jabber_away_states;
547        ret->set_away = jabber_set_away;
[f06894d]548//      ret->set_info = jabber_set_info;
[038d17f]549        ret->get_info = jabber_get_info;
[cfbb3a6]550        ret->add_buddy = jabber_add_buddy;
551        ret->remove_buddy = jabber_remove_buddy;
[43671b9]552        ret->chat_msg = jabber_chat_msg_;
[ef5c185]553        ret->chat_topic = jabber_chat_topic_;
[c058ff9]554        ret->chat_invite = jabber_chat_invite_;
[e35d1a1]555        ret->chat_leave = jabber_chat_leave_;
556        ret->chat_join = jabber_chat_join_;
[deff040]557        ret->keepalive = jabber_keepalive;
[a21a8ac]558        ret->send_typing = jabber_send_typing;
[f06894d]559        ret->handle_cmp = g_strcasecmp;
[2ff2076]560        ret->transfer_request = jabber_si_transfer_request;
[f06894d]561
[deff040]562        register_protocol( ret );
[f06894d]563}
Note: See TracBrowser for help on using the repository browser.