source: protocols/jabber/iq.c @ 2d317bb

Last change on this file since 2d317bb was f0cb961, checked in by Wilmer van der Gaast <wilmer@…>, at 2007-04-19T06:03:43Z

More API changes: buddy list management. imcb_add_buddy() is now a *real*
callback, it's only called from inside IM-modules. This makes sure a buddy
only gets added to the BitlBee structures if the add was successful. This
gets rid of the weirdness described in #55. Unfortunately for now this
change breaks A) automatic renaming of ICQ contacts (if there are names
stored in the contact list) B) add -tmp.

  • Property mode set to 100644
File size: 17.8 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - IQ packets                                               *
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 "jabber.h"
25
26static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
27static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
28
29xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
30{
31        struct im_connection *ic = data;
32        struct jabber_data *jd = ic->proto_data;
33        struct xt_node *c, *reply = NULL;
34        char *type, *s;
35        int st, pack = 1;
36       
37        type = xt_find_attr( node, "type" );
38       
39        if( !type )
40        {
41                imcb_error( ic, "Received IQ packet without type." );
42                imc_logout( ic, TRUE );
43                return XT_ABORT;
44        }
45       
46        if( strcmp( type, "result" ) == 0 || strcmp( type, "error" ) == 0 )
47        {
48                struct jabber_cache_entry *entry;
49               
50                if( ( s = xt_find_attr( node, "id" ) ) == NULL ||
51                    strncmp( s, JABBER_CACHED_ID, strlen( JABBER_CACHED_ID ) ) != 0 )
52                {
53                        /* Silently ignore it, without an ID (or a non-cache
54                           ID) we don't know how to handle the packet and we
55                           probably don't have to. */
56                        return XT_HANDLED;
57                }
58               
59                entry = g_hash_table_lookup( jd->node_cache, s );
60               
61                if( entry == NULL )
62                        imcb_log( ic, "WARNING: Received IQ-%s packet with unknown/expired ID %s!", type, s );
63                else if( entry->func )
64                        return entry->func( ic, node, entry->node );
65        }
66        else if( strcmp( type, "get" ) == 0 )
67        {
68                if( !( c = xt_find_node( node->children, "query" ) ) ||
69                    !( s = xt_find_attr( c, "xmlns" ) ) )
70                {
71                        imcb_log( ic, "WARNING: Received incomplete IQ-%s packet", type );
72                        return XT_HANDLED;
73                }
74               
75                reply = xt_new_node( "query", NULL, NULL );
76                xt_add_attr( reply, "xmlns", s );
77               
78                /* Of course this is a very essential query to support. ;-) */
79                if( strcmp( s, XMLNS_VERSION ) == 0 )
80                {
81                        xt_add_child( reply, xt_new_node( "name", "BitlBee", NULL ) );
82                        xt_add_child( reply, xt_new_node( "version", BITLBEE_VERSION, NULL ) );
83                        xt_add_child( reply, xt_new_node( "os", ARCH, NULL ) );
84                }
85                else if( strcmp( s, XMLNS_TIME ) == 0 )
86                {
87                        time_t time_ep;
88                        char buf[1024];
89                       
90                        buf[sizeof(buf)-1] = 0;
91                        time_ep = time( NULL );
92                       
93                        strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%S", gmtime( &time_ep ) );
94                        xt_add_child( reply, xt_new_node( "utc", buf, NULL ) );
95                       
96                        strftime( buf, sizeof( buf ) - 1, "%Z", localtime( &time_ep ) );
97                        xt_add_child( reply, xt_new_node( "tz", buf, NULL ) );
98                }
99                else if( strcmp( s, XMLNS_DISCOVER ) == 0 )
100                {
101                        c = xt_new_node( "identity", NULL, NULL );
102                        xt_add_attr( c, "category", "client" );
103                        xt_add_attr( c, "type", "pc" );
104                        xt_add_attr( c, "name", "BitlBee" );
105                        xt_add_child( reply, c );
106                       
107                        c = xt_new_node( "feature", NULL, NULL );
108                        xt_add_attr( c, "var", XMLNS_VERSION );
109                        xt_add_child( reply, c );
110                       
111                        c = xt_new_node( "feature", NULL, NULL );
112                        xt_add_attr( c, "var", XMLNS_TIME );
113                        xt_add_child( reply, c );
114                       
115                        c = xt_new_node( "feature", NULL, NULL );
116                        xt_add_attr( c, "var", XMLNS_CHATSTATES );
117                        xt_add_child( reply, c );
118                       
119                        /* Later this can be useful to announce things like
120                           MUC support. */
121                }
122                else
123                {
124                        xt_free_node( reply );
125                        reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" );
126                        pack = 0;
127                }
128        }
129        else if( strcmp( type, "set" ) == 0 )
130        {
131                if( !( c = xt_find_node( node->children, "query" ) ) ||
132                    !( s = xt_find_attr( c, "xmlns" ) ) )
133                {
134                        imcb_log( ic, "WARNING: Received incomplete IQ-%s packet", type );
135                        return XT_HANDLED;
136                }
137               
138                /* This is a roster push. XMPP servers send this when someone
139                   was added to (or removed from) the buddy list. AFAIK they're
140                   sent even if we added this buddy in our own session. */
141                if( strcmp( s, XMLNS_ROSTER ) == 0 )
142                {
143                        int bare_len = strlen( ic->acc->user );
144                       
145                        if( ( s = xt_find_attr( node, "from" ) ) == NULL ||
146                            ( strncmp( s, ic->acc->user, bare_len ) == 0 &&
147                              ( s[bare_len] == 0 || s[bare_len] == '/' ) ) )
148                        {
149                                jabber_parse_roster( ic, node, NULL );
150                               
151                                /* Should we generate a reply here? Don't think it's
152                                   very important... */
153                        }
154                        else
155                        {
156                                imcb_log( ic, "WARNING: %s tried to fake a roster push!", s ? s : "(unknown)" );
157                               
158                                xt_free_node( reply );
159                                reply = jabber_make_error_packet( node, "not-allowed", "cancel" );
160                                pack = 0;
161                        }
162                }
163                else
164                {
165                        xt_free_node( reply );
166                        reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" );
167                        pack = 0;
168                }
169        }
170       
171        /* If we recognized the xmlns and managed to generate a reply,
172           finish and send it. */
173        if( reply )
174        {
175                /* Normally we still have to pack it into an iq-result
176                   packet, but for errors, for example, we don't. */
177                if( pack )
178                {
179                        reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), reply );
180                        if( ( s = xt_find_attr( node, "id" ) ) )
181                                xt_add_attr( reply, "id", s );
182                }
183               
184                st = jabber_write_packet( ic, reply );
185                xt_free_node( reply );
186                if( !st )
187                        return XT_ABORT;
188        }
189       
190        return XT_HANDLED;
191}
192
193static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
194static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
195
196int jabber_init_iq_auth( struct im_connection *ic )
197{
198        struct jabber_data *jd = ic->proto_data;
199        struct xt_node *node;
200        int st;
201       
202        node = xt_new_node( "query", NULL, xt_new_node( "username", jd->username, NULL ) );
203        xt_add_attr( node, "xmlns", XMLNS_AUTH );
204        node = jabber_make_packet( "iq", "get", NULL, node );
205       
206        jabber_cache_add( ic, node, jabber_do_iq_auth );
207        st = jabber_write_packet( ic, node );
208       
209        return st;
210}
211
212static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
213{
214        struct jabber_data *jd = ic->proto_data;
215        struct xt_node *reply, *query;
216        xt_status st;
217        char *s;
218       
219        if( !( query = xt_find_node( node->children, "query" ) ) )
220        {
221                imcb_log( ic, "WARNING: Received incomplete IQ packet while authenticating" );
222                imc_logout( ic, FALSE );
223                return XT_HANDLED;
224        }
225       
226        /* Time to authenticate ourselves! */
227        reply = xt_new_node( "query", NULL, NULL );
228        xt_add_attr( reply, "xmlns", XMLNS_AUTH );
229        xt_add_child( reply, xt_new_node( "username", jd->username, NULL ) );
230        xt_add_child( reply, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) );
231       
232        if( xt_find_node( query->children, "digest" ) && ( s = xt_find_attr( jd->xt->root, "id" ) ) )
233        {
234                /* We can do digest authentication, it seems, and of
235                   course we prefer that. */
236                SHA_CTX sha;
237                char hash_hex[41];
238                unsigned char hash[20];
239                int i;
240               
241                shaInit( &sha );
242                shaUpdate( &sha, (unsigned char*) s, strlen( s ) );
243                shaUpdate( &sha, (unsigned char*) ic->acc->pass, strlen( ic->acc->pass ) );
244                shaFinal( &sha, hash );
245               
246                for( i = 0; i < 20; i ++ )
247                        sprintf( hash_hex + i * 2, "%02x", hash[i] );
248               
249                xt_add_child( reply, xt_new_node( "digest", hash_hex, NULL ) );
250        }
251        else if( xt_find_node( query->children, "password" ) )
252        {
253                /* We'll have to stick with plaintext. Let's hope we're using SSL/TLS... */
254                xt_add_child( reply, xt_new_node( "password", ic->acc->pass, NULL ) );
255        }
256        else
257        {
258                xt_free_node( reply );
259               
260                imcb_error( ic, "Can't find suitable authentication method" );
261                imc_logout( ic, FALSE );
262                return XT_ABORT;
263        }
264       
265        reply = jabber_make_packet( "iq", "set", NULL, reply );
266        jabber_cache_add( ic, reply, jabber_finish_iq_auth );
267        st = jabber_write_packet( ic, reply );
268       
269        return st ? XT_HANDLED : XT_ABORT;
270}
271
272static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
273{
274        struct jabber_data *jd = ic->proto_data;
275        char *type;
276       
277        if( !( type = xt_find_attr( node, "type" ) ) )
278        {
279                imcb_log( ic, "WARNING: Received incomplete IQ packet while authenticating" );
280                imc_logout( ic, FALSE );
281                return XT_HANDLED;
282        }
283       
284        if( strcmp( type, "error" ) == 0 )
285        {
286                imcb_error( ic, "Authentication failure" );
287                imc_logout( ic, FALSE );
288                return XT_ABORT;
289        }
290        else if( strcmp( type, "result" ) == 0 )
291        {
292                /* This happens when we just successfully authenticated the
293                   old (non-SASL) way. */
294                jd->flags |= JFLAG_AUTHENTICATED;
295                if( !jabber_get_roster( ic ) )
296                        return XT_ABORT;
297        }
298       
299        return XT_HANDLED;
300}
301
302xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
303{
304        struct jabber_data *jd = ic->proto_data;
305        struct xt_node *c;
306        char *s;
307       
308        if( ( c = xt_find_node( node->children, "bind" ) ) )
309        {
310                c = xt_find_node( c->children, "jid" );
311                if( c && c->text_len && ( s = strchr( c->text, '/' ) ) &&
312                    strcmp( s + 1, set_getstr( &ic->acc->set, "resource" ) ) != 0 )
313                        imcb_log( ic, "Server changed session resource string to `%s'", s + 1 );
314               
315                jd->flags &= ~JFLAG_WAIT_BIND;
316        }
317        else
318        {
319                jd->flags &= ~JFLAG_WAIT_SESSION;
320        }
321       
322        if( ( jd->flags & ( JFLAG_WAIT_BIND | JFLAG_WAIT_SESSION ) ) == 0 )
323        {
324                if( !jabber_get_roster( ic ) )
325                        return XT_ABORT;
326        }
327       
328        return XT_HANDLED;
329}
330
331int jabber_get_roster( struct im_connection *ic )
332{
333        struct xt_node *node;
334        int st;
335       
336        imcb_log( ic, "Authenticated, requesting buddy list" );
337       
338        node = xt_new_node( "query", NULL, NULL );
339        xt_add_attr( node, "xmlns", XMLNS_ROSTER );
340        node = jabber_make_packet( "iq", "get", NULL, node );
341       
342        jabber_cache_add( ic, node, jabber_parse_roster );
343        st = jabber_write_packet( ic, node );
344       
345        return st;
346}
347
348static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
349{
350        struct xt_node *query, *c;
351        int initial = ( orig != NULL );
352       
353        if( !( query = xt_find_node( node->children, "query" ) ) )
354        {
355                imcb_log( ic, "WARNING: Received NULL roster packet" );
356                return XT_HANDLED;
357        }
358       
359        c = query->children;
360        while( ( c = xt_find_node( c, "item" ) ) )
361        {
362                struct xt_node *group = xt_find_node( node->children, "group" );
363                char *jid = xt_find_attr( c, "jid" );
364                char *name = xt_find_attr( c, "name" );
365                char *sub = xt_find_attr( c, "subscription" );
366               
367                if( jid && sub )
368                {
369                        if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) )
370                        {
371                                if( initial || imcb_find_buddy( ic, jid ) == NULL )
372                                        imcb_add_buddy( ic, jid, ( group && group->text_len ) ?
373                                                                   group->text : NULL );
374                               
375                                imcb_rename_buddy( ic, jid, name );
376                        }
377                        else if( strcmp( sub, "remove" ) == 0 )
378                        {
379                                /* Don't have any API call for this yet! So let's
380                                   just try to handle this as well as we can. */
381                                jabber_buddy_remove_bare( ic, jid );
382                                imcb_buddy_status( ic, jid, 0, NULL, NULL );
383                                /* FIXME! */
384                        }
385                }
386               
387                c = c->next;
388        }
389       
390        if( initial )
391                imcb_connected( ic );
392       
393        return XT_HANDLED;
394}
395
396int jabber_get_vcard( struct im_connection *ic, char *bare_jid )
397{
398        struct xt_node *node;
399       
400        if( strchr( bare_jid, '/' ) )
401                return 1;       /* This was an error, but return 0 should only be done if the connection died... */
402       
403        node = xt_new_node( "vCard", NULL, NULL );
404        xt_add_attr( node, "xmlns", XMLNS_VCARD );
405        node = jabber_make_packet( "iq", "get", bare_jid, node );
406       
407        jabber_cache_add( ic, node, jabber_iq_display_vcard );
408        return jabber_write_packet( ic, node );
409}
410
411static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
412{
413        struct xt_node *vc, *c, *sc; /* subchild, ic is already in use ;-) */
414        GString *reply;
415        char *s;
416       
417        if( ( s = xt_find_attr( node, "type" ) ) == NULL ||
418            strcmp( s, "result" ) != 0 ||
419            ( vc = xt_find_node( node->children, "vCard" ) ) == NULL )
420        {
421                s = xt_find_attr( orig, "to" ); /* If this returns NULL something's wrong.. */
422                imcb_log( ic, "Could not retrieve vCard of %s", s ? s : "(NULL)" );
423                return XT_HANDLED;
424        }
425       
426        s = xt_find_attr( orig, "to" );
427        reply = g_string_new( "vCard information for " );
428        reply = g_string_append( reply, s ? s : "(NULL)" );
429        reply = g_string_append( reply, ":\n" );
430       
431        /* I hate this format, I really do... */
432       
433        if( ( c = xt_find_node( vc->children, "FN" ) ) && c->text_len )
434                g_string_append_printf( reply, "Name: %s\n", c->text );
435       
436        if( ( c = xt_find_node( vc->children, "N" ) ) && c->children )
437        {
438                reply = g_string_append( reply, "Full name:" );
439               
440                if( ( sc = xt_find_node( c->children, "PREFIX" ) ) && sc->text_len )
441                        g_string_append_printf( reply, " %s", sc->text );
442                if( ( sc = xt_find_node( c->children, "GIVEN" ) ) && sc->text_len )
443                        g_string_append_printf( reply, " %s", sc->text );
444                if( ( sc = xt_find_node( c->children, "MIDDLE" ) ) && sc->text_len )
445                        g_string_append_printf( reply, " %s", sc->text );
446                if( ( sc = xt_find_node( c->children, "FAMILY" ) ) && sc->text_len )
447                        g_string_append_printf( reply, " %s", sc->text );
448                if( ( sc = xt_find_node( c->children, "SUFFIX" ) ) && sc->text_len )
449                        g_string_append_printf( reply, " %s", sc->text );
450               
451                reply = g_string_append_c( reply, '\n' );
452        }
453       
454        if( ( c = xt_find_node( vc->children, "NICKNAME" ) ) && c->text_len )
455                g_string_append_printf( reply, "Nickname: %s\n", c->text );
456       
457        if( ( c = xt_find_node( vc->children, "BDAY" ) ) && c->text_len )
458                g_string_append_printf( reply, "Date of birth: %s\n", c->text );
459       
460        /* Slightly alternative use of for... ;-) */
461        for( c = vc->children; ( c = xt_find_node( c, "EMAIL" ) ); c = c->next )
462        {
463                if( ( sc = xt_find_node( c->children, "USERID" ) ) == NULL || sc->text_len == 0 )
464                        continue;
465               
466                if( xt_find_node( c->children, "HOME" ) )
467                        s = "Home";
468                else if( xt_find_node( c->children, "WORK" ) )
469                        s = "Work";
470                else
471                        s = "Misc.";
472               
473                g_string_append_printf( reply, "%s e-mail address: %s\n", s, sc->text );
474        }
475       
476        if( ( c = xt_find_node( vc->children, "URL" ) ) && c->text_len )
477                g_string_append_printf( reply, "Homepage: %s\n", c->text );
478       
479        /* Slightly alternative use of for... ;-) */
480        for( c = vc->children; ( c = xt_find_node( c, "ADR" ) ); c = c->next )
481        {
482                if( xt_find_node( c->children, "HOME" ) )
483                        s = "Home";
484                else if( xt_find_node( c->children, "WORK" ) )
485                        s = "Work";
486                else
487                        s = "Misc.";
488               
489                g_string_append_printf( reply, "%s address: ", s );
490               
491                if( ( sc = xt_find_node( c->children, "STREET" ) ) && sc->text_len )
492                        g_string_append_printf( reply, "%s ", sc->text );
493                if( ( sc = xt_find_node( c->children, "EXTADR" ) ) && sc->text_len )
494                        g_string_append_printf( reply, "%s, ", sc->text );
495                if( ( sc = xt_find_node( c->children, "PCODE" ) ) && sc->text_len )
496                        g_string_append_printf( reply, "%s, ", sc->text );
497                if( ( sc = xt_find_node( c->children, "LOCALITY" ) ) && sc->text_len )
498                        g_string_append_printf( reply, "%s, ", sc->text );
499                if( ( sc = xt_find_node( c->children, "REGION" ) ) && sc->text_len )
500                        g_string_append_printf( reply, "%s, ", sc->text );
501                if( ( sc = xt_find_node( c->children, "CTRY" ) ) && sc->text_len )
502                        g_string_append_printf( reply, "%s", sc->text );
503               
504                if( reply->str[reply->len-2] == ',' )
505                        reply = g_string_truncate( reply, reply->len-2 );
506               
507                reply = g_string_append_c( reply, '\n' );
508        }
509       
510        for( c = vc->children; ( c = xt_find_node( c, "TEL" ) ); c = c->next )
511        {
512                if( ( sc = xt_find_node( c->children, "NUMBER" ) ) == NULL || sc->text_len == 0 )
513                        continue;
514               
515                if( xt_find_node( c->children, "HOME" ) )
516                        s = "Home";
517                else if( xt_find_node( c->children, "WORK" ) )
518                        s = "Work";
519                else
520                        s = "Misc.";
521               
522                g_string_append_printf( reply, "%s phone number: %s\n", s, sc->text );
523        }
524       
525        if( ( c = xt_find_node( vc->children, "DESC" ) ) && c->text_len )
526                g_string_append_printf( reply, "Other information:\n%s", c->text );
527       
528        /* *sigh* */
529       
530        imcb_log( ic, "%s", reply->str );
531        g_string_free( reply, TRUE );
532       
533        return XT_HANDLED;
534}
535
536int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name )
537{
538        struct xt_node *node;
539        int st;
540       
541        /* Build the item entry */
542        node = xt_new_node( "item", NULL, NULL );
543        xt_add_attr( node, "jid", handle );
544        if( name )
545                xt_add_attr( node, "name", name );
546       
547        /* And pack it into a roster-add packet */
548        node = xt_new_node( "query", NULL, node );
549        xt_add_attr( node, "xmlns", XMLNS_ROSTER );
550        node = jabber_make_packet( "iq", "set", NULL, node );
551       
552        st = jabber_write_packet( ic, node );
553       
554        xt_free_node( node );
555        return st;
556}
557
558int jabber_remove_from_roster( struct im_connection *ic, char *handle )
559{
560        struct xt_node *node;
561        int st;
562       
563        /* Build the item entry */
564        node = xt_new_node( "item", NULL, NULL );
565        xt_add_attr( node, "jid", handle );
566        xt_add_attr( node, "subscription", "remove" );
567       
568        /* And pack it into a roster-add packet */
569        node = xt_new_node( "query", NULL, node );
570        xt_add_attr( node, "xmlns", XMLNS_ROSTER );
571        node = jabber_make_packet( "iq", "set", NULL, node );
572       
573        st = jabber_write_packet( ic, node );
574       
575        xt_free_node( node );
576        return st;
577}
Note: See TracBrowser for help on using the repository browser.