source: protocols/jabber/sasl.c @ 57b4525

Last change on this file since 57b4525 was 57b4525, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-07-22T18:29:25Z

Nothing useful yet, this just generates an auth URL. Things to do: Ability
to process the answer. This is hard because the GTalk server will time out
very quickly which means we lose our state/scope/etc. (And the ability to
even receive the answer, at least if I'd do this the same way the Twitter
module does it.)

And then, get the access token and use it, of course. :-)

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