source: protocols/jabber/io.c @ cc2cb2d

Last change on this file since cc2cb2d was cc2cb2d, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-10-04T18:14:41Z

Lack of TLS support is also detected now if the server doesn't support
XMPP 1.0 (properly), and restored immediate writes by splitting up the
jabber_write_callback() function.

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