source: lib/oauth.c @ 508c340

Last change on this file since 508c340 was 508c340, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-26T00:42:37Z

Successfully posted a tweet!

Twitter's tricky. It returns vars (user_id, screen_name) in the access token
that, the way I read the spec, should be included in all subsequent queries.
However, stuff only started to work when I dropped those vars.

This code's definitely not pretty ATM. Need to clean up now that it actually
works.

  • Property mode set to 100644
File size: 10.4 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"
[be28fe7]33
34#define CONSUMER_KEY "xsDNKJuNZYkZyMcu914uEA"
35#define CONSUMER_SECRET "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo"
36/* How can it be a secret if it's right here in the source code? No clue... */
37
38#define HMAC_BLOCK_SIZE 64
39
[da2efd4]40struct oauth_info;
41typedef void (*oauth_cb)( struct oauth_info * );
42
43struct oauth_info
[be28fe7]44{
[da2efd4]45        oauth_cb func;
46        void *data;
47       
48        struct http_request *http;
49       
50        char *auth_params;
51        char *token;
[b2bc25c]52       
53        char *access_token;
[be28fe7]54};
55
56static char *oauth_sign( const char *method, const char *url,
57                         const char *params, const char *token_secret )
58{
59        sha1_state_t sha1;
60        uint8_t hash[sha1_hash_size];
61        uint8_t key[HMAC_BLOCK_SIZE+1];
62        char *s;
63        int i;
64       
65        /* Create K. If our current key is >64 chars we have to hash it,
66           otherwise just pad. */
67        memset( key, 0, HMAC_BLOCK_SIZE );
[da2efd4]68        i = strlen( CONSUMER_SECRET ) + 1 + ( token_secret ? strlen( token_secret ) : 0 );
[be28fe7]69        if( i > HMAC_BLOCK_SIZE )
70        {
71                sha1_init( &sha1 );
[da2efd4]72                sha1_append( &sha1, (uint8_t*) CONSUMER_SECRET, strlen( CONSUMER_SECRET ) );
73                sha1_append( &sha1, (uint8_t*) "&", 1 );
[be28fe7]74                if( token_secret )
[da2efd4]75                        sha1_append( &sha1, (uint8_t*) token_secret, strlen( token_secret ) );
[be28fe7]76                sha1_finish( &sha1, key );
77        }
78        else
79        {
[da2efd4]80                g_snprintf( (gchar*) key, HMAC_BLOCK_SIZE + 1, "%s&%s",
[be28fe7]81                            CONSUMER_SECRET, token_secret ? : "" );
82        }
83       
84        /* Inner part: H(K XOR 0x36, text) */
85        sha1_init( &sha1 );
86       
87        for( i = 0; i < HMAC_BLOCK_SIZE; i ++ )
88                key[i] ^= 0x36;
89        sha1_append( &sha1, key, HMAC_BLOCK_SIZE );
90       
91        /* OAuth: text = method&url&params, all http_encoded. */
92        sha1_append( &sha1, (const uint8_t*) method, strlen( method ) );
93        sha1_append( &sha1, (const uint8_t*) "&", 1 );
94       
95        s = g_new0( char, strlen( url ) * 3 + 1 );
96        strcpy( s, url );
97        http_encode( s );
98        sha1_append( &sha1, (const uint8_t*) s, strlen( s ) );
99        sha1_append( &sha1, (const uint8_t*) "&", 1 );
100        g_free( s );
101       
102        s = g_new0( char, strlen( params ) * 3 + 1 );
103        strcpy( s, params );
104        http_encode( s );
105        sha1_append( &sha1, (const uint8_t*) s, strlen( s ) );
106        g_free( s );
107       
108        sha1_finish( &sha1, hash );
109       
110        /* Final result: H(K XOR 0x5C, inner stuff) */
111        sha1_init( &sha1 );
112        for( i = 0; i < HMAC_BLOCK_SIZE; i ++ )
113                key[i] ^= 0x36 ^ 0x5c;
114        sha1_append( &sha1, key, HMAC_BLOCK_SIZE );
115        sha1_append( &sha1, hash, sha1_hash_size );
116        sha1_finish( &sha1, hash );
117       
118        /* base64_encode it and we're done. */
119        return base64_encode( hash, sha1_hash_size );
120}
[da2efd4]121
122static char *oauth_nonce()
123{
124        unsigned char bytes[9];
125       
126        random_bytes( bytes, sizeof( bytes ) );
127        return base64_encode( bytes, sizeof( bytes ) );
128}
129
130void oauth_params_add( GSList **params, const char *key, const char *value )
131{
132        char *item;
133       
134        item = g_strdup_printf( "%s=%s", key, value );
135        *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp );
136}
137
138void oauth_params_del( GSList **params, const char *key )
139{
140        int key_len = strlen( key );
141        GSList *l, *n;
142       
143        for( l = *params; l; l = n )
144        {
145                n = l->next;
146               
147                if( strncmp( (char*) l->data, key, key_len ) == 0 &&
148                    ((char*)l->data)[key_len] == '=' )
149                {
150                        g_free( l->data );
[b2bc25c]151                        *params = g_slist_remove( *params, l->data );
[da2efd4]152                }
153        }
154}
155
156void oauth_params_set( GSList **params, const char *key, const char *value )
157{
158        oauth_params_del( params, key );
159        oauth_params_add( params, key, value );
160}
161
162const char *oauth_params_get( GSList **params, const char *key )
163{
164        int key_len = strlen( key );
165        GSList *l;
166       
167        for( l = *params; l; l = l->next )
168        {
169                if( strncmp( (char*) l->data, key, key_len ) == 0 &&
170                    ((char*)l->data)[key_len] == '=' )
171                        return (const char*) l->data + key_len + 1;
172        }
173       
174        return NULL;
175}
176
[508c340]177static void oauth_params_parse( GSList **params, char *in )
[da2efd4]178{
179        char *amp, *eq;
180       
181        while( in && *in )
182        {
183                eq = strchr( in, '=' );
184                if( !eq )
185                        break;
186               
187                *eq = '\0';
188                if( ( amp = strchr( eq + 1, '&' ) ) )
189                        *amp = '\0';
190               
[508c340]191                oauth_params_add( params, in, eq + 1 );
[da2efd4]192               
193                *eq = '=';
194                if( amp == NULL )
195                        break;
196               
197                *amp = '&';
198                in = amp + 1;
199        }
200}
201
202void oauth_params_free( GSList **params )
203{
204        while( params && *params )
205        {
206                g_free( (*params)->data );
207                *params = g_slist_remove( *params, (*params)->data );
208        }
209}
210
211char *oauth_params_string( GSList *params )
212{
213        GSList *l;
214        GString *str = g_string_new( "" );
215       
216        for( l = params; l; l = l->next )
217        {
218                g_string_append( str, l->data );
219                if( l->next )
220                        g_string_append_c( str, '&' );
221        }
222       
223        return g_string_free( str, FALSE );
224}
225
[b2bc25c]226static void oauth_add_default_params( GSList **params )
227{
228        char *s;
229       
230        oauth_params_set( params, "oauth_consumer_key", CONSUMER_KEY );
231        oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" );
232       
233        s = g_strdup_printf( "%d", (int) time( NULL ) );
234        oauth_params_set( params, "oauth_timestamp", s );
235        g_free( s );
236       
237        s = oauth_nonce();
238        oauth_params_set( params, "oauth_nonce", s );
239        g_free( s );
240       
241        oauth_params_set( params, "oauth_version", "1.0" );
242}
243
[da2efd4]244static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, void *data )
245{
246        GSList *params = NULL;
247        char *s, *params_s, *post;
248        void *req;
249        url_t url_p;
250       
251        if( !url_set( &url_p, url ) )
252        {
253                oauth_params_free( params_ );
254                return NULL;
255        }
256       
257        if( params_ )
258                params = *params_;
259       
[b2bc25c]260        oauth_add_default_params( &params );
[da2efd4]261       
262        params_s = oauth_params_string( params );
263        oauth_params_free( params_ );
264       
265        s = oauth_sign( "POST", url, params_s, NULL );
266        s = g_realloc( s, strlen( s ) * 3 + 1 );
267        http_encode( s );
268       
269        post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s );
270        g_free( params_s );
271        g_free( s );
272       
273        s = g_strdup_printf( "POST %s HTTP/1.0\r\n"
274                             "Host: %s\r\n"
275                             "Content-Type: application/x-www-form-urlencoded\r\n"
276                             "Content-Length: %zd\r\n"
277                             "\r\n"
278                             "%s", url_p.file, url_p.host, strlen( post ), post );
279        g_free( post );
280       
281        req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS,
282                              s, func, data );
283        g_free( s );
284       
285        return req;
286}
287
[346dfd9]288static void oauth_request_token_done( struct http_request *req );
[da2efd4]289
290void *oauth_request_token( const char *url, oauth_cb func, void *data )
291{
292        struct oauth_info *st = g_new0( struct oauth_info, 1 );
[346dfd9]293        GSList *params = NULL;
[da2efd4]294       
295        st->func = func;
296        st->data = data;
297       
[346dfd9]298        oauth_params_add( &params, "oauth_callback", "oob" );
299       
[da2efd4]300        return oauth_post_request( url, NULL, oauth_request_token_done, st );
301}
302
[346dfd9]303static void oauth_request_token_done( struct http_request *req )
[da2efd4]304{
305        struct oauth_info *st = req->data;
306       
307        st->http = req;
308       
309        if( req->status_code == 200 )
310        {
[508c340]311                GSList *params = NULL;
[da2efd4]312               
313                st->auth_params = g_strdup( req->reply_body );
[508c340]314                oauth_params_parse( &params, st->auth_params );
[da2efd4]315                st->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
316                oauth_params_free( &params );
317        }
318       
[346dfd9]319        //st->func( st );
320}
321
322static void oauth_access_token_done( struct http_request *req );
323
324void *oauth_access_token( const char *url, const char *pin, struct oauth_info *st )
325{
326        GSList *params = NULL;
327       
328        oauth_params_add( &params, "oauth_token", st->token );
329        oauth_params_add( &params, "oauth_verifier", pin );
330       
331        return oauth_post_request( url, &params, oauth_access_token_done, st );
332}
333
334static void oauth_access_token_done( struct http_request *req )
335{
[b2bc25c]336        struct oauth_info *st = req->data;
337       
338        if( req->status_code == 200 )
339                st->access_token = g_strdup( req->reply_body );
340       
341        //st->func( st );
342}
343
[508c340]344char *oauth_http_header( char *access_token, const char *method, const char *url, char *args )
[b2bc25c]345{
[508c340]346        GSList *params = NULL, *l;
347        char *token_secret, *sig, *params_s, *s;
[b2bc25c]348        GString *ret = NULL;
349       
[508c340]350        oauth_params_parse( &params, access_token );
[b2bc25c]351        if( params == NULL )
352                goto err;
353        token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
354        if( token_secret == NULL )
355                goto err;
356        oauth_params_del( &params, "oauth_token_secret" );
357       
358        oauth_add_default_params( &params );
359       
360        ret = g_string_new( "OAuth " );
361        for( l = params; l; l = l->next )
362        {
363                char *kv = l->data;
364                char *eq = strchr( kv, '=' );
365                char esc[strlen(kv)*3+1];
366               
367                if( eq == NULL )
368                        break; /* WTF */
369               
370                strcpy( esc, eq + 1 );
371                http_encode( esc );
372               
373                g_string_append_len( ret, kv, eq - kv + 1 );
374                g_string_append_c( ret, '"' );
375                g_string_append( ret, esc );
376                g_string_append( ret, "\", " );
377        }
378       
[508c340]379        if( args )
380                oauth_params_parse( &params, args );
381        if( ( s = strchr( url, '?' ) ) )
382        {
383                s = g_strdup( s + 1 );
384                oauth_params_parse( &params, s + 1 );
385                g_free( s );
386        }
387       
388        params_s = oauth_params_string( params );
389        sig = oauth_sign( method, url, params_s, token_secret );
390        g_free( params_s );
391        sig = g_realloc( sig, strlen( sig ) * 3 + 1 );
392        http_encode( sig );
393       
[b2bc25c]394        g_string_append_printf( ret, "oauth_signature=\"%s\"", sig );
395       
396err:
397        oauth_params_free( &params );
398        g_free( sig );
399        g_free( token_secret );
400       
401        return ret ? g_string_free( ret, FALSE ) : NULL;
[da2efd4]402}
Note: See TracBrowser for help on using the repository browser.