source: protocols/jabber/io.c @ fe7a554

Last change on this file since fe7a554 was fe7a554, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-09-22T18:39:31Z

Better detection of successful IQ authentication (using packet caching),
properly working SASL authentication (although only PLAIN so far).

  • Property mode set to 100644
File size: 9.8 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
26static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond );
27
28int jabber_write_packet( struct gaim_connection *gc, struct xt_node *node )
29{
30        char *buf;
31        int st;
32       
33        buf = xt_to_string( node );
34        st = jabber_write( gc, buf, strlen( buf ) );
35        g_free( buf );
36       
37        return st;
38}
39
40int jabber_write( struct gaim_connection *gc, char *buf, int len )
41{
42        struct jabber_data *jd = gc->proto_data;
43       
44        if( jd->tx_len == 0 )
45        {
46                /* If the queue is empty, allocate a new buffer. */
47                jd->tx_len = len;
48                jd->txq = g_memdup( buf, len );
49               
50                /* Try if we can write it immediately so we don't have to do
51                   it via the event handler. If not, add the handler. (In
52                   most cases it probably won't be necessary.) */
53                if( jabber_write_callback( gc, jd->fd, GAIM_INPUT_WRITE ) )
54                        jd->w_inpa = b_input_add( jd->fd, GAIM_INPUT_WRITE, jabber_write_callback, gc );
55        }
56        else
57        {
58                /* Just add it to the buffer if it's already filled. The
59                   event handler is already set. */
60                jd->txq = g_renew( char, jd->txq, jd->tx_len + len );
61                memcpy( jd->txq + jd->tx_len, buf, len );
62                jd->tx_len += len;
63        }
64       
65        /* FIXME: write_callback could've generated a real error! */
66        return 1;
67}
68
69static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond )
70{
71        struct gaim_connection *gc = data;
72        struct jabber_data *jd = gc->proto_data;
73        int st;
74       
75        if( jd->fd == -1 )
76                return FALSE;
77       
78        st = write( jd->fd, jd->txq, jd->tx_len );
79       
80        if( st == jd->tx_len )
81        {
82                /* We wrote everything, clear the buffer. */
83                g_free( jd->txq );
84                jd->txq = NULL;
85                jd->tx_len = 0;
86               
87                return FALSE;
88        }
89        else if( st == 0 || ( st < 0 && !sockerr_again() ) )
90        {
91                /* Set fd to -1 to make sure we won't write to it anymore. */
92                closesocket( jd->fd );  /* Shouldn't be necessary after errors? */
93                jd->fd = -1;
94               
95                hide_login_progress_error( gc, "Short write() to server" );
96                signoff( gc );
97                return FALSE;
98        }
99        else if( st > 0 )
100        {
101                char *s;
102               
103                s = g_memdup( jd->txq + st, jd->tx_len - st );
104                jd->tx_len -= st;
105                g_free( jd->txq );
106                jd->txq = s;
107               
108                return TRUE;
109        }
110        else
111        {
112                /* Just in case we had EINPROGRESS/EAGAIN: */
113               
114                return TRUE;
115        }
116}
117
118static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition cond )
119{
120        struct gaim_connection *gc = data;
121        struct jabber_data *jd = gc->proto_data;
122        char buf[512];
123        int st;
124       
125        if( jd->fd == -1 )
126                return FALSE;
127       
128        st = read( fd, buf, sizeof( buf ) );
129       
130        if( st > 0 )
131        {
132                /* Parse. */
133                if( !xt_feed( jd->xt, buf, st ) )
134                {
135                        hide_login_progress_error( gc, "XML stream error" );
136                        signoff( gc );
137                        return FALSE;
138                }
139               
140                /* Execute all handlers. */
141                if( !xt_handle( jd->xt, NULL ) )
142                {
143                        /* Don't do anything, the handlers should have
144                           aborted the connection already... Or not? FIXME */
145                        return FALSE;
146                }
147               
148                if( jd->flags & JFLAG_STREAM_RESTART )
149                {
150                        jd->flags &= ~JFLAG_STREAM_RESTART;
151                        jabber_start_stream( gc );
152                }
153               
154                /* Garbage collection. */
155                xt_cleanup( jd->xt, NULL );
156               
157                /* This is a bit hackish, unfortunately. Although xmltree
158                   has nifty event handler stuff, it only calls handlers
159                   when nodes are complete. Since the server should only
160                   send an opening <stream:stream> tag, we have to check
161                   this by hand. :-( */
162                if( !( jd->flags & JFLAG_STREAM_STARTED ) && jd->xt && jd->xt->root )
163                {
164                        if( g_strcasecmp( jd->xt->root->name, "stream:stream" ) == 0 )
165                        {
166                                jd->flags |= JFLAG_STREAM_STARTED;
167                               
168                                /* If there's no version attribute, assume
169                                   this is an old server that can't do SASL
170                                   authentication. */
171                                if( !sasl_supported( gc ) )
172                                        return jabber_start_iq_auth( gc );
173                        }
174                        else
175                        {
176                                hide_login_progress_error( gc, "XML stream error" );
177                                signoff( gc );
178                                return FALSE;
179                        }
180                }
181        }
182        else if( st == 0 || ( st < 0 && !sockerr_again() ) )
183        {
184                closesocket( jd->fd );
185                jd->fd = -1;
186               
187                hide_login_progress_error( gc, "Error while reading from server" );
188                signoff( gc );
189                return FALSE;
190        }
191       
192        /* EAGAIN/etc or a successful read. */
193        return TRUE;
194}
195
196gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond )
197{
198        struct gaim_connection *gc = data;
199       
200        if( source == -1 )
201        {
202                hide_login_progress( gc, "Could not connect to server" );
203                signoff( gc );
204                return FALSE;
205        }
206       
207        set_login_progress( gc, 1, "Connected to server, logging in" );
208       
209        return jabber_start_stream( gc );
210}
211
212static xt_status jabber_end_of_stream( struct xt_node *node, gpointer data )
213{
214        return XT_ABORT;
215}
216
217static xt_status jabber_pkt_features( struct xt_node *node, gpointer data )
218{
219        struct gaim_connection *gc = data;
220        struct jabber_data *jd = gc->proto_data;
221        struct xt_node *c, *reply;
222       
223        c = xt_find_node( node->children, "starttls" );
224        if( c )
225        {
226                /*
227                jd->flags |= JFLAG_SUPPORTS_TLS;
228                if( xt_find_node( c->children, "required" ) )
229                        jd->flags |= JFLAG_REQUIRES_TLS;
230                */
231        }
232       
233        if( ( c = xt_find_node( node->children, "bind" ) ) )
234        {
235                reply = xt_new_node( "bind", NULL, xt_new_node( "resource", set_getstr( &gc->acc->set, "resource" ), NULL ) );
236                xt_add_attr( reply, "xmlns", "urn:ietf:params:xml:ns:xmpp-bind" );
237                reply = jabber_make_packet( "iq", "set", NULL, reply );
238                jabber_cache_packet( gc, reply );
239               
240                if( !jabber_write_packet( gc, reply ) )
241                        return XT_ABORT;
242               
243                jd->flags |= JFLAG_WAIT_BIND;
244        }
245       
246        if( ( c = xt_find_node( node->children, "session" ) ) )
247        {
248                reply = xt_new_node( "session", NULL, NULL );
249                xt_add_attr( reply, "xmlns", "urn:ietf:params:xml:ns:xmpp-session" );
250                reply = jabber_make_packet( "iq", "set", NULL, reply );
251                jabber_cache_packet( gc, reply );
252               
253                if( !jabber_write_packet( gc, reply ) )
254                        return XT_ABORT;
255               
256                jd->flags |= JFLAG_WAIT_SESSION;
257        }
258       
259        /* This flag is already set if we authenticated via SASL, so now
260           we can resume the session in the new stream, if we don't have
261           to bind/initialize the session. */
262        if( jd->flags & JFLAG_AUTHENTICATED && ( jd->flags & ( JFLAG_WAIT_BIND | JFLAG_WAIT_SESSION ) ) == 0 )
263        {
264                if( !jabber_get_roster( gc ) )
265                        return XT_ABORT;
266        }
267       
268        return XT_HANDLED;
269}
270
271static xt_status jabber_pkt_misc( struct xt_node *node, gpointer data )
272{
273        printf( "Received unknown packet:\n" );
274        xt_print( node );
275       
276        return XT_HANDLED;
277}
278
279static const struct xt_handler_entry jabber_handlers[] = {
280        { "stream:stream",      "<root>",               jabber_end_of_stream },
281        { "message",            "stream:stream",        jabber_pkt_message },
282        { "presence",           "stream:stream",        jabber_pkt_presence },
283        { "iq",                 "stream:stream",        jabber_pkt_iq },
284        { "stream:features",    "stream:stream",        jabber_pkt_features },
285        { "mechanisms",         "stream:features",      sasl_pkt_mechanisms },
286        { "challenge",          "stream:stream",        sasl_pkt_challenge },
287        { "success",            "stream:stream",        sasl_pkt_result },
288        { "failure",            "stream:stream",        sasl_pkt_result },
289        { NULL,                 "stream:stream",        jabber_pkt_misc },
290        { NULL,                 NULL,                   NULL }
291};
292
293gboolean jabber_start_stream( struct gaim_connection *gc )
294{
295        struct jabber_data *jd = gc->proto_data;
296        int st;
297        char *greet;
298       
299        /* We'll start our stream now, so prepare everything to receive one
300           from the server too. */
301        xt_free( jd->xt );      /* In case we're RE-starting. */
302        jd->xt = xt_new( gc );
303        jd->xt->handlers = (struct xt_handler_entry*) jabber_handlers;
304       
305        if( jd->r_inpa <= 0 )
306                jd->r_inpa = b_input_add( jd->fd, GAIM_INPUT_READ, jabber_read_callback, gc );
307       
308        greet = g_strdup_printf( "<?xml version='1.0' ?>"
309                                 "<stream:stream to=\"%s\" xmlns=\"jabber:client\" "
310                                  "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">", jd->server );
311       
312        st = jabber_write( gc, greet, strlen( greet ) );
313       
314        g_free( greet );
315       
316        return st;
317}
318
319void jabber_end_stream( struct gaim_connection *gc )
320{
321        struct jabber_data *jd = gc->proto_data;
322       
323        /* Let's only do this if the queue is currently empty, otherwise it'd
324           take too long anyway. */
325        if( jd->tx_len == 0 )
326        {
327                char eos[] = "</stream:stream>";
328                struct xt_node *node;
329                int st = 1;
330               
331                if( gc->flags & OPT_LOGGED_IN )
332                {
333                        node = jabber_make_packet( "presence", "unavailable", NULL, NULL );
334                        st = jabber_write_packet( gc, node );
335                        xt_free_node( node );
336                }
337               
338                if( st )
339                        jabber_write( gc, eos, strlen( eos ) );
340        }
341}
Note: See TracBrowser for help on using the repository browser.