From 623da26184c4e642d5a2bcee09091ff412aea7d4 Mon Sep 17 00:00:00 2001 From: Piotr Piastucki Date: Mon, 10 Aug 2015 00:23:39 +0000 Subject: Added embedded Internet Explorer Window for MSN 2-factor authentication or other login issues where manual user intervention is necessary. git-svn-id: http://svn.miranda-ng.org/main/trunk@14890 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/MSN/src/msn_auth.cpp | 437 +++++++++++++++++++++++++++----------- protocols/MSN/src/msn_errors.cpp | 1 + protocols/MSN/src/msn_ieembed.cpp | 365 +++++++++++++++++++++++++++++++ protocols/MSN/src/msn_ieembed.h | 140 ++++++++++++ protocols/MSN/src/msn_proto.h | 3 + protocols/MSN/src/stdafx.h | 6 - 6 files changed, 819 insertions(+), 133 deletions(-) create mode 100644 protocols/MSN/src/msn_ieembed.cpp create mode 100644 protocols/MSN/src/msn_ieembed.h (limited to 'protocols/MSN') diff --git a/protocols/MSN/src/msn_auth.cpp b/protocols/MSN/src/msn_auth.cpp index ca0764fd11..0a7cff86bf 100644 --- a/protocols/MSN/src/msn_auth.cpp +++ b/protocols/MSN/src/msn_auth.cpp @@ -19,9 +19,21 @@ along with this program. If not, see . */ #include "stdafx.h" +#include #include "msn_proto.h" +#include "msn_ieembed.h" #include "des.h" +#define LOGIN_POST_PARAMS "client_id=00000000480BC46C&scope=service%3A%3Askype.com%3A%3AMBI_SSL&response_type=token&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf" +#define AUTH_URL "https://login.live.com/oauth20_authorize.srf?"LOGIN_POST_PARAMS +#define POST_URL "https://login.live.com/ppsecure/post.srf?"LOGIN_POST_PARAMS + + +/* WinINET delayloading */ +typedef BOOL (*pfnInternetGetCookieExA)(LPCSTR, LPCSTR, LPSTR, LPDWORD, DWORD, LPVOID); +pfnInternetGetCookieExA fpInternetGetCookieExA = NULL; + + /* SkyLogin Prototypes */ typedef void* SkyLogin; typedef SkyLogin (*pfnSkyLogin_Init)(); @@ -717,6 +729,164 @@ void CMsnProto::SaveAuthTokensDB(void) setString("authRefreshToken", authRefreshToken); } +typedef struct { + /* Input */ + CMsnProto *pProto; + NETLIBHTTPREQUEST *nlhr; + NETLIBHTTPREQUEST *nlhrReply; + /* Output */ + char *pszURL; + char *pszCookies; + /* Internal */ + IEEmbed *pEmbed; +} IEAUTH_PARAM; + +LRESULT CALLBACK AuthWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_SIZE: + { + IEAUTH_PARAM *pAuth = (IEAUTH_PARAM*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (pAuth && pAuth->pEmbed) pAuth->pEmbed->ResizeBrowser(); + return(0); + } + + case WM_CREATE: + { + IEAUTH_PARAM *pAuth = (IEAUTH_PARAM*)((LPCREATESTRUCT)lParam)->lpCreateParams; + pAuth->pEmbed = new IEEmbed(hwnd); + WCHAR *pwszCookies = mir_a2u(pAuth->nlhr->headers[1].szValue); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pAuth); + pAuth->pEmbed->addCookie(pwszCookies); + pAuth->pEmbed->navigate(AUTH_URL); + mir_free(pwszCookies); + return(0); + } + + case UM_DOCCOMPLETE: + { + if (!lParam) return 1; + IEAUTH_PARAM *pAuth = (IEAUTH_PARAM*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + if (wcsstr((WCHAR*)lParam, L"oauth20_authorize")) { + char *pszDoc = pAuth->pEmbed->GetHTMLDoc(); + CMStringA post; + + if (pszDoc) { + if (pAuth->pProto->parseLoginPage(pszDoc, pAuth->nlhr, &post)) { + pAuth->pEmbed->navigate(pAuth->nlhr); + } + mir_free(pszDoc); + } + } + else if (wcsstr((WCHAR*)lParam, L"access_token=")) { + DWORD cbCookie = 0; + + pAuth->pszURL = mir_u2a((WCHAR*)lParam); + + /* get_cookie doesn't give us all the necessary cookies, therefore we need to use + * InternetGetCookieExA + */ + if (!fpInternetGetCookieExA) { + HMODULE hMod = LoadLibrary(_T("wininet.dll")); + if (hMod) fpInternetGetCookieExA = (pfnInternetGetCookieExA)GetProcAddress(hMod, "InternetGetCookieExA"); + } + if (fpInternetGetCookieExA && + fpInternetGetCookieExA("https://login.live.com", NULL, NULL, &cbCookie, INTERNET_COOKIE_HTTPONLY, NULL) && + (pAuth->pszCookies = (char*)mir_alloc(cbCookie))) { + fpInternetGetCookieExA("https://login.live.com", NULL, pAuth->pszCookies, &cbCookie, INTERNET_COOKIE_HTTPONLY, NULL); + } + else pAuth->pszCookies = mir_u2a(pAuth->pEmbed->getCookies()); + PostMessage(hwnd, WM_CLOSE, 0, 0); + } + else if (wcsstr((WCHAR*)lParam, L"res=cancel")) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + } + return(0); + } + + case WM_CLOSE: + DestroyWindow(hwnd); + PostQuitMessage(0); + break; + + case WM_DESTROY: + { + IEAUTH_PARAM *pAuth = (IEAUTH_PARAM*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (pAuth && pAuth->pEmbed) delete pAuth->pEmbed; + return(0); + } + + } + + return(DefWindowProc(hwnd, uMsg, wParam, lParam)); +} + + +void __cdecl CMsnProto::msn_IEAuthThread(void *pParam) +{ + HWND hWnd; + MSG msg; + WNDCLASSEX wc={0}; + static const TCHAR *ClassName = _T("SkypeLoginWindow"); + + CoInitialize(NULL); + + wc.cbSize = sizeof(WNDCLASSEX); + wc.cbWndExtra = sizeof(void*); + wc.hInstance = hInst; + wc.lpfnWndProc = AuthWindowProc; + wc.lpszClassName = ClassName; + RegisterClassEx(&wc); + + if ((hWnd = CreateWindowEx(0, ClassName, _T("MSN Login"), WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, + HWND_DESKTOP, NULL, hInst, pParam))) { + ShowWindow( hWnd, SW_SHOW ); + UpdateWindow( hWnd ); + + while( GetMessage(&msg, NULL, 0, 0) ) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + } + + UnregisterClass(ClassName, hInst); + CoUninitialize(); +} + +bool CMsnProto::parseLoginPage(char *pszHTML, NETLIBHTTPREQUEST *nlhr, CMStringA *post) +{ + char *pPPFT, *pEnd; + + /* Get PPFT */ + if ((pPPFT = strstr(pszHTML, "name=\"PPFT\"")) && (pPPFT = strstr(pPPFT, "value=\"")) && (pEnd=strchr(pPPFT+7, '"'))) { + *pEnd=0; + pPPFT+=7; + + /* Get POST URL if available */ + if ((nlhr->szUrl = strstr(pszHTML, "urlPost:'")) && (pEnd=strchr(nlhr->szUrl+9, '\''))) { + *pEnd=0; + nlhr->szUrl += 9; + } else nlhr->szUrl = POST_URL; + + /* Create POST data */ + char szPassword[100]; + db_get_static(NULL, m_szModuleName, "Password", szPassword, sizeof(szPassword)); + szPassword[16] = 0; + post->Format("PPFT=%s&login=%s&passwd=%s", ptrA(mir_urlEncode(pPPFT)), + ptrA(mir_urlEncode(MyOptions.szEmail)), ptrA(mir_urlEncode(szPassword))); + + /* Do the login and get the required tokens */ + nlhr->dataLength = post->GetLength(); + nlhr->pData = (char*)post->GetString(); + return true; + } + return false; +} + // -1 - Error on login sequence // 0 - Login failed (invalid username?) // 1 - Login via Skype login server succeeded @@ -724,12 +894,14 @@ void CMsnProto::SaveAuthTokensDB(void) int CMsnProto::MSN_AuthOAuth(void) { int retVal = -1; - const char *pszPostParams = "client_id=00000000480BC46C&scope=service%3A%3Askype.com%3A%3AMBI_SSL&response_type=token&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf"; + bool bPassportAuth = true; NETLIBHTTPREQUEST nlhr = { 0 }; NETLIBHTTPREQUEST *nlhrReply; NETLIBHTTPHEADER headers[3]; time_t t; + if (bAskingForAuth) return 0; + // Load credentials from DB so that we don't have to do all this stuff if token isn't expired if (!authTokenExpiretime) LoadAuthTokensDB(); @@ -747,9 +919,7 @@ int CMsnProto::MSN_AuthOAuth(void) nlhr.headers[0].szValue = (char*)MSN_USER_AGENT; // Get oauth20 login data - CMStringA url; - url.Format("https://login.live.com/oauth20_authorize.srf?%s", pszPostParams); - nlhr.szUrl = (char*)(const char*)url; + nlhr.szUrl = AUTH_URL; mHttpsTS = clock(); nlhrReply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)hNetlibUserHttps, (LPARAM)&nlhr); mHttpsTS = clock(); @@ -758,35 +928,15 @@ int CMsnProto::MSN_AuthOAuth(void) hHttpsConnection = nlhrReply->nlc; if (nlhrReply->resultCode == 200 && nlhrReply->pData) { - char *pPPFT, *pEnd; - - /* Get PPFT */ - if ((pPPFT = strstr(nlhrReply->pData, "name=\"PPFT\"")) && (pPPFT = strstr(pPPFT, "value=\"")) && (pEnd=strchr(pPPFT+7, '"'))) { - *pEnd=0; - pPPFT+=7; - - /* Get POST URL if available */ - if ((nlhr.szUrl = strstr(nlhrReply->pData, "urlPost:'")) && (pEnd=strchr(nlhr.szUrl+9, '\''))) { - *pEnd=0; - nlhr.szUrl += 9; - } else { - url.Format("https://login.live.com/ppsecure/post.srf?%s", pszPostParams); - nlhr.szUrl = (char*)(const char*)url; - } + CMStringA post; + /* Get POST-Data and URL */ + if (parseLoginPage(nlhrReply->pData, &nlhr, &post)) { /* Get Cookies */ nlhr.headers[1].szValue = (char*)alloca(CopyCookies(nlhrReply, NULL)); CopyCookies(nlhrReply, &nlhr.headers[1]); if (*nlhr.headers[1].szValue) nlhr.headersCount++; - /* Create POST data */ - CMStringA post; - char szPassword[100]; - db_get_static(NULL, m_szModuleName, "Password", szPassword, sizeof(szPassword)); - szPassword[16] = 0; - post.Format("PPFT=%s&login=%s&passwd=%s", ptrA(mir_urlEncode(pPPFT)), - ptrA(mir_urlEncode(MyOptions.szEmail)), ptrA(mir_urlEncode(szPassword))); - /* Setup headers */ nlhr.headers[nlhr.headersCount].szName = "Content-Type"; nlhr.headers[nlhr.headersCount++].szValue = "application/x-www-form-urlencoded"; @@ -795,126 +945,159 @@ int CMsnProto::MSN_AuthOAuth(void) nlhr.requestType = REQUEST_POST; nlhr.flags &= (~NLHRF_REDIRECT); mHttpsTS = clock(); - nlhr.dataLength = (int)mir_strlen(post); - nlhr.pData = (char*)(const char*)post; nlhr.nlc = hHttpsConnection; NETLIBHTTPREQUEST *nlhrReply2 = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)hNetlibUserHttps, (LPARAM)&nlhr); - CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply); - nlhrReply = nlhrReply2; mHttpsTS = clock(); - if (nlhrReply) { - hHttpsConnection = nlhrReply->nlc; - - if (nlhrReply->resultCode == 302) { - char *pAccessToken; + if (nlhrReply2) { + char *pszURL=NULL, *pAccessToken, *pEnd; + hHttpsConnection = nlhrReply2->nlc; + if (nlhrReply2->resultCode == 302) { /* Extract access_token from Location can be found */ - for (int i = 0; i < nlhrReply->headersCount; i++) { - if (!mir_strcmpi(nlhrReply->headers[i].szName, "Location") && - (pAccessToken = strstr(nlhrReply->headers[i].szValue, "access_token=")) && - (pEnd = strchr(pAccessToken + 13, '&'))) { - char *pRefreshToken, *pExpires, szToken[1024]; - bool bLogin = false; - - *pEnd = 0; - pAccessToken += 13; - UrlDecode(pAccessToken); - replaceStr(authAccessToken, pAccessToken); - - /* Extract refresh token */ - if ((pRefreshToken = strstr(pEnd + 1, "refresh_token=")) && (pEnd = strchr(pRefreshToken + 14, '&'))) { - *pEnd = 0; - pRefreshToken += 14; - } - replaceStr(authRefreshToken, pRefreshToken); - - /* Extract expire time */ - time(&authTokenExpiretime); - if ((pExpires = strstr(pEnd + 1, "expires_in=")) && (pEnd = strchr(pExpires + 11, '&'))) { - *pEnd = 0; - pExpires += 11; - authTokenExpiretime += atoi(pExpires); - } - else authTokenExpiretime += 86400; + for (int i = 0; i < nlhrReply2->headersCount; i++) { + if (!mir_strcmpi(nlhrReply2->headers[i].szName, "Location")) { + pszURL = nlhrReply2->headers[i].szValue; + break; + } + } + } + else { + /* There may be a problem with login, i.e. M$ security measures. Open up browser + * window in order to let user login there. May also be used for 2-factor auth */ + if (nlhrReply2->resultCode == 200 && nlhrReply2->pData) { + UINT uThreadId; + IEAUTH_PARAM param = {this, &nlhr, nlhrReply2, NULL, NULL, NULL}; + + bAskingForAuth = true; + WaitForSingleObject(ForkThreadEx(&CMsnProto::msn_IEAuthThread, ¶m, &uThreadId), INFINITE); + pszURL = param.pszURL; + mir_free(authCookies); + authCookies = nlhr.headers[1].szValue = param.pszCookies; + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply2); + nlhrReply2 = NULL; + bAskingForAuth = false; + bPassportAuth = false; + } + } + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply); + nlhrReply = nlhrReply2; + + if (pszURL && + (pAccessToken = strstr(pszURL, "access_token=")) && + (pEnd = strchr(pAccessToken + 13, '&'))) { + char *pRefreshToken, *pExpires, szToken[1024]; + bool bLogin = false; + + *pEnd = 0; + pAccessToken += 13; + UrlDecode(pAccessToken); + replaceStr(authAccessToken, pAccessToken); + + /* Extract refresh token */ + if ((pRefreshToken = strstr(pEnd + 1, "refresh_token=")) && (pEnd = strchr(pRefreshToken + 14, '&'))) { + *pEnd = 0; + pRefreshToken += 14; + } + replaceStr(authRefreshToken, pRefreshToken); + + /* Extract expire time */ + time(&authTokenExpiretime); + if ((pExpires = strstr(pEnd + 1, "expires_in=")) && (pEnd = strchr(pExpires + 11, '&'))) { + *pEnd = 0; + pExpires += 11; + authTokenExpiretime += atoi(pExpires); + } + else authTokenExpiretime += 86400; - /* Copy auth Cookies to class for other web requests like contact list fetching to avoid ActiveSync */ - mir_free(authCookies); - authCookies = nlhr.headers[1].szValue = (char*)mir_alloc(CopyCookies(nlhrReply, NULL)); - CopyCookies(nlhrReply, &nlhr.headers[1]); + /* Copy auth Cookies to class for other web requests like contact list fetching to avoid ActiveSync */ + if (nlhrReply) { + mir_free(authCookies); + authCookies = nlhr.headers[1].szValue = (char*)mir_alloc(CopyCookies(nlhrReply, NULL)); + CopyCookies(nlhrReply, &nlhr.headers[1]); + } - int loginRet; - /* Do Login via Skype login server, if not possible switch to SkypeWebExperience login */ - if ((loginRet = LoginSkypeOAuth(pRefreshToken)) < 1) { - if (loginRet < 0) bLogin = true; else retVal = 0; - } - else { - /* SkyLogin succeeded, request required tokens */ - if (RefreshOAuth(pRefreshToken, "service::ssl.live.com::MBI_SSL", szToken)) { - replaceStr(authSSLToken, szToken); + int loginRet; + /* Do Login via Skype login server, if not possible switch to SkypeWebExperience login */ + if ((loginRet = LoginSkypeOAuth(pRefreshToken)) < 1) { + if (loginRet < 0) bLogin = true; else retVal = 0; + } + else { + /* SkyLogin succeeded, request required tokens */ + if (RefreshOAuth(pRefreshToken, "service::ssl.live.com::MBI_SSL", szToken)) { + replaceStr(authSSLToken, szToken); + replaceStr(authUser, MyOptions.szEmail); + authMethod = retVal = 1; + } + } + mir_free(authSkypeComToken); authSkypeComToken = NULL; + mir_free(authSkypeToken); authSkypeToken = NULL; + + + /* If you need Skypewebexperience login, as i.e. skylogin.dll is not available, we do this here */ + if (bLogin) { + /* Prepare headers*/ + nlhr.headers[2].szValue = "application/json"; + nlhr.pData = "{\"trouterurl\":\"https://\",\"connectionid\":\"a\"}"; + nlhr.dataLength = (int)mir_strlen(nlhr.pData); + nlhr.szUrl = "https://skypewebexperience.live.com/v1/User/Initialization"; + nlhr.nlc = hHttpsConnection; + + /* Request MappingContainer */ + mHttpsTS = clock(); + if (nlhrReply) CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply); + nlhrReply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)hNetlibUserHttps, (LPARAM)&nlhr); + mHttpsTS = clock(); + if (nlhrReply) { + hHttpsConnection = nlhrReply->nlc; + + if (nlhrReply->resultCode == 200 && nlhrReply->pData) { + /* Parse JSON stuff for MappingContainer */ + char *pMappingContainer; + + if ((pMappingContainer = strstr(nlhrReply->pData, "\"MappingContainer\":\"")) && + (pEnd = strchr(pMappingContainer + 20, '"'))) { + *pEnd = 0; + pMappingContainer += 20; + UrlDecode(pMappingContainer); + replaceStr(authUIC, pMappingContainer); replaceStr(authUser, MyOptions.szEmail); - authMethod = retVal = 1; - } - } - mir_free(authSkypeComToken); authSkypeComToken = NULL; - mir_free(authSkypeToken); authSkypeToken = NULL; - - - /* If you need Skypewebexperience login, as i.e. skylogin.dll is not available, we do this here */ - if (bLogin) { - /* Prepare headers*/ - nlhr.headers[2].szValue = "application/json"; - nlhr.pData = "{\"trouterurl\":\"https://\",\"connectionid\":\"a\"}"; - nlhr.dataLength = (int)mir_strlen(nlhr.pData); - nlhr.szUrl = "https://skypewebexperience.live.com/v1/User/Initialization"; - nlhr.nlc = hHttpsConnection; - - /* Request MappingContainer */ - mHttpsTS = clock(); - CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply); - nlhrReply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)hNetlibUserHttps, (LPARAM)&nlhr); - mHttpsTS = clock(); - if (nlhrReply) { - hHttpsConnection = nlhrReply->nlc; - - if (nlhrReply->resultCode == 200 && nlhrReply->pData) { - /* Parse JSON stuff for MappingContainer */ - char *pMappingContainer; - - if ((pMappingContainer = strstr(nlhrReply->pData, "\"MappingContainer\":\"")) && - (pEnd = strchr(pMappingContainer + 20, '"'))) { - *pEnd = 0; - pMappingContainer += 20; - UrlDecode(pMappingContainer); - replaceStr(authUIC, pMappingContainer); - replaceStr(authUser, MyOptions.szEmail); - authMethod = retVal = 2; - } - else retVal = 0; - } - else retVal = 0; + authMethod = retVal = 2; } - else hHttpsConnection = NULL; + else retVal = 0; } + else retVal = 0; } + else hHttpsConnection = NULL; } } - else { - /* There may be a problem with login, i.e. M$ security measures. Open up browser - * window with same URL in order to let user correct this */ - if (nlhrReply->resultCode == 200 && nlhrReply->pData) { - url.Format("https://login.live.com/oauth20_authorize.srf?%s", pszPostParams); - MSN_ShowPopup(TranslateT("MSN Protocol"),TranslateT(MSN_LOGIN_OAUTH),MSN_ALLOW_MSGBOX,url); - } - hHttpsConnection = NULL; - } } + else hHttpsConnection = NULL; } } if (nlhrReply) CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply); } else hHttpsConnection = NULL; if (retVal<=0) authTokenExpiretime=0; else { - MSN_GetPassportAuth(); + if (bPassportAuth) { + // Fast authentication with just 1 SOAP call supported :) + MSN_GetPassportAuth(); + } else { + // Slow authentication required by fetching multiple tokens, i.e. 2-factor auth :( + char szToken[1024]; + + if (authMethod == 2 && RefreshOAuth(authRefreshToken, "service::chatservice.live.com::MBI_SSL", szToken+2)) { + memcpy (szToken, "t=", 2); + replaceStr(authStrToken, szToken); + } + if (RefreshOAuth(authRefreshToken, "service::contacts.msn.com::MBI_SSL", szToken)) { + replaceStr(authContactToken, szToken); + setString("authContactToken", authContactToken); + } + if (RefreshOAuth(authRefreshToken, "service::storage.msn.com::MBI_SSL", szToken)) { + replaceStr(authStorageToken, szToken); + setString("authStorageToken", authStorageToken); + } + } SaveAuthTokensDB(); } return retVal; diff --git a/protocols/MSN/src/msn_errors.cpp b/protocols/MSN/src/msn_errors.cpp index 64a79fb45a..2f1e321c0f 100644 --- a/protocols/MSN/src/msn_errors.cpp +++ b/protocols/MSN/src/msn_errors.cpp @@ -71,6 +71,7 @@ int CMsnProto::MSN_HandleErrors(ThreadData* info, char* cmdString) case ERR_SERVER_BUSY: case ERR_SERVER_UNAVAILABLE: + case ERR_TIMEDOUT: MSN_ShowError("MSN Services are too busy, please try to connect later"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER); return 1; diff --git a/protocols/MSN/src/msn_ieembed.cpp b/protocols/MSN/src/msn_ieembed.cpp new file mode 100644 index 0000000000..ef9ce64285 --- /dev/null +++ b/protocols/MSN/src/msn_ieembed.cpp @@ -0,0 +1,365 @@ +/* +Plugin of Miranda IM for communicating with users of the MSN Messenger protocol. + +Copyright (c) 2012-2014 Miranda NG Team +Copyright (c) 2007-2012 Boris Krasnovskiy. + +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, see . +*/ + +#include "stdafx.h" +#include +#include "msn_proto.h" +#include "msn_ieembed.h" + +IEEmbedSink::IEEmbedSink(IEEmbed *smptr) +{ + ieWindow = smptr; +} + +IEEmbedSink::~IEEmbedSink() {} + +STDMETHODIMP IEEmbedSink::QueryInterface(REFIID riid, PVOID *ppv) +{ + *ppv = NULL; + if (IID_IUnknown == riid) + *ppv = (IUnknown *)this; + + if (IID_IDispatch == riid) + *ppv = (IDispatch *)this; + + if (DIID_DWebBrowserEvents2 == riid) + *ppv = (DWebBrowserEvents2*)this; + + if (NULL != *ppv) { + ((LPUNKNOWN)*ppv)->AddRef(); + return NOERROR; + } + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) IEEmbedSink::AddRef(void) +{ + ++m_cRef; + return m_cRef; +} + +STDMETHODIMP_(ULONG) IEEmbedSink::Release(void) +{ + --m_cRef; + return m_cRef; +} + +STDMETHODIMP IEEmbedSink::GetTypeInfoCount(UINT *) { return E_NOTIMPL; } +STDMETHODIMP IEEmbedSink::GetTypeInfo(UINT, LCID, LPTYPEINFO*) { return S_OK; } +STDMETHODIMP IEEmbedSink::GetIDsOfNames(REFIID, LPOLESTR*, UINT, LCID, DISPID*) { return S_OK; } + +STDMETHODIMP IEEmbedSink::Invoke(DISPID dispIdMember, REFIID, LCID, WORD, DISPPARAMS* pDispParams, VARIANT*, EXCEPINFO*, UINT*) +{ + if (!pDispParams) return E_INVALIDARG; + switch (dispIdMember) { + case DISPID_DOCUMENTCOMPLETE: + DocumentComplete( + pDispParams->rgvarg[1].pdispVal, + pDispParams->rgvarg[0].pvarVal); + return S_OK; + } + + return DISP_E_MEMBERNOTFOUND; +} +// DWebBrowserEvents2 + +void IEEmbedSink::StatusTextChange(BSTR) {} +void IEEmbedSink::ProgressChange(long, long) {} +void IEEmbedSink::CommandStateChange(long, VARIANT_BOOL) {} +void IEEmbedSink::DownloadBegin() {} +void IEEmbedSink::DownloadComplete() {} +void IEEmbedSink::TitleChange(BSTR) {} +void IEEmbedSink::PropertyChange(BSTR) {} +void IEEmbedSink::BeforeNavigate2(IDispatch*, VARIANT* , VARIANT*, VARIANT*, VARIANT*, VARIANT*, VARIANT_BOOL*) { } +void IEEmbedSink::NewWindow2(IDispatch**, VARIANT_BOOL*) {} +void IEEmbedSink::NavigateComplete(IDispatch*, VARIANT*) {} +void IEEmbedSink::DocumentComplete(IDispatch* , VARIANT* url) +{ + HWND hWnd; + ieWindow->GetWindow(&hWnd); + SendMessage(hWnd, UM_DOCCOMPLETE, 0, (LPARAM)url->bstrVal); +} +void IEEmbedSink::OnQuit() {} +void IEEmbedSink::OnVisible(VARIANT_BOOL) {} +void IEEmbedSink::OnToolBar(VARIANT_BOOL) {} +void IEEmbedSink::OnMenuBar(VARIANT_BOOL) {} +void IEEmbedSink::OnStatusBar(VARIANT_BOOL) {} +void IEEmbedSink::OnFullScreen(VARIANT_BOOL) {} +void IEEmbedSink::OnTheaterMode(VARIANT_BOOL) {} +void IEEmbedSink::WindowSetResizable(VARIANT_BOOL) {} +void IEEmbedSink::WindowSetLeft(long) {} +void IEEmbedSink::WindowSetTop(long) {} +void IEEmbedSink::WindowSetWidth(long) {} +void IEEmbedSink::WindowSetHeight(long) {} +void IEEmbedSink::WindowClosing(VARIANT_BOOL, VARIANT_BOOL*) {} +void IEEmbedSink::ClientToHostWindow(long *, long *) {} +void IEEmbedSink::SetSecureLockIcon(long) {} +void IEEmbedSink::FileDownload(VARIANT_BOOL*) {} + + +IEEmbed::IEEmbed(HWND _parent) +{ + MSG msg; + parent = _parent; + GetClientRect(_parent, &rcClient); + if (SUCCEEDED(pWebBrowser.CoCreateInstance(CLSID_WebBrowser, NULL, CLSCTX_INPROC))) { + CComPtr pOleObject; + if (SUCCEEDED(pWebBrowser.QueryInterface(&pOleObject))) { + pOleObject->SetClientSite(this); + pOleObject->DoVerb(OLEIVERB_INPLACEACTIVATE, &msg, this, 0, this->parent, &rcClient); + } + else MessageBox(NULL, TranslateT("IID_IOleObject failed."), TranslateT("RESULT"), MB_OK); + + CComPtr pOleInPlace; + if (SUCCEEDED(pWebBrowser.QueryInterface(&pOleInPlace))) + pOleInPlace->GetWindow(&hwnd); + else + MessageBox(NULL, TranslateT("IID_IOleInPlaceObject failed."), TranslateT("RESULT"), MB_OK); + + //setBorder(); + CComPtr pCPContainer; + // Step 1: Get a pointer to the connection point container. + if (SUCCEEDED(pWebBrowser.QueryInterface(&pCPContainer))) { + // m_pConnectionPoint is defined like this: + // Step 2: Find the connection point. + if (SUCCEEDED(pCPContainer->FindConnectionPoint(DIID_DWebBrowserEvents2, &m_pConnectionPoint))) { + // Step 3: Advise the connection point that you + // want to sink its events. + sink = new IEEmbedSink(this); + if (FAILED(m_pConnectionPoint->Advise(sink, &m_dwCookie))) + MessageBox(NULL, TranslateT("Failed to Advise"), TranslateT("C++ Event Sink"), MB_OK); + } + } + } + + pWebBrowser->put_RegisterAsDropTarget(VARIANT_FALSE); +} + +IEEmbed::~IEEmbed() +{ + CComPtr pOleObject; + if (SUCCEEDED(pWebBrowser.QueryInterface(&pOleObject))) + pOleObject->SetClientSite(NULL); + else + MessageBox(NULL, TranslateT("IID_IOleObject failed."), TranslateT("RESULT"), MB_OK); + + if (m_pConnectionPoint != NULL) + m_pConnectionPoint->Unadvise(m_dwCookie); + + if (sink != NULL) + delete sink; + DestroyWindow(hwnd); +} + +void IEEmbed::ResizeBrowser() +{ + CComPtr pOleInPlace; + + GetClientRect(parent, &rcClient); + if (SUCCEEDED(pWebBrowser.QueryInterface(&pOleInPlace))) + pOleInPlace->SetObjectRects(&rcClient, &rcClient); +} + + +// IUnknown +STDMETHODIMP IEEmbed::QueryInterface(REFIID riid, PVOID *ppv) +{ + *ppv = NULL; + if (IID_IUnknown == riid) + *ppv = this; + if (IID_IOleClientSite == riid) + *ppv = (IOleClientSite*)this;//Unknown)m_pIOleClientSite; + if (IID_IOleWindow == riid || IID_IOleInPlaceSite == riid) + *ppv = (IOleInPlaceSite*)this;//m_pIOleIPSite; + + if (NULL != *ppv) { + ((LPUNKNOWN)*ppv)->AddRef(); + return NOERROR; + } + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) IEEmbed::AddRef(void) +{ + ++m_cRef; + return m_cRef; +} + +STDMETHODIMP_(ULONG) IEEmbed::Release(void) +{ + --m_cRef; + return m_cRef; +} + +// IDispatch +STDMETHODIMP IEEmbed::GetTypeInfoCount(UINT *) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::GetTypeInfo(UINT, LCID, LPTYPEINFO*) { return S_OK; } +STDMETHODIMP IEEmbed::GetIDsOfNames(REFIID, LPOLESTR*, UINT, LCID, DISPID*) { return S_OK; } +STDMETHODIMP IEEmbed::Invoke(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*) { return DISP_E_MEMBERNOTFOUND; } + +// IOleWindow +STDMETHODIMP IEEmbed::GetWindow(HWND *phwnd) +{ + *phwnd = parent; + return S_OK; +} + +STDMETHODIMP IEEmbed::ContextSensitiveHelp(BOOL) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::CanInPlaceActivate(void) { return S_OK; } +STDMETHODIMP IEEmbed::OnInPlaceActivate(void) { return S_OK; } +STDMETHODIMP IEEmbed::OnUIActivate(void) { return E_NOTIMPL; } + +STDMETHODIMP IEEmbed::GetWindowContext(IOleInPlaceFrame **, IOleInPlaceUIWindow **, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO) +{ + lprcPosRect->left = rcClient.left; + lprcPosRect->top = rcClient.top; + lprcPosRect->right = rcClient.right; + lprcPosRect->bottom = rcClient.bottom; + lprcClipRect->left = rcClient.left; + lprcClipRect->top = rcClient.top; + lprcClipRect->right = rcClient.right; + lprcClipRect->bottom = rcClient.bottom; + return S_OK; +} + +STDMETHODIMP IEEmbed::Scroll(SIZE) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::OnUIDeactivate(BOOL) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::OnInPlaceDeactivate(void) { return S_OK; } +STDMETHODIMP IEEmbed::DiscardUndoState(void) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::DeactivateAndUndo(void) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::OnPosRectChange(LPCRECT) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::SaveObject(void) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::GetMoniker(DWORD, DWORD, IMoniker **) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::GetContainer(IOleContainer **) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::ShowObject(void) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::OnShowWindow(BOOL) { return E_NOTIMPL; } +STDMETHODIMP IEEmbed::RequestNewObjectLayout(void) { return E_NOTIMPL; } + + +void IEEmbed::write(const wchar_t *text) +{ + CComPtr document = getDocument(); + if (!document) return; + + SAFEARRAY *safe_array = ::SafeArrayCreateVector(VT_VARIANT, 0, 1); + if (safe_array != NULL) { + VARIANT *variant; + ::SafeArrayAccessData(safe_array, (LPVOID *)&variant); + variant->vt = VT_BSTR; + variant->bstrVal = ::SysAllocString(text); + ::SafeArrayUnaccessData(safe_array); + document->write(safe_array); + // ::SysFreeString(bstr); // don't free it !!!!!!! + ::SafeArrayDestroy(safe_array); + document->close(); + } +} + +void IEEmbed::addCookie(const wchar_t *cookieString) +{ + CComPtr document = getDocument(); + if (!document) return; + BSTR cookie = SysAllocString(cookieString); + + document->put_cookie(cookie); + + SysFreeString(cookie); +} + +BSTR IEEmbed::getCookies() +{ + CComPtr document = getDocument(); + BSTR cookie = NULL; + + if (!document) return NULL; + document->get_cookie(&cookie); + return cookie; +} + +IHTMLDocument2* IEEmbed::getDocument() +{ + CComPtr dispatch; + if (SUCCEEDED(pWebBrowser->get_Document(&dispatch)) && dispatch != NULL) { + CComPtr document; + dispatch.QueryInterface(&document); + return document.Detach(); + } + + return NULL; +} + +void IEEmbed::navigate(const wchar_t *url) +{ + pWebBrowser->Navigate((WCHAR *)url, NULL, NULL, NULL, NULL); +} + +void IEEmbed::navigate(char *url) +{ + wchar_t *pwszUrl = mir_a2u(url); + navigate(pwszUrl); + mir_free(pwszUrl); +} + +void IEEmbed::navigate(NETLIBHTTPREQUEST *nlhr) +{ + WCHAR *szUrl = mir_a2u(nlhr->szUrl); + BSTR bstrHeaders; + LPSAFEARRAY psa; + LPSTR pPostData; + VARIANT vPostData = {0}, vHeaders = {0}; + + bstrHeaders = SysAllocString(L"Content-Type: application/x-www-form-urlencoded\r\n"); + V_VT(&vHeaders) = VT_BSTR; + V_BSTR(&vHeaders) = bstrHeaders; + VariantInit(&vPostData); + psa = SafeArrayCreateVector(VT_UI1, 0, nlhr->dataLength); + SafeArrayAccessData(psa, (LPVOID*)&pPostData); + memcpy(pPostData, nlhr->pData, nlhr->dataLength); + SafeArrayUnaccessData(psa); + V_VT(&vPostData) = VT_ARRAY | VT_UI1; + V_ARRAY(&vPostData) = psa; + pWebBrowser->Navigate(szUrl, NULL, NULL, &vPostData, &vHeaders); + SysFreeString(bstrHeaders); + VariantClear(&vPostData); + mir_free(szUrl); +} + +char *IEEmbed::GetHTMLDoc() { + CComPtr spDispDoc; + char *pszRet = NULL; + + if (SUCCEEDED(pWebBrowser->get_Document(&spDispDoc))) { + CComPtr spDoc; + + if (SUCCEEDED(spDispDoc->QueryInterface(IID_IHTMLDocument3, (void**)&spDoc))) { + CComPtr spRootElement; + if (SUCCEEDED(spDoc->get_documentElement(&spRootElement))) + { + BSTR bstrDoc; + if (SUCCEEDED(spRootElement->get_outerHTML(&bstrDoc))) + { + pszRet = mir_u2a(bstrDoc); + SysFreeString(bstrDoc); + } + } + } + } + return pszRet; +} diff --git a/protocols/MSN/src/msn_ieembed.h b/protocols/MSN/src/msn_ieembed.h new file mode 100644 index 0000000000..76442b691d --- /dev/null +++ b/protocols/MSN/src/msn_ieembed.h @@ -0,0 +1,140 @@ +/* +Plugin of Miranda IM for communicating with users of the MSN Messenger protocol. + +Copyright (c) 2012-2014 Miranda NG Team +Copyright (c) 2007-2012 Boris Krasnovskiy. + +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, see . +*/ + +class IEEmbed; + +#include +#include +#include + +#include // CComPtr + +#ifndef IEEMBED_INCLUDED +#define IEEMBED_INCLUDED + +#define UM_DOCCOMPLETE (WM_USER+600) + +class IEEmbedSink :public DWebBrowserEvents2 { +private: + int m_cRef; + IEEmbed *ieWindow; +public: + IEEmbedSink(IEEmbed *); + virtual ~IEEmbedSink(); + // IDispatch + STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + STDMETHOD(GetTypeInfoCount)(UINT*); + STDMETHOD(GetTypeInfo)(UINT, LCID, LPTYPEINFO*); + STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*); + STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*); + // DWebBrowserEvents2 + STDMETHODIMP_(void)StatusTextChange(BSTR); + STDMETHODIMP_(void)ProgressChange(long, long); + STDMETHODIMP_(void)CommandStateChange(long, VARIANT_BOOL); + STDMETHODIMP_(void)DownloadBegin(); + STDMETHODIMP_(void)DownloadComplete(); + STDMETHODIMP_(void)TitleChange(BSTR Text); + STDMETHODIMP_(void)PropertyChange(BSTR Text); + STDMETHODIMP_(void)BeforeNavigate2(IDispatch*, VARIANT*, VARIANT*, VARIANT*, VARIANT*, VARIANT*, VARIANT_BOOL*); + STDMETHODIMP_(void)NewWindow2(IDispatch**, VARIANT_BOOL*); + STDMETHODIMP_(void)NavigateComplete(IDispatch*, VARIANT*); + STDMETHODIMP_(void)DocumentComplete(IDispatch*, VARIANT*); + STDMETHODIMP_(void)OnQuit(); + STDMETHODIMP_(void)OnVisible(VARIANT_BOOL); + STDMETHODIMP_(void)OnToolBar(VARIANT_BOOL); + STDMETHODIMP_(void)OnMenuBar(VARIANT_BOOL); + STDMETHODIMP_(void)OnStatusBar(VARIANT_BOOL); + STDMETHODIMP_(void)OnFullScreen(VARIANT_BOOL); + STDMETHODIMP_(void)OnTheaterMode(VARIANT_BOOL); + STDMETHODIMP_(void)WindowSetResizable(VARIANT_BOOL); + STDMETHODIMP_(void)WindowSetLeft(long); + STDMETHODIMP_(void)WindowSetTop(long); + STDMETHODIMP_(void)WindowSetWidth(long); + STDMETHODIMP_(void)WindowSetHeight(long); + STDMETHODIMP_(void)WindowClosing(VARIANT_BOOL, VARIANT_BOOL*); + STDMETHODIMP_(void)ClientToHostWindow(long*, long*); + STDMETHODIMP_(void)SetSecureLockIcon(long); + STDMETHODIMP_(void)FileDownload(VARIANT_BOOL*); +}; + +class IEEmbed :public IDispatch, public IOleClientSite, public IOleInPlaceSite //, public IDocHostUIHandler +{ +public: + HWND parent; + HWND hwnd; + int m_cRef; + RECT rcClient; + DWORD m_dwCookie; + CComPtr m_pConnectionPoint; + CComPtr pWebBrowser; + IEEmbedSink *sink; + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // IDispatch + STDMETHOD(GetTypeInfoCount)(UINT*); + STDMETHOD(GetTypeInfo)(UINT, LCID, LPTYPEINFO*); + STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*); + STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*); + // IOleWindow + STDMETHOD(GetWindow)(HWND *phwnd); + STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode); + // IOleInPlace + STDMETHOD(CanInPlaceActivate)(void); + STDMETHOD(OnInPlaceActivate)(void); + STDMETHOD(OnUIActivate)(void); + STDMETHOD(GetWindowContext)(IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc, + LPRECT lprcPosRect, LPRECT lprcClipRect, + LPOLEINPLACEFRAMEINFO lpFrameInfo); + STDMETHOD(Scroll)(SIZE scrollExtant); + + STDMETHOD(OnUIDeactivate)(BOOL fUndoable); + STDMETHOD(OnInPlaceDeactivate)(void); + STDMETHOD(DiscardUndoState)(void); + STDMETHOD(DeactivateAndUndo)(void); + STDMETHOD(OnPosRectChange)(LPCRECT lprcPosRect); + // IOleClientSite + STDMETHOD(SaveObject)(void); + STDMETHOD(GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk); + STDMETHOD(GetContainer)(IOleContainer **ppContainer); + STDMETHOD(ShowObject)(void); + STDMETHOD(OnShowWindow)(BOOL fShow); + STDMETHOD(RequestNewObjectLayout)(void); + + IHTMLDocument2 *getDocument(); + IEEmbed(HWND _parent); + virtual ~IEEmbed(); + + void ResizeBrowser(); + void navigate(const wchar_t *); + void navigate(char *url); + void navigate(NETLIBHTTPREQUEST *nlhr); + void write(const wchar_t *text); + void addCookie(const wchar_t *cookieString); + BSTR getCookies(); + char* GetHTMLDoc(); +}; +#endif diff --git a/protocols/MSN/src/msn_proto.h b/protocols/MSN/src/msn_proto.h index 952c32db05..f5a4fa8358 100644 --- a/protocols/MSN/src/msn_proto.h +++ b/protocols/MSN/src/msn_proto.h @@ -118,6 +118,7 @@ struct CMsnProto : public PROTO char *authStorageToken; char *hotSecretToken, *hotAuthToken; char *authUser, *authUIC, *authCookies, *authSSLToken, *authAccessToken, *authRefreshToken, *authSkypeComToken, *authSkypeToken; + bool bAskingForAuth; int authMethod; time_t authTokenExpiretime; bool bSentBND; @@ -275,6 +276,7 @@ struct CMsnProto : public PROTO void __cdecl msn_keepAliveThread(void* arg); void __cdecl msn_loginThread(void* arg); + void __cdecl msn_IEAuthThread(void* arg); void __cdecl MSNServerThread(void* arg); void __cdecl MsnFileAckThread(void* arg); @@ -492,6 +494,7 @@ struct CMsnProto : public PROTO char* GenerateLoginBlob(char* challenge); void LoadAuthTokensDB(void); void SaveAuthTokensDB(void); + bool parseLoginPage(char *pszHTML, NETLIBHTTPREQUEST *nlhr, CMStringA *post); int LoginSkypeOAuth(const char *pRefreshToken); bool RefreshOAuth(const char *pszRefreshToken, const char *pszService, char *pszAccessToken, char *pszOutRefreshToken=NULL, time_t *ptExpires=NULL); int MSN_AuthOAuth(void); diff --git a/protocols/MSN/src/stdafx.h b/protocols/MSN/src/stdafx.h index eedae789e9..1f34366308 100644 --- a/protocols/MSN/src/stdafx.h +++ b/protocols/MSN/src/stdafx.h @@ -193,12 +193,6 @@ const char MSN_USER_AGENT[] = NETLIB_USER_AGENT; extern const char sttVoidUid[]; -#define MSN_LOGIN_OAUTH LPGEN("Automatic authentication to MSN failed, possibly due to Captcha-Authentication. \ -Do you want to open a browser window to do a manual login?\n\n\ -This will not log you in to MSN in Miranda, but it allows you to fix potential problems like entering a captcha \ -that gets asked due to location change or to accept new TOS, which are required only once.\n\n\ -Also please note that MSN protocol doesn't support two-factor authentication.") - ///////////////////////////////////////////////////////////////////////////////////////// // MSN plugin functions -- cgit v1.2.3