source: protocols/jabber/sasl.c @ 87dddee

Last change on this file since 87dddee was 911d97a, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-08-04T15:19:54Z

Error handling fixes.

Found one double free() bug causing troubles when a buddy_msg() handler takes
down the IM connection immediately.

  • Property mode set to 100644
File size: 14.5 KB
RevLine 
[5997488]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
[af97b23]24#include <ctype.h>
25
[5997488]26#include "jabber.h"
27#include "base64.h"
[57b4525]28#include "oauth2.h"
[e1c926f]29#include "oauth.h"
[5997488]30
31xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data )
32{
[0da65d5]33        struct im_connection *ic = data;
34        struct jabber_data *jd = ic->proto_data;
[5997488]35        struct xt_node *c, *reply;
36        char *s;
[e1c926f]37        int sup_plain = 0, sup_digest = 0, sup_oauth2 = 0, sup_fb = 0;
[5997488]38       
[0da65d5]39        if( !sasl_supported( ic ) )
[8d74291]40        {
41                /* Should abort this now, since we should already be doing
42                   IQ authentication. Strange things happen when you try
43                   to do both... */
[84b045d]44                imcb_log( ic, "XMPP 1.0 non-compliant server seems to support SASL, please report this as a BitlBee bug!" );
[8d74291]45                return XT_HANDLED;
46        }
47       
[5997488]48        s = xt_find_attr( node, "xmlns" );
[47d3ac4]49        if( !s || strcmp( s, XMLNS_SASL ) != 0 )
[5997488]50        {
[84b045d]51                imcb_log( ic, "Stream error while authenticating" );
[c2fb3809]52                imc_logout( ic, FALSE );
[5997488]53                return XT_ABORT;
54        }
55       
56        c = node->children;
57        while( ( c = xt_find_node( c, "mechanism" ) ) )
58        {
59                if( c->text && g_strcasecmp( c->text, "PLAIN" ) == 0 )
60                        sup_plain = 1;
61                if( c->text && g_strcasecmp( c->text, "DIGEST-MD5" ) == 0 )
62                        sup_digest = 1;
[57b4525]63                if( c->text && g_strcasecmp( c->text, "X-OAUTH2" ) == 0 )
64                        sup_oauth2 = 1;
[e1c926f]65                if( c->text && g_strcasecmp( c->text, "X-FACEBOOK-PLATFORM" ) == 0 )
66                        sup_fb = 1;
[5997488]67               
68                c = c->next;
69        }
70       
71        if( !sup_plain && !sup_digest )
72        {
[84b045d]73                imcb_error( ic, "No known SASL authentication schemes supported" );
[c2fb3809]74                imc_logout( ic, FALSE );
[5997488]75                return XT_ABORT;
76        }
77       
78        reply = xt_new_node( "auth", NULL, NULL );
[47d3ac4]79        xt_add_attr( reply, "xmlns", XMLNS_SASL );
[5997488]80       
[4a5d885]81        if( set_getbool( &ic->acc->set, "oauth" ) )
[57b4525]82        {
[4a5d885]83                int len;
84               
85                if( !sup_oauth2 )
86                {
87                        imcb_error( ic, "OAuth requested, but not supported by server" );
88                        imc_logout( ic, FALSE );
89                        xt_free_node( reply );
90                        return XT_ABORT;
91                }
92               
93                /* X-OAUTH2 is, not *the* standard OAuth2 SASL/XMPP implementation.
94                   It's currently used by GTalk and vaguely documented on
95                   http://code.google.com/apis/cloudprint/docs/rawxmpp.html . */
96                xt_add_attr( reply, "mechanism", "X-OAUTH2" );
97               
98                len = strlen( jd->username ) + strlen( jd->oauth2_access_token ) + 2;
99                s = g_malloc( len + 1 );
100                s[0] = 0;
101                strcpy( s + 1, jd->username );
102                strcpy( s + 2 + strlen( jd->username ), jd->oauth2_access_token );
103                reply->text = base64_encode( (unsigned char *)s, len );
104                reply->text_len = strlen( reply->text );
105                g_free( s );
[57b4525]106        }
[e1c926f]107        else if( sup_fb && strstr( ic->acc->pass, "session_key=" ) )
108        {
109                xt_add_attr( reply, "mechanism", "X-FACEBOOK-PLATFORM" );
110                jd->flags |= JFLAG_SASL_FB;
111        }
[57b4525]112        else if( sup_digest )
[fe7a554]113        {
114                xt_add_attr( reply, "mechanism", "DIGEST-MD5" );
115               
116                /* The rest will be done later, when we receive a <challenge/>. */
117        }
118        else if( sup_plain )
[5997488]119        {
120                int len;
121               
122                xt_add_attr( reply, "mechanism", "PLAIN" );
123               
124                /* With SASL PLAIN in XMPP, the text should be b64(\0user\0pass) */
[0da65d5]125                len = strlen( jd->username ) + strlen( ic->acc->pass ) + 2;
[5997488]126                s = g_malloc( len + 1 );
127                s[0] = 0;
128                strcpy( s + 1, jd->username );
[0da65d5]129                strcpy( s + 2 + strlen( jd->username ), ic->acc->pass );
[3b6eadc]130                reply->text = base64_encode( (unsigned char *)s, len );
[5997488]131                reply->text_len = strlen( reply->text );
132                g_free( s );
133        }
134       
[57b4525]135        if( reply && !jabber_write_packet( ic, reply ) )
[5997488]136        {
137                xt_free_node( reply );
138                return XT_ABORT;
139        }
140        xt_free_node( reply );
141       
142        /* To prevent classic authentication from happening. */
143        jd->flags |= JFLAG_STREAM_STARTED;
144       
145        return XT_HANDLED;
146}
147
[af97b23]148/* Non-static function, but not mentioned in jabber.h because it's for internal
149   use, just that the unittest should be able to reach it... */
150char *sasl_get_part( char *data, char *field )
[d8e0484]151{
152        int i, len;
153       
154        len = strlen( field );
155       
[af97b23]156        while( isspace( *data ) || *data == ',' )
157                data ++;
158       
[d8e0484]159        if( g_strncasecmp( data, field, len ) == 0 && data[len] == '=' )
160        {
161                i = strlen( field ) + 1;
162        }
163        else
164        {
165                for( i = 0; data[i]; i ++ )
166                {
167                        /* If we have a ", skip until it's closed again. */
168                        if( data[i] == '"' )
169                        {
170                                i ++;
171                                while( data[i] != '"' || data[i-1] == '\\' )
172                                        i ++;
173                        }
174                       
[af97b23]175                        /* If we got a comma, we got a new field. Check it,
176                           find the next key after it. */
177                        if( data[i] == ',' )
[d8e0484]178                        {
[af97b23]179                                while( isspace( data[i] ) || data[i] == ',' )
180                                        i ++;
181                               
182                                if( g_strncasecmp( data + i, field, len ) == 0 &&
183                                    data[i+len] == '=' )
184                                {
185                                        i += len + 1;
186                                        break;
187                                }
[d8e0484]188                        }
189                }
190        }
191       
192        if( data[i] == '"' )
193        {
194                int j;
195                char *ret;
196               
197                i ++;
198                len = 0;
199                while( data[i+len] != '"' || data[i+len-1] == '\\' )
200                        len ++;
201               
202                ret = g_strndup( data + i, len );
203                for( i = j = 0; ret[i]; i ++ )
204                {
205                        if( ret[i] == '\\' )
206                        {
207                                ret[j++] = ret[++i];
208                        }
209                        else
210                        {
211                                ret[j++] = ret[i];
212                        }
213                }
214                ret[j] = 0;
215               
216                return ret;
217        }
218        else if( data[i] )
219        {
220                len = 0;
221                while( data[i+len] && data[i+len] != ',' )
222                        len ++;
223               
224                return g_strndup( data + i, len );
225        }
226        else
227        {
228                return NULL;
229        }
230}
231
[5997488]232xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data )
233{
[0da65d5]234        struct im_connection *ic = data;
235        struct jabber_data *jd = ic->proto_data;
[f138bd2]236        struct xt_node *reply_pkt = NULL;
[3b6eadc]237        char *nonce = NULL, *realm = NULL, *cnonce = NULL;
238        unsigned char cnonce_bin[30];
[d8e0484]239        char *digest_uri = NULL;
240        char *dec = NULL;
[f138bd2]241        char *s = NULL, *reply = NULL;
[d8e0484]242        xt_status ret = XT_ABORT;
243       
244        if( node->text_len == 0 )
245                goto error;
246       
247        dec = frombase64( node->text );
248       
[e1c926f]249        if( jd->flags & JFLAG_SASL_FB )
250        {
[f138bd2]251                /* Facebook proprietary authentication. Not as useful as it seemed, but
252                   the code's written now, may as well keep it..
253                   
254                   Mechanism is described on http://developers.facebook.com/docs/chat/
255                   and in their Python module. It's all mostly useless because the tokens
256                   expire after 24h. */
[e1c926f]257                GSList *p_in = NULL, *p_out = NULL, *p;
258                md5_state_t md5;
[f138bd2]259                char time[33], *token;
[e1c926f]260                const char *secret;
261               
262                oauth_params_parse( &p_in, dec );
263                oauth_params_add( &p_out, "nonce", oauth_params_get( &p_in, "nonce" ) );
264                oauth_params_add( &p_out, "method", oauth_params_get( &p_in, "method" ) );
265                oauth_params_free( &p_in );
266               
267                token = g_strdup( ic->acc->pass );
268                oauth_params_parse( &p_in, token );
269                g_free( token );
270                oauth_params_add( &p_out, "session_key", oauth_params_get( &p_in, "session_key" ) );
271               
272                g_snprintf( time, sizeof( time ), "%lld", (long long) ( gettime() * 1000 ) );
273                oauth_params_add( &p_out, "call_id", time );
274                oauth_params_add( &p_out, "api_key", oauth2_service_facebook.consumer_key );
275                oauth_params_add( &p_out, "v", "1.0" );
276                oauth_params_add( &p_out, "format", "XML" );
277               
278                md5_init( &md5 );
279                for( p = p_out; p; p = p->next )
280                        md5_append( &md5, p->data, strlen( p->data ) );
281               
282                secret = oauth_params_get( &p_in, "secret" );
[f138bd2]283                if( secret )
284                        md5_append( &md5, (unsigned char*) secret, strlen( secret ) );
[e1c926f]285                md5_finish_ascii( &md5, time );
286                oauth_params_add( &p_out, "sig", time );
287               
[f138bd2]288                reply = oauth_params_string( p_out );
[e1c926f]289                oauth_params_free( &p_out );
290                oauth_params_free( &p_in );
291        }
292        else if( !( s = sasl_get_part( dec, "rspauth" ) ) )
[d8e0484]293        {
294                /* See RFC 2831 for for information. */
295                md5_state_t A1, A2, H;
296                md5_byte_t A1r[16], A2r[16], Hr[16];
297                char A1h[33], A2h[33], Hh[33];
298                int i;
299               
300                nonce = sasl_get_part( dec, "nonce" );
301                realm = sasl_get_part( dec, "realm" );
302               
[d9282b4]303                if( !nonce )
[d8e0484]304                        goto error;
305               
[d9282b4]306                /* Jabber.Org considers the realm part optional and doesn't
307                   specify one. Oh well, actually they're right, but still,
308                   don't know if this is right... */
309                if( !realm )
310                        realm = g_strdup( jd->server );
311               
[3b6eadc]312                random_bytes( cnonce_bin, sizeof( cnonce_bin ) );
[d8e0484]313                cnonce = base64_encode( cnonce_bin, sizeof( cnonce_bin ) );
314                digest_uri = g_strdup_printf( "%s/%s", "xmpp", jd->server );
315               
316                /* Generate the MD5 hash of username:realm:password,
317                   I decided to call it H. */
318                md5_init( &H );
[0da65d5]319                s = g_strdup_printf( "%s:%s:%s", jd->username, realm, ic->acc->pass );
[d8e0484]320                md5_append( &H, (unsigned char *) s, strlen( s ) );
321                g_free( s );
322                md5_finish( &H, Hr );
323               
324                /* Now generate the hex. MD5 hash of H:nonce:cnonce, called A1. */
325                md5_init( &A1 );
326                s = g_strdup_printf( ":%s:%s", nonce, cnonce );
327                md5_append( &A1, Hr, 16 );
328                md5_append( &A1, (unsigned char *) s, strlen( s ) );
329                g_free( s );
330                md5_finish( &A1, A1r );
331                for( i = 0; i < 16; i ++ )
332                        sprintf( A1h + i * 2, "%02x", A1r[i] );
333               
334                /* A2... */
335                md5_init( &A2 );
336                s = g_strdup_printf( "%s:%s", "AUTHENTICATE", digest_uri );
337                md5_append( &A2, (unsigned char *) s, strlen( s ) );
338                g_free( s );
339                md5_finish( &A2, A2r );
340                for( i = 0; i < 16; i ++ )
341                        sprintf( A2h + i * 2, "%02x", A2r[i] );
342               
343                /* Final result: A1:nonce:00000001:cnonce:auth:A2. Let's reuse H for it. */
344                md5_init( &H );
345                s = g_strdup_printf( "%s:%s:%s:%s:%s:%s", A1h, nonce, "00000001", cnonce, "auth", A2h );
346                md5_append( &H, (unsigned char *) s, strlen( s ) );
347                g_free( s );
348                md5_finish( &H, Hr );
349                for( i = 0; i < 16; i ++ )
350                        sprintf( Hh + i * 2, "%02x", Hr[i] );
351               
352                /* Now build the SASL response string: */
[f138bd2]353                reply = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\","
354                                         "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s",
355                                         jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" );
[d8e0484]356        }
357        else
358        {
359                /* We found rspauth, but don't really care... */
360        }
361       
[f138bd2]362        s = reply ? tobase64( reply ) : NULL;
363        reply_pkt = xt_new_node( "response", s, NULL );
364        xt_add_attr( reply_pkt, "xmlns", XMLNS_SASL );
[d8e0484]365       
[f138bd2]366        if( !jabber_write_packet( ic, reply_pkt ) )
[d8e0484]367                goto silent_error;
368       
369        ret = XT_HANDLED;
370        goto silent_error;
371
372error:
[84b045d]373        imcb_error( ic, "Incorrect SASL challenge received" );
[c2fb3809]374        imc_logout( ic, FALSE );
[d8e0484]375
376silent_error:
377        g_free( digest_uri );
378        g_free( cnonce );
379        g_free( nonce );
[f138bd2]380        g_free( reply );
[d8e0484]381        g_free( realm );
382        g_free( dec );
383        g_free( s );
[f138bd2]384        xt_free_node( reply_pkt );
[d8e0484]385       
386        return ret;
[5997488]387}
388
389xt_status sasl_pkt_result( struct xt_node *node, gpointer data )
390{
[0da65d5]391        struct im_connection *ic = data;
392        struct jabber_data *jd = ic->proto_data;
[5997488]393        char *s;
394       
395        s = xt_find_attr( node, "xmlns" );
[47d3ac4]396        if( !s || strcmp( s, XMLNS_SASL ) != 0 )
[5997488]397        {
[84b045d]398                imcb_log( ic, "Stream error while authenticating" );
[c2fb3809]399                imc_logout( ic, FALSE );
[5997488]400                return XT_ABORT;
401        }
402       
403        if( strcmp( node->name, "success" ) == 0 )
404        {
[84b045d]405                imcb_log( ic, "Authentication finished" );
[5997488]406                jd->flags |= JFLAG_AUTHENTICATED | JFLAG_STREAM_RESTART;
407        }
408        else if( strcmp( node->name, "failure" ) == 0 )
409        {
[84b045d]410                imcb_error( ic, "Authentication failure" );
[c2fb3809]411                imc_logout( ic, FALSE );
[5997488]412                return XT_ABORT;
413        }
414       
415        return XT_HANDLED;
416}
[8d74291]417
418/* This one is needed to judge if we'll do authentication using IQ or SASL.
419   It's done by checking if the <stream:stream> from the server has a
420   version attribute. I don't know if this is the right way though... */
[0da65d5]421gboolean sasl_supported( struct im_connection *ic )
[8d74291]422{
[0da65d5]423        struct jabber_data *jd = ic->proto_data;
[8d74291]424       
[ef5c185]425        return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0;
[8d74291]426}
[4a5d885]427
428void sasl_oauth2_init( struct im_connection *ic )
429{
430        char *msg, *url;
431       
432        imcb_log( ic, "Starting OAuth authentication" );
433       
434        /* Temporary contact, just used to receive the OAuth response. */
435        imcb_add_buddy( ic, "jabber_oauth", NULL );
436        url = oauth2_url( &oauth2_service_google,
437                          "https://www.googleapis.com/auth/googletalk" );
438        msg = g_strdup_printf( "Open this URL in your browser to authenticate: %s", url );
439        imcb_buddy_msg( ic, "jabber_oauth", msg, 0, 0 );
440        imcb_buddy_msg( ic, "jabber_oauth", "Respond to this message with the returned "
441                                            "authorization token.", 0, 0 );
442       
443        g_free( msg );
444        g_free( url );
445}
446
447static gboolean sasl_oauth2_remove_contact( gpointer data, gint fd, b_input_condition cond )
448{
449        struct im_connection *ic = data;
[911d97a]450        if( g_slist_find( jabber_connections, ic ) )
451                imcb_remove_buddy( ic, "jabber_oauth", NULL );
[4a5d885]452        return FALSE;
453}
454
455static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token );
456
457int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg )
458{
459        char *code;
460        int ret;
461       
462        imcb_log( ic, "Requesting OAuth access token" );
463       
464        /* Don't do it here because the caller may get confused if the contact
465           we're currently sending a message to is deleted. */
466        b_timeout_add( 1, sasl_oauth2_remove_contact, ic );
467       
468        code = g_strdup( msg );
469        g_strstrip( code );
470        ret = oauth2_access_token( &oauth2_service_google, OAUTH2_AUTH_CODE,
471                                   code, sasl_oauth2_got_token, ic );
472       
473        g_free( code );
474        return ret;
475}
476
477int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token )
478{
479        return oauth2_access_token( &oauth2_service_google, OAUTH2_AUTH_REFRESH,
480                                    refresh_token, sasl_oauth2_got_token, ic );
481}
482
483static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token )
484{
485        struct im_connection *ic = data;
486        struct jabber_data *jd;
487       
488        if( g_slist_find( jabber_connections, ic ) == NULL )
489                return;
490       
491        jd = ic->proto_data;
492       
493        if( access_token == NULL )
494        {
495                imcb_error( ic, "OAuth failure (missing access token)" );
496                imc_logout( ic, TRUE );
[e1c926f]497                return;
[4a5d885]498        }
499        if( refresh_token != NULL )
500        {
501                g_free( ic->acc->pass );
502                ic->acc->pass = g_strdup_printf( "refresh_token=%s", refresh_token );
503        }
504       
505        g_free( jd->oauth2_access_token );
506        jd->oauth2_access_token = g_strdup( access_token );
507       
508        jabber_connect( ic );
509}
Note: See TracBrowser for help on using the repository browser.