source: protocols/jabber/jabber.c @ 71f87ba

Last change on this file since 71f87ba was 9dc67f4, checked in by dequis <dx@…>, at 2015-04-05T21:44:15Z

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