source: protocols/jabber/jabber_util.c @ ef5c185

Last change on this file since ef5c185 was 1baaef8, checked in by Wilmer van der Gaast <wilmer@…>, at 2007-07-30T19:12:06Z

Added jabber_error_parse() and using it for both stream- and stanza
(only presence so far) errors.

  • Property mode set to 100644
File size: 18.7 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        char *id = g_strdup_printf( "%s%05x", JABBER_CACHED_ID, ( next_id++ ) & 0xfffff );
136        struct jabber_cache_entry *entry = g_new0( struct jabber_cache_entry, 1 );
137       
138        xt_add_attr( node, "id", id );
139        g_free( id );
140       
141        entry->node = node;
142        entry->func = func;
143        g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry );
144}
145
146void jabber_cache_entry_free( gpointer data )
147{
148        struct jabber_cache_entry *entry = data;
149       
150        xt_free_node( entry->node );
151        g_free( entry );
152}
153
154gboolean jabber_cache_clean_entry( gpointer key, gpointer entry, gpointer nullpointer );
155
156/* This one should be called from time to time (from keepalive, in this case)
157   to make sure things don't stay in the node cache forever. By marking nodes
158   during the first run and deleting marked nodes during a next run, every
159   node should be available in the cache for at least a minute (assuming the
160   function is indeed called every minute). */
161void jabber_cache_clean( struct im_connection *ic )
162{
163        struct jabber_data *jd = ic->proto_data;
164       
165        g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, NULL );
166}
167
168gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer nullpointer )
169{
170        struct jabber_cache_entry *entry = entry_;
171        struct xt_node *node = entry->node;
172       
173        if( node->flags & XT_SEEN )
174                return TRUE;
175        else
176        {
177                node->flags |= XT_SEEN;
178                return FALSE;
179        }
180}
181
182const struct jabber_away_state jabber_away_state_list[] =
183{
184        { "away",  "Away" },
185        { "chat",  "Free for Chat" },
186        { "dnd",   "Do not Disturb" },
187        { "xa",    "Extended Away" },
188        { "",      "Online" },
189        { "",      NULL }
190};
191
192const struct jabber_away_state *jabber_away_state_by_code( char *code )
193{
194        int i;
195       
196        for( i = 0; jabber_away_state_list[i].full_name; i ++ )
197                if( g_strcasecmp( jabber_away_state_list[i].code, code ) == 0 )
198                        return jabber_away_state_list + i;
199       
200        return NULL;
201}
202
203const struct jabber_away_state *jabber_away_state_by_name( char *name )
204{
205        int i;
206       
207        for( i = 0; jabber_away_state_list[i].full_name; i ++ )
208                if( g_strcasecmp( jabber_away_state_list[i].full_name, name ) == 0 )
209                        return jabber_away_state_list + i;
210       
211        return NULL;
212}
213
214struct jabber_buddy_ask_data
215{
216        struct im_connection *ic;
217        char *handle;
218        char *realname;
219};
220
221static void jabber_buddy_ask_yes( gpointer w, struct jabber_buddy_ask_data *bla )
222{
223        presence_send_request( bla->ic, bla->handle, "subscribed" );
224       
225        if( imcb_find_buddy( bla->ic, bla->handle ) == NULL )
226                imcb_ask_add( bla->ic, bla->handle, NULL );
227       
228        g_free( bla->handle );
229        g_free( bla );
230}
231
232static void jabber_buddy_ask_no( gpointer w, struct jabber_buddy_ask_data *bla )
233{
234        presence_send_request( bla->ic, bla->handle, "subscribed" );
235       
236        g_free( bla->handle );
237        g_free( bla );
238}
239
240void jabber_buddy_ask( struct im_connection *ic, char *handle )
241{
242        struct jabber_buddy_ask_data *bla = g_new0( struct jabber_buddy_ask_data, 1 );
243        char *buf;
244       
245        bla->ic = ic;
246        bla->handle = g_strdup( handle );
247       
248        buf = g_strdup_printf( "The user %s wants to add you to his/her buddy list.", handle );
249        imcb_ask( ic, buf, bla, jabber_buddy_ask_yes, jabber_buddy_ask_no );
250        g_free( buf );
251}
252
253/* Returns a new string. Don't leak it! */
254char *jabber_normalize( const char *orig )
255{
256        int len, i;
257        char *new;
258       
259        len = strlen( orig );
260        new = g_new( char, len + 1 );
261        for( i = 0; i < len; i ++ )
262                new[i] = tolower( orig[i] );
263       
264        new[i] = 0;
265        return new;
266}
267
268/* Adds a buddy/resource to our list. Returns NULL if full_jid is not really a
269   FULL jid or if we already have this buddy/resource. XXX: No, great, actually
270   buddies from transports don't (usually) have resources. So we'll really have
271   to deal with that properly. Set their ->resource property to NULL. Do *NOT*
272   allow to mix this stuff, though... */
273struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_ )
274{
275        struct jabber_data *jd = ic->proto_data;
276        struct jabber_buddy *bud, *new, *bi;
277        char *s, *full_jid;
278       
279        full_jid = jabber_normalize( full_jid_ );
280       
281        if( ( s = strchr( full_jid, '/' ) ) )
282                *s = 0;
283       
284        new = g_new0( struct jabber_buddy, 1 );
285       
286        if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
287        {
288                /* If this is a transport buddy or whatever, it can't have more
289                   than one instance, so this is always wrong: */
290                if( s == NULL || bud->resource == NULL )
291                {
292                        if( s ) *s = '/';
293                        g_free( new );
294                        g_free( full_jid );
295                        return NULL;
296                }
297               
298                new->bare_jid = bud->bare_jid;
299               
300                /* We already have another resource for this buddy, add the
301                   new one to the list. */
302                for( bi = bud; bi; bi = bi->next )
303                {
304                        /* Check for dupes. */
305                        if( g_strcasecmp( bi->resource, s + 1 ) == 0 )
306                        {
307                                *s = '/';
308                                g_free( new );
309                                g_free( full_jid );
310                                return NULL;
311                        }
312                        /* Append the new item to the list. */
313                        else if( bi->next == NULL )
314                        {
315                                bi->next = new;
316                                break;
317                        }
318                }
319        }
320        else
321        {
322                /* Keep in mind that full_jid currently isn't really
323                   a full JID... */
324                new->bare_jid = g_strdup( full_jid );
325                g_hash_table_insert( jd->buddies, new->bare_jid, new );
326        }
327       
328        if( s )
329        {
330                *s = '/';
331                new->full_jid = full_jid;
332                new->resource = strchr( new->full_jid, '/' ) + 1;
333        }
334        else
335        {
336                /* Let's waste some more bytes of RAM instead of to make
337                   memory management a total disaster here. And it saves
338                   me one g_free() call in this function. :-P */
339                new->full_jid = full_jid;
340        }
341       
342        return new;
343}
344
345/* Finds a buddy from our structures. Can find both full- and bare JIDs. When
346   asked for a bare JID, it uses the "resource_select" setting to see which
347   resource to pick. */
348struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
349{
350        struct jabber_data *jd = ic->proto_data;
351        struct jabber_buddy *bud;
352        char *s, *jid;
353       
354        jid = jabber_normalize( jid_ );
355       
356        if( ( s = strchr( jid, '/' ) ) )
357        {
358                int none_found = 0;
359               
360                *s = 0;
361                if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
362                {
363                        /* Is this one of those no-resource buddies? */
364                        if( bud->resource == NULL )
365                        {
366                                g_free( jid );
367                                return NULL;
368                        }
369                        else
370                        {
371                                /* See if there's an exact match. */
372                                for( ; bud; bud = bud->next )
373                                        if( g_strcasecmp( bud->resource, s + 1 ) == 0 )
374                                                break;
375                        }
376                }
377                else
378                {
379                        /* This hack is there to make sure that O_CREAT will
380                           work if there's already another resouce present
381                           for this JID, even if it's an unknown buddy. This
382                           is done to handle conferences properly. */
383                        none_found = 1;
384                }
385               
386                if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && ( imcb_find_buddy( ic, jid ) || !none_found ) )
387                {
388                        *s = '/';
389                        bud = jabber_buddy_add( ic, jid );
390                }
391               
392                g_free( jid );
393                return bud;
394        }
395        else
396        {
397                struct jabber_buddy *best_prio, *best_time;
398                char *set;
399               
400                bud = g_hash_table_lookup( jd->buddies, jid );
401               
402                g_free( jid );
403               
404                if( bud == NULL )
405                        /* No match. Create it now? */
406                        return ( ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid_ ) ) ?
407                                   jabber_buddy_add( ic, jid_ ) : NULL;
408                else if( bud->resource && ( flags & GET_BUDDY_EXACT ) )
409                        /* We want an exact match, so in thise case there shouldn't be a /resource. */
410                        return NULL;
411                else if( ( bud->resource == NULL || bud->next == NULL ) )
412                        /* No need for selection if there's only one option. */
413                        return bud;
414               
415                best_prio = best_time = bud;
416                for( ; bud; bud = bud->next )
417                {
418                        if( bud->priority > best_prio->priority )
419                                best_prio = bud;
420                        if( bud->last_act > best_time->last_act )
421                                best_time = bud;
422                }
423               
424                if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL )
425                        return NULL;
426                else if( strcmp( set, "activity" ) == 0 )
427                        return best_time;
428                else /* if( strcmp( set, "priority" ) == 0 ) */
429                        return best_prio;
430        }
431}
432
433/* I'm keeping a separate ext_jid attribute to save a JID that makes sense
434   to export to BitlBee. This is mainly for groupchats right now. It's
435   a bit of a hack, but I just think having the user nickname in the hostname
436   part of the hostmask doesn't look nice on IRC. Normally you can convert
437   a normal JID to ext_jid by swapping the part before and after the / and
438   replacing the / with a =. But there should be some stripping (@s are
439   allowed in Jabber nicks...). */
440struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
441{
442        struct jabber_buddy *bud;
443        char *s, *jid;
444       
445        jid = jabber_normalize( jid_ );
446       
447        if( ( s = strchr( jid, '=' ) ) == NULL )
448                return NULL;
449       
450        for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next )
451        {
452                /* Hmmm, could happen if not all people in the chat are anonymized? */
453                if( bud->ext_jid == NULL )
454                        continue;
455               
456                if( strcmp( bud->ext_jid, jid ) == 0 )
457                        break;
458        }
459       
460        g_free( jid );
461       
462        return bud;
463}
464
465/* Remove one specific full JID from our list. Use this when a buddy goes
466   off-line (because (s)he can still be online from a different location.
467   XXX: See above, we should accept bare JIDs too... */
468int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
469{
470        struct jabber_data *jd = ic->proto_data;
471        struct jabber_buddy *bud, *prev, *bi;
472        char *s, *full_jid;
473       
474        full_jid = jabber_normalize( full_jid_ );
475       
476        if( ( s = strchr( full_jid, '/' ) ) )
477                *s = 0;
478       
479        if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
480        {
481                /* If there's only one item in the list (and if the resource
482                   matches), removing it is simple. (And the hash reference
483                   should be removed too!) */
484                if( bud->next == NULL && ( ( s == NULL || bud->resource == NULL ) || g_strcasecmp( bud->resource, s + 1 ) == 0 ) )
485                {
486                        g_hash_table_remove( jd->buddies, bud->bare_jid );
487                        g_free( bud->bare_jid );
488                        g_free( bud->ext_jid );
489                        g_free( bud->full_jid );
490                        g_free( bud->away_message );
491                        g_free( bud );
492                       
493                        g_free( full_jid );
494                       
495                        return 1;
496                }
497                else if( s == NULL || bud->resource == NULL )
498                {
499                        /* Tried to remove a bare JID while this JID does seem
500                           to have resources... (Or the opposite.) *sigh* */
501                        g_free( full_jid );
502                        return 0;
503                }
504                else
505                {
506                        for( bi = bud, prev = NULL; bi; bi = (prev=bi)->next )
507                                if( g_strcasecmp( bi->resource, s + 1 ) == 0 )
508                                        break;
509                       
510                        g_free( full_jid );
511                       
512                        if( bi )
513                        {
514                                if( prev )
515                                        prev->next = bi->next;
516                                else
517                                        /* The hash table should point at the second
518                                           item, because we're removing the first. */
519                                        g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
520                               
521                                g_free( bi->ext_jid );
522                                g_free( bi->full_jid );
523                                g_free( bi->away_message );
524                                g_free( bi );
525                               
526                                return 1;
527                        }
528                        else
529                        {
530                                return 0;
531                        }
532                }
533        }
534        else
535        {
536                g_free( full_jid );
537                return 0;
538        }
539}
540
541/* Remove a buddy completely; removes all resources that belong to the
542   specified bare JID. Use this when removing someone from the contact
543   list, for example. */
544int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
545{
546        struct jabber_data *jd = ic->proto_data;
547        struct jabber_buddy *bud, *next;
548       
549        if( strchr( bare_jid, '/' ) )
550                return 0;
551       
552        if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) )
553        {
554                /* Most important: Remove the hash reference. We don't know
555                   this buddy anymore. */
556                g_hash_table_remove( jd->buddies, bud->bare_jid );
557                g_free( bud->bare_jid );
558               
559                /* Deallocate the linked list of resources. */
560                while( bud )
561                {
562                        /* ext_jid && anonymous means that this buddy is
563                           specific to one groupchat (the one we're
564                           currently cleaning up) so it can be deleted
565                           completely. */
566                        if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS )
567                                imcb_remove_buddy( ic, bud->ext_jid, NULL );
568                       
569                        next = bud->next;
570                        g_free( bud->ext_jid );
571                        g_free( bud->full_jid );
572                        g_free( bud->away_message );
573                        g_free( bud );
574                        bud = next;
575                }
576               
577                return 1;
578        }
579        else
580        {
581                return 0;
582        }
583}
584
585struct groupchat *jabber_chat_by_name( struct im_connection *ic, const char *name )
586{
587        char *normalized = jabber_normalize( name );
588        struct groupchat *ret;
589        struct jabber_chat *jc;
590       
591        for( ret = ic->groupchats; ret; ret = ret->next )
592        {
593                jc = ret->data;
594                if( strcmp( normalized, jc->name ) == 0 )
595                        break;
596        }
597        g_free( normalized );
598       
599        return ret;
600}
601
602time_t jabber_get_timestamp( struct xt_node *xt )
603{
604        struct tm tp, utc;
605        struct xt_node *c;
606        time_t res, tres;
607        char *s = NULL;
608       
609        for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
610        {
611                if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 )
612                        break;
613        }
614       
615        if( !c || !( s = xt_find_attr( c, "stamp" ) ) )
616                return 0;
617       
618        memset( &tp, 0, sizeof( tp ) );
619        if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
620                                                &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 )
621                return 0;
622       
623        tp.tm_year -= 1900;
624        tp.tm_mon --;
625        tp.tm_isdst = -1; /* GRRRRRRRRRRR */
626       
627        res = mktime( &tp );
628        /* Problem is, mktime() just gave us the GMT timestamp for the
629           given local time... While the given time WAS NOT local. So
630           we should fix this now.
631       
632           Now I could choose between messing with environment variables
633           (kludgy) or using timegm() (not portable)... Or doing the
634           following, which I actually prefer... */
635        gmtime_r( &res, &utc );
636        utc.tm_isdst = -1; /* Once more: GRRRRRRRRRRRRRRRRRR!!! */
637        if( utc.tm_hour == tp.tm_hour && utc.tm_min == tp.tm_min )
638                /* Sweet! We're in UTC right now... */
639                return res;
640       
641        tres = mktime( &utc );
642        res += res - tres;
643       
644        /* Yes, this is a hack. And it will go wrong around DST changes.
645           BUT this is more likely to be threadsafe than messing with
646           environment variables, and possibly more portable... */
647       
648        return res;
649}
650
651struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
652{
653        struct jabber_error *err = g_new0( struct jabber_error, 1 );
654        struct xt_node *c;
655        char *s;
656       
657        err->type = xt_find_attr( node, "type" );
658       
659        for( c = node->children; c; c = c->next )
660        {
661                if( !( s = xt_find_attr( c, "xmlns" ) ) ||
662                    strcmp( s, xmlns ) != 0 )
663                        continue;
664               
665                if( strcmp( c->name, "text" ) != 0 )
666                {
667                        err->code = c->name;
668                }
669                /* Only use the text if it doesn't have an xml:lang attribute,
670                   if it's empty or if it's set to something English. */
671                else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
672                         !*s || strncmp( s, "en", 2 ) == 0 )
673                {
674                        err->text = c->text;
675                }
676        }
677       
678        return err;
679}
680
681void jabber_error_free( struct jabber_error *err )
682{
683        g_free( err );
684}
Note: See TracBrowser for help on using the repository browser.