source: protocols/jabber/io.c @ c1ed6527

Last change on this file since c1ed6527 was c1ed6527, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-10-02T13:19:13Z

No more double free()/crashes when trying to set up an SSL connection to
a non-SSL server, and better handling of TLS connection setup by initializing
the TLS session from a callback function (which guarantees a valid return
value from ssl_starttls() before any error callback could be called).

  • Property mode set to 100644
File size: 13.4 KB
RevLine 
[21167d2]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"
[42127dc]25#include "ssl_client.h"
[21167d2]26
27static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond );
28
29int jabber_write_packet( struct gaim_connection *gc, struct xt_node *node )
30{
31        char *buf;
32        int st;
33       
34        buf = xt_to_string( node );
35        st = jabber_write( gc, buf, strlen( buf ) );
36        g_free( buf );
37       
38        return st;
39}
40
41int jabber_write( struct gaim_connection *gc, char *buf, int len )
42{
43        struct jabber_data *jd = gc->proto_data;
44       
45        if( jd->tx_len == 0 )
46        {
47                /* If the queue is empty, allocate a new buffer. */
48                jd->tx_len = len;
49                jd->txq = g_memdup( buf, len );
50               
51                /* Try if we can write it immediately so we don't have to do
52                   it via the event handler. If not, add the handler. (In
53                   most cases it probably won't be necessary.) */
54                if( jabber_write_callback( gc, jd->fd, GAIM_INPUT_WRITE ) )
55                        jd->w_inpa = b_input_add( jd->fd, GAIM_INPUT_WRITE, jabber_write_callback, gc );
56        }
57        else
58        {
59                /* Just add it to the buffer if it's already filled. The
60                   event handler is already set. */
61                jd->txq = g_renew( char, jd->txq, jd->tx_len + len );
62                memcpy( jd->txq + jd->tx_len, buf, len );
63                jd->tx_len += len;
64        }
65       
66        /* FIXME: write_callback could've generated a real error! */
67        return 1;
68}
69
70static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond )
71{
72        struct gaim_connection *gc = data;
73        struct jabber_data *jd = gc->proto_data;
74        int st;
75       
[8d74291]76        if( jd->fd == -1 )
77                return FALSE;
78       
[42127dc]79        if( jd->ssl )
80                st = ssl_write( jd->ssl, jd->txq, jd->tx_len );
81        else
82                st = write( jd->fd, jd->txq, jd->tx_len );
83       
84//      if( st > 0 ) write( 1, jd->txq, st );
[21167d2]85       
86        if( st == jd->tx_len )
87        {
88                /* We wrote everything, clear the buffer. */
89                g_free( jd->txq );
90                jd->txq = NULL;
91                jd->tx_len = 0;
92               
93                return FALSE;
94        }
95        else if( st == 0 || ( st < 0 && !sockerr_again() ) )
96        {
[8d74291]97                /* Set fd to -1 to make sure we won't write to it anymore. */
98                closesocket( jd->fd );  /* Shouldn't be necessary after errors? */
99                jd->fd = -1;
100               
[21167d2]101                hide_login_progress_error( gc, "Short write() to server" );
102                signoff( gc );
103                return FALSE;
104        }
105        else if( st > 0 )
106        {
107                char *s;
108               
109                s = g_memdup( jd->txq + st, jd->tx_len - st );
110                jd->tx_len -= st;
111                g_free( jd->txq );
112                jd->txq = s;
113               
[8d74291]114                return TRUE;
[21167d2]115        }
116        else
117        {
118                /* Just in case we had EINPROGRESS/EAGAIN: */
119               
120                return TRUE;
121        }
122}
123
124static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition cond )
125{
126        struct gaim_connection *gc = data;
127        struct jabber_data *jd = gc->proto_data;
128        char buf[512];
129        int st;
130       
[8d74291]131        if( jd->fd == -1 )
132                return FALSE;
133       
[42127dc]134        if( jd->ssl )
135                st = ssl_read( jd->ssl, buf, sizeof( buf ) );
136        else
137                st = read( jd->fd, buf, sizeof( buf ) );
138       
139//      if( st > 0 ) write( 1, buf, st );
[21167d2]140       
141        if( st > 0 )
142        {
143                /* Parse. */
144                if( !xt_feed( jd->xt, buf, st ) )
145                {
146                        hide_login_progress_error( gc, "XML stream error" );
147                        signoff( gc );
148                        return FALSE;
149                }
150               
151                /* Execute all handlers. */
152                if( !xt_handle( jd->xt, NULL ) )
153                {
154                        /* Don't do anything, the handlers should have
155                           aborted the connection already... Or not? FIXME */
156                        return FALSE;
157                }
158               
[5997488]159                if( jd->flags & JFLAG_STREAM_RESTART )
160                {
161                        jd->flags &= ~JFLAG_STREAM_RESTART;
162                        jabber_start_stream( gc );
163                }
164               
[21167d2]165                /* Garbage collection. */
166                xt_cleanup( jd->xt, NULL );
167               
168                /* This is a bit hackish, unfortunately. Although xmltree
169                   has nifty event handler stuff, it only calls handlers
170                   when nodes are complete. Since the server should only
171                   send an opening <stream:stream> tag, we have to check
172                   this by hand. :-( */
173                if( !( jd->flags & JFLAG_STREAM_STARTED ) && jd->xt && jd->xt->root )
174                {
175                        if( g_strcasecmp( jd->xt->root->name, "stream:stream" ) == 0 )
176                        {
177                                jd->flags |= JFLAG_STREAM_STARTED;
[8d74291]178                               
179                                /* If there's no version attribute, assume
180                                   this is an old server that can't do SASL
181                                   authentication. */
182                                if( !sasl_supported( gc ) )
183                                        return jabber_start_iq_auth( gc );
[21167d2]184                        }
185                        else
186                        {
187                                hide_login_progress_error( gc, "XML stream error" );
188                                signoff( gc );
189                                return FALSE;
190                        }
191                }
192        }
193        else if( st == 0 || ( st < 0 && !sockerr_again() ) )
194        {
[8d74291]195                closesocket( jd->fd );
196                jd->fd = -1;
197               
[21167d2]198                hide_login_progress_error( gc, "Error while reading from server" );
199                signoff( gc );
200                return FALSE;
201        }
202       
203        /* EAGAIN/etc or a successful read. */
204        return TRUE;
205}
206
207gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond )
208{
209        struct gaim_connection *gc = data;
210       
211        if( source == -1 )
212        {
213                hide_login_progress( gc, "Could not connect to server" );
214                signoff( gc );
215                return FALSE;
216        }
217       
218        set_login_progress( gc, 1, "Connected to server, logging in" );
219       
220        return jabber_start_stream( gc );
221}
222
[42127dc]223gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond )
224{
225        struct gaim_connection *gc = data;
[c1ed6527]226        struct jabber_data *jd = gc->proto_data;
[42127dc]227       
228        if( source == NULL )
229        {
[c1ed6527]230                /* The SSL connection will be cleaned up by the SSL lib
231                   already, set it to NULL here to prevent a double cleanup: */
232                jd->ssl = NULL;
233               
[42127dc]234                hide_login_progress( gc, "Could not connect to server" );
235                signoff( gc );
236                return FALSE;
237        }
238       
239        set_login_progress( gc, 1, "Connected to server, logging in" );
240       
241        return jabber_start_stream( gc );
242}
243
[21167d2]244static xt_status jabber_end_of_stream( struct xt_node *node, gpointer data )
245{
246        return XT_ABORT;
247}
248
[8d74291]249static xt_status jabber_pkt_features( struct xt_node *node, gpointer data )
250{
251        struct gaim_connection *gc = data;
252        struct jabber_data *jd = gc->proto_data;
[fe7a554]253        struct xt_node *c, *reply;
[e101506]254        int trytls;
[8d74291]255       
[e101506]256        trytls = g_strcasecmp( set_getstr( &gc->acc->set, "tls" ), "try" ) == 0;
[8d74291]257        c = xt_find_node( node->children, "starttls" );
[42127dc]258        if( c && !jd->ssl )
[8d74291]259        {
[42127dc]260                /* If the server advertises the STARTTLS feature and if we're
261                   not in a secure connection already: */
262               
263                c = xt_find_node( c->children, "required" );
264               
[e101506]265                if( c && ( !trytls && !set_getbool( &gc->acc->set, "tls" ) ) )
266                {
267                        hide_login_progress( gc, "Server requires TLS connections, but TLS is turned off for this account" );
268                        signoff( gc );
269                       
270                        return XT_ABORT;
271                }
272               
[42127dc]273                /* Only run this if the tls setting is set to true or try: */
[e101506]274                if( ( trytls || set_getbool( &gc->acc->set, "tls" ) ) )
[42127dc]275                {
276                        reply = xt_new_node( "starttls", NULL, NULL );
277                        xt_add_attr( reply, "xmlns", "urn:ietf:params:xml:ns:xmpp-tls" );
278                        if( !jabber_write_packet( gc, reply ) )
279                        {
280                                xt_free_node( reply );
281                                return XT_ABORT;
282                        }
283                        xt_free_node( reply );
284                       
285                        return XT_HANDLED;
286                }
287        }
[e101506]288        else if( !c && !jd->ssl )
[42127dc]289        {
[e101506]290                /* If the server does not advertise the STARTTLS feature and
291                   we're not in a secure connection already: (Servers have a
292                   habit of not advertising <starttls/> anymore when already
293                   using SSL/TLS. */
294               
295                if( !trytls && set_getbool( &gc->acc->set, "tls" ) )
296                {
297                        hide_login_progress( gc, "TLS is turned on for this account, but is not supported by this server" );
298                        signoff( gc );
299                       
300                        return XT_ABORT;
301                }
[8d74291]302        }
303       
[42127dc]304        /* This one used to be in jabber_handlers[], but it has to be done
305           from here to make sure the TLS session will be initialized
306           properly before we attempt SASL authentication. */
307        if( ( c = xt_find_node( node->children, "mechanisms" ) ) )
[0e2d97f]308        {
[42127dc]309                if( sasl_pkt_mechanisms( c, data ) == XT_ABORT )
310                        return XT_ABORT;
[0e2d97f]311        }
[88591fd]312        /* If the server *SEEMS* to support SASL authentication but doesn't
313           support it after all, we should try to do authentication the
314           other way. jabber.com doesn't seem to do SASL while it pretends
315           to be XMPP 1.0 compliant! */
316        else if( !( jd->flags & JFLAG_AUTHENTICATED ) && sasl_supported( gc ) )
[0e2d97f]317        {
[88591fd]318                if( !jabber_start_iq_auth( gc ) )
319                        return XT_ABORT;
[0e2d97f]320        }
[42127dc]321       
[fe7a554]322        if( ( c = xt_find_node( node->children, "bind" ) ) )
323        {
324                reply = xt_new_node( "bind", NULL, xt_new_node( "resource", set_getstr( &gc->acc->set, "resource" ), NULL ) );
325                xt_add_attr( reply, "xmlns", "urn:ietf:params:xml:ns:xmpp-bind" );
326                reply = jabber_make_packet( "iq", "set", NULL, reply );
327                jabber_cache_packet( gc, reply );
328               
329                if( !jabber_write_packet( gc, reply ) )
330                        return XT_ABORT;
331               
332                jd->flags |= JFLAG_WAIT_BIND;
333        }
334       
335        if( ( c = xt_find_node( node->children, "session" ) ) )
336        {
337                reply = xt_new_node( "session", NULL, NULL );
338                xt_add_attr( reply, "xmlns", "urn:ietf:params:xml:ns:xmpp-session" );
339                reply = jabber_make_packet( "iq", "set", NULL, reply );
340                jabber_cache_packet( gc, reply );
341               
342                if( !jabber_write_packet( gc, reply ) )
343                        return XT_ABORT;
344               
345                jd->flags |= JFLAG_WAIT_SESSION;
346        }
347       
[8d74291]348        /* This flag is already set if we authenticated via SASL, so now
[fe7a554]349           we can resume the session in the new stream, if we don't have
350           to bind/initialize the session. */
351        if( jd->flags & JFLAG_AUTHENTICATED && ( jd->flags & ( JFLAG_WAIT_BIND | JFLAG_WAIT_SESSION ) ) == 0 )
[8d74291]352        {
[6baca2a]353                if( !jabber_get_roster( gc ) || !jabber_get_privacy( gc ) )
[8d74291]354                        return XT_ABORT;
355        }
356       
357        return XT_HANDLED;
358}
359
[42127dc]360static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )
361{
362        struct gaim_connection *gc = data;
363        struct jabber_data *jd = gc->proto_data;
364        char *xmlns;
365       
366        xmlns = xt_find_attr( node, "xmlns" );
367       
368        /* Just ignore it when it doesn't seem to be TLS-related (is that at
369           all possible??). */
370        if( !xmlns || strcmp( xmlns, "urn:ietf:params:xml:ns:xmpp-tls" ) != 0 )
371                return XT_HANDLED;
372       
373        /* We don't want event handlers to touch our TLS session while it's
374           still initializing! */
375        b_event_remove( jd->r_inpa );
376        if( jd->tx_len > 0 )
377        {
378                /* Actually the write queue should be empty here, but just
379                   to be sure... */
380                b_event_remove( jd->w_inpa );
381                g_free( jd->txq );
382                jd->txq = NULL;
383                jd->tx_len = 0;
384        }
385        jd->w_inpa = jd->r_inpa = 0;
386       
387        set_login_progress( gc, 1, "Converting stream to TLS" );
388       
389        jd->ssl = ssl_starttls( jd->fd, jabber_connected_ssl, gc );
390       
391        return XT_HANDLED;
392}
393
[21167d2]394static xt_status jabber_pkt_misc( struct xt_node *node, gpointer data )
395{
396        printf( "Received unknown packet:\n" );
397        xt_print( node );
398       
399        return XT_HANDLED;
400}
401
402static const struct xt_handler_entry jabber_handlers[] = {
403        { "stream:stream",      "<root>",               jabber_end_of_stream },
404        { "message",            "stream:stream",        jabber_pkt_message },
405        { "presence",           "stream:stream",        jabber_pkt_presence },
[8d74291]406        { "iq",                 "stream:stream",        jabber_pkt_iq },
407        { "stream:features",    "stream:stream",        jabber_pkt_features },
[42127dc]408        { "proceed",            "stream:stream",        jabber_pkt_proceed_tls },
[5997488]409        { "challenge",          "stream:stream",        sasl_pkt_challenge },
410        { "success",            "stream:stream",        sasl_pkt_result },
411        { "failure",            "stream:stream",        sasl_pkt_result },
[21167d2]412        { NULL,                 "stream:stream",        jabber_pkt_misc },
413        { NULL,                 NULL,                   NULL }
414};
415
[4a0614e]416gboolean jabber_start_stream( struct gaim_connection *gc )
[21167d2]417{
418        struct jabber_data *jd = gc->proto_data;
419        int st;
420        char *greet;
421       
422        /* We'll start our stream now, so prepare everything to receive one
423           from the server too. */
424        xt_free( jd->xt );      /* In case we're RE-starting. */
425        jd->xt = xt_new( gc );
426        jd->xt->handlers = (struct xt_handler_entry*) jabber_handlers;
427       
[8d74291]428        if( jd->r_inpa <= 0 )
429                jd->r_inpa = b_input_add( jd->fd, GAIM_INPUT_READ, jabber_read_callback, gc );
[21167d2]430       
431        greet = g_strdup_printf( "<?xml version='1.0' ?>"
432                                 "<stream:stream to=\"%s\" xmlns=\"jabber:client\" "
[5997488]433                                  "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">", jd->server );
[21167d2]434       
435        st = jabber_write( gc, greet, strlen( greet ) );
436       
437        g_free( greet );
438       
439        return st;
440}
[4a0614e]441
[5bcf70a]442void jabber_end_stream( struct gaim_connection *gc )
[4a0614e]443{
444        struct jabber_data *jd = gc->proto_data;
445       
446        /* Let's only do this if the queue is currently empty, otherwise it'd
447           take too long anyway. */
[5bcf70a]448        if( jd->tx_len == 0 )
449        {
450                char eos[] = "</stream:stream>";
451                struct xt_node *node;
[8d74291]452                int st = 1;
[5bcf70a]453               
[8d74291]454                if( gc->flags & OPT_LOGGED_IN )
455                {
456                        node = jabber_make_packet( "presence", "unavailable", NULL, NULL );
457                        st = jabber_write_packet( gc, node );
458                        xt_free_node( node );
459                }
[5bcf70a]460               
461                if( st )
462                        jabber_write( gc, eos, strlen( eos ) );
463        }
[4a0614e]464}
Note: See TracBrowser for help on using the repository browser.