source: protocols/jabber/io.c @ e900442

Last change on this file since e900442 was bb95d43, checked in by Wilmer van der Gaast <wilmer@…>, at 2007-06-04T11:32:37Z

Added a real XML-console to the Jabber module! Add the handle "xmlconsole"
(without any @server part) to your contact list and you'll see all XMPP
traffic going in and out, and messages sent to the buddy will be sent as
packets to the server.

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