source: lib/oauth.c @ 0dd6570

Last change on this file since 0dd6570 was 4be0e34, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-19T00:41:40Z

Give a list of SASL mechanisms supported by a server when reporting we don't
support any of them.

  • Property mode set to 100644
File size: 12.3 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple OAuth client (consumer) implementation.                           *
5*                                                                           *
6*  Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net>                   *
7*                                                                           *
8*  This library is free software; you can redistribute it and/or            *
9*  modify it under the terms of the GNU Lesser General Public               *
10*  License as published by the Free Software Foundation, version            *
11*  2.1.                                                                     *
12*                                                                           *
13*  This library 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 GNU        *
16*  Lesser General Public License for more details.                          *
17*                                                                           *
18*  You should have received a copy of the GNU Lesser General Public License *
19*  along with this library; if not, write to the Free Software Foundation,  *
20*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
21*                                                                           *
22\***************************************************************************/
23
24#include <glib.h>
25#include <gmodule.h>
26#include <stdlib.h>
27#include <string.h>
28#include "http_client.h"
29#include "base64.h"
30#include "misc.h"
31#include "sha1.h"
32#include "url.h"
33#include "oauth.h"
34
35#define HMAC_BLOCK_SIZE 64
36
37static char *oauth_sign( const char *method, const char *url,
38                         const char *params, struct oauth_info *oi )
39{
40        sha1_state_t sha1;
41        uint8_t hash[sha1_hash_size];
42        uint8_t key[HMAC_BLOCK_SIZE+1];
43        char *s;
44        int i;
45       
46        /* Create K. If our current key is >64 chars we have to hash it,
47           otherwise just pad. */
48        memset( key, 0, HMAC_BLOCK_SIZE );
49        i = strlen( oi->sp->consumer_secret ) + 1 + ( oi->token_secret ? strlen( oi->token_secret ) : 0 );
50        if( i > HMAC_BLOCK_SIZE )
51        {
52                sha1_init( &sha1 );
53                sha1_append( &sha1, (uint8_t*) oi->sp->consumer_secret, strlen( oi->sp->consumer_secret ) );
54                sha1_append( &sha1, (uint8_t*) "&", 1 );
55                if( oi->token_secret )
56                        sha1_append( &sha1, (uint8_t*) oi->token_secret, strlen( oi->token_secret ) );
57                sha1_finish( &sha1, key );
58        }
59        else
60        {
61                g_snprintf( (gchar*) key, HMAC_BLOCK_SIZE + 1, "%s&%s",
62                            oi->sp->consumer_secret, oi->token_secret ? oi->token_secret : "" );
63        }
64       
65        /* Inner part: H(K XOR 0x36, text) */
66        sha1_init( &sha1 );
67       
68        for( i = 0; i < HMAC_BLOCK_SIZE; i ++ )
69                key[i] ^= 0x36;
70        sha1_append( &sha1, key, HMAC_BLOCK_SIZE );
71       
72        /* OAuth: text = method&url&params, all http_encoded. */
73        sha1_append( &sha1, (const uint8_t*) method, strlen( method ) );
74        sha1_append( &sha1, (const uint8_t*) "&", 1 );
75       
76        s = g_new0( char, strlen( url ) * 3 + 1 );
77        strcpy( s, url );
78        http_encode( s );
79        sha1_append( &sha1, (const uint8_t*) s, strlen( s ) );
80        sha1_append( &sha1, (const uint8_t*) "&", 1 );
81        g_free( s );
82       
83        s = g_new0( char, strlen( params ) * 3 + 1 );
84        strcpy( s, params );
85        http_encode( s );
86        sha1_append( &sha1, (const uint8_t*) s, strlen( s ) );
87        g_free( s );
88       
89        sha1_finish( &sha1, hash );
90       
91        /* Final result: H(K XOR 0x5C, inner stuff) */
92        sha1_init( &sha1 );
93        for( i = 0; i < HMAC_BLOCK_SIZE; i ++ )
94                key[i] ^= 0x36 ^ 0x5c;
95        sha1_append( &sha1, key, HMAC_BLOCK_SIZE );
96        sha1_append( &sha1, hash, sha1_hash_size );
97        sha1_finish( &sha1, hash );
98       
99        /* base64_encode + HTTP escape it (both consumers
100           need it that away) and we're done. */
101        s = base64_encode( hash, sha1_hash_size );
102        s = g_realloc( s, strlen( s ) * 3 + 1 );
103        http_encode( s );
104       
105        return s;
106}
107
108static char *oauth_nonce()
109{
110        unsigned char bytes[21];
111        char *ret = g_new0( char, sizeof( bytes) / 3 * 4 + 1 );
112       
113        random_bytes( bytes, sizeof( bytes ) );
114        base64_encode_real( bytes, sizeof( bytes), (unsigned char*) ret, "0123456789"
115                            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0A" );
116       
117        return ret;
118}
119
120void oauth_params_add( GSList **params, const char *key, const char *value )
121{
122        char *item;
123       
124        if( !key || !value )
125                return;
126       
127        item = g_strdup_printf( "%s=%s", key, value );
128        *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp );
129}
130
131void oauth_params_del( GSList **params, const char *key )
132{
133        int key_len = strlen( key );
134        GSList *l, *n;
135       
136        if( params == NULL )
137                return;
138       
139        for( l = *params; l; l = n )
140        {
141                n = l->next;
142               
143                if( strncmp( (char*) l->data, key, key_len ) == 0 &&
144                    ((char*)l->data)[key_len] == '=' )
145                {
146                        g_free( l->data );
147                        *params = g_slist_remove( *params, l->data );
148                }
149        }
150}
151
152void oauth_params_set( GSList **params, const char *key, const char *value )
153{
154        oauth_params_del( params, key );
155        oauth_params_add( params, key, value );
156}
157
158const char *oauth_params_get( GSList **params, const char *key )
159{
160        int key_len = strlen( key );
161        GSList *l;
162       
163        if( params == NULL )
164                return NULL;
165       
166        for( l = *params; l; l = l->next )
167        {
168                if( strncmp( (char*) l->data, key, key_len ) == 0 &&
169                    ((char*)l->data)[key_len] == '=' )
170                        return (const char*) l->data + key_len + 1;
171        }
172       
173        return NULL;
174}
175
176void oauth_params_parse( GSList **params, char *in )
177{
178        char *amp, *eq, *s;
179       
180        while( in && *in )
181        {
182                eq = strchr( in, '=' );
183                if( !eq )
184                        break;
185               
186                *eq = '\0';
187                if( ( amp = strchr( eq + 1, '&' ) ) )
188                        *amp = '\0';
189               
190                s = g_strdup( eq + 1 );
191                http_decode( s );
192                oauth_params_add( params, in, s );
193                g_free( s );
194               
195                *eq = '=';
196                if( amp == NULL )
197                        break;
198               
199                *amp = '&';
200                in = amp + 1;
201        }
202}
203
204void oauth_params_free( GSList **params )
205{
206        while( params && *params )
207        {
208                g_free( (*params)->data );
209                *params = g_slist_remove( *params, (*params)->data );
210        }
211}
212
213char *oauth_params_string( GSList *params )
214{
215        GSList *l;
216        GString *str = g_string_new( "" );
217       
218        for( l = params; l; l = l->next )
219        {
220                char *s, *eq;
221               
222                s = g_malloc( strlen( l->data ) * 3 + 1 );
223                strcpy( s, l->data );
224                if( ( eq = strchr( s, '=' ) ) )
225                        http_encode( eq + 1 );
226                g_string_append( str, s );
227                g_free( s );
228               
229                if( l->next )
230                        g_string_append_c( str, '&' );
231        }
232       
233        return g_string_free( str, FALSE );
234}
235
236void oauth_info_free( struct oauth_info *info )
237{
238        if( info )
239        {
240                g_free( info->auth_url );
241                g_free( info->request_token );
242                g_free( info->token );
243                g_free( info->token_secret );
244                oauth_params_free( &info->params );
245                g_free( info );
246        }
247}
248
249static void oauth_add_default_params( GSList **params, const struct oauth_service *sp )
250{
251        char *s;
252       
253        oauth_params_set( params, "oauth_consumer_key", sp->consumer_key );
254        oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" );
255       
256        s = g_strdup_printf( "%d", (int) time( NULL ) );
257        oauth_params_set( params, "oauth_timestamp", s );
258        g_free( s );
259       
260        s = oauth_nonce();
261        oauth_params_set( params, "oauth_nonce", s );
262        g_free( s );
263       
264        oauth_params_set( params, "oauth_version", "1.0" );
265}
266
267static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, struct oauth_info *oi )
268{
269        GSList *params = NULL;
270        char *s, *params_s, *post;
271        void *req;
272        url_t url_p;
273       
274        if( !url_set( &url_p, url ) )
275        {
276                oauth_params_free( params_ );
277                return NULL;
278        }
279       
280        if( params_ )
281                params = *params_;
282       
283        oauth_add_default_params( &params, oi->sp );
284       
285        params_s = oauth_params_string( params );
286        oauth_params_free( &params );
287       
288        s = oauth_sign( "POST", url, params_s, oi );
289        post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s );
290        g_free( params_s );
291        g_free( s );
292       
293        s = g_strdup_printf( "POST %s HTTP/1.0\r\n"
294                             "Host: %s\r\n"
295                             "Content-Type: application/x-www-form-urlencoded\r\n"
296                             "Content-Length: %zd\r\n"
297                             "Connection: close\r\n"
298                             "\r\n"
299                             "%s", url_p.file, url_p.host, strlen( post ), post );
300        g_free( post );
301       
302        req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS,
303                              s, func, oi );
304        g_free( s );
305       
306        return req;
307}
308
309static void oauth_request_token_done( struct http_request *req );
310
311struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data )
312{
313        struct oauth_info *st = g_new0( struct oauth_info, 1 );
314        GSList *params = NULL;
315       
316        st->func = func;
317        st->data = data;
318        st->sp = sp;
319       
320        oauth_params_add( &params, "oauth_callback", "oob" );
321       
322        if( !oauth_post_request( sp->url_request_token, &params, oauth_request_token_done, st ) )
323        {
324                oauth_info_free( st );
325                return NULL;
326        }
327       
328        return st;
329}
330
331static void oauth_request_token_done( struct http_request *req )
332{
333        struct oauth_info *st = req->data;
334       
335        st->http = req;
336       
337        if( req->status_code == 200 )
338        {
339                GSList *params = NULL;
340               
341                st->auth_url = g_strdup_printf( "%s?%s", st->sp->url_authorize, req->reply_body );
342                oauth_params_parse( &params, req->reply_body );
343                st->request_token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
344                oauth_params_free( &params );
345        }
346       
347        st->stage = OAUTH_REQUEST_TOKEN;
348        st->func( st );
349}
350
351static void oauth_access_token_done( struct http_request *req );
352
353gboolean oauth_access_token( const char *pin, struct oauth_info *st )
354{
355        GSList *params = NULL;
356       
357        oauth_params_add( &params, "oauth_token", st->request_token );
358        oauth_params_add( &params, "oauth_verifier", pin );
359       
360        return oauth_post_request( st->sp->url_access_token, &params, oauth_access_token_done, st ) != NULL;
361}
362
363static void oauth_access_token_done( struct http_request *req )
364{
365        struct oauth_info *st = req->data;
366       
367        st->http = req;
368       
369        if( req->status_code == 200 )
370        {
371                oauth_params_parse( &st->params, req->reply_body );
372                st->token = g_strdup( oauth_params_get( &st->params, "oauth_token" ) );
373                st->token_secret = g_strdup( oauth_params_get( &st->params, "oauth_token_secret" ) );
374        }
375       
376        st->stage = OAUTH_ACCESS_TOKEN;
377        if( st->func( st ) )
378        {
379                /* Don't need these anymore, but keep the rest. */
380                g_free( st->auth_url );
381                st->auth_url = NULL;
382                g_free( st->request_token );
383                st->request_token = NULL;
384                oauth_params_free( &st->params );
385        }
386}
387
388char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args )
389{
390        GSList *params = NULL, *l;
391        char *sig = NULL, *params_s, *s;
392        GString *ret = NULL;
393       
394        oauth_params_add( &params, "oauth_token", oi->token );
395        oauth_add_default_params( &params, oi->sp );
396       
397        /* Start building the OAuth header. 'key="value", '... */
398        ret = g_string_new( "OAuth " );
399        for( l = params; l; l = l->next )
400        {
401                char *kv = l->data;
402                char *eq = strchr( kv, '=' );
403                char esc[strlen(kv)*3+1];
404               
405                if( eq == NULL )
406                        break; /* WTF */
407               
408                strcpy( esc, eq + 1 );
409                http_encode( esc );
410               
411                g_string_append_len( ret, kv, eq - kv + 1 );
412                g_string_append_c( ret, '"' );
413                g_string_append( ret, esc );
414                g_string_append( ret, "\", " );
415        }
416       
417        /* Now, before generating the signature, add GET/POST arguments to params
418           since they should be included in the base signature string (but not in
419           the HTTP header). */
420        if( args )
421                oauth_params_parse( &params, args );
422        if( ( s = strchr( url, '?' ) ) )
423        {
424                s = g_strdup( s + 1 );
425                oauth_params_parse( &params, s );
426                g_free( s );
427        }
428       
429        /* Append the signature and we're done! */
430        params_s = oauth_params_string( params );
431        sig = oauth_sign( method, url, params_s, oi );
432        g_string_append_printf( ret, "oauth_signature=\"%s\"", sig );
433        g_free( params_s );
434       
435        oauth_params_free( &params );
436        g_free( sig );
437       
438        return ret ? g_string_free( ret, FALSE ) : NULL;
439}
440
441char *oauth_to_string( struct oauth_info *oi )
442{
443        GSList *params = NULL;
444        char *ret;
445       
446        oauth_params_add( &params, "oauth_token", oi->token );
447        oauth_params_add( &params, "oauth_token_secret", oi->token_secret );
448        ret = oauth_params_string( params );
449        oauth_params_free( &params );
450       
451        return ret;
452}
453
454struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp )
455{
456        struct oauth_info *oi = g_new0( struct oauth_info, 1 );
457        GSList *params = NULL;
458       
459        oauth_params_parse( &params, in );
460        oi->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
461        oi->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
462        oauth_params_free( &params );
463        oi->sp = sp;
464       
465        return oi;
466}
Note: See TracBrowser for help on using the repository browser.