source: protocols/jabber/jabber_util.c @ 424e663

Last change on this file since 424e663 was 98de2cc, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-06-21T23:51:18Z

Now preserving case in JID resources, and handling them with case sensitivity
since apparently that's how the RFC wants it. (While the rest of the JID
should be case IN-sensitive. Consistency is hard to find these days...) Also
extended the unittests a little bit. Closes #422.

  • Property mode set to 100644
File size: 19.6 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Misc. stuff                                              *
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 unsigned int next_id = 1;
27
28char *set_eval_priority( set_t *set, char *value )
29{
30        account_t *acc = set->data;
31        int i;
32       
33        if( sscanf( value, "%d", &i ) == 1 )
34        {
35                /* Priority is a signed 8-bit integer, according to RFC 3921. */
36                if( i < -128 || i > 127 )
37                        return NULL;
38        }
39        else
40                return NULL;
41       
42        /* Only run this stuff if the account is online ATM,
43           and if the setting seems to be acceptable. */
44        if( acc->ic )
45        {
46                /* Although set_eval functions usually are very nice and
47                   convenient, they have one disadvantage: If I would just
48                   call p_s_u() now to send the new prio setting, it would
49                   send the old setting because the set->value gets changed
50                   after the (this) eval returns a non-NULL value.
51                   
52                   So now I can choose between implementing post-set
53                   functions next to evals, or just do this little hack: */
54               
55                g_free( set->value );
56                set->value = g_strdup( value );
57               
58                /* (Yes, sorry, I prefer the hack. :-P) */
59               
60                presence_send_update( acc->ic );
61        }
62       
63        return value;
64}
65
66char *set_eval_tls( set_t *set, char *value )
67{
68        if( g_strcasecmp( value, "try" ) == 0 )
69                return value;
70        else
71                return set_eval_bool( set, value );
72}
73
74struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children )
75{
76        struct xt_node *node;
77       
78        node = xt_new_node( name, NULL, children );
79       
80        if( type )
81                xt_add_attr( node, "type", type );
82        if( to )
83                xt_add_attr( node, "to", to );
84       
85        /* IQ packets should always have an ID, so let's generate one. It
86           might get overwritten by jabber_cache_add() if this packet has
87           to be saved until we receive a response. Cached packets get
88           slightly different IDs so we can recognize them. */
89        if( strcmp( name, "iq" ) == 0 )
90        {
91                char *id = g_strdup_printf( "%s%05x", JABBER_PACKET_ID, ( next_id++ ) & 0xfffff );
92                xt_add_attr( node, "id", id );
93                g_free( id );
94        }
95       
96        return node;
97}
98
99struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type )
100{
101        struct xt_node *node, *c;
102        char *to;
103       
104        /* Create the "defined-condition" tag. */
105        c = xt_new_node( err_cond, NULL, NULL );
106        xt_add_attr( c, "xmlns", XMLNS_STANZA_ERROR );
107       
108        /* Put it in an <error> tag. */
109        c = xt_new_node( "error", NULL, c );
110        xt_add_attr( c, "type", err_type );
111       
112        /* To make the actual error packet, we copy the original packet and
113           add our <error>/type="error" tag. Including the original packet
114           is recommended, so let's just do it. */
115        node = xt_dup( orig );
116        xt_add_child( node, c );
117        xt_add_attr( node, "type", "error" );
118       
119        /* Return to sender. */
120        if( ( to = xt_find_attr( node, "from" ) ) )
121        {
122                xt_add_attr( node, "to", to );
123                xt_remove_attr( node, "from" );
124        }
125               
126        return node;
127}
128
129/* Cache a node/packet for later use. Mainly useful for IQ packets if you need
130   them when you receive the response. Use this BEFORE sending the packet so
131   it'll get a new id= tag, and do NOT free() the packet after sending it! */
132void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func )
133{
134        struct jabber_data *jd = ic->proto_data;
135        struct jabber_cache_entry *entry = g_new0( struct jabber_cache_entry, 1 );
136        char *id;
137       
138        id = g_strdup_printf( "%s%05x", jd->cached_id_prefix, ( next_id++ ) & 0xfffff );
139        xt_add_attr( node, "id", id );
140        g_free( id );
141       
142        entry->node = node;
143        entry->func = func;
144        entry->saved_at = time( NULL );
145        g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry );
146}
147
148void jabber_cache_entry_free( gpointer data )
149{
150        struct jabber_cache_entry *entry = data;
151       
152        xt_free_node( entry->node );
153        g_free( entry );
154}
155
156gboolean jabber_cache_clean_entry( gpointer key, gpointer entry, gpointer nullpointer );
157
158/* This one should be called from time to time (from keepalive, in this case)
159   to make sure things don't stay in the node cache forever. By marking nodes
160   during the first run and deleting marked nodes during a next run, every
161   node should be available in the cache for at least a minute (assuming the
162   function is indeed called every minute). */
163void jabber_cache_clean( struct im_connection *ic )
164{
165        struct jabber_data *jd = ic->proto_data;
166        time_t threshold = time( NULL ) - JABBER_CACHE_MAX_AGE;
167       
168        g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, &threshold );
169}
170
171gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer threshold_ )
172{
173        struct jabber_cache_entry *entry = entry_;
174        time_t *threshold = threshold_;
175       
176        return entry->saved_at < *threshold;
177}
178
179xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node )
180{
181        struct jabber_data *jd = ic->proto_data;
182        struct jabber_cache_entry *entry;
183        char *s;
184       
185        if( ( s = xt_find_attr( node, "id" ) ) == NULL ||
186            strncmp( s, jd->cached_id_prefix, strlen( jd->cached_id_prefix ) ) != 0 )
187        {
188                /* Silently ignore it, without an ID (or a non-cache
189                   ID) we don't know how to handle the packet and we
190                   probably don't have to. */
191                return XT_HANDLED;
192        }
193       
194        entry = g_hash_table_lookup( jd->node_cache, s );
195       
196        if( entry == NULL )
197        {
198                imcb_log( ic, "Warning: Received %s-%s packet with unknown/expired ID %s!",
199                              node->name, xt_find_attr( node, "type" ) ? : "(no type)", s );
200        }
201        else if( entry->func )
202        {
203                return entry->func( ic, node, entry->node );
204        }
205       
206        return XT_HANDLED;
207}
208
209const struct jabber_away_state jabber_away_state_list[] =
210{
211        { "away",  "Away" },
212        { "chat",  "Free for Chat" },
213        { "dnd",   "Do not Disturb" },
214        { "xa",    "Extended Away" },
215        { "",      "Online" },
216        { "",      NULL }
217};
218
219const struct jabber_away_state *jabber_away_state_by_code( char *code )
220{
221        int i;
222       
223        for( i = 0; jabber_away_state_list[i].full_name; i ++ )
224                if( g_strcasecmp( jabber_away_state_list[i].code, code ) == 0 )
225                        return jabber_away_state_list + i;
226       
227        return NULL;
228}
229
230const struct jabber_away_state *jabber_away_state_by_name( char *name )
231{
232        int i;
233       
234        for( i = 0; jabber_away_state_list[i].full_name; i ++ )
235                if( g_strcasecmp( jabber_away_state_list[i].full_name, name ) == 0 )
236                        return jabber_away_state_list + i;
237       
238        return NULL;
239}
240
241struct jabber_buddy_ask_data
242{
243        struct im_connection *ic;
244        char *handle;
245        char *realname;
246};
247
248static void jabber_buddy_ask_yes( void *data )
249{
250        struct jabber_buddy_ask_data *bla = data;
251       
252        presence_send_request( bla->ic, bla->handle, "subscribed" );
253       
254        if( imcb_find_buddy( bla->ic, bla->handle ) == NULL )
255                imcb_ask_add( bla->ic, bla->handle, NULL );
256       
257        g_free( bla->handle );
258        g_free( bla );
259}
260
261static void jabber_buddy_ask_no( void *data )
262{
263        struct jabber_buddy_ask_data *bla = data;
264       
265        presence_send_request( bla->ic, bla->handle, "subscribed" );
266       
267        g_free( bla->handle );
268        g_free( bla );
269}
270
271void jabber_buddy_ask( struct im_connection *ic, char *handle )
272{
273        struct jabber_buddy_ask_data *bla = g_new0( struct jabber_buddy_ask_data, 1 );
274        char *buf;
275       
276        bla->ic = ic;
277        bla->handle = g_strdup( handle );
278       
279        buf = g_strdup_printf( "The user %s wants to add you to his/her buddy list.", handle );
280        imcb_ask( ic, buf, bla, jabber_buddy_ask_yes, jabber_buddy_ask_no );
281        g_free( buf );
282}
283
284/* Returns a new string. Don't leak it! */
285char *jabber_normalize( const char *orig )
286{
287        int len, i;
288        char *new;
289       
290        len = strlen( orig );
291        new = g_new( char, len + 1 );
292       
293        /* So it turns out the /resource part is case sensitive. Yeah, and
294           it's Unicode but feck Unicode. :-P So stop once we see a slash. */
295        for( i = 0; i < len && orig[i] != '/' ; i ++ )
296                new[i] = tolower( orig[i] );
297        for( ; orig[i]; i ++ )
298                new[i] = orig[i];
299       
300        new[i] = 0;
301        return new;
302}
303
304/* Adds a buddy/resource to our list. Returns NULL if full_jid is not really a
305   FULL jid or if we already have this buddy/resource. XXX: No, great, actually
306   buddies from transports don't (usually) have resources. So we'll really have
307   to deal with that properly. Set their ->resource property to NULL. Do *NOT*
308   allow to mix this stuff, though... */
309struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_ )
310{
311        struct jabber_data *jd = ic->proto_data;
312        struct jabber_buddy *bud, *new, *bi;
313        char *s, *full_jid;
314       
315        full_jid = jabber_normalize( full_jid_ );
316       
317        if( ( s = strchr( full_jid, '/' ) ) )
318                *s = 0;
319       
320        new = g_new0( struct jabber_buddy, 1 );
321       
322        if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
323        {
324                /* If this is a transport buddy or whatever, it can't have more
325                   than one instance, so this is always wrong: */
326                if( s == NULL || bud->resource == NULL )
327                {
328                        if( s ) *s = '/';
329                        g_free( new );
330                        g_free( full_jid );
331                        return NULL;
332                }
333               
334                new->bare_jid = bud->bare_jid;
335               
336                /* We already have another resource for this buddy, add the
337                   new one to the list. */
338                for( bi = bud; bi; bi = bi->next )
339                {
340                        /* Check for dupes. */
341                        if( strcmp( bi->resource, s + 1 ) == 0 )
342                        {
343                                *s = '/';
344                                g_free( new );
345                                g_free( full_jid );
346                                return NULL;
347                        }
348                        /* Append the new item to the list. */
349                        else if( bi->next == NULL )
350                        {
351                                bi->next = new;
352                                break;
353                        }
354                }
355        }
356        else
357        {
358                /* Keep in mind that full_jid currently isn't really
359                   a full JID... */
360                new->bare_jid = g_strdup( full_jid );
361                g_hash_table_insert( jd->buddies, new->bare_jid, new );
362        }
363       
364        if( s )
365        {
366                *s = '/';
367                new->full_jid = full_jid;
368                new->resource = strchr( new->full_jid, '/' ) + 1;
369        }
370        else
371        {
372                /* Let's waste some more bytes of RAM instead of to make
373                   memory management a total disaster here. And it saves
374                   me one g_free() call in this function. :-P */
375                new->full_jid = full_jid;
376        }
377       
378        return new;
379}
380
381/* Finds a buddy from our structures. Can find both full- and bare JIDs. When
382   asked for a bare JID, it uses the "resource_select" setting to see which
383   resource to pick. */
384struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
385{
386        struct jabber_data *jd = ic->proto_data;
387        struct jabber_buddy *bud;
388        char *s, *jid;
389       
390        jid = jabber_normalize( jid_ );
391       
392        if( ( s = strchr( jid, '/' ) ) )
393        {
394                int bare_exists = 0;
395               
396                *s = 0;
397                if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
398                {
399                        /* Just return the first one for this bare JID. */
400                        if( flags & GET_BUDDY_FIRST )
401                        {
402                                *s = '/';
403                                g_free( jid );
404                                return bud;
405                        }
406                       
407                        /* Is this one of those no-resource buddies? */
408                        if( bud->resource == NULL )
409                        {
410                                *s = '/';
411                                g_free( jid );
412                                return NULL;
413                        }
414                       
415                        /* See if there's an exact match. */
416                        for( ; bud; bud = bud->next )
417                                if( strcmp( bud->resource, s + 1 ) == 0 )
418                                        break;
419                }
420                else
421                {
422                        /* This variable tells the if down here that the bare
423                           JID already exists and we should feel free to add
424                           more resources, if the caller asked for that. */
425                        bare_exists = 1;
426                }
427               
428                if( bud == NULL && ( flags & GET_BUDDY_CREAT ) &&
429                    ( !bare_exists || imcb_find_buddy( ic, jid ) ) )
430                {
431                        *s = '/';
432                        bud = jabber_buddy_add( ic, jid );
433                }
434               
435                g_free( jid );
436                return bud;
437        }
438        else
439        {
440                struct jabber_buddy *best_prio, *best_time;
441                char *set;
442               
443                bud = g_hash_table_lookup( jd->buddies, jid );
444               
445                g_free( jid );
446               
447                if( bud == NULL )
448                        /* No match. Create it now? */
449                        return ( ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid_ ) ) ?
450                                   jabber_buddy_add( ic, jid_ ) : NULL;
451                else if( bud->resource && ( flags & GET_BUDDY_EXACT ) )
452                        /* We want an exact match, so in thise case there shouldn't be a /resource. */
453                        return NULL;
454                else if( bud->resource == NULL || bud->next == NULL )
455                        /* No need for selection if there's only one option. */
456                        return bud;
457                else if( flags & GET_BUDDY_FIRST )
458                        /* Looks like the caller doesn't care about details. */
459                        return bud;
460               
461                best_prio = best_time = bud;
462                for( ; bud; bud = bud->next )
463                {
464                        if( bud->priority > best_prio->priority )
465                                best_prio = bud;
466                        if( bud->last_act > best_time->last_act )
467                                best_time = bud;
468                }
469               
470                if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL )
471                        return NULL;
472                else if( strcmp( set, "activity" ) == 0 )
473                        return best_time;
474                else /* if( strcmp( set, "priority" ) == 0 ) */
475                        return best_prio;
476        }
477}
478
479/* I'm keeping a separate ext_jid attribute to save a JID that makes sense
480   to export to BitlBee. This is mainly for groupchats right now. It's
481   a bit of a hack, but I just think having the user nickname in the hostname
482   part of the hostmask doesn't look nice on IRC. Normally you can convert
483   a normal JID to ext_jid by swapping the part before and after the / and
484   replacing the / with a =. But there should be some stripping (@s are
485   allowed in Jabber nicks...). */
486struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
487{
488        struct jabber_buddy *bud;
489        char *s, *jid;
490       
491        jid = jabber_normalize( jid_ );
492       
493        if( ( s = strchr( jid, '=' ) ) == NULL )
494                return NULL;
495       
496        for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next )
497        {
498                /* Hmmm, could happen if not all people in the chat are anonymized? */
499                if( bud->ext_jid == NULL )
500                        continue;
501               
502                if( strcmp( bud->ext_jid, jid ) == 0 )
503                        break;
504        }
505       
506        g_free( jid );
507       
508        return bud;
509}
510
511/* Remove one specific full JID from our list. Use this when a buddy goes
512   off-line (because (s)he can still be online from a different location.
513   XXX: See above, we should accept bare JIDs too... */
514int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
515{
516        struct jabber_data *jd = ic->proto_data;
517        struct jabber_buddy *bud, *prev, *bi;
518        char *s, *full_jid;
519       
520        full_jid = jabber_normalize( full_jid_ );
521       
522        if( ( s = strchr( full_jid, '/' ) ) )
523                *s = 0;
524       
525        if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
526        {
527                /* If there's only one item in the list (and if the resource
528                   matches), removing it is simple. (And the hash reference
529                   should be removed too!) */
530                if( bud->next == NULL &&
531                    ( ( s == NULL && bud->resource == NULL ) ||
532                      ( bud->resource && s && strcmp( bud->resource, s + 1 ) == 0 ) ) )
533                {
534                        g_hash_table_remove( jd->buddies, bud->bare_jid );
535                        g_free( bud->bare_jid );
536                        g_free( bud->ext_jid );
537                        g_free( bud->full_jid );
538                        g_free( bud->away_message );
539                        g_free( bud );
540                       
541                        g_free( full_jid );
542                       
543                        return 1;
544                }
545                else if( s == NULL || bud->resource == NULL )
546                {
547                        /* Tried to remove a bare JID while this JID does seem
548                           to have resources... (Or the opposite.) *sigh* */
549                        g_free( full_jid );
550                        return 0;
551                }
552                else
553                {
554                        for( bi = bud, prev = NULL; bi; bi = (prev=bi)->next )
555                                if( strcmp( bi->resource, s + 1 ) == 0 )
556                                        break;
557                       
558                        g_free( full_jid );
559                       
560                        if( bi )
561                        {
562                                if( prev )
563                                        prev->next = bi->next;
564                                else
565                                        /* The hash table should point at the second
566                                           item, because we're removing the first. */
567                                        g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
568                               
569                                g_free( bi->ext_jid );
570                                g_free( bi->full_jid );
571                                g_free( bi->away_message );
572                                g_free( bi );
573                               
574                                return 1;
575                        }
576                        else
577                        {
578                                return 0;
579                        }
580                }
581        }
582        else
583        {
584                g_free( full_jid );
585                return 0;
586        }
587}
588
589/* Remove a buddy completely; removes all resources that belong to the
590   specified bare JID. Use this when removing someone from the contact
591   list, for example. */
592int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
593{
594        struct jabber_data *jd = ic->proto_data;
595        struct jabber_buddy *bud, *next;
596       
597        if( strchr( bare_jid, '/' ) )
598                return 0;
599       
600        if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) )
601        {
602                /* Most important: Remove the hash reference. We don't know
603                   this buddy anymore. */
604                g_hash_table_remove( jd->buddies, bud->bare_jid );
605                g_free( bud->bare_jid );
606               
607                /* Deallocate the linked list of resources. */
608                while( bud )
609                {
610                        /* ext_jid && anonymous means that this buddy is
611                           specific to one groupchat (the one we're
612                           currently cleaning up) so it can be deleted
613                           completely. */
614                        if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS )
615                                imcb_remove_buddy( ic, bud->ext_jid, NULL );
616                       
617                        next = bud->next;
618                        g_free( bud->ext_jid );
619                        g_free( bud->full_jid );
620                        g_free( bud->away_message );
621                        g_free( bud );
622                        bud = next;
623                }
624               
625                return 1;
626        }
627        else
628        {
629                return 0;
630        }
631}
632
633time_t jabber_get_timestamp( struct xt_node *xt )
634{
635        struct tm tp, utc;
636        struct xt_node *c;
637        time_t res, tres;
638        char *s = NULL;
639       
640        for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
641        {
642                if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 )
643                        break;
644        }
645       
646        if( !c || !( s = xt_find_attr( c, "stamp" ) ) )
647                return 0;
648       
649        memset( &tp, 0, sizeof( tp ) );
650        if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
651                                                &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 )
652                return 0;
653       
654        tp.tm_year -= 1900;
655        tp.tm_mon --;
656        tp.tm_isdst = -1; /* GRRRRRRRRRRR */
657       
658        res = mktime( &tp );
659        /* Problem is, mktime() just gave us the GMT timestamp for the
660           given local time... While the given time WAS NOT local. So
661           we should fix this now.
662       
663           Now I could choose between messing with environment variables
664           (kludgy) or using timegm() (not portable)... Or doing the
665           following, which I actually prefer... */
666        gmtime_r( &res, &utc );
667        utc.tm_isdst = -1; /* Once more: GRRRRRRRRRRRRRRRRRR!!! */
668        if( utc.tm_hour == tp.tm_hour && utc.tm_min == tp.tm_min )
669                /* Sweet! We're in UTC right now... */
670                return res;
671       
672        tres = mktime( &utc );
673        res += res - tres;
674       
675        /* Yes, this is a hack. And it will go wrong around DST changes.
676           BUT this is more likely to be threadsafe than messing with
677           environment variables, and possibly more portable... */
678       
679        return res;
680}
681
682struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
683{
684        struct jabber_error *err;
685        struct xt_node *c;
686        char *s;
687       
688        if( node == NULL )
689                return NULL;
690       
691        err = g_new0( struct jabber_error, 1 );
692        err->type = xt_find_attr( node, "type" );
693       
694        for( c = node->children; c; c = c->next )
695        {
696                if( !( s = xt_find_attr( c, "xmlns" ) ) ||
697                    strcmp( s, xmlns ) != 0 )
698                        continue;
699               
700                if( strcmp( c->name, "text" ) != 0 )
701                {
702                        err->code = c->name;
703                }
704                /* Only use the text if it doesn't have an xml:lang attribute,
705                   if it's empty or if it's set to something English. */
706                else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
707                         !*s || strncmp( s, "en", 2 ) == 0 )
708                {
709                        err->text = c->text;
710                }
711        }
712       
713        return err;
714}
715
716void jabber_error_free( struct jabber_error *err )
717{
718        g_free( err );
719}
Note: See TracBrowser for help on using the repository browser.