//*************************************************************************************** // // Google Extension plugin for the Miranda IM's Jabber protocol // Copyright (c) 2011 bems@jabber.org, George Hazan (ghazan@jabber.ru) // // 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 "stdafx.h" #include "inbox.h" #include "notifications.h" #include "db.h" #include "options.h" static const LPTSTR COMMON_GMAIL_HOST1 = _T("gmail.com"); static const LPTSTR COMMON_GMAIL_HOST2 = _T("googlemail.com"); static const LPSTR AUTH_REQUEST_URL = "https://www.google.com/accounts/ClientAuth"; static const LPSTR AUTH_REQUEST_PARAMS = "Email=%s&Passwd=%s&" "accountType=HOSTED_OR_GOOGLE&" "skipvpage=true&" "PersistentCookie=false"; static const LPSTR ISSUE_TOKEN_REQUEST_URL = "https://www.google.com/accounts/IssueAuthToken"; static const LPSTR ISSUE_TOKEN_REQUEST_PARAMS = "SID=%s&LSID=%s&" "Session=true&" "skipvpage=true&" "service=gaia"; static const LPSTR TOKEN_AUTH_URL = "https://www.google.com/accounts/TokenAuth?"\ "auth=%s&" "service=mail&" "continue=%s&" "source=googletalk"; static const NETLIBHTTPHEADER HEADER_URL_ENCODED = {"Content-Type", "application/x-www-form-urlencoded"}; static const int HTTP_OK = 200; static const LPSTR SID_KEY_NAME = "SID="; static const LPSTR LSID_KEY_NAME = "LSID="; static const LPSTR LOGIN_PASS_SETTING_NAME = "LoginPassword"; static const LPTSTR INBOX_URL_FORMAT = _T("https://mail.google.com/%s%s/#inbox"); static const DWORD SIZE_OF_JABBER_OPTIONS = 243 * sizeof(DWORD); // 3 lines from netlib.h #define GetNetlibHandleType(h) (h?*(int*)h:NLH_INVALID) #define NLH_INVALID 0 #define NLH_USER 'USER' char to_hex(char code) { static char hex[] = "0123456789abcdef"; return hex[code & 15]; } char *url_encode(char *str) { char *pstr = str, *buf = (char*)malloc(strlen(str) * 3 + 1), *pbuf = buf; while (*pstr) { if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == ',' || *pstr == '~') *pbuf++ = *pstr; else if (*pstr == ' ') *pbuf++ = '+'; else *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); pstr++; } *pbuf = '\0'; return buf; } LPSTR HttpPost(HANDLE hUser, LPSTR reqUrl, LPSTR reqParams) { NETLIBHTTPREQUEST nlhr = {0}; nlhr.cbSize = sizeof(nlhr); nlhr.requestType = REQUEST_POST; nlhr.flags = NLHRF_GENERATEHOST | NLHRF_SMARTAUTHHEADER | NLHRF_HTTP11 | NLHRF_SSL | NLHRF_NODUMP | NLHRF_NODUMPHEADERS; nlhr.szUrl = reqUrl; nlhr.headers = (NETLIBHTTPHEADER*)&HEADER_URL_ENCODED; nlhr.headersCount = 1; nlhr.pData = reqParams; nlhr.dataLength = lstrlenA(reqParams); NETLIBHTTPREQUEST *pResp = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)hUser, (LPARAM)&nlhr); if (!pResp) return NULL; __try { if (HTTP_OK == pResp->resultCode) return _strdup(pResp->pData); else return NULL; } __finally { CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)pResp); } } LPSTR MakeRequest(HANDLE hUser, LPSTR reqUrl, LPSTR reqParamsFormat, LPSTR p1, LPSTR p2) { LPSTR encodedP1 = url_encode(p1); __try { LPSTR encodedP2 = url_encode(p2); __try { LPSTR reqParams = (LPSTR)malloc(lstrlenA(reqParamsFormat) + 1 + lstrlenA(encodedP1) + lstrlenA(encodedP2)); __try { sprintf(reqParams, reqParamsFormat, encodedP1, encodedP2); return HttpPost(hUser, reqUrl, reqParams); } __finally { free(reqParams); } } __finally { free(encodedP2); } } __finally { free(encodedP1); } } LPSTR FindSid(LPSTR resp, LPSTR *LSID) { LPSTR SID = strstr(resp, SID_KEY_NAME); *LSID = strstr(resp, LSID_KEY_NAME); if (!SID || !*LSID) return NULL; if (SID - 1 == *LSID) SID = strstr(SID + 1, SID_KEY_NAME); if (!SID) return NULL; SID += lstrlenA(SID_KEY_NAME); LPSTR term = strstr(SID, "\n"); if (term) term[0] = 0; *LSID += lstrlenA(LSID_KEY_NAME); term = strstr(*LSID, "\n"); if (term) term[0] = 0; return SID; } void DoOpenUrl(LPSTR tokenResp, LPSTR url) { LPSTR encodedUrl = url_encode(url); __try { LPSTR encodedToken = url_encode(tokenResp); __try { LPSTR composedUrl = (LPSTR)malloc(lstrlenA(TOKEN_AUTH_URL) + 1 + lstrlenA(encodedToken) + lstrlenA(encodedUrl)); __try { sprintf(composedUrl, TOKEN_AUTH_URL, encodedToken, encodedUrl); ShellExecuteA(0, NULL, composedUrl, NULL, NULL, SW_SHOW); } __finally { free(composedUrl); } } __finally { free(encodedToken); } } __finally { free(encodedUrl); } } BOOL AuthAndOpen(HANDLE hUser, LPSTR url, LPSTR mailbox, LPSTR pwd) { LPSTR authResp = MakeRequest(hUser, AUTH_REQUEST_URL, AUTH_REQUEST_PARAMS, mailbox, pwd); if (!authResp) return FALSE; __try { LPSTR LSID; LPSTR SID = FindSid(authResp, &LSID); LPSTR tokenResp = MakeRequest(hUser, ISSUE_TOKEN_REQUEST_URL, ISSUE_TOKEN_REQUEST_PARAMS, SID, LSID); if (!tokenResp) return FALSE; __try { DoOpenUrl(tokenResp, url); return TRUE; } __finally { free(tokenResp); } } __finally { free(authResp); } } struct OPEN_URL_HEADER { LPSTR url; LPSTR mailbox; LPSTR pwd; LPCSTR acc; }; HANDLE FindNetUserHandle(LPCSTR acc) { IJabberInterface *ji = getJabberApi(acc); if (!ji) return NULL; PBYTE m_psProto = *(PBYTE*)((PBYTE)ji + sizeof(*ji)); // see CJabberInterface in jabber_proto.h PHANDLE pResult = (PHANDLE)(m_psProto + // see CJabberProto in jabber_proto.h sizeof(PVOID) + // skip vtable ptr sizeof(PVOID) + // skip m_ThreadInfo SIZE_OF_JABBER_OPTIONS); // skip m_options for (int i=0; i < 100; i++) { __try { if (GetNetlibHandleType(*pResult) == NLH_USER) break; } __except (EXCEPTION_EXECUTE_HANDLER){ } pResult++; } assert(GetNetlibHandleType(*pResult) == NLH_USER); return *pResult; } unsigned __stdcall OpenUrlThread(OPEN_URL_HEADER* data) { __try { HANDLE hUser = FindNetUserHandle(data->acc); if (!hUser || !AuthAndOpen(hUser, data->url, data->mailbox, data->pwd)) ShellExecuteA(0, NULL, data->url, NULL, NULL, SW_SHOW); } __finally { free(data); } return 0; } void __forceinline DecryptString(LPSTR str, int len) { for (--len; len >= 0; len--) { const char c = str[len] ^ 0xc3; if (c) str[len] = c; } } int GetMailboxPwd(LPCSTR acc, LPCTSTR mailbox, LPSTR *pwd, int buffSize) { char buff[256]; DBCONTACTGETSETTING cgs; DBVARIANT dbv; cgs.szModule = acc; cgs.szSetting = LOGIN_PASS_SETTING_NAME; cgs.pValue = &dbv; dbv.type = DBVT_ASCIIZ; dbv.pszVal = &buff[0]; dbv.cchVal = sizeof(buff); if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC, 0, (LPARAM)&cgs)) return 0; int result = dbv.cchVal; if (pwd) { if (buffSize < result + 1) result = buffSize - 1; memcpy(*pwd, &buff, result + 1); DecryptString(*pwd, result); } return result; } BOOL OpenUrlWithAuth(LPCSTR acc, LPCTSTR mailbox, LPCTSTR url) { int pwdLen = GetMailboxPwd(acc, mailbox, NULL, 0); if (!pwdLen++) return FALSE; int urlLen = lstrlen(url) + 1; int mailboxLen = lstrlen(mailbox) + 1; OPEN_URL_HEADER *data = (OPEN_URL_HEADER*)malloc(sizeof(OPEN_URL_HEADER) + urlLen + mailboxLen + pwdLen); __try { data->url = (LPSTR)data + sizeof(OPEN_URL_HEADER); LPSTR ansi = WtoA(url); __try { memcpy(data->url, ansi, urlLen); } __finally { free(ansi); } data->mailbox = data->url + urlLen; ansi = WtoA(mailbox); __try { memcpy(data->mailbox, ansi, mailboxLen); } __finally { free(ansi); } data->pwd = data->mailbox + mailboxLen; if (!GetMailboxPwd(acc, mailbox, &data->pwd, pwdLen)) return FALSE; data->acc = acc; if (HANDLE h = mir_forkthreadex((pThreadFuncEx)OpenUrlThread, data, NULL)) { CloseHandle(h); data = NULL; } else return FALSE; } __finally { free(data); } return TRUE; } unsigned __stdcall ShellExecuteThread(PVOID param) { __try { ShellExecute(0, NULL, (LPTSTR)param, NULL, NULL, SW_SHOW); } __finally { free(param); } return 0; } void StartShellExecuteThread(LPCTSTR url) { LPTSTR urlCopy = _tcsdup(url); __try { if (HANDLE h = mir_forkthreadex(ShellExecuteThread, urlCopy, NULL)) { CloseHandle(h); urlCopy = NULL; } } __finally { free(urlCopy); } } void OpenUrl(LPCSTR acc, LPCTSTR mailbox, LPCTSTR url) { extern DWORD itlsSettings; if (!ReadCheckbox(0, IDC_AUTHONMAILBOX, (DWORD)TlsGetValue(itlsSettings)) || !OpenUrlWithAuth(acc, mailbox, url)) StartShellExecuteThread(url); } LPTSTR CraftInboxUrl(LPTSTR jid) { LPTSTR host = _tcsstr(jid, _T("@")) + 1; LPTSTR result = (LPTSTR)malloc((lstrlen(INBOX_URL_FORMAT) + 1 + lstrlen(jid)) * sizeof(TCHAR)); __try { if (lstrcmpi(host, COMMON_GMAIL_HOST1) && lstrcmpi(host, COMMON_GMAIL_HOST2)) wsprintf(result, INBOX_URL_FORMAT, _T("a/"), host); // hosted else wsprintf(result, INBOX_URL_FORMAT, NULL, _T("mail")); // common } __except ( free(result), EXCEPTION_CONTINUE_SEARCH ) {} return result; } void OpenContactInbox(HANDLE hContact) { LPSTR acc = (LPSTR)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); if (!acc) return; DBVARIANT dbv; if (!DBGetContactSettingTString(0, acc, "jid", &dbv)) __try { LPTSTR url = CraftInboxUrl(dbv.ptszVal); __try { OpenUrl(acc, dbv.ptszVal, url); } __finally { free(url); } } __finally { DBFreeVariant(&dbv); } }