/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth client (consumer) implementation. * * * * Copyright 2010 Wilmer van der Gaast * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include #include #include #include #include "http_client.h" #include "base64.h" #include "misc.h" #include "sha1.h" #include "url.h" #include "oauth.h" #define HMAC_BLOCK_SIZE 64 static char *oauth_sign( const char *method, const char *url, const char *params, struct oauth_info *oi ) { uint8_t hash[sha1_hash_size]; GString *payload = g_string_new( "" ); char *key; char *s; key = g_strdup_printf( "%s&%s", oi->sp->consumer_secret, oi->token_secret ? oi->token_secret : "" ); g_string_append_printf( payload, "%s&", method ); s = g_new0( char, strlen( url ) * 3 + 1 ); strcpy( s, url ); http_encode( s ); g_string_append_printf( payload, "%s&", s ); g_free( s ); s = g_new0( char, strlen( params ) * 3 + 1 ); strcpy( s, params ); http_encode( s ); g_string_append( payload, s ); g_free( s ); sha1_hmac( key, 0, payload->str, 0, hash ); g_free( key ); g_string_free( payload, TRUE ); /* base64_encode + HTTP escape it (both consumers need it that away) and we're done. */ s = base64_encode( hash, sha1_hash_size ); s = g_realloc( s, strlen( s ) * 3 + 1 ); http_encode( s ); return s; } static char *oauth_nonce() { unsigned char bytes[21]; random_bytes( bytes, sizeof( bytes ) ); return base64_encode( bytes, sizeof( bytes ) ); } void oauth_params_add( GSList **params, const char *key, const char *value ) { char *item; if( !key || !value ) return; item = g_strdup_printf( "%s=%s", key, value ); *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp ); } void oauth_params_del( GSList **params, const char *key ) { int key_len = strlen( key ); GSList *l, *n; if( params == NULL ) return; for( l = *params; l; l = n ) { n = l->next; if( strncmp( (char*) l->data, key, key_len ) == 0 && ((char*)l->data)[key_len] == '=' ) { g_free( l->data ); *params = g_slist_remove( *params, l->data ); } } } void oauth_params_set( GSList **params, const char *key, const char *value ) { oauth_params_del( params, key ); oauth_params_add( params, key, value ); } const char *oauth_params_get( GSList **params, const char *key ) { int key_len = strlen( key ); GSList *l; if( params == NULL ) return NULL; for( l = *params; l; l = l->next ) { if( strncmp( (char*) l->data, key, key_len ) == 0 && ((char*)l->data)[key_len] == '=' ) return (const char*) l->data + key_len + 1; } return NULL; } void oauth_params_parse( GSList **params, char *in ) { char *amp, *eq, *s; while( in && *in ) { eq = strchr( in, '=' ); if( !eq ) break; *eq = '\0'; if( ( amp = strchr( eq + 1, '&' ) ) ) *amp = '\0'; s = g_strdup( eq + 1 ); http_decode( s ); oauth_params_add( params, in, s ); g_free( s ); *eq = '='; if( amp == NULL ) break; *amp = '&'; in = amp + 1; } } void oauth_params_free( GSList **params ) { while( params && *params ) { g_free( (*params)->data ); *params = g_slist_remove( *params, (*params)->data ); } } char *oauth_params_string( GSList *params ) { GSList *l; GString *str = g_string_new( "" ); for( l = params; l; l = l->next ) { char *s, *eq; s = g_malloc( strlen( l->data ) * 3 + 1 ); strcpy( s, l->data ); if( ( eq = strchr( s, '=' ) ) ) http_encode( eq + 1 ); g_string_append( str, s ); g_free( s ); if( l->next ) g_string_append_c( str, '&' ); } return g_string_free( str, FALSE ); } void oauth_info_free( struct oauth_info *info ) { if( info ) { g_free( info->auth_url ); g_free( info->request_token ); g_free( info->token ); g_free( info->token_secret ); oauth_params_free( &info->params ); g_free( info ); } } static void oauth_add_default_params( GSList **params, const struct oauth_service *sp ) { char *s; oauth_params_set( params, "oauth_consumer_key", sp->consumer_key ); oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" ); s = g_strdup_printf( "%d", (int) time( NULL ) ); oauth_params_set( params, "oauth_timestamp", s ); g_free( s ); s = oauth_nonce(); oauth_params_set( params, "oauth_nonce", s ); g_free( s ); oauth_params_set( params, "oauth_version", "1.0" ); } static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, struct oauth_info *oi ) { GSList *params = NULL; char *s, *params_s, *post; void *req; url_t url_p; if( !url_set( &url_p, url ) ) { oauth_params_free( params_ ); return NULL; } if( params_ ) params = *params_; oauth_add_default_params( ¶ms, oi->sp ); params_s = oauth_params_string( params ); oauth_params_free( ¶ms ); s = oauth_sign( "POST", url, params_s, oi ); post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s ); g_free( params_s ); g_free( s ); s = g_strdup_printf( "POST %s HTTP/1.0\r\n" "Host: %s\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %zd\r\n" "\r\n" "%s", url_p.file, url_p.host, strlen( post ), post ); g_free( post ); req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS, s, func, oi ); g_free( s ); return req; } static void oauth_request_token_done( struct http_request *req ); struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data ) { struct oauth_info *st = g_new0( struct oauth_info, 1 ); GSList *params = NULL; st->func = func; st->data = data; st->sp = sp; oauth_params_add( ¶ms, "oauth_callback", "oob" ); if( !oauth_post_request( sp->url_request_token, ¶ms, oauth_request_token_done, st ) ) { oauth_info_free( st ); return NULL; } return st; } static void oauth_request_token_done( struct http_request *req ) { struct oauth_info *st = req->data; st->http = req; if( req->status_code == 200 ) { GSList *params = NULL; st->auth_url = g_strdup_printf( "%s?%s", st->sp->url_authorize, req->reply_body ); oauth_params_parse( ¶ms, req->reply_body ); st->request_token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); st->token_secret = g_strdup( oauth_params_get( ¶ms, "oauth_token_secret" ) ); oauth_params_free( ¶ms ); } st->stage = OAUTH_REQUEST_TOKEN; st->func( st ); } static void oauth_access_token_done( struct http_request *req ); gboolean oauth_access_token( const char *pin, struct oauth_info *st ) { GSList *params = NULL; oauth_params_add( ¶ms, "oauth_token", st->request_token ); oauth_params_add( ¶ms, "oauth_verifier", pin ); return oauth_post_request( st->sp->url_access_token, ¶ms, oauth_access_token_done, st ) != NULL; } static void oauth_access_token_done( struct http_request *req ) { struct oauth_info *st = req->data; st->http = req; if( req->status_code == 200 ) { oauth_params_parse( &st->params, req->reply_body ); st->token = g_strdup( oauth_params_get( &st->params, "oauth_token" ) ); g_free( st->token_secret ); st->token_secret = g_strdup( oauth_params_get( &st->params, "oauth_token_secret" ) ); } st->stage = OAUTH_ACCESS_TOKEN; if( st->func( st ) ) { /* Don't need these anymore, but keep the rest. */ g_free( st->auth_url ); st->auth_url = NULL; g_free( st->request_token ); st->request_token = NULL; oauth_params_free( &st->params ); } } char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args ) { GSList *params = NULL, *l; char *sig = NULL, *params_s, *s; GString *ret = NULL; oauth_params_add( ¶ms, "oauth_token", oi->token ); oauth_add_default_params( ¶ms, oi->sp ); /* Start building the OAuth header. 'key="value", '... */ ret = g_string_new( "OAuth " ); for( l = params; l; l = l->next ) { char *kv = l->data; char *eq = strchr( kv, '=' ); char esc[strlen(kv)*3+1]; if( eq == NULL ) break; /* WTF */ strcpy( esc, eq + 1 ); http_encode( esc ); g_string_append_len( ret, kv, eq - kv + 1 ); g_string_append_c( ret, '"' ); g_string_append( ret, esc ); g_string_append( ret, "\", " ); } /* Now, before generating the signature, add GET/POST arguments to params since they should be included in the base signature string (but not in the HTTP header). */ if( args ) oauth_params_parse( ¶ms, args ); if( ( s = strchr( url, '?' ) ) ) { s = g_strdup( s + 1 ); oauth_params_parse( ¶ms, s ); g_free( s ); } /* Append the signature and we're done! */ params_s = oauth_params_string( params ); sig = oauth_sign( method, url, params_s, oi ); g_string_append_printf( ret, "oauth_signature=\"%s\"", sig ); g_free( params_s ); oauth_params_free( ¶ms ); g_free( sig ); return ret ? g_string_free( ret, FALSE ) : NULL; } char *oauth_to_string( struct oauth_info *oi ) { GSList *params = NULL; char *ret; oauth_params_add( ¶ms, "oauth_token", oi->token ); oauth_params_add( ¶ms, "oauth_token_secret", oi->token_secret ); ret = oauth_params_string( params ); oauth_params_free( ¶ms ); return ret; } struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp ) { struct oauth_info *oi = g_new0( struct oauth_info, 1 ); GSList *params = NULL; oauth_params_parse( ¶ms, in ); oi->token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); oi->token_secret = g_strdup( oauth_params_get( ¶ms, "oauth_token_secret" ) ); oauth_params_free( ¶ms ); oi->sp = sp; return oi; }