source: protocols/jabber/io.c @ fcfd9c5

Last change on this file since fcfd9c5 was b5c8a34, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-01-24T22:49:47Z

Keeping track of valid Jabber connections so _connected() events will be
ignored if the connection's dead already. Necessary if using GLib for event
handling for now. :-/

  • Property mode set to 100644
File size: 16.0 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        /* EAGAIN/etc or a successful read. */
244        return TRUE;
245}
246
247gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond )
248{
249        struct im_connection *ic = data;
250       
251        if( g_slist_find( jabber_connections, ic ) == NULL )
252                return FALSE;
253       
254        if( source == -1 )
255        {
256                imcb_error( ic, "Could not connect to server" );
257                imc_logout( ic, TRUE );
258                return FALSE;
259        }
260       
261        imcb_log( ic, "Connected to server, logging in" );
262       
263        return jabber_start_stream( ic );
264}
265
266gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond )
267{
268        struct im_connection *ic = data;
269        struct jabber_data *jd;
270       
271        if( g_slist_find( jabber_connections, ic ) == NULL )
272                return FALSE;
273       
274        jd = ic->proto_data;
275       
276        if( source == NULL )
277        {
278                /* The SSL connection will be cleaned up by the SSL lib
279                   already, set it to NULL here to prevent a double cleanup: */
280                jd->ssl = NULL;
281               
282                imcb_error( ic, "Could not connect to server" );
283                imc_logout( ic, TRUE );
284                return FALSE;
285        }
286       
287        imcb_log( ic, "Connected to server, logging in" );
288       
289        return jabber_start_stream( ic );
290}
291
292static xt_status jabber_end_of_stream( struct xt_node *node, gpointer data )
293{
294        imc_logout( data, TRUE );
295        return XT_ABORT;
296}
297
298static xt_status jabber_pkt_features( struct xt_node *node, gpointer data )
299{
300        struct im_connection *ic = data;
301        struct jabber_data *jd = ic->proto_data;
302        struct xt_node *c, *reply;
303        int trytls;
304       
305        trytls = g_strcasecmp( set_getstr( &ic->acc->set, "tls" ), "try" ) == 0;
306        c = xt_find_node( node->children, "starttls" );
307        if( c && !jd->ssl )
308        {
309                /* If the server advertises the STARTTLS feature and if we're
310                   not in a secure connection already: */
311               
312                c = xt_find_node( c->children, "required" );
313               
314                if( c && ( !trytls && !set_getbool( &ic->acc->set, "tls" ) ) )
315                {
316                        imcb_error( ic, "Server requires TLS connections, but TLS is turned off for this account" );
317                        imc_logout( ic, FALSE );
318                       
319                        return XT_ABORT;
320                }
321               
322                /* Only run this if the tls setting is set to true or try: */
323                if( ( trytls || set_getbool( &ic->acc->set, "tls" ) ) )
324                {
325                        reply = xt_new_node( "starttls", NULL, NULL );
326                        xt_add_attr( reply, "xmlns", XMLNS_TLS );
327                        if( !jabber_write_packet( ic, reply ) )
328                        {
329                                xt_free_node( reply );
330                                return XT_ABORT;
331                        }
332                        xt_free_node( reply );
333                       
334                        return XT_HANDLED;
335                }
336        }
337        else if( !c && !jd->ssl )
338        {
339                /* If the server does not advertise the STARTTLS feature and
340                   we're not in a secure connection already: (Servers have a
341                   habit of not advertising <starttls/> anymore when already
342                   using SSL/TLS. */
343               
344                if( !trytls && set_getbool( &ic->acc->set, "tls" ) )
345                {
346                        imcb_error( ic, "TLS is turned on for this account, but is not supported by this server" );
347                        imc_logout( ic, FALSE );
348                       
349                        return XT_ABORT;
350                }
351        }
352       
353        /* This one used to be in jabber_handlers[], but it has to be done
354           from here to make sure the TLS session will be initialized
355           properly before we attempt SASL authentication. */
356        if( ( c = xt_find_node( node->children, "mechanisms" ) ) )
357        {
358                if( sasl_pkt_mechanisms( c, data ) == XT_ABORT )
359                        return XT_ABORT;
360        }
361        /* If the server *SEEMS* to support SASL authentication but doesn't
362           support it after all, we should try to do authentication the
363           other way. jabber.com doesn't seem to do SASL while it pretends
364           to be XMPP 1.0 compliant! */
365        else if( !( jd->flags & JFLAG_AUTHENTICATED ) && sasl_supported( ic ) )
366        {
367                if( !jabber_init_iq_auth( ic ) )
368                        return XT_ABORT;
369        }
370       
371        if( ( c = xt_find_node( node->children, "bind" ) ) )
372        {
373                reply = xt_new_node( "bind", NULL, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) );
374                xt_add_attr( reply, "xmlns", XMLNS_BIND );
375                reply = jabber_make_packet( "iq", "set", NULL, reply );
376                jabber_cache_add( ic, reply, jabber_pkt_bind_sess );
377               
378                if( !jabber_write_packet( ic, reply ) )
379                        return XT_ABORT;
380               
381                jd->flags |= JFLAG_WAIT_BIND;
382        }
383       
384        if( ( c = xt_find_node( node->children, "session" ) ) )
385        {
386                reply = xt_new_node( "session", NULL, NULL );
387                xt_add_attr( reply, "xmlns", XMLNS_SESSION );
388                reply = jabber_make_packet( "iq", "set", NULL, reply );
389                jabber_cache_add( ic, reply, jabber_pkt_bind_sess );
390               
391                if( !jabber_write_packet( ic, reply ) )
392                        return XT_ABORT;
393               
394                jd->flags |= JFLAG_WAIT_SESSION;
395        }
396       
397        /* This flag is already set if we authenticated via SASL, so now
398           we can resume the session in the new stream, if we don't have
399           to bind/initialize the session. */
400        if( jd->flags & JFLAG_AUTHENTICATED && ( jd->flags & ( JFLAG_WAIT_BIND | JFLAG_WAIT_SESSION ) ) == 0 )
401        {
402                if( !jabber_get_roster( ic ) )
403                        return XT_ABORT;
404        }
405       
406        return XT_HANDLED;
407}
408
409static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )
410{
411        struct im_connection *ic = data;
412        struct jabber_data *jd = ic->proto_data;
413        char *xmlns;
414       
415        xmlns = xt_find_attr( node, "xmlns" );
416       
417        /* Just ignore it when it doesn't seem to be TLS-related (is that at
418           all possible??). */
419        if( !xmlns || strcmp( xmlns, XMLNS_TLS ) != 0 )
420                return XT_HANDLED;
421       
422        /* We don't want event handlers to touch our TLS session while it's
423           still initializing! */
424        b_event_remove( jd->r_inpa );
425        if( jd->tx_len > 0 )
426        {
427                /* Actually the write queue should be empty here, but just
428                   to be sure... */
429                b_event_remove( jd->w_inpa );
430                g_free( jd->txq );
431                jd->txq = NULL;
432                jd->tx_len = 0;
433        }
434        jd->w_inpa = jd->r_inpa = 0;
435       
436        imcb_log( ic, "Converting stream to TLS" );
437       
438        jd->ssl = ssl_starttls( jd->fd, jabber_connected_ssl, ic );
439       
440        return XT_HANDLED;
441}
442
443static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data )
444{
445        struct im_connection *ic = data;
446        int allow_reconnect = TRUE;
447        struct jabber_error *err;
448       
449        err = jabber_error_parse( node, XMLNS_STREAM_ERROR );
450       
451        /* Tssk... */
452        if( err->code == NULL )
453        {
454                imcb_error( ic, "Unknown stream error reported by server" );
455                imc_logout( ic, allow_reconnect );
456                jabber_error_free( err );
457                return XT_ABORT;
458        }
459       
460        /* We know that this is a fatal error. If it's a "conflict" error, we
461           should turn off auto-reconnect to make sure we won't get some nasty
462           infinite loop! */
463        if( strcmp( err->code, "conflict" ) == 0 )
464        {
465                imcb_error( ic, "Account and resource used from a different location" );
466                allow_reconnect = FALSE;
467        }
468        else
469        {
470                imcb_error( ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "",
471                            err->text ? err->text : "" );
472        }
473       
474        jabber_error_free( err );
475        imc_logout( ic, allow_reconnect );
476       
477        return XT_ABORT;
478}
479
480static xt_status jabber_xmlconsole( struct xt_node *node, gpointer data )
481{
482        struct im_connection *ic = data;
483        struct jabber_data *jd = ic->proto_data;
484       
485        if( jd->flags & JFLAG_XMLCONSOLE )
486        {
487                char *msg, *pkt;
488               
489                pkt = xt_to_string( node );
490                msg = g_strdup_printf( "RX: %s", pkt );
491                imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
492                g_free( msg );
493                g_free( pkt );
494        }
495       
496        return XT_NEXT;
497}
498
499static const struct xt_handler_entry jabber_handlers[] = {
500        { NULL,                 "stream:stream",        jabber_xmlconsole },
501        { "stream:stream",      "<root>",               jabber_end_of_stream },
502        { "message",            "stream:stream",        jabber_pkt_message },
503        { "presence",           "stream:stream",        jabber_pkt_presence },
504        { "iq",                 "stream:stream",        jabber_pkt_iq },
505        { "stream:features",    "stream:stream",        jabber_pkt_features },
506        { "stream:error",       "stream:stream",        jabber_pkt_stream_error },
507        { "proceed",            "stream:stream",        jabber_pkt_proceed_tls },
508        { "challenge",          "stream:stream",        sasl_pkt_challenge },
509        { "success",            "stream:stream",        sasl_pkt_result },
510        { "failure",            "stream:stream",        sasl_pkt_result },
511        { NULL,                 NULL,                   NULL }
512};
513
514gboolean jabber_start_stream( struct im_connection *ic )
515{
516        struct jabber_data *jd = ic->proto_data;
517        int st;
518        char *greet;
519       
520        /* We'll start our stream now, so prepare everything to receive one
521           from the server too. */
522        xt_free( jd->xt );      /* In case we're RE-starting. */
523        jd->xt = xt_new( ic );
524        jd->xt->handlers = (struct xt_handler_entry*) jabber_handlers;
525       
526        if( jd->r_inpa <= 0 )
527                jd->r_inpa = b_input_add( jd->fd, GAIM_INPUT_READ, jabber_read_callback, ic );
528       
529        greet = g_strdup_printf( "<?xml version='1.0' ?>"
530                                 "<stream:stream to=\"%s\" xmlns=\"jabber:client\" "
531                                  "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">", jd->server );
532       
533        st = jabber_write( ic, greet, strlen( greet ) );
534       
535        g_free( greet );
536       
537        return st;
538}
539
540void jabber_end_stream( struct im_connection *ic )
541{
542        struct jabber_data *jd = ic->proto_data;
543       
544        /* Let's only do this if the queue is currently empty, otherwise it'd
545           take too long anyway. */
546        if( jd->tx_len == 0 )
547        {
548                char eos[] = "</stream:stream>";
549                struct xt_node *node;
550                int st = 1;
551               
552                if( ic->flags & OPT_LOGGED_IN )
553                {
554                        node = jabber_make_packet( "presence", "unavailable", NULL, NULL );
555                        st = jabber_write_packet( ic, node );
556                        xt_free_node( node );
557                }
558               
559                if( st )
560                        jabber_write( ic, eos, strlen( eos ) );
561        }
562}
Note: See TracBrowser for help on using the repository browser.