source: protocols/jabber/jabber.c @ b09ce17

Last change on this file since b09ce17 was 71074ac, checked in by dequis <dx@…>, at 2015-04-21T03:47:57Z

jabber: Register "hipchat" protocol (only minimal support for now)

Another take on the subprotocols idea that, IMO, was a failure.

Unlike the other implementation, this one doesn't touch gtalk/facebook
accounts, it just adds another copy of the "jabber" prpl called "hipchat".

And, based on the protocol name:

  • sets JFLAG_HIPCHAT to jabber_data
  • sets the default value of the "server" setting
  • only includes the oauth setting for jabber-type accounts

This is slightly more "hardcoded" but honestly facebook and gtalk are
just as hardcoded as this.

Copying the prpl is needed because the meaning of the usernames is
completely different (there's no srv lookup stuff either)

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