source: protocols/jabber/io.c @ fd45e1d1

Last change on this file since fd45e1d1 was 315dd4c, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-15T01:25:47Z

Oops.. Today's Jabber fix could get stuck in a somewhat infinite loop if a
Jabber server returns an empty <iq type="result"/> response to the session
establishment request (which is valid and actually done by the example, but
my test Jabberd shows different behaviour). Fixed.

  • Property mode set to 100644
File size: 15.4 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 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 )
48        {
49                char *msg;
50               
51                msg = g_strdup_printf( "TX: %s", buf );
52                imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
53                g_free( msg );
54        }
55       
56        if( jd->tx_len == 0 )
57        {
58                /* If the queue is empty, allocate a new buffer. */
59                jd->tx_len = len;
60                jd->txq = g_memdup( buf, len );
61               
62                /* Try if we can write it immediately so we don't have to do
63                   it via the event handler. If not, add the handler. (In
64                   most cases it probably won't be necessary.) */
65                if( ( ret = jabber_write_queue( ic ) ) && jd->tx_len > 0 )
66                        jd->w_inpa = b_input_add( jd->fd, GAIM_INPUT_WRITE, jabber_write_callback, ic );
67        }
68        else
69        {
70                /* Just add it to the buffer if it's already filled. The
71                   event handler is already set. */
72                jd->txq = g_renew( char, jd->txq, jd->tx_len + len );
73                memcpy( jd->txq + jd->tx_len, buf, len );
74                jd->tx_len += len;
75               
76                /* The return value for write() doesn't necessarily mean
77                   that everything got sent, it mainly means that the
78                   connection (officially) still exists and can still
79                   be accessed without hitting SIGSEGV. IOW: */
80                ret = TRUE;
81        }
82       
83        return ret;
84}
85
86/* Splitting up in two separate functions: One to use as a callback and one
87   to use in the function above to escape from having to wait for the event
88   handler to call us, if possible.
89   
90   Two different functions are necessary because of the return values: The
91   callback should only return TRUE if the write was successful AND if the
92   buffer is not empty yet (ie. if the handler has to be called again when
93   the socket is ready for more data). */
94static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond )
95{
96        struct jabber_data *jd = ((struct im_connection *)data)->proto_data;
97       
98        return jd->fd != -1 &&
99               jabber_write_queue( data ) &&
100               jd->tx_len > 0;
101}
102
103static gboolean jabber_write_queue( struct im_connection *ic )
104{
105        struct jabber_data *jd = ic->proto_data;
106        int st;
107       
108        if( jd->ssl )
109                st = ssl_write( jd->ssl, jd->txq, jd->tx_len );
110        else
111                st = write( jd->fd, jd->txq, jd->tx_len );
112       
113        if( st == jd->tx_len )
114        {
115                /* We wrote everything, clear the buffer. */
116                g_free( jd->txq );
117                jd->txq = NULL;
118                jd->tx_len = 0;
119               
120                return TRUE;
121        }
122        else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) )
123        {
124                /* Set fd to -1 to make sure we won't write to it anymore. */
125                closesocket( jd->fd );  /* Shouldn't be necessary after errors? */
126                jd->fd = -1;
127               
128                imcb_error( ic, "Short write() to server" );
129                imc_logout( ic, TRUE );
130                return FALSE;
131        }
132        else if( st > 0 )
133        {
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        }
143        else
144        {
145                /* Just in case we had EINPROGRESS/EAGAIN: */
146               
147                return TRUE;
148        }
149}
150
151static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition cond )
152{
153        struct im_connection *ic = data;
154        struct jabber_data *jd = ic->proto_data;
155        char buf[512];
156        int st;
157       
158        if( jd->fd == -1 )
159                return FALSE;
160       
161        if( jd->ssl )
162                st = ssl_read( jd->ssl, buf, sizeof( buf ) );
163        else
164                st = read( jd->fd, buf, sizeof( buf ) );
165       
166        if( st > 0 )
167        {
168                /* Parse. */
169                if( xt_feed( jd->xt, buf, st ) < 0 )
170                {
171                        imcb_error( ic, "XML stream error" );
172                        imc_logout( ic, TRUE );
173                        return FALSE;
174                }
175               
176                /* Execute all handlers. */
177                if( !xt_handle( jd->xt, NULL, 1 ) )
178                {
179                        /* Don't do anything, the handlers should have
180                           aborted the connection already. */
181                        return FALSE;
182                }
183               
184                if( jd->flags & JFLAG_STREAM_RESTART )
185                {
186                        jd->flags &= ~JFLAG_STREAM_RESTART;
187                        jabber_start_stream( ic );
188                }
189               
190                /* Garbage collection. */
191                xt_cleanup( jd->xt, NULL, 1 );
192               
193                /* This is a bit hackish, unfortunately. Although xmltree
194                   has nifty event handler stuff, it only calls handlers
195                   when nodes are complete. Since the server should only
196                   send an opening <stream:stream> tag, we have to check
197                   this by hand. :-( */
198                if( !( jd->flags & JFLAG_STREAM_STARTED ) && jd->xt && jd->xt->root )
199                {
200                        if( g_strcasecmp( jd->xt->root->name, "stream:stream" ) == 0 )
201                        {
202                                jd->flags |= JFLAG_STREAM_STARTED;
203                               
204                                /* If there's no version attribute, assume
205                                   this is an old server that can't do SASL
206                                   authentication. */
207                                if( !sasl_supported( ic ) )
208                                {
209                                        /* If there's no version= tag, we suppose
210                                           this server does NOT implement: XMPP 1.0,
211                                           SASL and TLS. */
212                                        if( set_getbool( &ic->acc->set, "tls" ) )
213                                        {
214                                                imcb_error( ic, "TLS is turned on for this "
215                                                          "account, but is not supported by this server" );
216                                                imc_logout( ic, FALSE );
217                                                return FALSE;
218                                        }
219                                        else
220                                        {
221                                                return jabber_init_iq_auth( ic );
222                                        }
223                                }
224                        }
225                        else
226                        {
227                                imcb_error( ic, "XML stream error" );
228                                imc_logout( ic, TRUE );
229                                return FALSE;
230                        }
231                }
232        }
233        else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) )
234        {
235                closesocket( jd->fd );
236                jd->fd = -1;
237               
238                imcb_error( ic, "Error while reading from server" );
239                imc_logout( ic, TRUE );
240                return FALSE;
241        }
242       
243        if( ssl_pending( jd->ssl ) )
244                /* OpenSSL empties the TCP buffers completely but may keep some
245                   data in its internap buffers. select() won't see that, but
246                   ssl_pending() does. */
247                return jabber_read_callback( data, fd, cond );
248        else
249                return TRUE;
250}
251
252gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond )
253{
254        struct im_connection *ic = data;
255       
256        if( g_slist_find( jabber_connections, ic ) == NULL )
257                return FALSE;
258       
259        if( source == -1 )
260        {
261                imcb_error( ic, "Could not connect to server" );
262                imc_logout( ic, TRUE );
263                return FALSE;
264        }
265       
266        imcb_log( ic, "Connected to server, logging in" );
267       
268        return jabber_start_stream( ic );
269}
270
271gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond )
272{
273        struct im_connection *ic = data;
274        struct jabber_data *jd;
275       
276        if( g_slist_find( jabber_connections, ic ) == NULL )
277                return FALSE;
278       
279        jd = ic->proto_data;
280       
281        if( source == NULL )
282        {
283                /* The SSL connection will be cleaned up by the SSL lib
284                   already, set it to NULL here to prevent a double cleanup: */
285                jd->ssl = NULL;
286               
287                imcb_error( ic, "Could not connect to server" );
288                imc_logout( ic, TRUE );
289                return FALSE;
290        }
291       
292        imcb_log( ic, "Connected to server, logging in" );
293       
294        return jabber_start_stream( ic );
295}
296
297static xt_status jabber_end_of_stream( struct xt_node *node, gpointer data )
298{
299        imc_logout( data, TRUE );
300        return XT_ABORT;
301}
302
303static xt_status jabber_pkt_features( struct xt_node *node, gpointer data )
304{
305        struct im_connection *ic = data;
306        struct jabber_data *jd = ic->proto_data;
307        struct xt_node *c, *reply;
308        int trytls;
309       
310        trytls = g_strcasecmp( set_getstr( &ic->acc->set, "tls" ), "try" ) == 0;
311        c = xt_find_node( node->children, "starttls" );
312        if( c && !jd->ssl )
313        {
314                /* If the server advertises the STARTTLS feature and if we're
315                   not in a secure connection already: */
316               
317                c = xt_find_node( c->children, "required" );
318               
319                if( c && ( !trytls && !set_getbool( &ic->acc->set, "tls" ) ) )
320                {
321                        imcb_error( ic, "Server requires TLS connections, but TLS is turned off for this account" );
322                        imc_logout( ic, FALSE );
323                       
324                        return XT_ABORT;
325                }
326               
327                /* Only run this if the tls setting is set to true or try: */
328                if( ( trytls || set_getbool( &ic->acc->set, "tls" ) ) )
329                {
330                        reply = xt_new_node( "starttls", NULL, NULL );
331                        xt_add_attr( reply, "xmlns", XMLNS_TLS );
332                        if( !jabber_write_packet( ic, reply ) )
333                        {
334                                xt_free_node( reply );
335                                return XT_ABORT;
336                        }
337                        xt_free_node( reply );
338                       
339                        return XT_HANDLED;
340                }
341        }
342        else if( !c && !jd->ssl )
343        {
344                /* If the server does not advertise the STARTTLS feature and
345                   we're not in a secure connection already: (Servers have a
346                   habit of not advertising <starttls/> anymore when already
347                   using SSL/TLS. */
348               
349                if( !trytls && set_getbool( &ic->acc->set, "tls" ) )
350                {
351                        imcb_error( ic, "TLS is turned on for this account, but is not supported by this server" );
352                        imc_logout( ic, FALSE );
353                       
354                        return XT_ABORT;
355                }
356        }
357       
358        /* This one used to be in jabber_handlers[], but it has to be done
359           from here to make sure the TLS session will be initialized
360           properly before we attempt SASL authentication. */
361        if( ( c = xt_find_node( node->children, "mechanisms" ) ) )
362        {
363                if( sasl_pkt_mechanisms( c, data ) == XT_ABORT )
364                        return XT_ABORT;
365        }
366        /* If the server *SEEMS* to support SASL authentication but doesn't
367           support it after all, we should try to do authentication the
368           other way. jabber.com doesn't seem to do SASL while it pretends
369           to be XMPP 1.0 compliant! */
370        else if( !( jd->flags & JFLAG_AUTHENTICATED ) && sasl_supported( ic ) )
371        {
372                if( !jabber_init_iq_auth( ic ) )
373                        return XT_ABORT;
374        }
375       
376        if( ( c = xt_find_node( node->children, "bind" ) ) )
377                jd->flags |= JFLAG_WANT_BIND;
378       
379        if( ( c = xt_find_node( node->children, "session" ) ) )
380                jd->flags |= JFLAG_WANT_SESSION;
381       
382        if( jd->flags & JFLAG_AUTHENTICATED )
383                return jabber_pkt_bind_sess( ic, NULL, NULL );
384       
385        return XT_HANDLED;
386}
387
388static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )
389{
390        struct im_connection *ic = data;
391        struct jabber_data *jd = ic->proto_data;
392        char *xmlns;
393       
394        xmlns = xt_find_attr( node, "xmlns" );
395       
396        /* Just ignore it when it doesn't seem to be TLS-related (is that at
397           all possible??). */
398        if( !xmlns || strcmp( xmlns, XMLNS_TLS ) != 0 )
399                return XT_HANDLED;
400       
401        /* We don't want event handlers to touch our TLS session while it's
402           still initializing! */
403        b_event_remove( jd->r_inpa );
404        if( jd->tx_len > 0 )
405        {
406                /* Actually the write queue should be empty here, but just
407                   to be sure... */
408                b_event_remove( jd->w_inpa );
409                g_free( jd->txq );
410                jd->txq = NULL;
411                jd->tx_len = 0;
412        }
413        jd->w_inpa = jd->r_inpa = 0;
414       
415        imcb_log( ic, "Converting stream to TLS" );
416       
417        jd->flags |= JFLAG_STARTTLS_DONE;
418        jd->ssl = ssl_starttls( jd->fd, jabber_connected_ssl, ic );
419       
420        return XT_HANDLED;
421}
422
423static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data )
424{
425        struct im_connection *ic = data;
426        int allow_reconnect = TRUE;
427        struct jabber_error *err;
428       
429        err = jabber_error_parse( node, XMLNS_STREAM_ERROR );
430       
431        /* Tssk... */
432        if( err->code == NULL )
433        {
434                imcb_error( ic, "Unknown stream error reported by server" );
435                imc_logout( ic, allow_reconnect );
436                jabber_error_free( err );
437                return XT_ABORT;
438        }
439       
440        /* We know that this is a fatal error. If it's a "conflict" error, we
441           should turn off auto-reconnect to make sure we won't get some nasty
442           infinite loop! */
443        if( strcmp( err->code, "conflict" ) == 0 )
444        {
445                imcb_error( ic, "Account and resource used from a different location" );
446                allow_reconnect = FALSE;
447        }
448        else
449        {
450                imcb_error( ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "",
451                            err->text ? err->text : "" );
452        }
453       
454        jabber_error_free( err );
455        imc_logout( ic, allow_reconnect );
456       
457        return XT_ABORT;
458}
459
460static xt_status jabber_xmlconsole( struct xt_node *node, gpointer data )
461{
462        struct im_connection *ic = data;
463        struct jabber_data *jd = ic->proto_data;
464       
465        if( jd->flags & JFLAG_XMLCONSOLE )
466        {
467                char *msg, *pkt;
468               
469                pkt = xt_to_string( node );
470                msg = g_strdup_printf( "RX: %s", pkt );
471                imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
472                g_free( msg );
473                g_free( pkt );
474        }
475       
476        return XT_NEXT;
477}
478
479static const struct xt_handler_entry jabber_handlers[] = {
480        { NULL,                 "stream:stream",        jabber_xmlconsole },
481        { "stream:stream",      "<root>",               jabber_end_of_stream },
482        { "message",            "stream:stream",        jabber_pkt_message },
483        { "presence",           "stream:stream",        jabber_pkt_presence },
484        { "iq",                 "stream:stream",        jabber_pkt_iq },
485        { "stream:features",    "stream:stream",        jabber_pkt_features },
486        { "stream:error",       "stream:stream",        jabber_pkt_stream_error },
487        { "proceed",            "stream:stream",        jabber_pkt_proceed_tls },
488        { "challenge",          "stream:stream",        sasl_pkt_challenge },
489        { "success",            "stream:stream",        sasl_pkt_result },
490        { "failure",            "stream:stream",        sasl_pkt_result },
491        { NULL,                 NULL,                   NULL }
492};
493
494gboolean jabber_start_stream( struct im_connection *ic )
495{
496        struct jabber_data *jd = ic->proto_data;
497        int st;
498        char *greet;
499       
500        /* We'll start our stream now, so prepare everything to receive one
501           from the server too. */
502        xt_free( jd->xt );      /* In case we're RE-starting. */
503        jd->xt = xt_new( jabber_handlers, ic );
504       
505        if( jd->r_inpa <= 0 )
506                jd->r_inpa = b_input_add( jd->fd, GAIM_INPUT_READ, jabber_read_callback, ic );
507       
508        greet = g_strdup_printf( "%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" "
509                                  "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">", 
510                                  ( jd->flags & JFLAG_STARTTLS_DONE ) ? "" : "<?xml version='1.0' ?>",
511                                  jd->server );
512       
513        st = jabber_write( ic, greet, strlen( greet ) );
514       
515        g_free( greet );
516       
517        return st;
518}
519
520void jabber_end_stream( struct im_connection *ic )
521{
522        struct jabber_data *jd = ic->proto_data;
523       
524        /* Let's only do this if the queue is currently empty, otherwise it'd
525           take too long anyway. */
526        if( jd->tx_len == 0 )
527        {
528                char eos[] = "</stream:stream>";
529                struct xt_node *node;
530                int st = 1;
531               
532                if( ic->flags & OPT_LOGGED_IN )
533                {
534                        node = jabber_make_packet( "presence", "unavailable", NULL, NULL );
535                        st = jabber_write_packet( ic, node );
536                        xt_free_node( node );
537                }
538               
539                if( st )
540                        jabber_write( ic, eos, strlen( eos ) );
541        }
542}
Note: See TracBrowser for help on using the repository browser.