source: protocols/jabber/io.c

Last change on this file was 0605498, checked in by dequis <dx@…>, at 2015-08-26T05:41:10Z

jabber/io: Split input buffer parsing to a jabber_feed_input() function

To simplify testing. Also allow passing a -1 as size to use strlen()

Minor behavior change: The jabber_init_iq_auth() branch can no longer
return immediately, which means it will continue through the
ssl_pending() check in jabber_read_callback().

Other than that, the size -1 change, and one indentation level less, the
function body is the same as before.

  • Property mode set to 100644
File size: 17.1 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - I/O stuff (plain, SSL), queues, etc                      *
5*                                                                           *
6*  Copyright 2006-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "jabber.h"
25#include "ssl_client.h"
26
27static gboolean jabber_write_callback(gpointer data, gint fd, b_input_condition cond);
28static gboolean jabber_write_queue(struct im_connection *ic);
29
30int jabber_write_packet(struct im_connection *ic, struct xt_node *node)
31{
32        char *buf;
33        int st;
34
35        buf = xt_to_string(node);
36        st = jabber_write(ic, buf, strlen(buf));
37        g_free(buf);
38
39        return st;
40}
41
42int jabber_write(struct im_connection *ic, char *buf, int len)
43{
44        struct jabber_data *jd = ic->proto_data;
45        gboolean ret;
46
47        if (jd->flags & JFLAG_XMLCONSOLE && !(ic->flags & OPT_LOGGING_OUT)) {
48                char *msg, *s;
49
50                msg = g_strdup_printf("TX: %s", buf);
51                /* Don't include auth info in XML logs. */
52                if (strncmp(msg, "TX: <auth ", 10) == 0 && (s = strchr(msg, '>'))) {
53                        s++;
54                        while (*s && *s != '<') {
55                                *(s++) = '*';
56                        }
57                }
58                imcb_buddy_msg(ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0);
59                g_free(msg);
60        }
61
62        if (jd->tx_len == 0) {
63                /* If the queue is empty, allocate a new buffer. */
64                jd->tx_len = len;
65                jd->txq = g_memdup(buf, len);
66
67                /* Try if we can write it immediately so we don't have to do
68                   it via the event handler. If not, add the handler. (In
69                   most cases it probably won't be necessary.) */
70                if ((ret = jabber_write_queue(ic)) && jd->tx_len > 0) {
71                        jd->w_inpa = b_input_add(jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic);
72                }
73        } else {
74                /* Just add it to the buffer if it's already filled. The
75                   event handler is already set. */
76                jd->txq = g_renew(char, jd->txq, jd->tx_len + len);
77                memcpy(jd->txq + jd->tx_len, buf, len);
78                jd->tx_len += len;
79
80                /* The return value for write() doesn't necessarily mean
81                   that everything got sent, it mainly means that the
82                   connection (officially) still exists and can still
83                   be accessed without hitting SIGSEGV. IOW: */
84                ret = TRUE;
85        }
86
87        return ret;
88}
89
90/* Splitting up in two separate functions: One to use as a callback and one
91   to use in the function above to escape from having to wait for the event
92   handler to call us, if possible.
93
94   Two different functions are necessary because of the return values: The
95   callback should only return TRUE if the write was successful AND if the
96   buffer is not empty yet (ie. if the handler has to be called again when
97   the socket is ready for more data). */
98static gboolean jabber_write_callback(gpointer data, gint fd, b_input_condition cond)
99{
100        struct jabber_data *jd = ((struct im_connection *) data)->proto_data;
101
102        return jd->fd != -1 &&
103               jabber_write_queue(data) &&
104               jd->tx_len > 0;
105}
106
107static gboolean jabber_write_queue(struct im_connection *ic)
108{
109        struct jabber_data *jd = ic->proto_data;
110        int st;
111
112        if (jd->ssl) {
113                st = ssl_write(jd->ssl, jd->txq, jd->tx_len);
114        } else {
115                st = write(jd->fd, jd->txq, jd->tx_len);
116        }
117
118        if (st == jd->tx_len) {
119                /* We wrote everything, clear the buffer. */
120                g_free(jd->txq);
121                jd->txq = NULL;
122                jd->tx_len = 0;
123
124                return TRUE;
125        } else if (st == 0 || (st < 0 && !ssl_sockerr_again(jd->ssl))) {
126                /* Set fd to -1 to make sure we won't write to it anymore. */
127                closesocket(jd->fd);    /* Shouldn't be necessary after errors? */
128                jd->fd = -1;
129
130                imcb_error(ic, "Short write() to server");
131                imc_logout(ic, TRUE);
132                return FALSE;
133        } else if (st > 0) {
134                char *s;
135
136                s = g_memdup(jd->txq + st, jd->tx_len - st);
137                jd->tx_len -= st;
138                g_free(jd->txq);
139                jd->txq = s;
140
141                return TRUE;
142        } else {
143                /* Just in case we had EINPROGRESS/EAGAIN: */
144
145                return TRUE;
146        }
147}
148
149static gboolean jabber_feed_input(struct im_connection *ic, char *buf, int size)
150{
151        struct jabber_data *jd = ic->proto_data;
152
153        /* Allow not passing a size for debugging purposes.
154         * This never happens when reading from the socket */
155        if (size == -1) {
156                size = strlen(buf);
157        }
158
159        /* Parse. */
160        if (xt_feed(jd->xt, buf, size) < 0) {
161                imcb_error(ic, "XML stream error");
162                imc_logout(ic, TRUE);
163                return FALSE;
164        }
165
166        /* Execute all handlers. */
167        if (!xt_handle(jd->xt, NULL, 1)) {
168                /* Don't do anything, the handlers should have
169                   aborted the connection already. */
170                return FALSE;
171        }
172
173        if (jd->flags & JFLAG_STREAM_RESTART) {
174                jd->flags &= ~JFLAG_STREAM_RESTART;
175                jabber_start_stream(ic);
176        }
177
178        /* Garbage collection. */
179        xt_cleanup(jd->xt, NULL, 1);
180
181        /* This is a bit hackish, unfortunately. Although xmltree
182           has nifty event handler stuff, it only calls handlers
183           when nodes are complete. Since the server should only
184           send an opening <stream:stream> tag, we have to check
185           this by hand. :-( */
186        if (!(jd->flags & JFLAG_STREAM_STARTED) && jd->xt && jd->xt->root) {
187                if (g_strcasecmp(jd->xt->root->name, "stream:stream") == 0) {
188                        jd->flags |= JFLAG_STREAM_STARTED;
189
190                        /* If there's no version attribute, assume
191                           this is an old server that can't do SASL
192                           authentication. */
193                        if (!set_getbool(&ic->acc->set, "sasl") || !sasl_supported(ic)) {
194                                /* If there's no version= tag, we suppose
195                                   this server does NOT implement: XMPP 1.0,
196                                   SASL and TLS. */
197                                if (set_getbool(&ic->acc->set, "tls")) {
198                                        imcb_error(ic, "TLS is turned on for this "
199                                                   "account, but is not supported by this server");
200                                        imc_logout(ic, FALSE);
201                                        return FALSE;
202                                } else {
203                                        if (!jabber_init_iq_auth(ic)) {
204                                                return FALSE;
205                                        }
206                                }
207                        }
208                } else {
209                        imcb_error(ic, "XML stream error");
210                        imc_logout(ic, TRUE);
211                        return FALSE;
212                }
213        }
214
215        return TRUE;
216}
217
218
219static gboolean jabber_read_callback(gpointer data, gint fd, b_input_condition cond)
220{
221        struct im_connection *ic = data;
222        struct jabber_data *jd = ic->proto_data;
223        char buf[512];
224        int st;
225
226        if (jd->fd == -1) {
227                return FALSE;
228        }
229
230        if (jd->ssl) {
231                st = ssl_read(jd->ssl, buf, sizeof(buf));
232        } else {
233                st = read(jd->fd, buf, sizeof(buf));
234        }
235
236        if (st > 0) {
237                if (!jabber_feed_input(ic, buf, st)) {
238                        return FALSE;
239                }
240        } else if (st == 0 || (st < 0 && !ssl_sockerr_again(jd->ssl))) {
241                closesocket(jd->fd);
242                jd->fd = -1;
243
244                imcb_error(ic, "Error while reading from server");
245                imc_logout(ic, TRUE);
246                return FALSE;
247        }
248
249        if (ssl_pending(jd->ssl)) {
250                /* OpenSSL empties the TCP buffers completely but may keep some
251                   data in its internap buffers. select() won't see that, but
252                   ssl_pending() does. */
253                return jabber_read_callback(data, fd, cond);
254        } else {
255                return TRUE;
256        }
257}
258
259gboolean jabber_connected_plain(gpointer data, gint source, b_input_condition cond)
260{
261        struct im_connection *ic = data;
262
263        if (g_slist_find(jabber_connections, ic) == NULL) {
264                return FALSE;
265        }
266
267        if (source == -1) {
268                imcb_error(ic, "Could not connect to server");
269                imc_logout(ic, TRUE);
270                return FALSE;
271        }
272
273        imcb_log(ic, "Connected to server, logging in");
274
275        return jabber_start_stream(ic);
276}
277
278gboolean jabber_connected_ssl(gpointer data, int returncode, void *source, b_input_condition cond)
279{
280        struct im_connection *ic = data;
281        struct jabber_data *jd;
282
283        if (g_slist_find(jabber_connections, ic) == NULL) {
284                return FALSE;
285        }
286
287        jd = ic->proto_data;
288
289        if (source == NULL) {
290                /* The SSL connection will be cleaned up by the SSL lib
291                   already, set it to NULL here to prevent a double cleanup: */
292                jd->ssl = NULL;
293
294                if (returncode != 0) {
295                        char *err = ssl_verify_strerror(returncode);
296                        imcb_error(ic, "Certificate verification problem 0x%x: %s",
297                                   returncode, err ? err : "Unknown");
298                        g_free(err);
299                        imc_logout(ic, FALSE);
300                } else {
301                        imcb_error(ic, "Could not connect to server");
302                        imc_logout(ic, TRUE);
303                }
304
305                return FALSE;
306        }
307
308        imcb_log(ic, "Connected to server, logging in");
309
310        return jabber_start_stream(ic);
311}
312
313static xt_status jabber_end_of_stream(struct xt_node *node, gpointer data)
314{
315        imc_logout(data, TRUE);
316        return XT_ABORT;
317}
318
319static xt_status jabber_pkt_features(struct xt_node *node, gpointer data)
320{
321        struct im_connection *ic = data;
322        struct jabber_data *jd = ic->proto_data;
323        struct xt_node *c, *reply;
324        int trytls;
325
326        trytls = g_strcasecmp(set_getstr(&ic->acc->set, "tls"), "try") == 0;
327        c = xt_find_node(node->children, "starttls");
328        if (c && !jd->ssl) {
329                /* If the server advertises the STARTTLS feature and if we're
330                   not in a secure connection already: */
331
332                c = xt_find_node(c->children, "required");
333
334                if (c && (!trytls && !set_getbool(&ic->acc->set, "tls"))) {
335                        imcb_error(ic, "Server requires TLS connections, but TLS is turned off for this account");
336                        imc_logout(ic, FALSE);
337
338                        return XT_ABORT;
339                }
340
341                /* Only run this if the tls setting is set to true or try: */
342                if ((trytls || set_getbool(&ic->acc->set, "tls"))) {
343                        reply = xt_new_node("starttls", NULL, NULL);
344                        xt_add_attr(reply, "xmlns", XMLNS_TLS);
345                        if (!jabber_write_packet(ic, reply)) {
346                                xt_free_node(reply);
347                                return XT_ABORT;
348                        }
349                        xt_free_node(reply);
350
351                        return XT_HANDLED;
352                }
353        } else if (!c && !jd->ssl) {
354                /* If the server does not advertise the STARTTLS feature and
355                   we're not in a secure connection already: (Servers have a
356                   habit of not advertising <starttls/> anymore when already
357                   using SSL/TLS. */
358
359                if (!trytls && set_getbool(&ic->acc->set, "tls")) {
360                        imcb_error(ic, "TLS is turned on for this account, but is not supported by this server");
361                        imc_logout(ic, FALSE);
362
363                        return XT_ABORT;
364                }
365        }
366
367        /* This one used to be in jabber_handlers[], but it has to be done
368           from here to make sure the TLS session will be initialized
369           properly before we attempt SASL authentication. */
370        if ((c = xt_find_node(node->children, "mechanisms"))) {
371                if (sasl_pkt_mechanisms(c, data) == XT_ABORT) {
372                        return XT_ABORT;
373                }
374        }
375        /* If the server *SEEMS* to support SASL authentication but doesn't
376           support it after all, we should try to do authentication the
377           other way. jabber.com doesn't seem to do SASL while it pretends
378           to be XMPP 1.0 compliant! */
379        else if (!(jd->flags & JFLAG_AUTHENTICATED) && set_getbool(&ic->acc->set, "sasl") && sasl_supported(ic)) {
380                if (!jabber_init_iq_auth(ic)) {
381                        return XT_ABORT;
382                }
383        }
384
385        if ((c = xt_find_node(node->children, "bind"))) {
386                jd->flags |= JFLAG_WANT_BIND;
387        }
388
389        if ((c = xt_find_node(node->children, "session"))) {
390                jd->flags |= JFLAG_WANT_SESSION;
391        }
392
393        if (jd->flags & JFLAG_AUTHENTICATED) {
394                return jabber_pkt_bind_sess(ic, NULL, NULL);
395        }
396
397        return XT_HANDLED;
398}
399
400static xt_status jabber_pkt_proceed_tls(struct xt_node *node, gpointer data)
401{
402        struct im_connection *ic = data;
403        struct jabber_data *jd = ic->proto_data;
404        char *xmlns, *tlsname;
405
406        xmlns = xt_find_attr(node, "xmlns");
407
408        /* Just ignore it when it doesn't seem to be TLS-related (is that at
409           all possible??). */
410        if (!xmlns || strcmp(xmlns, XMLNS_TLS) != 0) {
411                return XT_HANDLED;
412        }
413
414        /* We don't want event handlers to touch our TLS session while it's
415           still initializing! */
416        b_event_remove(jd->r_inpa);
417        if (jd->tx_len > 0) {
418                /* Actually the write queue should be empty here, but just
419                   to be sure... */
420                b_event_remove(jd->w_inpa);
421                g_free(jd->txq);
422                jd->txq = NULL;
423                jd->tx_len = 0;
424        }
425        jd->w_inpa = jd->r_inpa = 0;
426
427        imcb_log(ic, "Converting stream to TLS");
428
429        jd->flags |= JFLAG_STARTTLS_DONE;
430
431        /* If the user specified a server for the account, use this server as the
432         * hostname in the certificate verification. Else we use the domain from
433         * the username. */
434        if (ic->acc->server && *ic->acc->server) {
435                tlsname = ic->acc->server;
436        } else {
437                tlsname = jd->server;
438        }
439
440        jd->ssl = ssl_starttls(jd->fd, tlsname, set_getbool(&ic->acc->set, "tls_verify"),
441                               jabber_connected_ssl, ic);
442
443        return XT_HANDLED;
444}
445
446static xt_status jabber_pkt_stream_error(struct xt_node *node, gpointer data)
447{
448        struct im_connection *ic = data;
449        struct jabber_data *jd = ic->proto_data;
450        int allow_reconnect = TRUE;
451        struct jabber_error *err;
452        struct xt_node *host;
453
454        if (!(ic->flags & OPT_LOGGED_IN) &&
455            (host = xt_find_node(node->children, "see-other-host")) &&
456            host->text) {
457                char *s;
458                int port = set_getint(&ic->acc->set, "port");
459
460                /* Let's try to obey this request, if we're not logged
461                   in yet (i.e. not have too much state yet). */
462                if (jd->ssl) {
463                        ssl_disconnect(jd->ssl);
464                }
465                closesocket(jd->fd);
466                b_event_remove(jd->r_inpa);
467                b_event_remove(jd->w_inpa);
468
469                jd->ssl = NULL;
470                jd->r_inpa = jd->w_inpa = 0;
471                jd->flags &= JFLAG_XMLCONSOLE;
472
473                s = strchr(host->text, ':');
474                if (s != NULL) {
475                        sscanf(s + 1, "%d", &port);
476                }
477
478                imcb_log(ic, "Redirected to %s", host->text);
479                jd->fd = proxy_connect(host->text, port, jabber_connected_plain, ic);
480
481                return XT_ABORT;
482        }
483
484        err = jabber_error_parse(node, XMLNS_STREAM_ERROR);
485
486        /* Tssk... */
487        if (err->code == NULL) {
488                imcb_error(ic, "Unknown stream error reported by server");
489                imc_logout(ic, allow_reconnect);
490                jabber_error_free(err);
491                return XT_ABORT;
492        }
493
494        /* We know that this is a fatal error. If it's a "conflict" error, we
495           should turn off auto-reconnect to make sure we won't get some nasty
496           infinite loop! */
497        if (strcmp(err->code, "conflict") == 0) {
498                imcb_error(ic, "Account and resource used from a different location");
499                allow_reconnect = FALSE;
500        } else if (strcmp(err->code, "not-authorized") == 0) {
501                imcb_error(ic, "Not authorized");
502                allow_reconnect = FALSE;
503        } else {
504                imcb_error(ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "",
505                           err->text ? err->text : "");
506        }
507
508        jabber_error_free(err);
509        imc_logout(ic, allow_reconnect);
510
511        return XT_ABORT;
512}
513
514static xt_status jabber_xmlconsole(struct xt_node *node, gpointer data)
515{
516        struct im_connection *ic = data;
517        struct jabber_data *jd = ic->proto_data;
518
519        if (jd->flags & JFLAG_XMLCONSOLE) {
520                char *msg, *pkt;
521
522                pkt = xt_to_string(node);
523                msg = g_strdup_printf("RX: %s", pkt);
524                imcb_buddy_msg(ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0);
525                g_free(msg);
526                g_free(pkt);
527        }
528
529        return XT_NEXT;
530}
531
532static const struct xt_handler_entry jabber_handlers[] = {
533        { NULL,                 "stream:stream",        jabber_xmlconsole },
534        { "stream:stream",      "<root>",               jabber_end_of_stream },
535        { "message",            "stream:stream",        jabber_pkt_message },
536        { "presence",           "stream:stream",        jabber_pkt_presence },
537        { "iq",                 "stream:stream",        jabber_pkt_iq },
538        { "stream:features",    "stream:stream",        jabber_pkt_features },
539        { "stream:error",       "stream:stream",        jabber_pkt_stream_error },
540        { "proceed",            "stream:stream",        jabber_pkt_proceed_tls },
541        { "challenge",          "stream:stream",        sasl_pkt_challenge },
542        { "success",            "stream:stream",        sasl_pkt_result },
543        { "failure",            "stream:stream",        sasl_pkt_result },
544        { NULL,                 NULL,                   NULL }
545};
546
547gboolean jabber_start_stream(struct im_connection *ic)
548{
549        struct jabber_data *jd = ic->proto_data;
550        int st;
551        char *greet;
552
553        /* We'll start our stream now, so prepare everything to receive one
554           from the server too. */
555        xt_free(jd->xt);        /* In case we're RE-starting. */
556        jd->xt = xt_new(jabber_handlers, ic);
557
558        if (jd->r_inpa <= 0) {
559                jd->r_inpa = b_input_add(jd->fd, B_EV_IO_READ, jabber_read_callback, ic);
560        }
561
562        greet = g_strdup_printf("%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" "
563                                "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">",
564                                (jd->flags & JFLAG_STARTTLS_DONE) ? "" : "<?xml version='1.0' ?>",
565                                jd->server);
566
567        st = jabber_write(ic, greet, strlen(greet));
568
569        g_free(greet);
570
571        return st;
572}
573
574void jabber_end_stream(struct im_connection *ic)
575{
576        struct jabber_data *jd = ic->proto_data;
577
578        /* Let's only do this if the queue is currently empty, otherwise it'd
579           take too long anyway. */
580        if (jd->tx_len == 0) {
581                char eos[] = "</stream:stream>";
582                struct xt_node *node;
583                int st = 1;
584
585                if (ic->flags & OPT_LOGGED_IN) {
586                        node = jabber_make_packet("presence", "unavailable", NULL, NULL);
587                        st = jabber_write_packet(ic, node);
588                        xt_free_node(node);
589                }
590
591                if (st) {
592                        jabber_write(ic, eos, strlen(eos));
593                }
594        }
595}
Note: See TracBrowser for help on using the repository browser.