source: protocols/jabber/jabber.c @ a33ee0f

Last change on this file since a33ee0f was 67ea361, checked in by dequis <dx@…>, at 2016-08-30T20:40:19Z

hipchat: Add basic support for personal oauth tokens

Fixes trac ticket 1265 - see the comments on that ticket for reasons on
why personal tokens and not the usual oauth flow. TL;DR hipchat doesn't
allow third party apps to own oauth client secrets. Instead, those are
generated when the "addon" is "installed" which requires a flow that is
either impossible or too awkward to use in bitlbee.

So, after giving up on the right way and telling the users to manage
tokens the ugly way, what's left to do is easy, just a few tweaks in the
sasl blob and short-circuit most of the actual oauth stuff. I didn't
even bother changing the service struct from google. It's not used.

This also updates the gtalk SASL X-OAUTH2 code to use GStrings instead of
juggling with malloc/strlen/strcpy, and simplifies a bit the way
GStrings are used in the equivalent SASL PLAIN code.

  • Property mode set to 100644
File size: 22.1 KB
RevLine 
[f06894d]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Main file                                                *
5*                                                                           *
[0e788f5]6*  Copyright 2006-2013 Wilmer van der Gaast <wilmer@gaast.net>              *
[f06894d]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
[21167d2]30#include "ssl_client.h"
[f06894d]31#include "xmltree.h"
32#include "bitlbee.h"
33#include "jabber.h"
[e14b47b8]34#include "oauth.h"
[608f8cf]35#include "md5.h"
[f06894d]36
[b5c8a34]37GSList *jabber_connections;
38
[7f69740]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
[5ebff60]56static void jabber_init(account_t *acc)
[f06894d]57{
58        set_t *s;
[7f69740]59        char str[16];
[5ebff60]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);
[f06894d]67        s->flags |= ACC_SET_OFFLINE_ONLY;
[1c3008a]68
[5ebff60]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);
[ebe7b36]74        s->flags |= ACC_SET_OFFLINE_ONLY;
[5ebff60]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);
[ce199b7]79        s->flags |= ACC_SET_OFFLINE_ONLY | SET_HIDDEN_DEFAULT;
[5ebff60]80
81        s = set_add(&acc->set, "server", NULL, set_eval_account, acc);
[bb5ce568]82        s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
[5ebff60]83
[67ea361]84        set_add(&acc->set, "oauth", "false", set_eval_oauth, acc);
85
[71074ac]86        if (strcmp(acc->prpl->name, "hipchat") == 0) {
87                set_setstr(&acc->set, "server", "chat.hipchat.com");
88        } else {
[73dd021]89                /* this reuses set_eval_oauth, which clears the password */
90                set_add(&acc->set, "anonymous", "false", set_eval_oauth, acc);
[71074ac]91        }
92
[5ebff60]93        s = set_add(&acc->set, "ssl", "false", set_eval_bool, acc);
[f06894d]94        s->flags |= ACC_SET_OFFLINE_ONLY;
[5ebff60]95
96        s = set_add(&acc->set, "tls", "true", set_eval_tls, acc);
[f06894d]97        s->flags |= ACC_SET_OFFLINE_ONLY;
[5ebff60]98
99        s = set_add(&acc->set, "tls_verify", "true", set_eval_bool, acc);
[486ddb5]100        s->flags |= ACC_SET_OFFLINE_ONLY;
[34ded90]101
[5ebff60]102        s = set_add(&acc->set, "user_agent", "BitlBee", NULL, acc);
103
104        s = set_add(&acc->set, "xmlconsole", "false", set_eval_bool, acc);
[dd43c62]105
[b38f655]106        s = set_add(&acc->set, "mail_notifications", "false", set_eval_bool, acc);
[a6df0b5]107        s->flags |= ACC_SET_OFFLINE_ONLY;
[5ebff60]108
[faeb521]109        /* changing this is rarely needed so keeping it secret */
[b38f655]110        s = set_add(&acc->set, "mail_notifications_limit", "5", set_eval_int, acc);
[faeb521]111        s->flags |= SET_HIDDEN_DEFAULT;
112
[b38f655]113        s = set_add(&acc->set, "mail_notifications_handle", NULL, NULL, acc);
[dd43c62]114        s->flags |= ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
115
[fa8f57b]116        s = set_add(&acc->set, "carbons", "true", set_eval_bool, acc);
117        s->flags |= ACC_SET_OFFLINE_ONLY;
118
[06eef80]119        acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE |
120                      ACC_FLAG_HANDLE_DOMAINS;
[f06894d]121}
122
[5ebff60]123static void jabber_generate_id_hash(struct jabber_data *jd);
[608f8cf]124
[5ebff60]125static void jabber_login(account_t *acc)
[f06894d]126{
[5ebff60]127        struct im_connection *ic = imcb_new(acc);
128        struct jabber_data *jd = g_new0(struct jabber_data, 1);
[4a5d885]129        char *s;
[5ebff60]130
[b5c8a34]131        /* For now this is needed in the _connected() handlers if using
132           GLib event handling, to make sure we're not handling events
133           on dead connections. */
[5ebff60]134        jabber_connections = g_slist_prepend(jabber_connections, ic);
135
[0da65d5]136        jd->ic = ic;
137        ic->proto_data = jd;
[5ebff60]138
139        jabber_set_me(ic, acc->user);
140
[de03374]141        jd->fd = jd->r_inpa = jd->w_inpa = -1;
[5ebff60]142
[71074ac]143        if (strcmp(acc->prpl->name, "hipchat") == 0) {
144                jd->flags |= JFLAG_HIPCHAT;
145        }
146
[5ebff60]147        if (jd->server == NULL) {
148                imcb_error(ic, "Incomplete account name (format it like <username@jabberserver.name>)");
149                imc_logout(ic, FALSE);
[21167d2]150                return;
151        }
[5ebff60]152
[9b02bab]153        if (strstr(jd->server, ".facebook.com")) {
154                imcb_error(ic, "Facebook's XMPP service is gone. Try this instead: https://wiki.bitlbee.org/HowtoFacebookMQTT");
155                imc_logout(ic, FALSE);
156                return;
157        }
158
[5ebff60]159        if ((s = strchr(jd->server, '/'))) {
[3b3cd693]160                *s = 0;
[5ebff60]161                set_setstr(&acc->set, "resource", s + 1);
162
[3b3cd693]163                /* Also remove the /resource from the original variable so we
164                   won't have to do this again every time. */
[5ebff60]165                s = strchr(acc->user, '/');
[3b3cd693]166                *s = 0;
167        }
[5ebff60]168
169        jd->node_cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, jabber_cache_entry_free);
170        jd->buddies = g_hash_table_new(g_str_hash, g_str_equal);
171
172        if (set_getbool(&acc->set, "oauth")) {
[e14b47b8]173                GSList *p_in = NULL;
174                const char *tok;
[5ebff60]175
[f138bd2]176                jd->fd = jd->r_inpa = jd->w_inpa = -1;
[5ebff60]177
[9b02bab]178                /* There are no other options atm, so assume google for everything
179                   Facebook and MSN XMPP used to be here. RIP. */
180                jd->oauth2_service = &oauth2_service_google;
[5ebff60]181
182                oauth_params_parse(&p_in, ic->acc->pass);
183
[64b6635]184                /* First see if we have a refresh token, in which case any
185                   access token we *might* have has probably expired already
186                   anyway. */
[5ebff60]187                if ((tok = oauth_params_get(&p_in, "refresh_token"))) {
188                        sasl_oauth2_refresh(ic, tok);
[64b6635]189                }
190                /* If we don't have a refresh token, let's hope the access
191                   token is still usable. */
[5ebff60]192                else if ((tok = oauth_params_get(&p_in, "access_token"))) {
193                        jd->oauth2_access_token = g_strdup(tok);
194                        jabber_connect(ic);
[64b6635]195                }
196                /* If we don't have any, start the OAuth process now. Don't
197                   even open an XMPP connection yet. */
[5ebff60]198                else {
199                        sasl_oauth2_init(ic);
[f988ad3]200                        ic->flags |= OPT_SLOW_LOGIN;
201                }
[5ebff60]202
203                oauth_params_free(&p_in);
204        } else {
205                jabber_connect(ic);
[4a5d885]206        }
207}
208
[3314ced]209static void jabber_xmlconsole_enable(struct im_connection *ic)
210{
211        struct jabber_data *jd = ic->proto_data;
212        const char *handle = JABBER_XMLCONSOLE_HANDLE;
213        bee_user_t *bu;
214       
215        jd->flags |= JFLAG_XMLCONSOLE;
216
217        if (!(bu = bee_user_by_handle(ic->bee, ic, handle))) {
218                bu = bee_user_new(ic->bee, ic, handle, 0);
219                bu->flags |= BEE_USER_NOOTR;
220        }
221}
222
[4a5d885]223/* Separate this from jabber_login() so we can do OAuth first if necessary.
224   Putting this in io.c would probably be more correct. */
[5ebff60]225void jabber_connect(struct im_connection *ic)
[4a5d885]226{
227        account_t *acc = ic->acc;
228        struct jabber_data *jd = ic->proto_data;
229        int i;
230        char *connect_to;
231        struct ns_srv_reply **srvl = NULL, *srv = NULL;
[5ebff60]232
[36e9f62]233        /* Figure out the hostname to connect to. */
[5ebff60]234        if (acc->server && *acc->server) {
[36e9f62]235                connect_to = acc->server;
[5ebff60]236        } else if ((srvl = srv_lookup("xmpp-client", "tcp", jd->server)) ||
237                   (srvl = srv_lookup("jabber-client", "tcp", jd->server))) {
[ffdf2e7]238                /* Find the lowest-priority one. These usually come
239                   back in random/shuffled order. Not looking at
240                   weights etc for now. */
241                srv = *srvl;
[5ebff60]242                for (i = 1; srvl[i]; i++) {
243                        if (srvl[i]->prio < srv->prio) {
[ffdf2e7]244                                srv = srvl[i];
[5ebff60]245                        }
246                }
247
[36e9f62]248                connect_to = srv->name;
[5ebff60]249        } else {
[36e9f62]250                connect_to = jd->server;
[5ebff60]251        }
252
253        imcb_log(ic, "Connecting");
254
255        for (i = 0; jabber_port_list[i] > 0; i++) {
256                if (set_getint(&acc->set, "port") == jabber_port_list[i]) {
[7f69740]257                        break;
[5ebff60]258                }
259        }
[7f69740]260
[5ebff60]261        if (jabber_port_list[i] == 0) {
262                imcb_log(ic, "Illegal port number");
263                imc_logout(ic, FALSE);
[0f4c1bb5]264                return;
265        }
[5ebff60]266
[36e9f62]267        /* For non-SSL connections we can try to use the port # from the SRV
268           reply, but let's not do that when using SSL, SSL usually runs on
269           non-standard ports... */
[5ebff60]270        if (set_getbool(&acc->set, "ssl")) {
271                jd->ssl = ssl_connect(connect_to, set_getint(&acc->set, "port"), set_getbool(&acc->set,
272                                                                                             "tls_verify"), jabber_connected_ssl,
273                                      ic);
274                jd->fd = jd->ssl ? ssl_getfd(jd->ssl) : -1;
275        } else {
276                jd->fd = proxy_connect(connect_to, srv ? srv->port : set_getint(&acc->set,
277                                                                                "port"), jabber_connected_plain, ic);
278        }
279        srv_free(srvl);
280
281        if (jd->fd == -1) {
282                imcb_error(ic, "Could not connect to server");
283                imc_logout(ic, TRUE);
284
[fb4ebcc5]285                return;
[35f6677]286        }
[5ebff60]287
288        if (set_getbool(&acc->set, "xmlconsole")) {
[3314ced]289                jabber_xmlconsole_enable(ic);
[a6df0b5]290        }
[b38f655]291
292        if (set_getbool(&acc->set, "mail_notifications")) {
293                /* It's gmail specific, but it checks for server support before enabling it */
[dd43c62]294                jd->flags |= JFLAG_GMAILNOTIFY;
[b38f655]295                if (set_getstr(&acc->set, "mail_notifications_handle")) {
296                        imcb_add_buddy(ic, set_getstr(&acc->set, "mail_notifications_handle"), NULL);
[dd43c62]297                }
298        }
[5ebff60]299
300        jabber_generate_id_hash(jd);
[608f8cf]301}
302
[89d736a]303/* This generates an unfinished md5_state_t variable. Every time we generate
304   an ID, we finish the state by adding a sequence number and take the hash. */
[5ebff60]305static void jabber_generate_id_hash(struct jabber_data *jd)
[608f8cf]306{
[89d736a]307        md5_byte_t binbuf[4];
[608f8cf]308        char *s;
[5ebff60]309
310        md5_init(&jd->cached_id_prefix);
311        md5_append(&jd->cached_id_prefix, (unsigned char *) jd->username, strlen(jd->username));
312        md5_append(&jd->cached_id_prefix, (unsigned char *) jd->server, strlen(jd->server));
313        s = set_getstr(&jd->ic->acc->set, "resource");
314        md5_append(&jd->cached_id_prefix, (unsigned char *) s, strlen(s));
315        random_bytes(binbuf, 4);
316        md5_append(&jd->cached_id_prefix, binbuf, 4);
[f06894d]317}
318
[5ebff60]319static void jabber_logout(struct im_connection *ic)
[f06894d]320{
[0da65d5]321        struct jabber_data *jd = ic->proto_data;
[4ac647d]322
[5ebff60]323        while (jd->filetransfers) {
324                imcb_file_canceled(ic, (( struct jabber_transfer *) jd->filetransfers->data)->ft, "Logging out");
325        }
326
327        while (jd->streamhosts) {
[4ac647d]328                jabber_streamhost_t *sh = jd->streamhosts->data;
[5ebff60]329                jd->streamhosts = g_slist_remove(jd->streamhosts, sh);
330                g_free(sh->jid);
331                g_free(sh->host);
332                g_free(sh);
333        }
334
335        if (jd->fd >= 0) {
336                jabber_end_stream(ic);
337        }
338
339        while (ic->groupchats) {
340                jabber_chat_free(ic->groupchats->data);
341        }
342
343        if (jd->r_inpa >= 0) {
344                b_event_remove(jd->r_inpa);
345        }
346        if (jd->w_inpa >= 0) {
347                b_event_remove(jd->w_inpa);
348        }
349
350        if (jd->ssl) {
351                ssl_disconnect(jd->ssl);
352        }
353        if (jd->fd >= 0) {
[0db6618]354                proxy_disconnect(jd->fd);
[5ebff60]355        }
356
357        if (jd->tx_len) {
358                g_free(jd->txq);
359        }
360
361        if (jd->node_cache) {
362                g_hash_table_destroy(jd->node_cache);
363        }
364
[666722e]365        if (jd->buddies) {
366                jabber_buddy_remove_all(ic);
367        }
[5ebff60]368
369        xt_free(jd->xt);
370
371        md5_free(&jd->cached_id_prefix);
372
373        g_free(jd->oauth2_access_token);
374        g_free(jd->away_message);
375        g_free(jd->internal_jid);
[dd43c62]376        g_free(jd->gmail_tid);
[9c8dbc7]377        g_free(jd->muc_host);
[5ebff60]378        g_free(jd->username);
379        g_free(jd->me);
380        g_free(jd);
381
382        jabber_connections = g_slist_remove(jabber_connections, ic);
[f06894d]383}
384
[5ebff60]385static int jabber_buddy_msg(struct im_connection *ic, char *who, char *message, int flags)
[f06894d]386{
[0da65d5]387        struct jabber_data *jd = ic->proto_data;
[a21a8ac]388        struct jabber_buddy *bud;
[cc2cb2d]389        struct xt_node *node;
[b9f8b87]390        char *s;
[4a0614e]391        int st;
[5ebff60]392
393        if (g_strcasecmp(who, JABBER_XMLCONSOLE_HANDLE) == 0) {
394                return jabber_write(ic, message, strlen(message));
395        }
396
397        if (g_strcasecmp(who, JABBER_OAUTH_HANDLE) == 0 &&
398            !(jd->flags & OPT_LOGGED_IN) && jd->fd == -1) {
[67ea361]399
400                if (jd->flags & JFLAG_HIPCHAT) {
401                        sasl_oauth2_got_token(ic, message, NULL, NULL);
402                        return 1;
403                } else if (sasl_oauth2_get_refresh_token(ic, message)) {
[4a5d885]404                        return 1;
[5ebff60]405                } else {
406                        imcb_error(ic, "OAuth failure");
407                        imc_logout(ic, TRUE);
[911d97a]408                        return 0;
[4a5d885]409                }
410        }
[5ebff60]411
412        if ((s = strchr(who, '=')) && jabber_chat_by_jid(ic, s + 1)) {
413                bud = jabber_buddy_by_ext_jid(ic, who, 0);
414        } else {
415                bud = jabber_buddy_by_jid(ic, who, GET_BUDDY_BARE_OK);
416        }
417
418        node = xt_new_node("body", message, NULL);
419        node = jabber_make_packet("message", "chat", bud ? bud->full_jid : who, node);
420
421        if (bud && (jd->flags & JFLAG_WANT_TYPING) &&
422            ((bud->flags & JBFLAG_DOES_XEP85) ||
423             !(bud->flags & JBFLAG_PROBED_XEP85))) {
[a21a8ac]424                struct xt_node *act;
[5ebff60]425
[a21a8ac]426                /* If the user likes typing notification and if we don't know
[788a1af]427                   (and didn't probe before) if this resource supports XEP85,
[abbd8ed]428                   include a probe in this packet now. Also, if we know this
429                   buddy does support XEP85, we have to send this <active/>
430                   tag to tell that the user stopped typing (well, that's what
431                   we guess when s/he pressed Enter...). */
[5ebff60]432                act = xt_new_node("active", NULL, NULL);
433                xt_add_attr(act, "xmlns", XMLNS_CHATSTATES);
434                xt_add_child(node, act);
435
[a21a8ac]436                /* Just make sure we do this only once. */
[788a1af]437                bud->flags |= JBFLAG_PROBED_XEP85;
[a21a8ac]438        }
[5ebff60]439
[3a620ff]440        /* XEP-0364 suggests we add message processing hints (XEP-0334) to OTR messages,
441           mostly to avoid carbons (XEP-0280) and server-side message archiving.
442           OTR messages are roughly like this: /^\?OTR(.*\?| Error:|:)/
443           But I'm going to simplify it to messages starting with "?OTR". */
444        if (g_str_has_prefix(message, "?OTR")) {
445                int i;
446                char *hints[] = {
447                        "no-copy", XMLNS_HINTS,
448                        "no-permanent-store", XMLNS_HINTS,
449                        "private", XMLNS_CARBONS,
450                        NULL
451                };
452                       
453                for (i = 0; hints[i]; i += 2) {
454                        struct xt_node *hint;
455                        hint = xt_new_node(hints[i], NULL, NULL);
456                        xt_add_attr(hint, "xmlns", hints[i + 1]);
457                        xt_add_child(node, hint);
458                }
459        }
460
[5ebff60]461        st = jabber_write_packet(ic, node);
462        xt_free_node(node);
463
[4a0614e]464        return st;
[f06894d]465}
466
[5ebff60]467static GList *jabber_away_states(struct im_connection *ic)
[dd788bb]468{
[5e202b0]469        static GList *l = NULL;
470        int i;
[5ebff60]471
472        if (l == NULL) {
473                for (i = 0; jabber_away_state_list[i].full_name; i++) {
474                        l = g_list_append(l, (void *) jabber_away_state_list[i].full_name);
475                }
476        }
477
[5e202b0]478        return l;
[dd788bb]479}
480
[5ebff60]481static void jabber_get_info(struct im_connection *ic, char *who)
[038d17f]482{
[6a1128d]483        struct jabber_buddy *bud;
[5ebff60]484
485        bud = jabber_buddy_by_jid(ic, who, GET_BUDDY_FIRST);
486
487        while (bud) {
488                imcb_log(ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority);
489                if (bud->away_state) {
490                        imcb_log(ic, "Away state: %s", bud->away_state->full_name);
491                }
492                imcb_log(ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)");
493
[6a1128d]494                bud = bud->next;
495        }
[5ebff60]496
[5535a47]497        jabber_get_vcard(ic, who);
[038d17f]498}
499
[5ebff60]500static void jabber_set_away(struct im_connection *ic, char *state_txt, char *message)
[dd788bb]501{
[0da65d5]502        struct jabber_data *jd = ic->proto_data;
[5ebff60]503
[840bba8]504        /* state_txt == NULL -> Not away.
505           Unknown state -> fall back to the first defined away state. */
[5ebff60]506        if (state_txt == NULL) {
[daae10f]507                jd->away_state = NULL;
[5ebff60]508        } else if ((jd->away_state = jabber_away_state_by_name(state_txt)) == NULL) {
[daae10f]509                jd->away_state = jabber_away_state_list;
[5ebff60]510        }
511
512        g_free(jd->away_message);
513        jd->away_message = (message && *message) ? g_strdup(message) : NULL;
514
515        presence_send_update(ic);
[deff040]516}
517
[5ebff60]518static void jabber_add_buddy(struct im_connection *ic, char *who, char *group)
[cfbb3a6]519{
[5ebff60]520        if (g_strcasecmp(who, JABBER_XMLCONSOLE_HANDLE) == 0) {
[3314ced]521                jabber_xmlconsole_enable(ic);
[bb95d43]522                return;
523        }
[5ebff60]524
525        if (jabber_add_to_roster(ic, who, NULL, group)) {
526                presence_send_request(ic, who, "subscribe");
527        }
[cfbb3a6]528}
529
[5ebff60]530static void jabber_remove_buddy(struct im_connection *ic, char *who, char *group)
[cfbb3a6]531{
[bb95d43]532        struct jabber_data *jd = ic->proto_data;
[5ebff60]533
534        if (g_strcasecmp(who, JABBER_XMLCONSOLE_HANDLE) == 0) {
[bb95d43]535                jd->flags &= ~JFLAG_XMLCONSOLE;
[a3d5766]536                /* Not necessary for now. And for now the code isn't too
537                   happy if the buddy is completely gone right after calling
538                   this function already.
[998b103]539                imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
[a3d5766]540                */
[bb95d43]541                return;
542        }
[5ebff60]543
[788a1af]544        /* We should always do this part. Clean up our administration a little bit. */
[5ebff60]545        jabber_buddy_remove_bare(ic, who);
546
547        if (jabber_remove_from_roster(ic, who)) {
548                presence_send_request(ic, who, "unsubscribe");
549        }
[cfbb3a6]550}
551
[5ebff60]552static struct groupchat *jabber_chat_join_(struct im_connection *ic, const char *room, const char *nick,
553                                           const char *password, set_t **sets)
[e35d1a1]554{
[441a67e]555        struct jabber_data *jd = ic->proto_data;
[fcb2c2e]556        char *final_nick;
[5ebff60]557
[fcb2c2e]558        /* Ignore the passed nick parameter if we have our own default */
[5ebff60]559        if (!(final_nick = set_getstr(sets, "nick")) &&
560            !(final_nick = set_getstr(&ic->acc->set, "display_name"))) {
[fcb2c2e]561                /* Well, whatever, actually use the provided default, then */
562                final_nick = (char *) nick;
563        }
564
[9c8dbc7]565        if (jd->flags & JFLAG_HIPCHAT && jd->muc_host && !g_str_has_suffix(room, jd->muc_host)) {
566                char *guessed_name = hipchat_guess_channel_name(ic, room);
567                if (guessed_name) {
568                        set_setstr(sets, "room", guessed_name);
569                        g_free(guessed_name);
570
571                        /* call this same function again with the fixed name */
572                        return jabber_chat_join_(ic, set_getstr(sets, "room"), nick, password, sets);
573                }
574        }
575
[5ebff60]576        if (strchr(room, '@') == NULL) {
577                imcb_error(ic, "%s is not a valid Jabber room name. Maybe you mean %s@conference.%s?",
578                           room, room, jd->server);
579        } else if (jabber_chat_by_jid(ic, room)) {
580                imcb_error(ic, "Already present in chat `%s'", room);
581        } else {
[9c8dbc7]582                /* jabber_chat_join without the underscore is the conference.c one */
[3320d6d]583                return jabber_chat_join(ic, room, final_nick, set_getstr(sets, "password"),
584                                        set_getbool(sets, "always_use_nicks"));
[5ebff60]585        }
586
[e35d1a1]587        return NULL;
588}
589
[5ebff60]590static struct groupchat *jabber_chat_with_(struct im_connection *ic, char *who)
[fc0640e]591{
[5ebff60]592        return jabber_chat_with(ic, who);
[fc0640e]593}
594
[5ebff60]595static void jabber_chat_msg_(struct groupchat *c, char *message, int flags)
[43671b9]596{
[5ebff60]597        if (c && message) {
598                jabber_chat_msg(c, message, flags);
599        }
[43671b9]600}
601
[5ebff60]602static void jabber_chat_topic_(struct groupchat *c, char *topic)
[ef5c185]603{
[5ebff60]604        if (c && topic) {
605                jabber_chat_topic(c, topic);
606        }
[ef5c185]607}
608
[5ebff60]609static void jabber_chat_leave_(struct groupchat *c)
[e35d1a1]610{
[5ebff60]611        if (c) {
612                jabber_chat_leave(c, NULL);
613        }
[e35d1a1]614}
615
[5ebff60]616static void jabber_chat_invite_(struct groupchat *c, char *who, char *msg)
[c058ff9]617{
[68286eb]618        struct jabber_data *jd = c->ic->proto_data;
[c058ff9]619        struct jabber_chat *jc = c->data;
620        gchar *msg_alt = NULL;
621
[5ebff60]622        if (msg == NULL) {
623                msg_alt = g_strdup_printf("%s invited you to %s", jd->me, jc->name);
624        }
625
626        if (c && who) {
627                jabber_chat_invite(c, who, msg ? msg : msg_alt);
628        }
629
630        g_free(msg_alt);
[c058ff9]631}
632
[5ebff60]633static void jabber_keepalive(struct im_connection *ic)
[deff040]634{
635        /* Just any whitespace character is enough as a keepalive for XMPP sessions. */
[5ebff60]636        if (!jabber_write(ic, "\n", 1)) {
[38ff846]637                return;
[5ebff60]638        }
639
[038d17f]640        /* This runs the garbage collection every minute, which means every packet
641           is in the cache for about a minute (which should be enough AFAIK). */
[5ebff60]642        jabber_cache_clean(ic);
[dd788bb]643}
644
[5ebff60]645static int jabber_send_typing(struct im_connection *ic, char *who, int typing)
[a21a8ac]646{
[0da65d5]647        struct jabber_data *jd = ic->proto_data;
[0c78bb7]648        struct jabber_buddy *bud, *bare;
[5ebff60]649
[a21a8ac]650        /* Enable typing notification related code from now. */
651        jd->flags |= JFLAG_WANT_TYPING;
[5ebff60]652
[0c78bb7]653        if ((bud = jabber_buddy_by_jid(ic, who, 0)) == NULL ||
654            (bare = jabber_buddy_by_jid(ic, who, GET_BUDDY_BARE)) == NULL) {
[788a1af]655                /* Sending typing notifications to unknown buddies is
656                   unsupported for now. Shouldn't be a problem, I think. */
657                return 0;
658        }
[5ebff60]659
[0c78bb7]660
661        if (bud->flags & JBFLAG_DOES_XEP85 || bare->flags & JBFLAG_DOES_XEP85) {
[a21a8ac]662                /* We're only allowed to send this stuff if we know the other
[0c78bb7]663                   side supports it. If the bare JID has the flag, all other
664                   resources get it, too (That is the case in gtalk) */
[5ebff60]665
[a21a8ac]666                struct xt_node *node;
667                char *type;
668                int st;
[5ebff60]669
670                if (typing & OPT_TYPING) {
[a21a8ac]671                        type = "composing";
[5ebff60]672                } else if (typing & OPT_THINKING) {
[df1fb67]673                        type = "paused";
[5ebff60]674                } else {
[df1fb67]675                        type = "active";
[5ebff60]676                }
677
678                node = xt_new_node(type, NULL, NULL);
679                xt_add_attr(node, "xmlns", XMLNS_CHATSTATES);
680                node = jabber_make_packet("message", "chat", bud->full_jid, node);
681
682                st = jabber_write_packet(ic, node);
683                xt_free_node(node);
684
[a21a8ac]685                return st;
686        }
[5ebff60]687
[a21a8ac]688        return 1;
689}
690
[5ebff60]691void jabber_chat_add_settings(account_t *acc, set_t **head)
[6d544a1]692{
[3320d6d]693        set_add(head, "always_use_nicks", "false", set_eval_bool, NULL);
694
[6d544a1]695        /* Meh. Stupid room passwords. Not trying to obfuscate/hide
696           them from the user for now. */
[5ebff60]697        set_add(head, "password", NULL, NULL, NULL);
[6d544a1]698}
699
[5ebff60]700void jabber_chat_free_settings(account_t *acc, set_t **head)
[6d544a1]701{
[3320d6d]702        set_del(head, "always_use_nicks");
703
[5ebff60]704        set_del(head, "password");
[6d544a1]705}
706
[5ebff60]707GList *jabber_buddy_action_list(bee_user_t *bu)
[d88c92a]708{
709        static GList *ret = NULL;
[5ebff60]710
711        if (ret == NULL) {
[a97a336]712                static const struct buddy_action ba[2] = {
[d88c92a]713                        { "VERSION", "Get client (version) information" },
714                };
[5ebff60]715
716                ret = g_list_prepend(ret, (void *) ba + 0);
[d88c92a]717        }
[5ebff60]718
[d88c92a]719        return ret;
720}
721
[5ebff60]722void *jabber_buddy_action(struct bee_user *bu, const char *action, char * const args[], void *data)
[d88c92a]723{
[5ebff60]724        if (g_strcasecmp(action, "VERSION") == 0) {
[d88c92a]725                struct jabber_buddy *bud;
[5ebff60]726
727                if ((bud = jabber_buddy_by_ext_jid(bu->ic, bu->handle, 0)) == NULL) {
728                        bud = jabber_buddy_by_jid(bu->ic, bu->handle, GET_BUDDY_FIRST);
729                }
730                for (; bud; bud = bud->next) {
731                        jabber_iq_version_send(bu->ic, bud, data);
732                }
[d88c92a]733        }
[5ebff60]734
[d88c92a]735        return NULL;
736}
737
[5ebff60]738gboolean jabber_handle_is_self(struct im_connection *ic, const char *who)
739{
[be1efa3]740        struct jabber_data *jd = ic->proto_data;
[5ebff60]741
742        return ((g_strcasecmp(who, ic->acc->user) == 0) ||
743                (jd->internal_jid &&
744                 g_strcasecmp(who, jd->internal_jid) == 0));
[be1efa3]745}
746
[0da65d5]747void jabber_initmodule()
[f06894d]748{
[5ebff60]749        struct prpl *ret = g_new0(struct prpl, 1);
[71074ac]750        struct prpl *hipchat = NULL;
[5ebff60]751
[f06894d]752        ret->name = "jabber";
[6d544a1]753        ret->mms = 0;                        /* no limit */
[f06894d]754        ret->login = jabber_login;
[0da65d5]755        ret->init = jabber_init;
756        ret->logout = jabber_logout;
[f6c963b]757        ret->buddy_msg = jabber_buddy_msg;
[dd788bb]758        ret->away_states = jabber_away_states;
759        ret->set_away = jabber_set_away;
[f06894d]760//      ret->set_info = jabber_set_info;
[038d17f]761        ret->get_info = jabber_get_info;
[cfbb3a6]762        ret->add_buddy = jabber_add_buddy;
763        ret->remove_buddy = jabber_remove_buddy;
[43671b9]764        ret->chat_msg = jabber_chat_msg_;
[ef5c185]765        ret->chat_topic = jabber_chat_topic_;
[c058ff9]766        ret->chat_invite = jabber_chat_invite_;
[e35d1a1]767        ret->chat_leave = jabber_chat_leave_;
768        ret->chat_join = jabber_chat_join_;
[fc0640e]769        ret->chat_with = jabber_chat_with_;
[6d544a1]770        ret->chat_add_settings = jabber_chat_add_settings;
771        ret->chat_free_settings = jabber_chat_free_settings;
[deff040]772        ret->keepalive = jabber_keepalive;
[a21a8ac]773        ret->send_typing = jabber_send_typing;
[f06894d]774        ret->handle_cmp = g_strcasecmp;
[be1efa3]775        ret->handle_is_self = jabber_handle_is_self;
[2ff2076]776        ret->transfer_request = jabber_si_transfer_request;
[d88c92a]777        ret->buddy_action_list = jabber_buddy_action_list;
778        ret->buddy_action = jabber_buddy_action;
[f06894d]779
[5ebff60]780        register_protocol(ret);
[71074ac]781
782        /* Another one for hipchat, which has completely different logins */
783        hipchat = g_memdup(ret, sizeof(struct prpl));
784        hipchat->name = "hipchat";
785        register_protocol(hipchat);
[f06894d]786}
Note: See TracBrowser for help on using the repository browser.