aboutsummaryrefslogtreecommitdiffstats
path: root/lib/oauth2.c
blob: 0348d0d03e653125a6ba3f34b515d81b5040ec6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight
/***************************************************************************\
*                                                                           *
*  BitlBee - An IRC to IM gateway                                           *
*  Simple OAuth client (consumer) implementation.                           *
*                                                                           *
*  Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net>              *
*                                                                           *
*  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 <glib.h>
#include "http_client.h"
#include "oauth2.h"
#include "oauth.h"
#include "url.h"

char *oauth2_url( const struct oauth2_service *sp )
{
	return g_strconcat( sp->auth_url,
	                    "?scope=", sp->scope,
	                    "&response_type=code"
	                    "&redirect_uri=", sp->redirect_url, 
	                    "&client_id=", sp->consumer_key,
	                    NULL );
}

struct oauth2_access_token_data
{
	oauth2_token_callback func;
	gpointer data;
};

static char *oauth2_json_dumb_get( const char *json, const char *key );
static void oauth2_access_token_done( struct http_request *req );

int oauth2_access_token( const struct oauth2_service *sp,
                         const char *auth_type, const char *auth,
                         oauth2_token_callback func, gpointer data )
{
	GSList *args = NULL;
	char *args_s, *s;
	url_t url_p;
	struct http_request *req;
	struct oauth2_access_token_data *cb_data;
	
	if( !url_set( &url_p, sp->token_url ) )
		return 0;
	
	oauth_params_add( &args, "client_id", sp->consumer_key );
	oauth_params_add( &args, "client_secret", sp->consumer_secret );
	oauth_params_add( &args, "grant_type", auth_type );
	if( strcmp( auth_type, OAUTH2_AUTH_CODE ) == 0 )
	{
		oauth_params_add( &args, "redirect_uri", sp->redirect_url );
		oauth_params_add( &args, "code", auth );
	}
	else
	{
		oauth_params_add( &args, "refresh_token", auth );
	}
	args_s = oauth_params_string( args );
	oauth_params_free( &args );
	
	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"
	                     "Connection: close\r\n"
	                     "\r\n"
	                     "%s", url_p.file, url_p.host, strlen( args_s ), args_s );
	g_free( args_s );
	
	cb_data = g_new0( struct oauth2_access_token_data, 1 );
	cb_data->func = func;
	cb_data->data = data;
	
	req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS,
	                      s, oauth2_access_token_done, cb_data );
	
	g_free( s );
	
	if( req == NULL )
		g_free( cb_data );
	
	return req != NULL;
}

static void oauth2_access_token_done( struct http_request *req )
{
	struct oauth2_access_token_data *cb_data = req->data;
	char *atoken = NULL, *rtoken = NULL;
	const char *content_type;
	
	if( getenv( "BITLBEE_DEBUG" ) && req->reply_body )
		printf( "%s\n", req->reply_body );
	
	content_type = get_rfc822_header( req->reply_headers, "Content-Type", 0 );
	
	if( req->status_code != 200 )
	{
	}
	else if( content_type && strstr( content_type, "application/json" ) )
	{
		atoken = oauth2_json_dumb_get( req->reply_body, "access_token" );
		rtoken = oauth2_json_dumb_get( req->reply_body, "refresh_token" );
		if( getenv( "BITLBEE_DEBUG" ) )
			printf( "Extracted atoken=%s rtoken=%s\n", atoken, rtoken );
	}
	else
	{
		/* Facebook use their own odd format here, seems to be URL-encoded. */
		GSList *p_in = NULL;
		
		oauth_params_parse( &p_in, req->reply_body );
		atoken = g_strdup( oauth_params_get( &p_in, "access_token" ) );
		rtoken = g_strdup( oauth_params_get( &p_in, "refresh_token" ) );
		oauth_params_free( &p_in );
	}
	cb_data->func( cb_data->data, atoken, rtoken );
	g_free( atoken );
	g_free( rtoken );
	g_free( cb_data );
}

/* Super dumb. I absolutely refuse to use/add a complete json parser library
   (adding a new dependency to BitlBee for the first time in.. 6 years?) just
   to parse 100 bytes of data. So I have to do my own parsing because OAuth2
   dropped support for XML. (GRRR!) This is very dumb and for example won't
   work for integer values, nor will it strip/handle backslashes. */
static char *oauth2_json_dumb_get( const char *json, const char *key )
{
	int is_key; /* 1 == reading key, 0 == reading value */
	int found_key = 0;
		
	while( json && *json )
	{
		/* Grab strings and see if they're what we're looking for. */
		if( *json == '"' || *json == '\'' )
		{
			char q = *json;
			const char *str_start;
			json ++;
			str_start = json;
			
			while( *json )
			{
				/* \' and \" are not string terminators. */
				if( *json == '\\' && json[1] == q )
					json ++;
				/* But without a \ it is. */
				else if( *json == q )
					break;
				json ++;
			}
			if( *json == '\0' )
				return NULL;
			
			if( is_key && strncmp( str_start, key, strlen( key ) ) == 0 )
			{
				found_key = 1;
			}
			else if( !is_key && found_key )
			{
				char *ret = g_memdup( str_start, json - str_start + 1 );
				ret[json-str_start] = '\0';
				return ret;
			}
			
		}
		else if( *json == '{' || *json == ',' )
		{
			found_key = 0;
			is_key = 1;
		}
		else if( *json == ':' )
			is_key = 0;
		
		json ++;
	}
	
	return NULL;
}