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 program is free software; you can redistribute it and/or modify * |
---|
9 | * it under the terms of the GNU General Public License as published by * |
---|
10 | * the Free Software Foundation; either version 2 of the License, or * |
---|
11 | * (at your option) any later version. * |
---|
12 | * * |
---|
13 | * This program 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 * |
---|
16 | * GNU General Public License for more details. * |
---|
17 | * * |
---|
18 | * You should have received a copy of the GNU General Public License along * |
---|
19 | * with this program; if not, write to the Free Software Foundation, Inc., * |
---|
20 | * 51 Franklin Street, 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 | |
---|
37 | static char *oauth_sign(const char *method, const char *url, |
---|
38 | const char *params, struct oauth_info *oi) |
---|
39 | { |
---|
40 | uint8_t hash[SHA1_HASH_SIZE]; |
---|
41 | GString *payload = g_string_new(""); |
---|
42 | char *key; |
---|
43 | char *s; |
---|
44 | |
---|
45 | key = g_strdup_printf("%s&%s", oi->sp->consumer_secret, oi->token_secret ? oi->token_secret : ""); |
---|
46 | |
---|
47 | g_string_append_printf(payload, "%s&", method); |
---|
48 | |
---|
49 | s = g_new0(char, strlen(url) * 3 + 1); |
---|
50 | strcpy(s, url); |
---|
51 | http_encode(s); |
---|
52 | g_string_append_printf(payload, "%s&", s); |
---|
53 | g_free(s); |
---|
54 | |
---|
55 | s = g_new0(char, strlen(params) * 3 + 1); |
---|
56 | strcpy(s, params); |
---|
57 | http_encode(s); |
---|
58 | g_string_append(payload, s); |
---|
59 | g_free(s); |
---|
60 | |
---|
61 | sha1_hmac(key, 0, payload->str, 0, hash); |
---|
62 | |
---|
63 | g_free(key); |
---|
64 | g_string_free(payload, TRUE); |
---|
65 | |
---|
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; |
---|
73 | } |
---|
74 | |
---|
75 | static char *oauth_nonce() |
---|
76 | { |
---|
77 | unsigned char bytes[21]; |
---|
78 | |
---|
79 | random_bytes(bytes, sizeof(bytes)); |
---|
80 | return base64_encode(bytes, sizeof(bytes)); |
---|
81 | } |
---|
82 | |
---|
83 | void oauth_params_add(GSList **params, const char *key, const char *value) |
---|
84 | { |
---|
85 | char *item; |
---|
86 | |
---|
87 | if (!key || !value) { |
---|
88 | return; |
---|
89 | } |
---|
90 | |
---|
91 | item = g_strdup_printf("%s=%s", key, value); |
---|
92 | *params = g_slist_insert_sorted(*params, item, (GCompareFunc) strcmp); |
---|
93 | } |
---|
94 | |
---|
95 | void oauth_params_del(GSList **params, const char *key) |
---|
96 | { |
---|
97 | int key_len = strlen(key); |
---|
98 | GSList *l, *n; |
---|
99 | |
---|
100 | if (params == NULL) { |
---|
101 | return; |
---|
102 | } |
---|
103 | |
---|
104 | for (l = *params; l; l = n) { |
---|
105 | n = l->next; |
---|
106 | |
---|
107 | if (strncmp((char *) l->data, key, key_len) == 0 && |
---|
108 | ((char *) l->data)[key_len] == '=') { |
---|
109 | *params = g_slist_remove(*params, l->data); |
---|
110 | g_free(l->data); |
---|
111 | } |
---|
112 | } |
---|
113 | } |
---|
114 | |
---|
115 | void oauth_params_set(GSList **params, const char *key, const char *value) |
---|
116 | { |
---|
117 | oauth_params_del(params, key); |
---|
118 | oauth_params_add(params, key, value); |
---|
119 | } |
---|
120 | |
---|
121 | const char *oauth_params_get(GSList **params, const char *key) |
---|
122 | { |
---|
123 | int key_len = strlen(key); |
---|
124 | GSList *l; |
---|
125 | |
---|
126 | if (params == NULL) { |
---|
127 | return NULL; |
---|
128 | } |
---|
129 | |
---|
130 | for (l = *params; l; l = l->next) { |
---|
131 | if (strncmp((char *) l->data, key, key_len) == 0 && |
---|
132 | ((char *) l->data)[key_len] == '=') { |
---|
133 | return (const char *) l->data + key_len + 1; |
---|
134 | } |
---|
135 | } |
---|
136 | |
---|
137 | return NULL; |
---|
138 | } |
---|
139 | |
---|
140 | void oauth_params_parse(GSList **params, char *in) |
---|
141 | { |
---|
142 | char *amp, *eq, *s; |
---|
143 | |
---|
144 | while (in && *in) { |
---|
145 | eq = strchr(in, '='); |
---|
146 | if (!eq) { |
---|
147 | break; |
---|
148 | } |
---|
149 | |
---|
150 | *eq = '\0'; |
---|
151 | if ((amp = strchr(eq + 1, '&'))) { |
---|
152 | *amp = '\0'; |
---|
153 | } |
---|
154 | |
---|
155 | s = g_strdup(eq + 1); |
---|
156 | http_decode(s); |
---|
157 | oauth_params_add(params, in, s); |
---|
158 | g_free(s); |
---|
159 | |
---|
160 | *eq = '='; |
---|
161 | if (amp == NULL) { |
---|
162 | break; |
---|
163 | } |
---|
164 | |
---|
165 | *amp = '&'; |
---|
166 | in = amp + 1; |
---|
167 | } |
---|
168 | } |
---|
169 | |
---|
170 | void oauth_params_free(GSList **params) |
---|
171 | { |
---|
172 | while (params && *params) { |
---|
173 | g_free((*params)->data); |
---|
174 | *params = g_slist_remove(*params, (*params)->data); |
---|
175 | } |
---|
176 | } |
---|
177 | |
---|
178 | char *oauth_params_string(GSList *params) |
---|
179 | { |
---|
180 | GSList *l; |
---|
181 | GString *str = g_string_new(""); |
---|
182 | |
---|
183 | for (l = params; l; l = l->next) { |
---|
184 | char *s, *eq; |
---|
185 | |
---|
186 | s = g_malloc(strlen(l->data) * 3 + 1); |
---|
187 | strcpy(s, l->data); |
---|
188 | if ((eq = strchr(s, '='))) { |
---|
189 | http_encode(eq + 1); |
---|
190 | } |
---|
191 | g_string_append(str, s); |
---|
192 | g_free(s); |
---|
193 | |
---|
194 | if (l->next) { |
---|
195 | g_string_append_c(str, '&'); |
---|
196 | } |
---|
197 | } |
---|
198 | |
---|
199 | return g_string_free(str, FALSE); |
---|
200 | } |
---|
201 | |
---|
202 | void oauth_info_free(struct oauth_info *info) |
---|
203 | { |
---|
204 | if (info) { |
---|
205 | g_free(info->auth_url); |
---|
206 | g_free(info->request_token); |
---|
207 | g_free(info->token); |
---|
208 | g_free(info->token_secret); |
---|
209 | oauth_params_free(&info->params); |
---|
210 | g_free(info); |
---|
211 | } |
---|
212 | } |
---|
213 | |
---|
214 | static void oauth_add_default_params(GSList **params, const struct oauth_service *sp) |
---|
215 | { |
---|
216 | char *s; |
---|
217 | |
---|
218 | oauth_params_set(params, "oauth_consumer_key", sp->consumer_key); |
---|
219 | oauth_params_set(params, "oauth_signature_method", "HMAC-SHA1"); |
---|
220 | |
---|
221 | s = g_strdup_printf("%d", (int) time(NULL)); |
---|
222 | oauth_params_set(params, "oauth_timestamp", s); |
---|
223 | g_free(s); |
---|
224 | |
---|
225 | s = oauth_nonce(); |
---|
226 | oauth_params_set(params, "oauth_nonce", s); |
---|
227 | g_free(s); |
---|
228 | |
---|
229 | oauth_params_set(params, "oauth_version", "1.0"); |
---|
230 | } |
---|
231 | |
---|
232 | static void *oauth_post_request(const char *url, GSList **params_, http_input_function func, struct oauth_info *oi) |
---|
233 | { |
---|
234 | GSList *params = NULL; |
---|
235 | char *s, *params_s, *post; |
---|
236 | void *req; |
---|
237 | url_t url_p; |
---|
238 | |
---|
239 | if (!url_set(&url_p, url)) { |
---|
240 | oauth_params_free(params_); |
---|
241 | return NULL; |
---|
242 | } |
---|
243 | |
---|
244 | if (params_) { |
---|
245 | params = *params_; |
---|
246 | } |
---|
247 | |
---|
248 | oauth_add_default_params(¶ms, oi->sp); |
---|
249 | |
---|
250 | params_s = oauth_params_string(params); |
---|
251 | oauth_params_free(¶ms); |
---|
252 | |
---|
253 | s = oauth_sign("POST", url, params_s, oi); |
---|
254 | post = g_strdup_printf("%s&oauth_signature=%s", params_s, s); |
---|
255 | g_free(params_s); |
---|
256 | g_free(s); |
---|
257 | |
---|
258 | s = g_strdup_printf("POST %s HTTP/1.0\r\n" |
---|
259 | "Host: %s\r\n" |
---|
260 | "Content-Type: application/x-www-form-urlencoded\r\n" |
---|
261 | "Content-Length: %zd\r\n" |
---|
262 | "\r\n" |
---|
263 | "%s", url_p.file, url_p.host, strlen(post), post); |
---|
264 | g_free(post); |
---|
265 | |
---|
266 | req = http_dorequest(url_p.host, url_p.port, url_p.proto == PROTO_HTTPS, |
---|
267 | s, func, oi); |
---|
268 | g_free(s); |
---|
269 | |
---|
270 | return req; |
---|
271 | } |
---|
272 | |
---|
273 | static void oauth_request_token_done(struct http_request *req); |
---|
274 | |
---|
275 | struct oauth_info *oauth_request_token(const struct oauth_service *sp, oauth_cb func, void *data) |
---|
276 | { |
---|
277 | struct oauth_info *st = g_new0(struct oauth_info, 1); |
---|
278 | GSList *params = NULL; |
---|
279 | |
---|
280 | st->func = func; |
---|
281 | st->data = data; |
---|
282 | st->sp = sp; |
---|
283 | |
---|
284 | oauth_params_add(¶ms, "oauth_callback", "oob"); |
---|
285 | |
---|
286 | if (!oauth_post_request(sp->url_request_token, ¶ms, oauth_request_token_done, st)) { |
---|
287 | oauth_info_free(st); |
---|
288 | return NULL; |
---|
289 | } |
---|
290 | |
---|
291 | return st; |
---|
292 | } |
---|
293 | |
---|
294 | static void oauth_request_token_done(struct http_request *req) |
---|
295 | { |
---|
296 | struct oauth_info *st = req->data; |
---|
297 | |
---|
298 | st->http = req; |
---|
299 | |
---|
300 | if (req->status_code == 200) { |
---|
301 | GSList *params = NULL; |
---|
302 | |
---|
303 | st->auth_url = g_strdup_printf("%s?%s", st->sp->url_authorize, req->reply_body); |
---|
304 | oauth_params_parse(¶ms, req->reply_body); |
---|
305 | st->request_token = g_strdup(oauth_params_get(¶ms, "oauth_token")); |
---|
306 | st->token_secret = g_strdup(oauth_params_get(¶ms, "oauth_token_secret")); |
---|
307 | oauth_params_free(¶ms); |
---|
308 | } |
---|
309 | |
---|
310 | st->stage = OAUTH_REQUEST_TOKEN; |
---|
311 | st->func(st); |
---|
312 | } |
---|
313 | |
---|
314 | static void oauth_access_token_done(struct http_request *req); |
---|
315 | |
---|
316 | gboolean oauth_access_token(const char *pin, struct oauth_info *st) |
---|
317 | { |
---|
318 | GSList *params = NULL; |
---|
319 | |
---|
320 | oauth_params_add(¶ms, "oauth_token", st->request_token); |
---|
321 | oauth_params_add(¶ms, "oauth_verifier", pin); |
---|
322 | |
---|
323 | return oauth_post_request(st->sp->url_access_token, ¶ms, oauth_access_token_done, st) != NULL; |
---|
324 | } |
---|
325 | |
---|
326 | static void oauth_access_token_done(struct http_request *req) |
---|
327 | { |
---|
328 | struct oauth_info *st = req->data; |
---|
329 | |
---|
330 | st->http = req; |
---|
331 | |
---|
332 | if (req->status_code == 200) { |
---|
333 | oauth_params_parse(&st->params, req->reply_body); |
---|
334 | st->token = g_strdup(oauth_params_get(&st->params, "oauth_token")); |
---|
335 | g_free(st->token_secret); |
---|
336 | st->token_secret = g_strdup(oauth_params_get(&st->params, "oauth_token_secret")); |
---|
337 | } |
---|
338 | |
---|
339 | st->stage = OAUTH_ACCESS_TOKEN; |
---|
340 | if (st->func(st)) { |
---|
341 | /* Don't need these anymore, but keep the rest. */ |
---|
342 | g_free(st->auth_url); |
---|
343 | st->auth_url = NULL; |
---|
344 | g_free(st->request_token); |
---|
345 | st->request_token = NULL; |
---|
346 | oauth_params_free(&st->params); |
---|
347 | } |
---|
348 | } |
---|
349 | |
---|
350 | char *oauth_http_header(struct oauth_info *oi, const char *method, const char *url, char *args) |
---|
351 | { |
---|
352 | GSList *params = NULL, *l; |
---|
353 | char *sig = NULL, *params_s, *s; |
---|
354 | GString *ret = NULL; |
---|
355 | |
---|
356 | oauth_params_add(¶ms, "oauth_token", oi->token); |
---|
357 | oauth_add_default_params(¶ms, oi->sp); |
---|
358 | |
---|
359 | /* Start building the OAuth header. 'key="value", '... */ |
---|
360 | ret = g_string_new("OAuth "); |
---|
361 | for (l = params; l; l = l->next) { |
---|
362 | char *kv = l->data; |
---|
363 | char *eq = strchr(kv, '='); |
---|
364 | char esc[strlen(kv) * 3 + 1]; |
---|
365 | |
---|
366 | if (eq == NULL) { |
---|
367 | break; /* WTF */ |
---|
368 | |
---|
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 | |
---|
379 | /* Now, before generating the signature, add GET/POST arguments to params |
---|
380 | since they should be included in the base signature string (but not in |
---|
381 | the HTTP header). */ |
---|
382 | if (args) { |
---|
383 | oauth_params_parse(¶ms, args); |
---|
384 | } |
---|
385 | if ((s = strchr(url, '?'))) { |
---|
386 | s = g_strdup(s + 1); |
---|
387 | oauth_params_parse(¶ms, s); |
---|
388 | g_free(s); |
---|
389 | } |
---|
390 | |
---|
391 | /* Append the signature and we're done! */ |
---|
392 | params_s = oauth_params_string(params); |
---|
393 | sig = oauth_sign(method, url, params_s, oi); |
---|
394 | g_string_append_printf(ret, "oauth_signature=\"%s\"", sig); |
---|
395 | g_free(params_s); |
---|
396 | |
---|
397 | oauth_params_free(¶ms); |
---|
398 | g_free(sig); |
---|
399 | |
---|
400 | return ret ? g_string_free(ret, FALSE) : NULL; |
---|
401 | } |
---|
402 | |
---|
403 | char *oauth_to_string(struct oauth_info *oi) |
---|
404 | { |
---|
405 | GSList *params = NULL; |
---|
406 | char *ret; |
---|
407 | |
---|
408 | oauth_params_add(¶ms, "oauth_token", oi->token); |
---|
409 | oauth_params_add(¶ms, "oauth_token_secret", oi->token_secret); |
---|
410 | ret = oauth_params_string(params); |
---|
411 | oauth_params_free(¶ms); |
---|
412 | |
---|
413 | return ret; |
---|
414 | } |
---|
415 | |
---|
416 | struct oauth_info *oauth_from_string(char *in, const struct oauth_service *sp) |
---|
417 | { |
---|
418 | struct oauth_info *oi = g_new0(struct oauth_info, 1); |
---|
419 | GSList *params = NULL; |
---|
420 | |
---|
421 | oauth_params_parse(¶ms, in); |
---|
422 | oi->token = g_strdup(oauth_params_get(¶ms, "oauth_token")); |
---|
423 | oi->token_secret = g_strdup(oauth_params_get(¶ms, "oauth_token_secret")); |
---|
424 | oauth_params_free(¶ms); |
---|
425 | oi->sp = sp; |
---|
426 | |
---|
427 | return oi; |
---|
428 | } |
---|