source: protocols/jabber/jabber.c @ 34afea7

Last change on this file since 34afea7 was 34afea7, checked in by dequis <dx@…>, at 2015-01-31T23:58:57Z

Use glib's GChecksum for md5/sha1

This changes behavior slightly:

  • md5_init()/sha1_init() allocate a GChecksum
  • md5_finish()/sha1_finish() close and free() it
  • md5_digest_keep() was added (no sha1 equivalent needed)

And yes, glib has this concept of "closing" the GChecksum, which means
it can't be used anymore after g_checksum_get_digest().

jabber_cache_add() actually seems to need to do that to generate some
random-ish values, so i kept that working by adding a md5_digest_keep()
function that copies the GChecksum before it gets closed

GChecksum was introduced in glib 2.16, so the configure script version
was bumped. We were already depending on glib 2.16 accidentally
(some post-3.2.2 code uses GHashTableIter)

  • Property mode set to 100644
File size: 19.1 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Main file                                                *
5*                                                                           *
6*  Copyright 2006-2013 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include <glib.h>
25#include <string.h>
26#include <unistd.h>
27#include <ctype.h>
28#include <stdio.h>
29
30#include "ssl_client.h"
31#include "xmltree.h"
32#include "bitlbee.h"
33#include "jabber.h"
34#include "oauth.h"
35#include "md5.h"
36
37GSList *jabber_connections;
38
39/* First enty is the default */
40static const int jabber_port_list[] = {
41        5222,
42        5223,
43        5220,
44        5221,
45        5224,
46        5225,
47        5226,
48        5227,
49        5228,
50        5229,
51        80,
52        443,
53        0
54};
55
56static void jabber_init( account_t *acc )
57{
58        set_t *s;
59        char str[16];
60       
61        s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc );
62       
63        s = set_add( &acc->set, "oauth", "false", set_eval_oauth, acc );
64
65        s = set_add( &acc->set, "display_name", NULL, NULL, acc );
66       
67        g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
68        s = set_add( &acc->set, "port", str, set_eval_int, acc );
69        s->flags |= ACC_SET_OFFLINE_ONLY;
70       
71        s = set_add( &acc->set, "priority", "0", set_eval_priority, acc );
72
73        s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc );
74       
75        s = set_add( &acc->set, "resource", "BitlBee", NULL, acc );
76        s->flags |= ACC_SET_OFFLINE_ONLY;
77       
78        s = set_add( &acc->set, "resource_select", "activity", NULL, acc );
79       
80        s = set_add( &acc->set, "sasl", "true", set_eval_bool, acc );
81        s->flags |= ACC_SET_OFFLINE_ONLY | SET_HIDDEN_DEFAULT;
82       
83        s = set_add( &acc->set, "server", NULL, set_eval_account, acc );
84        s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
85       
86        s = set_add( &acc->set, "ssl", "false", set_eval_bool, acc );
87        s->flags |= ACC_SET_OFFLINE_ONLY;
88       
89        s = set_add( &acc->set, "tls", "true", set_eval_tls, acc );
90        s->flags |= ACC_SET_OFFLINE_ONLY;
91       
92        s = set_add( &acc->set, "tls_verify", "true", set_eval_bool, acc );
93        s->flags |= ACC_SET_OFFLINE_ONLY;
94
95        s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc );
96       
97        s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
98        s->flags |= ACC_SET_OFFLINE_ONLY;
99       
100        acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE |
101                      ACC_FLAG_HANDLE_DOMAINS;
102}
103
104static void jabber_generate_id_hash( struct jabber_data *jd );
105
106static void jabber_login( account_t *acc )
107{
108        struct im_connection *ic = imcb_new( acc );
109        struct jabber_data *jd = g_new0( struct jabber_data, 1 );
110        char *s;
111       
112        /* For now this is needed in the _connected() handlers if using
113           GLib event handling, to make sure we're not handling events
114           on dead connections. */
115        jabber_connections = g_slist_prepend( jabber_connections, ic );
116       
117        jd->ic = ic;
118        ic->proto_data = jd;
119       
120        jabber_set_me( ic, acc->user );
121       
122        jd->fd = jd->r_inpa = jd->w_inpa = -1;
123       
124        if( jd->server == NULL )
125        {
126                imcb_error( ic, "Incomplete account name (format it like <username@jabberserver.name>)" );
127                imc_logout( ic, FALSE );
128                return;
129        }
130       
131        if( ( s = strchr( jd->server, '/' ) ) )
132        {
133                *s = 0;
134                set_setstr( &acc->set, "resource", s + 1 );
135               
136                /* Also remove the /resource from the original variable so we
137                   won't have to do this again every time. */
138                s = strchr( acc->user, '/' );
139                *s = 0;
140        }
141       
142        jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free );
143        jd->buddies = g_hash_table_new( g_str_hash, g_str_equal );
144       
145        if( set_getbool( &acc->set, "oauth" ) )
146        {
147                GSList *p_in = NULL;
148                const char *tok;
149               
150                jd->fd = jd->r_inpa = jd->w_inpa = -1;
151               
152                if( strstr( jd->server, ".facebook.com" ) )
153                        jd->oauth2_service = &oauth2_service_facebook;
154                else
155                        jd->oauth2_service = &oauth2_service_google;
156               
157                oauth_params_parse( &p_in, ic->acc->pass );
158               
159                /* First see if we have a refresh token, in which case any
160                   access token we *might* have has probably expired already
161                   anyway. */
162                if( ( tok = oauth_params_get( &p_in, "refresh_token" ) ) )
163                {
164                        sasl_oauth2_refresh( ic, tok );
165                }
166                /* If we don't have a refresh token, let's hope the access
167                   token is still usable. */
168                else if( ( tok = oauth_params_get( &p_in, "access_token" ) ) )
169                {
170                        jd->oauth2_access_token = g_strdup( tok );
171                        jabber_connect( ic );
172                }
173                /* If we don't have any, start the OAuth process now. Don't
174                   even open an XMPP connection yet. */
175                else
176                {
177                        sasl_oauth2_init( ic );
178                        ic->flags |= OPT_SLOW_LOGIN;
179                }
180               
181                oauth_params_free( &p_in );
182        }
183        else
184                jabber_connect( ic );
185}
186
187/* Separate this from jabber_login() so we can do OAuth first if necessary.
188   Putting this in io.c would probably be more correct. */
189void jabber_connect( struct im_connection *ic )
190{
191        account_t *acc = ic->acc;
192        struct jabber_data *jd = ic->proto_data;
193        int i;
194        char *connect_to;
195        struct ns_srv_reply **srvl = NULL, *srv = NULL;
196       
197        /* Figure out the hostname to connect to. */
198        if( acc->server && *acc->server )
199                connect_to = acc->server;
200        else if( ( srvl = srv_lookup( "xmpp-client", "tcp", jd->server ) ) ||
201                 ( srvl = srv_lookup( "jabber-client", "tcp", jd->server ) ) )
202        {
203                /* Find the lowest-priority one. These usually come
204                   back in random/shuffled order. Not looking at
205                   weights etc for now. */
206                srv = *srvl;
207                for( i = 1; srvl[i]; i ++ )
208                        if( srvl[i]->prio < srv->prio )
209                                srv = srvl[i];
210               
211                connect_to = srv->name;
212        }
213        else
214                connect_to = jd->server;
215       
216        imcb_log( ic, "Connecting" );
217       
218        for( i = 0; jabber_port_list[i] > 0; i ++ )
219                if( set_getint( &acc->set, "port" ) == jabber_port_list[i] )
220                        break;
221
222        if( jabber_port_list[i] == 0 )
223        {
224                imcb_log( ic, "Illegal port number" );
225                imc_logout( ic, FALSE );
226                return;
227        }
228       
229        /* For non-SSL connections we can try to use the port # from the SRV
230           reply, but let's not do that when using SSL, SSL usually runs on
231           non-standard ports... */
232        if( set_getbool( &acc->set, "ssl" ) )
233        {
234                jd->ssl = ssl_connect( connect_to, set_getint( &acc->set, "port" ), set_getbool( &acc->set, "tls_verify" ), jabber_connected_ssl, ic );
235                jd->fd = jd->ssl ? ssl_getfd( jd->ssl ) : -1;
236        }
237        else
238        {
239                jd->fd = proxy_connect( connect_to, srv ? srv->port : set_getint( &acc->set, "port" ), jabber_connected_plain, ic );
240        }
241        srv_free( srvl );
242       
243        if( jd->fd == -1 )
244        {
245                imcb_error( ic, "Could not connect to server" );
246                imc_logout( ic, TRUE );
247               
248                return;
249        }
250       
251        if( set_getbool( &acc->set, "xmlconsole" ) )
252        {
253                jd->flags |= JFLAG_XMLCONSOLE;
254                /* Shouldn't really do this at this stage already, maybe. But
255                   I think this shouldn't break anything. */
256                imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
257        }
258       
259        jabber_generate_id_hash( jd );
260}
261
262/* This generates an unfinished md5_state_t variable. Every time we generate
263   an ID, we finish the state by adding a sequence number and take the hash. */
264static void jabber_generate_id_hash( struct jabber_data *jd )
265{
266        md5_byte_t binbuf[4];
267        char *s;
268       
269        md5_init( &jd->cached_id_prefix );
270        md5_append( &jd->cached_id_prefix, (unsigned char *) jd->username, strlen( jd->username ) );
271        md5_append( &jd->cached_id_prefix, (unsigned char *) jd->server, strlen( jd->server ) );
272        s = set_getstr( &jd->ic->acc->set, "resource" );
273        md5_append( &jd->cached_id_prefix, (unsigned char *) s, strlen( s ) );
274        random_bytes( binbuf, 4 );
275        md5_append( &jd->cached_id_prefix, binbuf, 4 );
276}
277
278static void jabber_logout( struct im_connection *ic )
279{
280        struct jabber_data *jd = ic->proto_data;
281       
282        while( jd->filetransfers )
283                imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" );
284
285        while( jd->streamhosts )
286        {
287                jabber_streamhost_t *sh = jd->streamhosts->data;
288                jd->streamhosts = g_slist_remove( jd->streamhosts, sh );
289                g_free( sh->jid );
290                g_free( sh->host );
291                g_free( sh );
292        }
293
294        if( jd->fd >= 0 )
295                jabber_end_stream( ic );
296       
297        while( ic->groupchats )
298                jabber_chat_free( ic->groupchats->data );
299       
300        if( jd->r_inpa >= 0 )
301                b_event_remove( jd->r_inpa );
302        if( jd->w_inpa >= 0 )
303                b_event_remove( jd->w_inpa );
304       
305        if( jd->ssl )
306                ssl_disconnect( jd->ssl );
307        if( jd->fd >= 0 )
308                closesocket( jd->fd );
309       
310        if( jd->tx_len )
311                g_free( jd->txq );
312       
313        if( jd->node_cache )
314                g_hash_table_destroy( jd->node_cache );
315       
316        jabber_buddy_remove_all( ic );
317       
318        xt_free( jd->xt );
319
320        md5_free( &jd->cached_id_prefix );
321       
322        g_free( jd->oauth2_access_token );
323        g_free( jd->away_message );
324        g_free( jd->internal_jid );
325        g_free( jd->username );
326        g_free( jd->me );
327        g_free( jd );
328       
329        jabber_connections = g_slist_remove( jabber_connections, ic );
330}
331
332static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
333{
334        struct jabber_data *jd = ic->proto_data;
335        struct jabber_buddy *bud;
336        struct xt_node *node;
337        char *s;
338        int st;
339       
340        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
341                return jabber_write( ic, message, strlen( message ) );
342       
343        if( g_strcasecmp( who, JABBER_OAUTH_HANDLE ) == 0 &&
344            !( jd->flags & OPT_LOGGED_IN ) && jd->fd == -1 )
345        {
346                if( sasl_oauth2_get_refresh_token( ic, message ) )
347                {
348                        return 1;
349                }
350                else
351                {
352                        imcb_error( ic, "OAuth failure" );
353                        imc_logout( ic, TRUE );
354                        return 0;
355                }
356        }
357       
358        if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
359                bud = jabber_buddy_by_ext_jid( ic, who, 0 );
360        else
361                bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK );
362       
363        node = xt_new_node( "body", message, NULL );
364        node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
365       
366        if( bud && ( jd->flags & JFLAG_WANT_TYPING ) &&
367            ( ( bud->flags & JBFLAG_DOES_XEP85 ) ||
368             !( bud->flags & JBFLAG_PROBED_XEP85 ) ) )
369        {
370                struct xt_node *act;
371               
372                /* If the user likes typing notification and if we don't know
373                   (and didn't probe before) if this resource supports XEP85,
374                   include a probe in this packet now. Also, if we know this
375                   buddy does support XEP85, we have to send this <active/>
376                   tag to tell that the user stopped typing (well, that's what
377                   we guess when s/he pressed Enter...). */
378                act = xt_new_node( "active", NULL, NULL );
379                xt_add_attr( act, "xmlns", XMLNS_CHATSTATES );
380                xt_add_child( node, act );
381               
382                /* Just make sure we do this only once. */
383                bud->flags |= JBFLAG_PROBED_XEP85;
384        }
385       
386        st = jabber_write_packet( ic, node );
387        xt_free_node( node );
388       
389        return st;
390}
391
392static GList *jabber_away_states( struct im_connection *ic )
393{
394        static GList *l = NULL;
395        int i;
396       
397        if( l == NULL )
398                for( i = 0; jabber_away_state_list[i].full_name; i ++ )
399                        l = g_list_append( l, (void*) jabber_away_state_list[i].full_name );
400       
401        return l;
402}
403
404static void jabber_get_info( struct im_connection *ic, char *who )
405{
406        struct jabber_buddy *bud;
407       
408        bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST );
409       
410        while( bud )
411        {
412                imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority );
413                if( bud->away_state )
414                        imcb_log( ic, "Away state: %s", bud->away_state->full_name );
415                imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" );
416               
417                bud = bud->next;
418        }
419       
420        jabber_get_vcard( ic, bud ? bud->full_jid : who );
421}
422
423static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message )
424{
425        struct jabber_data *jd = ic->proto_data;
426       
427        /* state_txt == NULL -> Not away.
428           Unknown state -> fall back to the first defined away state. */
429        if( state_txt == NULL )
430                jd->away_state = NULL;
431        else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL )
432                jd->away_state = jabber_away_state_list;
433       
434        g_free( jd->away_message );
435        jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL;
436       
437        presence_send_update( ic );
438}
439
440static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
441{
442        struct jabber_data *jd = ic->proto_data;
443       
444        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
445        {
446                jd->flags |= JFLAG_XMLCONSOLE;
447                imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
448                return;
449        }
450       
451        if( jabber_add_to_roster( ic, who, NULL, group ) )
452                presence_send_request( ic, who, "subscribe" );
453}
454
455static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
456{
457        struct jabber_data *jd = ic->proto_data;
458       
459        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
460        {
461                jd->flags &= ~JFLAG_XMLCONSOLE;
462                /* Not necessary for now. And for now the code isn't too
463                   happy if the buddy is completely gone right after calling
464                   this function already.
465                imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
466                */
467                return;
468        }
469       
470        /* We should always do this part. Clean up our administration a little bit. */
471        jabber_buddy_remove_bare( ic, who );
472       
473        if( jabber_remove_from_roster( ic, who ) )
474                presence_send_request( ic, who, "unsubscribe" );
475}
476
477static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets )
478{
479        struct jabber_data *jd = ic->proto_data;
480        char *final_nick;
481       
482        /* Ignore the passed nick parameter if we have our own default */
483        if ( !( final_nick = set_getstr( sets, "nick" ) ) &&
484             !( final_nick = set_getstr( &ic->acc->set, "display_name" ) ) ) {
485                /* Well, whatever, actually use the provided default, then */
486                final_nick = (char *) nick;
487        }
488
489        if( strchr( room, '@' ) == NULL )
490                imcb_error( ic, "%s is not a valid Jabber room name. Maybe you mean %s@conference.%s?",
491                            room, room, jd->server );
492        else if( jabber_chat_by_jid( ic, room ) )
493                imcb_error( ic, "Already present in chat `%s'", room );
494        else
495                return jabber_chat_join( ic, room, final_nick, set_getstr( sets, "password" ) );
496       
497        return NULL;
498}
499
500static struct groupchat *jabber_chat_with_( struct im_connection *ic, char *who )
501{
502        return jabber_chat_with( ic, who );
503}
504
505static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
506{
507        if( c && message )
508                jabber_chat_msg( c, message, flags );
509}
510
511static void jabber_chat_topic_( struct groupchat *c, char *topic )
512{
513        if( c && topic )
514                jabber_chat_topic( c, topic );
515}
516
517static void jabber_chat_leave_( struct groupchat *c )
518{
519        if( c )
520                jabber_chat_leave( c, NULL );
521}
522
523static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg )
524{
525        struct jabber_data *jd = c->ic->proto_data;
526        struct jabber_chat *jc = c->data;
527        gchar *msg_alt = NULL;
528
529        if( msg == NULL )
530                msg_alt = g_strdup_printf( "%s invited you to %s", jd->me, jc->name );
531       
532        if( c && who )
533                jabber_chat_invite( c, who, msg ? msg : msg_alt );
534       
535        g_free( msg_alt );
536}
537
538static void jabber_keepalive( struct im_connection *ic )
539{
540        /* Just any whitespace character is enough as a keepalive for XMPP sessions. */
541        if( !jabber_write( ic, "\n", 1 ) )
542                return;
543       
544        /* This runs the garbage collection every minute, which means every packet
545           is in the cache for about a minute (which should be enough AFAIK). */
546        jabber_cache_clean( ic );
547}
548
549static int jabber_send_typing( struct im_connection *ic, char *who, int typing )
550{
551        struct jabber_data *jd = ic->proto_data;
552        struct jabber_buddy *bud;
553       
554        /* Enable typing notification related code from now. */
555        jd->flags |= JFLAG_WANT_TYPING;
556       
557        if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL )
558        {
559                /* Sending typing notifications to unknown buddies is
560                   unsupported for now. Shouldn't be a problem, I think. */
561                return 0;
562        }
563       
564        if( bud->flags & JBFLAG_DOES_XEP85 )
565        {
566                /* We're only allowed to send this stuff if we know the other
567                   side supports it. */
568               
569                struct xt_node *node;
570                char *type;
571                int st;
572               
573                if( typing & OPT_TYPING )
574                        type = "composing";
575                else if( typing & OPT_THINKING )
576                        type = "paused";
577                else
578                        type = "active";
579               
580                node = xt_new_node( type, NULL, NULL );
581                xt_add_attr( node, "xmlns", XMLNS_CHATSTATES );
582                node = jabber_make_packet( "message", "chat", bud->full_jid, node );
583               
584                st = jabber_write_packet( ic, node );
585                xt_free_node( node );
586               
587                return st;
588        }
589       
590        return 1;
591}
592
593void jabber_chat_add_settings( account_t *acc, set_t **head )
594{
595        /* Meh. Stupid room passwords. Not trying to obfuscate/hide
596           them from the user for now. */
597        set_add( head, "password", NULL, NULL, NULL );
598}
599
600void jabber_chat_free_settings( account_t *acc, set_t **head )
601{
602        set_del( head, "password" );
603}
604
605GList *jabber_buddy_action_list( bee_user_t *bu )
606{
607        static GList *ret = NULL;
608       
609        if( ret == NULL )
610        {
611                static const struct buddy_action ba[2] = {
612                        { "VERSION", "Get client (version) information" },
613                };
614               
615                ret = g_list_prepend( ret, (void*) ba + 0 );
616        }
617       
618        return ret;
619}
620
621void *jabber_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data )
622{
623        if( g_strcasecmp( action, "VERSION" ) == 0 )
624        {
625                struct jabber_buddy *bud;
626               
627                if( ( bud = jabber_buddy_by_ext_jid( bu->ic, bu->handle, 0 ) ) == NULL )
628                        bud = jabber_buddy_by_jid( bu->ic, bu->handle, GET_BUDDY_FIRST );
629                for( ; bud; bud = bud->next )
630                        jabber_iq_version_send( bu->ic, bud, data );
631        }
632       
633        return NULL;
634}
635
636gboolean jabber_handle_is_self( struct im_connection *ic, const char *who ) {
637        struct jabber_data *jd = ic->proto_data;
638        return ( ( g_strcasecmp( who, ic->acc->user ) == 0 ) ||
639                 ( jd->internal_jid &&
640                   g_strcasecmp( who, jd->internal_jid ) == 0 ) );
641}
642
643void jabber_initmodule()
644{
645        struct prpl *ret = g_new0( struct prpl, 1 );
646       
647        ret->name = "jabber";
648        ret->mms = 0;                        /* no limit */
649        ret->login = jabber_login;
650        ret->init = jabber_init;
651        ret->logout = jabber_logout;
652        ret->buddy_msg = jabber_buddy_msg;
653        ret->away_states = jabber_away_states;
654        ret->set_away = jabber_set_away;
655//      ret->set_info = jabber_set_info;
656        ret->get_info = jabber_get_info;
657        ret->add_buddy = jabber_add_buddy;
658        ret->remove_buddy = jabber_remove_buddy;
659        ret->chat_msg = jabber_chat_msg_;
660        ret->chat_topic = jabber_chat_topic_;
661        ret->chat_invite = jabber_chat_invite_;
662        ret->chat_leave = jabber_chat_leave_;
663        ret->chat_join = jabber_chat_join_;
664        ret->chat_with = jabber_chat_with_;
665        ret->chat_add_settings = jabber_chat_add_settings;
666        ret->chat_free_settings = jabber_chat_free_settings;
667        ret->keepalive = jabber_keepalive;
668        ret->send_typing = jabber_send_typing;
669        ret->handle_cmp = g_strcasecmp;
670        ret->handle_is_self = jabber_handle_is_self;
671        ret->transfer_request = jabber_si_transfer_request;
672        ret->buddy_action_list = jabber_buddy_action_list;
673        ret->buddy_action = jabber_buddy_action;
674
675        register_protocol( ret );
676}
Note: See TracBrowser for help on using the repository browser.