source: lib/oauth.c @ 434a2d0

Last change on this file since 434a2d0 was ca8037e, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-06-09T21:17:45Z

Add better handling of HTTP/1.1 and/or keepalive connections. This should
let me close #641, and more importantly, prepares the Twitter module for
an upcoming API change. https://dev.twitter.com/blog/deprecating-http-1.0-streaming-api

  • Property mode set to 100644
File size: 11.2 KB
RevLine 
[be28fe7]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>
[da2efd4]28#include "http_client.h"
[be28fe7]29#include "base64.h"
30#include "misc.h"
31#include "sha1.h"
[da2efd4]32#include "url.h"
[acba168]33#include "oauth.h"
[be28fe7]34
35#define HMAC_BLOCK_SIZE 64
36
37static char *oauth_sign( const char *method, const char *url,
[c2ecadc]38                         const char *params, struct oauth_info *oi )
[be28fe7]39{
40        uint8_t hash[sha1_hash_size];
[31db8165]41        GString *payload = g_string_new( "" );
42        char *key;
[be28fe7]43        char *s;
44       
[31db8165]45        key = g_strdup_printf( "%s&%s", oi->sp->consumer_secret, oi->token_secret ? oi->token_secret : "" );
[be28fe7]46       
[31db8165]47        g_string_append_printf( payload, "%s&", method );
[be28fe7]48       
49        s = g_new0( char, strlen( url ) * 3 + 1 );
50        strcpy( s, url );
51        http_encode( s );
[31db8165]52        g_string_append_printf( payload, "%s&", s );
[be28fe7]53        g_free( s );
54       
55        s = g_new0( char, strlen( params ) * 3 + 1 );
56        strcpy( s, params );
57        http_encode( s );
[31db8165]58        g_string_append( payload, s );
[be28fe7]59        g_free( s );
60       
[31db8165]61        sha1_hmac( key, 0, payload->str, 0, hash );
[be28fe7]62       
[31db8165]63        g_free( key );
64        g_string_free( payload, TRUE );
[be28fe7]65       
[ee84bdb]66        /* base64_encode + HTTP escape it (both consumers
67           need it that away) and we're done. */
68        s = base64_encode( hash, sha1_hash_size );
69        s = g_realloc( s, strlen( s ) * 3 + 1 );
70        http_encode( s );
71       
72        return s;
[be28fe7]73}
[da2efd4]74
75static char *oauth_nonce()
76{
[ce617f0]77        unsigned char bytes[21];
78        char *ret = g_new0( char, sizeof( bytes) / 3 * 4 + 1 );
[da2efd4]79       
80        random_bytes( bytes, sizeof( bytes ) );
[ce617f0]81        base64_encode_real( bytes, sizeof( bytes), (unsigned char*) ret, "0123456789"
82                            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0A" );
83       
84        return ret;
[da2efd4]85}
86
87void oauth_params_add( GSList **params, const char *key, const char *value )
88{
89        char *item;
90       
[f138bd2]91        if( !key || !value )
92                return;
93       
[da2efd4]94        item = g_strdup_printf( "%s=%s", key, value );
95        *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp );
96}
97
98void oauth_params_del( GSList **params, const char *key )
99{
100        int key_len = strlen( key );
101        GSList *l, *n;
102       
[bf57cd1]103        if( params == NULL )
[4be0e34]104                return;
[bf57cd1]105       
[da2efd4]106        for( l = *params; l; l = n )
107        {
108                n = l->next;
109               
110                if( strncmp( (char*) l->data, key, key_len ) == 0 &&
111                    ((char*)l->data)[key_len] == '=' )
112                {
113                        g_free( l->data );
[b2bc25c]114                        *params = g_slist_remove( *params, l->data );
[da2efd4]115                }
116        }
117}
118
119void oauth_params_set( GSList **params, const char *key, const char *value )
120{
121        oauth_params_del( params, key );
122        oauth_params_add( params, key, value );
123}
124
125const char *oauth_params_get( GSList **params, const char *key )
126{
127        int key_len = strlen( key );
128        GSList *l;
129       
[bf57cd1]130        if( params == NULL )
131                return NULL;
132       
[da2efd4]133        for( l = *params; l; l = l->next )
134        {
135                if( strncmp( (char*) l->data, key, key_len ) == 0 &&
136                    ((char*)l->data)[key_len] == '=' )
137                        return (const char*) l->data + key_len + 1;
138        }
139       
140        return NULL;
141}
142
[aa9f1ac]143void oauth_params_parse( GSList **params, char *in )
[da2efd4]144{
[ee84bdb]145        char *amp, *eq, *s;
[da2efd4]146       
147        while( in && *in )
148        {
149                eq = strchr( in, '=' );
150                if( !eq )
151                        break;
152               
153                *eq = '\0';
154                if( ( amp = strchr( eq + 1, '&' ) ) )
155                        *amp = '\0';
156               
[ee84bdb]157                s = g_strdup( eq + 1 );
158                http_decode( s );
159                oauth_params_add( params, in, s );
160                g_free( s );
[da2efd4]161               
162                *eq = '=';
163                if( amp == NULL )
164                        break;
165               
166                *amp = '&';
167                in = amp + 1;
168        }
169}
170
171void oauth_params_free( GSList **params )
172{
173        while( params && *params )
174        {
175                g_free( (*params)->data );
176                *params = g_slist_remove( *params, (*params)->data );
177        }
178}
179
180char *oauth_params_string( GSList *params )
181{
182        GSList *l;
183        GString *str = g_string_new( "" );
184       
185        for( l = params; l; l = l->next )
186        {
[ee84bdb]187                char *s, *eq;
188               
189                s = g_malloc( strlen( l->data ) * 3 + 1 );
190                strcpy( s, l->data );
191                if( ( eq = strchr( s, '=' ) ) )
192                        http_encode( eq + 1 );
193                g_string_append( str, s );
194                g_free( s );
195               
[da2efd4]196                if( l->next )
197                        g_string_append_c( str, '&' );
198        }
199       
200        return g_string_free( str, FALSE );
201}
202
[18dbb20]203void oauth_info_free( struct oauth_info *info )
204{
205        if( info )
206        {
[c2ecadc]207                g_free( info->auth_url );
[18dbb20]208                g_free( info->request_token );
[c2ecadc]209                g_free( info->token );
210                g_free( info->token_secret );
[93cc86f]211                oauth_params_free( &info->params );
[18dbb20]212                g_free( info );
213        }
214}
215
[3b878a1]216static void oauth_add_default_params( GSList **params, const struct oauth_service *sp )
[b2bc25c]217{
218        char *s;
219       
[c2ecadc]220        oauth_params_set( params, "oauth_consumer_key", sp->consumer_key );
[b2bc25c]221        oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" );
222       
223        s = g_strdup_printf( "%d", (int) time( NULL ) );
224        oauth_params_set( params, "oauth_timestamp", s );
225        g_free( s );
226       
227        s = oauth_nonce();
228        oauth_params_set( params, "oauth_nonce", s );
229        g_free( s );
230       
231        oauth_params_set( params, "oauth_version", "1.0" );
232}
233
[c2ecadc]234static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, struct oauth_info *oi )
[da2efd4]235{
236        GSList *params = NULL;
237        char *s, *params_s, *post;
238        void *req;
239        url_t url_p;
240       
241        if( !url_set( &url_p, url ) )
242        {
243                oauth_params_free( params_ );
244                return NULL;
245        }
246       
247        if( params_ )
248                params = *params_;
249       
[c2ecadc]250        oauth_add_default_params( &params, oi->sp );
[da2efd4]251       
252        params_s = oauth_params_string( params );
[0bff877]253        oauth_params_free( &params );
[da2efd4]254       
[c2ecadc]255        s = oauth_sign( "POST", url, params_s, oi );
[da2efd4]256        post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s );
257        g_free( params_s );
258        g_free( s );
259       
260        s = g_strdup_printf( "POST %s HTTP/1.0\r\n"
261                             "Host: %s\r\n"
262                             "Content-Type: application/x-www-form-urlencoded\r\n"
263                             "Content-Length: %zd\r\n"
264                             "\r\n"
265                             "%s", url_p.file, url_p.host, strlen( post ), post );
266        g_free( post );
267       
268        req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS,
[c2ecadc]269                              s, func, oi );
[da2efd4]270        g_free( s );
271       
272        return req;
273}
274
[346dfd9]275static void oauth_request_token_done( struct http_request *req );
[da2efd4]276
[3b878a1]277struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data )
[da2efd4]278{
279        struct oauth_info *st = g_new0( struct oauth_info, 1 );
[346dfd9]280        GSList *params = NULL;
[da2efd4]281       
282        st->func = func;
283        st->data = data;
[c2ecadc]284        st->sp = sp;
[da2efd4]285       
[346dfd9]286        oauth_params_add( &params, "oauth_callback", "oob" );
287       
[c2ecadc]288        if( !oauth_post_request( sp->url_request_token, &params, oauth_request_token_done, st ) )
[18dbb20]289        {
290                oauth_info_free( st );
291                return NULL;
292        }
293       
294        return st;
[da2efd4]295}
296
[346dfd9]297static void oauth_request_token_done( struct http_request *req )
[da2efd4]298{
299        struct oauth_info *st = req->data;
300       
301        st->http = req;
302       
303        if( req->status_code == 200 )
304        {
[508c340]305                GSList *params = NULL;
[da2efd4]306               
[c2ecadc]307                st->auth_url = g_strdup_printf( "%s?%s", st->sp->url_authorize, req->reply_body );
308                oauth_params_parse( &params, req->reply_body );
[713d611]309                st->request_token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
[e306fbf]310                st->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
[da2efd4]311                oauth_params_free( &params );
312        }
313       
[c42e8b9]314        st->stage = OAUTH_REQUEST_TOKEN;
[c2ecadc]315        st->func( st );
[346dfd9]316}
317
318static void oauth_access_token_done( struct http_request *req );
319
[c2ecadc]320gboolean oauth_access_token( const char *pin, struct oauth_info *st )
[346dfd9]321{
322        GSList *params = NULL;
323       
[713d611]324        oauth_params_add( &params, "oauth_token", st->request_token );
[346dfd9]325        oauth_params_add( &params, "oauth_verifier", pin );
326       
[c2ecadc]327        return oauth_post_request( st->sp->url_access_token, &params, oauth_access_token_done, st ) != NULL;
[346dfd9]328}
329
330static void oauth_access_token_done( struct http_request *req )
331{
[b2bc25c]332        struct oauth_info *st = req->data;
333       
[0bff877]334        st->http = req;
335       
[b2bc25c]336        if( req->status_code == 200 )
[c42e8b9]337        {
[93cc86f]338                oauth_params_parse( &st->params, req->reply_body );
339                st->token = g_strdup( oauth_params_get( &st->params, "oauth_token" ) );
[e306fbf]340                g_free( st->token_secret );
[93cc86f]341                st->token_secret = g_strdup( oauth_params_get( &st->params, "oauth_token_secret" ) );
[c42e8b9]342        }
[b2bc25c]343       
[c42e8b9]344        st->stage = OAUTH_ACCESS_TOKEN;
[c2ecadc]345        if( st->func( st ) )
346        {
347                /* Don't need these anymore, but keep the rest. */
348                g_free( st->auth_url );
349                st->auth_url = NULL;
350                g_free( st->request_token );
351                st->request_token = NULL;
[93cc86f]352                oauth_params_free( &st->params );
[c2ecadc]353        }
[b2bc25c]354}
355
[c2ecadc]356char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args )
[b2bc25c]357{
[508c340]358        GSList *params = NULL, *l;
[c2ecadc]359        char *sig = NULL, *params_s, *s;
[b2bc25c]360        GString *ret = NULL;
361       
[c2ecadc]362        oauth_params_add( &params, "oauth_token", oi->token );
363        oauth_add_default_params( &params, oi->sp );
[b2bc25c]364       
[18dbb20]365        /* Start building the OAuth header. 'key="value", '... */
[b2bc25c]366        ret = g_string_new( "OAuth " );
367        for( l = params; l; l = l->next )
368        {
369                char *kv = l->data;
370                char *eq = strchr( kv, '=' );
371                char esc[strlen(kv)*3+1];
372               
373                if( eq == NULL )
374                        break; /* WTF */
375               
376                strcpy( esc, eq + 1 );
377                http_encode( esc );
378               
379                g_string_append_len( ret, kv, eq - kv + 1 );
380                g_string_append_c( ret, '"' );
381                g_string_append( ret, esc );
382                g_string_append( ret, "\", " );
383        }
384       
[18dbb20]385        /* Now, before generating the signature, add GET/POST arguments to params
386           since they should be included in the base signature string (but not in
387           the HTTP header). */
[508c340]388        if( args )
389                oauth_params_parse( &params, args );
390        if( ( s = strchr( url, '?' ) ) )
391        {
392                s = g_strdup( s + 1 );
[c2ecadc]393                oauth_params_parse( &params, s );
[508c340]394                g_free( s );
395        }
396       
[18dbb20]397        /* Append the signature and we're done! */
[508c340]398        params_s = oauth_params_string( params );
[c2ecadc]399        sig = oauth_sign( method, url, params_s, oi );
[b2bc25c]400        g_string_append_printf( ret, "oauth_signature=\"%s\"", sig );
[ee84bdb]401        g_free( params_s );
[b2bc25c]402       
403        oauth_params_free( &params );
404        g_free( sig );
405       
406        return ret ? g_string_free( ret, FALSE ) : NULL;
[da2efd4]407}
[f4b0911]408
409char *oauth_to_string( struct oauth_info *oi )
410{
411        GSList *params = NULL;
412        char *ret;
413       
414        oauth_params_add( &params, "oauth_token", oi->token );
415        oauth_params_add( &params, "oauth_token_secret", oi->token_secret );
416        ret = oauth_params_string( params );
417        oauth_params_free( &params );
418       
419        return ret;
420}
421
[3b878a1]422struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp )
[f4b0911]423{
424        struct oauth_info *oi = g_new0( struct oauth_info, 1 );
425        GSList *params = NULL;
426       
427        oauth_params_parse( &params, in );
428        oi->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
429        oi->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
430        oauth_params_free( &params );
431        oi->sp = sp;
432       
433        return oi;
434}
Note: See TracBrowser for help on using the repository browser.