source: protocols/jabber/sasl.c @ 51a799e

Last change on this file since 51a799e was af97b23, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-02-16T13:17:52Z

Improved sasl_get_part() to deal with whitespace in challenge strings, as
described in RFC 2831 secion 7.1 (the #rule description). Closes bug #362.

  • Property mode set to 100644
File size: 9.4 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - SASL authentication                                      *
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 <ctype.h>
25
26#include "jabber.h"
27#include "base64.h"
28
29xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data )
30{
31        struct im_connection *ic = data;
32        struct jabber_data *jd = ic->proto_data;
33        struct xt_node *c, *reply;
34        char *s;
35        int sup_plain = 0, sup_digest = 0;
36       
37        if( !sasl_supported( ic ) )
38        {
39                /* Should abort this now, since we should already be doing
40                   IQ authentication. Strange things happen when you try
41                   to do both... */
42                imcb_log( ic, "XMPP 1.0 non-compliant server seems to support SASL, please report this as a BitlBee bug!" );
43                return XT_HANDLED;
44        }
45       
46        s = xt_find_attr( node, "xmlns" );
47        if( !s || strcmp( s, XMLNS_SASL ) != 0 )
48        {
49                imcb_log( ic, "Stream error while authenticating" );
50                imc_logout( ic, FALSE );
51                return XT_ABORT;
52        }
53       
54        c = node->children;
55        while( ( c = xt_find_node( c, "mechanism" ) ) )
56        {
57                if( c->text && g_strcasecmp( c->text, "PLAIN" ) == 0 )
58                        sup_plain = 1;
59                if( c->text && g_strcasecmp( c->text, "DIGEST-MD5" ) == 0 )
60                        sup_digest = 1;
61               
62                c = c->next;
63        }
64       
65        if( !sup_plain && !sup_digest )
66        {
67                imcb_error( ic, "No known SASL authentication schemes supported" );
68                imc_logout( ic, FALSE );
69                return XT_ABORT;
70        }
71       
72        reply = xt_new_node( "auth", NULL, NULL );
73        xt_add_attr( reply, "xmlns", XMLNS_SASL );
74       
75        if( sup_digest )
76        {
77                xt_add_attr( reply, "mechanism", "DIGEST-MD5" );
78               
79                /* The rest will be done later, when we receive a <challenge/>. */
80        }
81        else if( sup_plain )
82        {
83                int len;
84               
85                xt_add_attr( reply, "mechanism", "PLAIN" );
86               
87                /* With SASL PLAIN in XMPP, the text should be b64(\0user\0pass) */
88                len = strlen( jd->username ) + strlen( ic->acc->pass ) + 2;
89                s = g_malloc( len + 1 );
90                s[0] = 0;
91                strcpy( s + 1, jd->username );
92                strcpy( s + 2 + strlen( jd->username ), ic->acc->pass );
93                reply->text = base64_encode( (unsigned char *)s, len );
94                reply->text_len = strlen( reply->text );
95                g_free( s );
96        }
97       
98        if( !jabber_write_packet( ic, reply ) )
99        {
100                xt_free_node( reply );
101                return XT_ABORT;
102        }
103        xt_free_node( reply );
104       
105        /* To prevent classic authentication from happening. */
106        jd->flags |= JFLAG_STREAM_STARTED;
107       
108        return XT_HANDLED;
109}
110
111/* Non-static function, but not mentioned in jabber.h because it's for internal
112   use, just that the unittest should be able to reach it... */
113char *sasl_get_part( char *data, char *field )
114{
115        int i, len;
116       
117        len = strlen( field );
118       
119        while( isspace( *data ) || *data == ',' )
120                data ++;
121       
122        if( g_strncasecmp( data, field, len ) == 0 && data[len] == '=' )
123        {
124                i = strlen( field ) + 1;
125        }
126        else
127        {
128                for( i = 0; data[i]; i ++ )
129                {
130                        /* If we have a ", skip until it's closed again. */
131                        if( data[i] == '"' )
132                        {
133                                i ++;
134                                while( data[i] != '"' || data[i-1] == '\\' )
135                                        i ++;
136                        }
137                       
138                        /* If we got a comma, we got a new field. Check it,
139                           find the next key after it. */
140                        if( data[i] == ',' )
141                        {
142                                while( isspace( data[i] ) || data[i] == ',' )
143                                        i ++;
144                               
145                                if( g_strncasecmp( data + i, field, len ) == 0 &&
146                                    data[i+len] == '=' )
147                                {
148                                        i += len + 1;
149                                        break;
150                                }
151                        }
152                }
153        }
154       
155        if( data[i] == '"' )
156        {
157                int j;
158                char *ret;
159               
160                i ++;
161                len = 0;
162                while( data[i+len] != '"' || data[i+len-1] == '\\' )
163                        len ++;
164               
165                ret = g_strndup( data + i, len );
166                for( i = j = 0; ret[i]; i ++ )
167                {
168                        if( ret[i] == '\\' )
169                        {
170                                ret[j++] = ret[++i];
171                        }
172                        else
173                        {
174                                ret[j++] = ret[i];
175                        }
176                }
177                ret[j] = 0;
178               
179                return ret;
180        }
181        else if( data[i] )
182        {
183                len = 0;
184                while( data[i+len] && data[i+len] != ',' )
185                        len ++;
186               
187                return g_strndup( data + i, len );
188        }
189        else
190        {
191                return NULL;
192        }
193}
194
195xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data )
196{
197        struct im_connection *ic = data;
198        struct jabber_data *jd = ic->proto_data;
199        struct xt_node *reply = NULL;
200        char *nonce = NULL, *realm = NULL, *cnonce = NULL;
201        unsigned char cnonce_bin[30];
202        char *digest_uri = NULL;
203        char *dec = NULL;
204        char *s = NULL;
205        xt_status ret = XT_ABORT;
206       
207        if( node->text_len == 0 )
208                goto error;
209       
210        dec = frombase64( node->text );
211       
212        if( !( s = sasl_get_part( dec, "rspauth" ) ) )
213        {
214                /* See RFC 2831 for for information. */
215                md5_state_t A1, A2, H;
216                md5_byte_t A1r[16], A2r[16], Hr[16];
217                char A1h[33], A2h[33], Hh[33];
218                int i;
219               
220                nonce = sasl_get_part( dec, "nonce" );
221                realm = sasl_get_part( dec, "realm" );
222               
223                if( !nonce )
224                        goto error;
225               
226                /* Jabber.Org considers the realm part optional and doesn't
227                   specify one. Oh well, actually they're right, but still,
228                   don't know if this is right... */
229                if( !realm )
230                        realm = g_strdup( jd->server );
231               
232                random_bytes( cnonce_bin, sizeof( cnonce_bin ) );
233                cnonce = base64_encode( cnonce_bin, sizeof( cnonce_bin ) );
234                digest_uri = g_strdup_printf( "%s/%s", "xmpp", jd->server );
235               
236                /* Generate the MD5 hash of username:realm:password,
237                   I decided to call it H. */
238                md5_init( &H );
239                s = g_strdup_printf( "%s:%s:%s", jd->username, realm, ic->acc->pass );
240                md5_append( &H, (unsigned char *) s, strlen( s ) );
241                g_free( s );
242                md5_finish( &H, Hr );
243               
244                /* Now generate the hex. MD5 hash of H:nonce:cnonce, called A1. */
245                md5_init( &A1 );
246                s = g_strdup_printf( ":%s:%s", nonce, cnonce );
247                md5_append( &A1, Hr, 16 );
248                md5_append( &A1, (unsigned char *) s, strlen( s ) );
249                g_free( s );
250                md5_finish( &A1, A1r );
251                for( i = 0; i < 16; i ++ )
252                        sprintf( A1h + i * 2, "%02x", A1r[i] );
253               
254                /* A2... */
255                md5_init( &A2 );
256                s = g_strdup_printf( "%s:%s", "AUTHENTICATE", digest_uri );
257                md5_append( &A2, (unsigned char *) s, strlen( s ) );
258                g_free( s );
259                md5_finish( &A2, A2r );
260                for( i = 0; i < 16; i ++ )
261                        sprintf( A2h + i * 2, "%02x", A2r[i] );
262               
263                /* Final result: A1:nonce:00000001:cnonce:auth:A2. Let's reuse H for it. */
264                md5_init( &H );
265                s = g_strdup_printf( "%s:%s:%s:%s:%s:%s", A1h, nonce, "00000001", cnonce, "auth", A2h );
266                md5_append( &H, (unsigned char *) s, strlen( s ) );
267                g_free( s );
268                md5_finish( &H, Hr );
269                for( i = 0; i < 16; i ++ )
270                        sprintf( Hh + i * 2, "%02x", Hr[i] );
271               
272                /* Now build the SASL response string: */
273                g_free( dec );
274                dec = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\","
275                                       "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s",
276                                       jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" );
277                s = tobase64( dec );
278        }
279        else
280        {
281                /* We found rspauth, but don't really care... */
282                g_free( s );
283                s = NULL;
284        }
285       
286        reply = xt_new_node( "response", s, NULL );
287        xt_add_attr( reply, "xmlns", XMLNS_SASL );
288       
289        if( !jabber_write_packet( ic, reply ) )
290                goto silent_error;
291       
292        ret = XT_HANDLED;
293        goto silent_error;
294
295error:
296        imcb_error( ic, "Incorrect SASL challenge received" );
297        imc_logout( ic, FALSE );
298
299silent_error:
300        g_free( digest_uri );
301        g_free( cnonce );
302        g_free( nonce );
303        g_free( realm );
304        g_free( dec );
305        g_free( s );
306        xt_free_node( reply );
307       
308        return ret;
309}
310
311xt_status sasl_pkt_result( struct xt_node *node, gpointer data )
312{
313        struct im_connection *ic = data;
314        struct jabber_data *jd = ic->proto_data;
315        char *s;
316       
317        s = xt_find_attr( node, "xmlns" );
318        if( !s || strcmp( s, XMLNS_SASL ) != 0 )
319        {
320                imcb_log( ic, "Stream error while authenticating" );
321                imc_logout( ic, FALSE );
322                return XT_ABORT;
323        }
324       
325        if( strcmp( node->name, "success" ) == 0 )
326        {
327                imcb_log( ic, "Authentication finished" );
328                jd->flags |= JFLAG_AUTHENTICATED | JFLAG_STREAM_RESTART;
329        }
330        else if( strcmp( node->name, "failure" ) == 0 )
331        {
332                imcb_error( ic, "Authentication failure" );
333                imc_logout( ic, FALSE );
334                return XT_ABORT;
335        }
336       
337        return XT_HANDLED;
338}
339
340/* This one is needed to judge if we'll do authentication using IQ or SASL.
341   It's done by checking if the <stream:stream> from the server has a
342   version attribute. I don't know if this is the right way though... */
343gboolean sasl_supported( struct im_connection *ic )
344{
345        struct jabber_data *jd = ic->proto_data;
346       
347        return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0;
348}
Note: See TracBrowser for help on using the repository browser.