From 4649bcfc2b1cbbe2f004d7bec963a7528866c072 Mon Sep 17 00:00:00 2001 From: George Hazan Date: Sun, 15 Jul 2012 16:44:52 +0000 Subject: =?UTF-8?q?z=20ca=C5=82ym=20szacunkiem=20dla=20naszych=20polskich?= =?UTF-8?q?=20u=C5=BCytkownik=C3=B3w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://svn.miranda-ng.org/main/trunk@977 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/Gadu-Gadu/oauth.cpp | 584 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 protocols/Gadu-Gadu/oauth.cpp (limited to 'protocols/Gadu-Gadu/oauth.cpp') diff --git a/protocols/Gadu-Gadu/oauth.cpp b/protocols/Gadu-Gadu/oauth.cpp new file mode 100644 index 0000000000..d652a79020 --- /dev/null +++ b/protocols/Gadu-Gadu/oauth.cpp @@ -0,0 +1,584 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2010 Bartosz Białek +// +// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +//////////////////////////////////////////////////////////////////////////////// + +#include "gg.h" +#include +#include +#include "protocol.h" + +////////////////////////////////////////////////////////// +// OAuth 1.0 implementation + +// Service Provider must accept the HTTP Authorization header + +// RSA-SHA1 signature method (see RFC 3447 section 8.2 +// and RSASSA-PKCS1-v1_5 algorithm) is unimplemented + +typedef struct +{ + char *name; + char *value; +} OAUTHPARAMETER; + +typedef enum +{ + HMACSHA1, + RSASHA1, + PLAINTEXT +} OAUTHSIGNMETHOD; + +static int paramsortFunc(const OAUTHPARAMETER *p1, const OAUTHPARAMETER *p2) +{ + int res = strcmp(p1->name, p2->name); + return res != 0 ? res : strcmp(p1->value, p2->value); +} + +// HMAC-SHA1 (see RFC 2104 for details) +void hmacsha1_hash(mir_sha1_byte_t *text, int text_len, mir_sha1_byte_t *key, int key_len, + mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE]) +{ + mir_sha1_ctx context; + mir_sha1_byte_t k_ipad[64]; + mir_sha1_byte_t k_opad[64]; + int i; + + if (key_len > 64) { + mir_sha1_ctx tctx; + mir_sha1_byte_t tk[MIR_SHA1_HASH_SIZE]; + + mir_sha1_init(&tctx); + mir_sha1_append(&tctx, key, key_len); + mir_sha1_finish(&tctx, tk); + + key = tk; + key_len = MIR_SHA1_HASH_SIZE; + } + + memset(k_ipad, 0x36, 64); + memset(k_opad, 0x5c, 64); + + for (i = 0; i < key_len; i++) { + k_ipad[i] ^= key[i]; + k_opad[i] ^= key[i]; + } + + mir_sha1_init(&context); + mir_sha1_append(&context, k_ipad, 64); + mir_sha1_append(&context, text, text_len); + mir_sha1_finish(&context, digest); + + mir_sha1_init(&context); + mir_sha1_append(&context, k_opad, 64); + mir_sha1_append(&context, digest, MIR_SHA1_HASH_SIZE); + mir_sha1_finish(&context, digest); +} + +// see RFC 3986 for details +#define isunreserved(c) ( isalnum((unsigned char)c) || c == '-' || c == '.' || c == '_' || c == '~') +char *oauth_uri_escape(const char *str) +{ + char *res; + int size, ix = 0; + + if (str == NULL) return mir_strdup(""); + + size = (int)strlen(str) + 1; + res = (char *)mir_alloc(size); + + while (*str) { + if (!isunreserved(*str)) { + size += 2; + res = (char *)mir_realloc(res, size); + mir_snprintf(&res[ix], 4, "%%%X%X", (*str >> 4) & 15, *str & 15); + ix += 3; + } + else + res[ix++] = *str; + str++; + } + res[ix] = 0; + + return res; +} + +// generates Signature Base String + +char *oauth_generate_signature(LIST ¶ms, const char *httpmethod, const char *url) +{ + char *res, *urlenc, *urlnorm; + OAUTHPARAMETER *p; + int i, ix = 0, size; + + if (httpmethod == NULL || url == NULL || !params.getCount()) return mir_strdup(""); + + urlnorm = (char *)mir_alloc(strlen(url) + 1); + while (*url) { + if (*url == '?' || *url == '#') break; // see RFC 3986 section 3 + urlnorm[ix++] = tolower(*url); + url++; + } + urlnorm[ix] = 0; + if ((res = strstr(urlnorm, ":80")) != NULL) + memmove(res, res + 3, strlen(res) - 2); + else if ((res = strstr(urlnorm, ":443")) != NULL) + memmove(res, res + 4, strlen(res) - 3); + + urlenc = oauth_uri_escape(urlnorm); + mir_free(urlnorm); + size = (int)strlen(httpmethod) + (int)strlen(urlenc) + 1 + 2; + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + if (!strcmp(p->name, "oauth_signature")) continue; + if (i > 0) size += 3; + size += (int)strlen(p->name) + (int)strlen(p->value) + 3; + } + + res = (char *)mir_alloc(size); + strcpy(res, httpmethod); + strcat(res, "&"); + strcat(res, urlenc); + mir_free(urlenc); + strcat(res, "&"); + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + if (!strcmp(p->name, "oauth_signature")) continue; + if (i > 0) strcat(res, "%26"); + strcat(res, p->name); + strcat(res, "%3D"); + strcat(res, p->value); + } + + return res; +} + +char *oauth_getparam(LIST ¶ms, const char *name) +{ + OAUTHPARAMETER *p; + int i; + + if (name == NULL) return NULL; + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + if (!strcmp(p->name, name)) + return p->value; + } + + return NULL; +} + +void oauth_setparam(LIST ¶ms, const char *name, const char *value) +{ + OAUTHPARAMETER *p; + int i; + + if (name == NULL) return; + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + if (!strcmp(p->name, name)) { + mir_free(p->value); + p->value = oauth_uri_escape(value); + return; + } + } + + p = (OAUTHPARAMETER*)mir_alloc(sizeof(OAUTHPARAMETER)); + p->name = oauth_uri_escape(name); + p->value = oauth_uri_escape(value); + params.insert(p); +} + +void oauth_freeparams(LIST ¶ms) +{ + OAUTHPARAMETER *p; + int i; + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + mir_free(p->name); + mir_free(p->value); + } +} + +int oauth_sign_request(LIST ¶ms, const char *httpmethod, const char *url, + const char *consumer_secret, const char *token_secret) +{ + char *sign = NULL, *signmethod; + + if (!params.getCount()) return -1; + + signmethod = oauth_getparam(params, "oauth_signature_method"); + if (signmethod == NULL) return -1; + + if (!strcmp(signmethod, "HMAC-SHA1")) { + char *text = oauth_generate_signature(params, httpmethod, url); + char *key; + char *csenc = oauth_uri_escape(consumer_secret); + char *tsenc = oauth_uri_escape(token_secret); + mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE]; + NETLIBBASE64 nlb64 = {0}; + int signlen; + + key = (char *)mir_alloc(strlen(csenc) + strlen(tsenc) + 2); + strcpy(key, csenc); + strcat(key, "&"); + strcat(key, tsenc); + mir_free(csenc); + mir_free(tsenc); + + hmacsha1_hash((BYTE*)text, (int)strlen(text), (BYTE*)key, (int)strlen(key), digest); + + signlen = Netlib_GetBase64EncodedBufferSize(MIR_SHA1_HASH_SIZE); + sign = (char *)mir_alloc(signlen); + nlb64.pszEncoded = sign; + nlb64.cchEncoded = signlen; + nlb64.pbDecoded = digest; + nlb64.cbDecoded = MIR_SHA1_HASH_SIZE; + CallService(MS_NETLIB_BASE64ENCODE, 0, (LPARAM)&nlb64); + + mir_free(text); + mir_free(key); + } +// else if (!strcmp(signmethod, "RSA-SHA1")) { // unimplemented +// } + else { // PLAINTEXT + char *csenc = oauth_uri_escape(consumer_secret); + char *tsenc = oauth_uri_escape(token_secret); + + sign = (char *)mir_alloc(strlen(csenc) + strlen(tsenc) + 2); + strcpy(sign, csenc); + strcat(sign, "&"); + strcat(sign, tsenc); + mir_free(csenc); + mir_free(tsenc); + } + + oauth_setparam(params, "oauth_signature", sign); + mir_free(sign); + + return 0; +} + +char *oauth_generate_nonce() +{ + mir_md5_byte_t digest[16]; + char *result, *str, timestamp[22], randnum[16]; + int i; + + mir_snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL)); + CallService(MS_UTILS_GETRANDOM, (WPARAM)sizeof(randnum), (LPARAM)randnum); + + str = (char *)mir_alloc(strlen(timestamp) + strlen(randnum) + 1); + strcpy(str, timestamp); + strcat(str, randnum); + mir_md5_hash((BYTE*)str, (int)strlen(str), digest); + mir_free(str); + + result = (char *)mir_alloc(32 + 1); + for (i = 0; i < 16; i++) + sprintf(result + (i<<1), "%02x", digest[i]); + + return result; +} + +char *oauth_auth_header(const char *httpmethod, const char *url, OAUTHSIGNMETHOD signmethod, + const char *consumer_key, const char *consumer_secret, + const char *token, const char *token_secret) +{ + int i, size; + char *res, timestamp[22], *nonce; + + if (httpmethod == NULL || url == NULL) return NULL; + + LIST oauth_parameters(1, paramsortFunc); + oauth_setparam(oauth_parameters, "oauth_consumer_key", consumer_key); + oauth_setparam(oauth_parameters, "oauth_version", "1.0"); + switch (signmethod) { + case HMACSHA1: oauth_setparam(oauth_parameters, "oauth_signature_method", "HMAC-SHA1"); break; + case RSASHA1: oauth_setparam(oauth_parameters, "oauth_signature_method", "RSA-SHA1"); break; + default: oauth_setparam(oauth_parameters, "oauth_signature_method", "PLAINTEXT"); break; + }; + mir_snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL)); + oauth_setparam(oauth_parameters, "oauth_timestamp", timestamp); + nonce = oauth_generate_nonce(); + oauth_setparam(oauth_parameters, "oauth_nonce", nonce); + mir_free(nonce); + if (token != NULL && *token) + oauth_setparam(oauth_parameters, "oauth_token", token); + + if (oauth_sign_request(oauth_parameters, httpmethod, url, consumer_secret, token_secret)) { + oauth_freeparams(oauth_parameters); + oauth_parameters.destroy(); + return NULL; + } + + size = 7; + for (i = 0; i < oauth_parameters.getCount(); i++) { + OAUTHPARAMETER *p = oauth_parameters[i]; + if (i > 0) size++; + size += (int)strlen(p->name) + (int)strlen(p->value) + 3; + } + + res = (char *)mir_alloc(size); + strcpy(res, "OAuth "); + + for (i = 0; i < oauth_parameters.getCount(); i++) { + OAUTHPARAMETER *p = oauth_parameters[i]; + if (i > 0) strcat(res, ","); + strcat(res, p->name); + strcat(res, "=\""); + strcat(res, p->value); + strcat(res, "\""); + } + + oauth_freeparams(oauth_parameters); + oauth_parameters.destroy(); + return res; +} + +char* GGPROTO::oauth_header(const char *httpmethod, const char *url) +{ + char *res, uin[32], *password = NULL, *token = NULL, *token_secret = NULL; + DBVARIANT dbv; + + UIN2ID(db_get_b(NULL, m_szModuleName, GG_KEY_UIN, 0), uin); + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + password = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKEN, &dbv, DBVT_ASCIIZ)) { + token = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + token_secret = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + + res = oauth_auth_header(httpmethod, url, HMACSHA1, uin, password, token, token_secret); + mir_free(password); + mir_free(token); + mir_free(token_secret); + + return res; +} + +int GGPROTO::oauth_receivetoken() +{ + NETLIBHTTPHEADER httpHeaders[3]; + NETLIBHTTPREQUEST req = {0}; + NETLIBHTTPREQUEST *resp; + char szUrl[256], uin[32], *password = NULL, *str, *token = NULL, *token_secret = NULL; + DBVARIANT dbv; + int res = 0; + HANDLE nlc = NULL; + + UIN2ID(db_get_b(NULL, m_szModuleName, GG_KEY_UIN, 0), uin); + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + password = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + + // 1. Obtaining an Unauthorized Request Token + netlog("gg_oauth_receivetoken(): Obtaining an Unauthorized Request Token..."); + strcpy(szUrl, "http://api.gadu-gadu.pl/request_token"); + str = oauth_auth_header("POST", szUrl, HMACSHA1, uin, password, NULL, NULL); + + req.cbSize = sizeof(req); + req.requestType = REQUEST_POST; + req.szUrl = szUrl; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_PERSISTENT; + req.headersCount = 3; + req.headers = httpHeaders; + httpHeaders[0].szName = "User-Agent"; + httpHeaders[0].szValue = GG8_VERSION; + httpHeaders[1].szName = "Authorization"; + httpHeaders[1].szValue = str; + httpHeaders[2].szName = "Accept"; + httpHeaders[2].szValue = "*/*"; + + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) { + nlc = resp->nlc; + if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) { + HXML hXml; + TCHAR *xmlAction; + TCHAR *tag; + + xmlAction = mir_a2t(resp->pData); + tag = mir_a2t("result"); + hXml = xi.parseString(xmlAction, 0, tag); + if (hXml != NULL) { + HXML node; + + mir_free(tag); tag = mir_a2t("oauth_token"); + node = xi.getChildByPath(hXml, tag, 0); + token = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + mir_free(tag); tag = mir_a2t("oauth_token_secret"); + node = xi.getChildByPath(hXml, tag, 0); + token_secret = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + xi.destroyNode(hXml); + } + mir_free(tag); + mir_free(xmlAction); + } + else netlog("gg_oauth_receivetoken(): Invalid response code from HTTP request"); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else netlog("gg_oauth_receivetoken(): No response from HTTP request"); + + // 2. Obtaining User Authorization + netlog("gg_oauth_receivetoken(): Obtaining User Authorization..."); + mir_free(str); + str = oauth_uri_escape("http://www.mojageneracja.pl"); + + mir_snprintf(szUrl, 256, "callback_url=%s&request_token=%s&uin=%s&password=%s", + str, token, uin, password); + mir_free(str); + str = mir_strdup(szUrl); + + ZeroMemory(&req, sizeof(req)); + req.cbSize = sizeof(req); + req.requestType = REQUEST_POST; + req.szUrl = szUrl; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11; + req.headersCount = 3; + req.headers = httpHeaders; + strcpy(szUrl, "https://login.gadu-gadu.pl/authorize"); + httpHeaders[1].szName = "Content-Type"; + httpHeaders[1].szValue = "application/x-www-form-urlencoded"; + req.pData = str; + req.dataLength = (int)strlen(str); + + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + else netlog("gg_oauth_receivetoken(): No response from HTTP request"); + + // 3. Obtaining an Access Token + netlog("gg_oauth_receivetoken(): Obtaining an Access Token..."); + strcpy(szUrl, "http://api.gadu-gadu.pl/access_token"); + mir_free(str); + str = oauth_auth_header("POST", szUrl, HMACSHA1, uin, password, token, token_secret); + mir_free(token); + mir_free(token_secret); + token = NULL; + token_secret = NULL; + + ZeroMemory(&req, sizeof(req)); + req.cbSize = sizeof(req); + req.requestType = REQUEST_POST; + req.szUrl = szUrl; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_PERSISTENT; + req.nlc = nlc; + req.headersCount = 3; + req.headers = httpHeaders; + httpHeaders[1].szName = "Authorization"; + httpHeaders[1].szValue = str; + + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) { + if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) { + HXML hXml; + TCHAR *xmlAction; + TCHAR *tag; + + xmlAction = mir_a2t(resp->pData); + tag = mir_a2t("result"); + hXml = xi.parseString(xmlAction, 0, tag); + if (hXml != NULL) { + HXML node; + + mir_free(tag); tag = mir_a2t("oauth_token"); + node = xi.getChildByPath(hXml, tag, 0); + token = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + mir_free(tag); tag = mir_a2t("oauth_token_secret"); + node = xi.getChildByPath(hXml, tag, 0); + token_secret = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + xi.destroyNode(hXml); + } + mir_free(tag); + mir_free(xmlAction); + } + else netlog("gg_oauth_receivetoken(): Invalid response code from HTTP request"); + Netlib_CloseHandle(resp->nlc); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else netlog("gg_oauth_receivetoken(): No response from HTTP request"); + + mir_free(password); + mir_free(str); + + if (token != NULL && token_secret != NULL) { + db_set_s(NULL, m_szModuleName, GG_KEY_TOKEN, token); + CallService(MS_DB_CRYPT_ENCODESTRING, (WPARAM)(int)strlen(token_secret) + 1, (LPARAM) token_secret); + db_set_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, token_secret); + netlog("gg_oauth_receivetoken(): Access Token obtained successfully."); + res = 1; + } + else { + db_unset(NULL, m_szModuleName, GG_KEY_TOKEN); + db_unset(NULL, m_szModuleName, GG_KEY_TOKENSECRET); + netlog("gg_oauth_receivetoken(): Failed to obtain Access Token."); + } + mir_free(token); + mir_free(token_secret); + + return res; +} + +int GGPROTO::oauth_checktoken(int force) +{ + if (!force) { + char *token = NULL, *token_secret = NULL; + DBVARIANT dbv; + int res = 1; + + if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKEN, &dbv, DBVT_ASCIIZ)) { + token = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + token_secret = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + + if (token == NULL || token_secret == NULL) { + res = oauth_receivetoken(); + } + + mir_free(token); + mir_free(token_secret); + + return res; + } + + return oauth_receivetoken(); +} -- cgit v1.2.3