/***************************************************************************\ * * * 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) { return; } for (l = *params; l; l = n) { n = l->next; char *data = l->data; if (strncmp(data, key, key_len) == 0 && data[key_len] == '=') { *params = g_slist_remove(*params, data); g_free(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; }