From 48540940b6c28bb4378abfeb500ec45a625b37b6 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Tue, 15 May 2012 10:38:20 +0000 Subject: initial commit git-svn-id: http://svn.miranda-ng.org/main/trunk@2 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- src/modules/netlib/netlib.cpp | 640 +++++++++++++++ src/modules/netlib/netlib.h | 209 +++++ src/modules/netlib/netlibautoproxy.cpp | 460 +++++++++++ src/modules/netlib/netlibbind.cpp | 287 +++++++ src/modules/netlib/netlibhttp.cpp | 1331 ++++++++++++++++++++++++++++++++ src/modules/netlib/netlibhttpproxy.cpp | 522 +++++++++++++ src/modules/netlib/netliblog.cpp | 637 +++++++++++++++ src/modules/netlib/netlibopenconn.cpp | 944 ++++++++++++++++++++++ src/modules/netlib/netlibopts.cpp | 556 +++++++++++++ src/modules/netlib/netlibpktrecver.cpp | 85 ++ src/modules/netlib/netlibsecurity.cpp | 570 ++++++++++++++ src/modules/netlib/netlibsock.cpp | 203 +++++ src/modules/netlib/netlibssl.cpp | 981 +++++++++++++++++++++++ src/modules/netlib/netlibupnp.cpp | 885 +++++++++++++++++++++ 14 files changed, 8310 insertions(+) create mode 100644 src/modules/netlib/netlib.cpp create mode 100644 src/modules/netlib/netlib.h create mode 100644 src/modules/netlib/netlibautoproxy.cpp create mode 100644 src/modules/netlib/netlibbind.cpp create mode 100644 src/modules/netlib/netlibhttp.cpp create mode 100644 src/modules/netlib/netlibhttpproxy.cpp create mode 100644 src/modules/netlib/netliblog.cpp create mode 100644 src/modules/netlib/netlibopenconn.cpp create mode 100644 src/modules/netlib/netlibopts.cpp create mode 100644 src/modules/netlib/netlibpktrecver.cpp create mode 100644 src/modules/netlib/netlibsecurity.cpp create mode 100644 src/modules/netlib/netlibsock.cpp create mode 100644 src/modules/netlib/netlibssl.cpp create mode 100644 src/modules/netlib/netlibupnp.cpp (limited to 'src/modules/netlib') diff --git a/src/modules/netlib/netlib.cpp b/src/modules/netlib/netlib.cpp new file mode 100644 index 0000000000..000639474f --- /dev/null +++ b/src/modules/netlib/netlib.cpp @@ -0,0 +1,640 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +static BOOL bModuleInitialized = FALSE; + +HANDLE hConnectionHeaderMutex, hConnectionOpenMutex; +DWORD g_LastConnectionTick; +int connectionTimeout; +HANDLE hSendEvent=NULL, hRecvEvent=NULL; + +typedef BOOL (WINAPI *tGetProductInfo)(DWORD, DWORD, DWORD, DWORD, PDWORD); + +static int CompareNetlibUser(const NetlibUser* p1, const NetlibUser* p2) +{ + return strcmp(p1->user.szSettingsModule, p2->user.szSettingsModule); +} + +LIST netlibUser(5, CompareNetlibUser); +CRITICAL_SECTION csNetlibUser; + +SSL_API si; + +void NetlibFreeUserSettingsStruct(NETLIBUSERSETTINGS *settings) +{ + mir_free(settings->szIncomingPorts); + mir_free(settings->szOutgoingPorts); + mir_free(settings->szProxyAuthPassword); + mir_free(settings->szProxyAuthUser); + mir_free(settings->szProxyServer); +} + +void NetlibInitializeNestedCS(struct NetlibNestedCriticalSection *nlncs) +{ + nlncs->dwOwningThreadId= 0; + nlncs->lockCount=0; + nlncs->hMutex=CreateMutex(NULL,FALSE,NULL); +} + +void NetlibDeleteNestedCS(struct NetlibNestedCriticalSection *nlncs) +{ + CloseHandle(nlncs->hMutex); +} + +int NetlibEnterNestedCS(struct NetlibConnection *nlc,int which) +{ + struct NetlibNestedCriticalSection *nlncs; + DWORD dwCurrentThreadId=GetCurrentThreadId(); + + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + if(nlc==NULL || nlc->handleType!=NLH_CONNECTION) { + ReleaseMutex(hConnectionHeaderMutex); + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + nlncs = (which == NLNCS_SEND) ? &nlc->ncsSend : &nlc->ncsRecv; + if(nlncs->lockCount && nlncs->dwOwningThreadId==dwCurrentThreadId) { + nlncs->lockCount++; + ReleaseMutex(hConnectionHeaderMutex); + return 1; + } + InterlockedIncrement(&nlc->dontCloseNow); + ResetEvent(nlc->hOkToCloseEvent); + ReleaseMutex(hConnectionHeaderMutex); + WaitForSingleObject(nlncs->hMutex,INFINITE); + nlncs->dwOwningThreadId=dwCurrentThreadId; + nlncs->lockCount=1; + if(InterlockedDecrement(&nlc->dontCloseNow)==0) + SetEvent(nlc->hOkToCloseEvent); + return 1; +} + +void NetlibLeaveNestedCS(struct NetlibNestedCriticalSection *nlncs) +{ + if(--nlncs->lockCount==0) { + nlncs->dwOwningThreadId=0; + ReleaseMutex(nlncs->hMutex); + } +} + +static INT_PTR GetNetlibUserSettingInt(const char *szUserModule,const char *szSetting,int defValue) +{ + DBVARIANT dbv; + if(DBGetContactSetting(NULL,szUserModule,szSetting,&dbv) + && DBGetContactSetting(NULL,"Netlib",szSetting,&dbv)) + return defValue; + if(dbv.type==DBVT_BYTE) return dbv.bVal; + if(dbv.type==DBVT_WORD) return dbv.wVal; + return dbv.dVal; +} + +static char *GetNetlibUserSettingString(const char *szUserModule,const char *szSetting,int decode) +{ + DBVARIANT dbv; + if(DBGetContactSettingString(NULL,szUserModule,szSetting,&dbv) + && DBGetContactSettingString(NULL,"Netlib",szSetting,&dbv)) { + return NULL; + } + else { + char *szRet; + if(decode) CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + szRet=mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + if(szRet==NULL) SetLastError(ERROR_OUTOFMEMORY); + return szRet; + } +} + +static INT_PTR NetlibRegisterUser(WPARAM,LPARAM lParam) +{ + NETLIBUSER *nlu=(NETLIBUSER*)lParam; + struct NetlibUser *thisUser; + + if(nlu==NULL || nlu->cbSize!=sizeof(NETLIBUSER) || nlu->szSettingsModule==NULL + || (!(nlu->flags&NUF_NOOPTIONS) && nlu->szDescriptiveName==NULL) + || (nlu->flags&NUF_HTTPGATEWAY && (nlu->pfnHttpGatewayInit==NULL))) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + + thisUser = (struct NetlibUser*)mir_calloc(sizeof(struct NetlibUser)); + thisUser->handleType = NLH_USER; + thisUser->user = *nlu; + + EnterCriticalSection(&csNetlibUser); + if (netlibUser.getIndex(thisUser) >= 0) + { + LeaveCriticalSection(&csNetlibUser); + mir_free(thisUser); + SetLastError(ERROR_DUP_NAME); + return 0; + } + LeaveCriticalSection(&csNetlibUser); + + if (nlu->szDescriptiveName) { + thisUser->user.ptszDescriptiveName = (thisUser->user.flags&NUF_UNICODE ? mir_u2t((WCHAR*)nlu->ptszDescriptiveName) : mir_a2t(nlu->szDescriptiveName)); + } + if((thisUser->user.szSettingsModule=mir_strdup(nlu->szSettingsModule))==NULL + || (nlu->szDescriptiveName && thisUser->user.ptszDescriptiveName ==NULL) + || (nlu->szHttpGatewayUserAgent && (thisUser->user.szHttpGatewayUserAgent=mir_strdup(nlu->szHttpGatewayUserAgent))==NULL)) + { + mir_free(thisUser); + SetLastError(ERROR_OUTOFMEMORY); + return 0; + } + if (nlu->szHttpGatewayHello) + thisUser->user.szHttpGatewayHello=mir_strdup(nlu->szHttpGatewayHello); + else + thisUser->user.szHttpGatewayHello=NULL; + + thisUser->settings.cbSize=sizeof(NETLIBUSERSETTINGS); + thisUser->settings.useProxy=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLUseProxy",0); + thisUser->settings.proxyType=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLProxyType",PROXYTYPE_SOCKS5); + if(thisUser->user.flags&NUF_NOHTTPSOPTION && thisUser->settings.proxyType==PROXYTYPE_HTTPS) + thisUser->settings.proxyType=PROXYTYPE_HTTP; + if(!(thisUser->user.flags&(NUF_HTTPCONNS|NUF_HTTPGATEWAY)) && thisUser->settings.proxyType==PROXYTYPE_HTTP) { + thisUser->settings.useProxy=0; + thisUser->settings.proxyType=PROXYTYPE_SOCKS5; + } + thisUser->settings.szProxyServer=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLProxyServer",0); + thisUser->settings.wProxyPort=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLProxyPort",1080); + thisUser->settings.useProxyAuth=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLUseProxyAuth",0); + thisUser->settings.szProxyAuthUser=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLProxyAuthUser",0); + thisUser->settings.szProxyAuthPassword=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLProxyAuthPassword",1); + thisUser->settings.dnsThroughProxy=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLDnsThroughProxy",1); + thisUser->settings.specifyIncomingPorts=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLSpecifyIncomingPorts",0); + thisUser->settings.szIncomingPorts=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLIncomingPorts",0); + thisUser->settings.specifyOutgoingPorts=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLSpecifyOutgoingPorts",0); + thisUser->settings.szOutgoingPorts=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLOutgoingPorts",0); + thisUser->settings.enableUPnP=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLEnableUPnP",1); //default to on + thisUser->settings.validateSSL=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLValidateSSL",0); + + thisUser->toLog=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLlog",1); + + EnterCriticalSection(&csNetlibUser); + netlibUser.insert(thisUser); + LeaveCriticalSection(&csNetlibUser); + return (INT_PTR)thisUser; +} + +static INT_PTR NetlibGetUserSettings(WPARAM wParam,LPARAM lParam) +{ + NETLIBUSERSETTINGS *nlus=(NETLIBUSERSETTINGS*)lParam; + struct NetlibUser *nlu=(struct NetlibUser*)wParam; + + if(GetNetlibHandleType(nlu)!=NLH_USER || nlus==NULL || nlus->cbSize!=sizeof(NETLIBUSERSETTINGS)) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + *nlus=nlu->settings; + return 1; +} + +static INT_PTR NetlibSetUserSettings(WPARAM wParam,LPARAM lParam) +{ + NETLIBUSERSETTINGS *nlus=(NETLIBUSERSETTINGS*)lParam; + struct NetlibUser *nlu=(struct NetlibUser*)wParam; + + if(GetNetlibHandleType(nlu)!=NLH_USER || nlus==NULL || nlus->cbSize!=sizeof(NETLIBUSERSETTINGS)) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + NetlibSaveUserSettingsStruct(nlu->user.szSettingsModule,nlus); + return 1; +} + +void NetlibDoClose(NetlibConnection *nlc, bool noShutdown) +{ + if (nlc->s == INVALID_SOCKET) return; + + NetlibLogf(nlc->nlu, "(%p:%u) Connection closed internal", nlc, nlc->s); + if (nlc->hSsl) + { + if (!noShutdown) si.shutdown(nlc->hSsl); + si.sfree(nlc->hSsl); + nlc->hSsl = NULL; + } + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; +} + +INT_PTR NetlibCloseHandle(WPARAM wParam, LPARAM) +{ + switch(GetNetlibHandleType(wParam)) { + case NLH_USER: + { struct NetlibUser *nlu=(struct NetlibUser*)wParam; + int i; + + EnterCriticalSection(&csNetlibUser); + i = netlibUser.getIndex(nlu); + if (i >= 0) netlibUser.remove(i); + LeaveCriticalSection(&csNetlibUser); + + NetlibFreeUserSettingsStruct(&nlu->settings); + mir_free(nlu->user.szSettingsModule); + mir_free(nlu->user.szDescriptiveName); + mir_free(nlu->user.szHttpGatewayHello); + mir_free(nlu->user.szHttpGatewayUserAgent); + mir_free(nlu->szStickyHeaders); + break; + } + case NLH_CONNECTION: + { struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + HANDLE waitHandles[4]; + DWORD waitResult; + + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + if (nlc->usingHttpGateway) + { + HttpGatewayRemovePacket(nlc, -1); + } + else + { + if(nlc->s != INVALID_SOCKET) { + NetlibDoClose(nlc); + } + if (nlc->s2 != INVALID_SOCKET) closesocket(nlc->s2); + nlc->s2 = INVALID_SOCKET; + } + ReleaseMutex(hConnectionHeaderMutex); + + waitHandles[0]=hConnectionHeaderMutex; + waitHandles[1]=nlc->hOkToCloseEvent; + waitHandles[2]=nlc->ncsRecv.hMutex; + waitHandles[3]=nlc->ncsSend.hMutex; + waitResult=WaitForMultipleObjects( SIZEOF(waitHandles),waitHandles,TRUE,INFINITE); + if(waitResult= WAIT_OBJECT_0 + SIZEOF(waitHandles)) { + ReleaseMutex(hConnectionHeaderMutex); + SetLastError(ERROR_INVALID_PARAMETER); //already been closed + return 0; + } + nlc->handleType=0; + mir_free(nlc->nlhpi.szHttpPostUrl); + mir_free(nlc->nlhpi.szHttpGetUrl); + mir_free(nlc->dataBuffer); + mir_free((char*)nlc->nloc.szHost); + mir_free(nlc->szNewUrl); + mir_free(nlc->szProxyServer); + NetlibDeleteNestedCS(&nlc->ncsRecv); + NetlibDeleteNestedCS(&nlc->ncsSend); + CloseHandle(nlc->hOkToCloseEvent); + DeleteCriticalSection(&nlc->csHttpSequenceNums); + ReleaseMutex(hConnectionHeaderMutex); + NetlibLogf(nlc->nlu,"(%p:%u) Connection closed",nlc,nlc->s); + break; + } + case NLH_BOUNDPORT: + return NetlibFreeBoundPort((struct NetlibBoundPort*)wParam); + case NLH_PACKETRECVER: + { struct NetlibPacketRecver *nlpr=(struct NetlibPacketRecver*)wParam; + mir_free(nlpr->packetRecver.buffer); + break; + } + default: + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + mir_free((void*)wParam); + return 1; +} + +static INT_PTR NetlibGetSocket(WPARAM wParam, LPARAM) +{ + SOCKET s; + if(wParam==0) { + s=INVALID_SOCKET; + SetLastError(ERROR_INVALID_PARAMETER); + } + else { + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + switch(GetNetlibHandleType(wParam)) { + case NLH_CONNECTION: + s=((struct NetlibConnection*)wParam)->s; + break; + case NLH_BOUNDPORT: + s=((struct NetlibBoundPort*)wParam)->s; + break; + default: + s=INVALID_SOCKET; + SetLastError(ERROR_INVALID_PARAMETER); + break; + } + ReleaseMutex(hConnectionHeaderMutex); + } + return s; +} + +INT_PTR NetlibShutdown(WPARAM wParam, LPARAM) +{ + if (wParam) + { + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + switch(GetNetlibHandleType(wParam)) { + case NLH_CONNECTION: + { + struct NetlibConnection* nlc = (struct NetlibConnection*)wParam; + if (nlc->hSsl) si.shutdown(nlc->hSsl); + if (nlc->s != INVALID_SOCKET) shutdown(nlc->s, 2); + if (nlc->s2 != INVALID_SOCKET) shutdown(nlc->s2, 2); + nlc->termRequested = true; + } + break; + case NLH_BOUNDPORT: + { + struct NetlibBoundPort* nlb = (struct NetlibBoundPort*)wParam; + if (nlb->s != INVALID_SOCKET) shutdown(nlb->s, 2); + } + break; + } + ReleaseMutex(hConnectionHeaderMutex); + + } + return 0; +} + +static const char szHexDigits[]="0123456789ABCDEF"; +INT_PTR NetlibHttpUrlEncode(WPARAM,LPARAM lParam) +{ + unsigned char *szOutput,*szInput=(unsigned char*)lParam; + unsigned char *pszIn,*pszOut; + int outputLen; + + if(szInput==NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + return (INT_PTR)(char*)NULL; + } + for(outputLen=0,pszIn=szInput;*pszIn;pszIn++) { + if ( (48 <= *pszIn && *pszIn <= 57) ||//0-9 + (65 <= *pszIn && *pszIn <= 90) ||//ABC...XYZ + (97 <= *pszIn && *pszIn <= 122) ||//abc...xyz + *pszIn == '-' || *pszIn == '_' || *pszIn == '.' || *pszIn == ' ') outputLen++; + else outputLen+=3; + } + szOutput=(unsigned char*)HeapAlloc(GetProcessHeap(),0,outputLen+1); + if(szOutput==NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return (INT_PTR)(unsigned char*)NULL; + } + for(pszOut=szOutput,pszIn=szInput;*pszIn;pszIn++) { + if ( (48 <= *pszIn && *pszIn <= 57) || + (65 <= *pszIn && *pszIn <= 90) || + (97 <= *pszIn && *pszIn <= 122) || + *pszIn == '-' || *pszIn == '_' || *pszIn == '.') *pszOut++=*pszIn; + else if(*pszIn==' ') *pszOut++='+'; + else { + *pszOut++='%'; + *pszOut++=szHexDigits[*pszIn>>4]; + *pszOut++=szHexDigits[*pszIn&0xF]; + } + } + *pszOut='\0'; + return (INT_PTR)szOutput; +} + +static const char base64chars[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +INT_PTR NetlibBase64Encode(WPARAM, LPARAM lParam) +{ + NETLIBBASE64 *nlb64=(NETLIBBASE64*)lParam; + int iIn; + char *pszOut; + PBYTE pbIn; + + if(nlb64==NULL || nlb64->pszEncoded==NULL || nlb64->pbDecoded==NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if(nlb64->cchEncodedcbDecoded)) { + SetLastError(ERROR_BUFFER_OVERFLOW); + return 0; + } + nlb64->cchEncoded=Netlib_GetBase64EncodedBufferSize(nlb64->cbDecoded); + for(iIn=0,pbIn=nlb64->pbDecoded,pszOut=nlb64->pszEncoded;iIncbDecoded;iIn+=3,pbIn+=3,pszOut+=4) { + pszOut[0]=base64chars[pbIn[0]>>2]; + if(nlb64->cbDecoded-iIn==1) { + pszOut[1]=base64chars[(pbIn[0]&3)<<4]; + pszOut[2]='='; + pszOut[3]='='; + pszOut+=4; + break; + } + pszOut[1]=base64chars[((pbIn[0]&3)<<4)|(pbIn[1]>>4)]; + if(nlb64->cbDecoded-iIn==2) { + pszOut[2]=base64chars[(pbIn[1]&0xF)<<2]; + pszOut[3]='='; + pszOut+=4; + break; + } + pszOut[2]=base64chars[((pbIn[1]&0xF)<<2)|(pbIn[2]>>6)]; + pszOut[3]=base64chars[pbIn[2]&0x3F]; + } + pszOut[0]='\0'; + return 1; +} + +static BYTE Base64CharToInt(char c) +{ + if(c>='A' && c<='Z') return c-'A'; + if(c>='a' && c<='z') return c-'a'+26; + if(c>='0' && c<='9') return c-'0'+52; + if(c=='+') return 62; + if(c=='/') return 63; + if(c=='=') return 64; + return 255; +} + +INT_PTR NetlibBase64Decode(WPARAM, LPARAM lParam) +{ + NETLIBBASE64 *nlb64=(NETLIBBASE64*)lParam; + char *pszIn; + PBYTE pbOut; + BYTE b1,b2,b3,b4; + int iIn; + + if(nlb64==NULL || nlb64->pszEncoded==NULL || nlb64->pbDecoded==NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if(nlb64->cchEncoded&3) { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + if(nlb64->cbDecodedcchEncoded)) { + SetLastError(ERROR_BUFFER_OVERFLOW); + return 0; + } + nlb64->cbDecoded=Netlib_GetBase64DecodedBufferSize(nlb64->cchEncoded); + for(iIn=0,pszIn=nlb64->pszEncoded,pbOut=nlb64->pbDecoded;iIncchEncoded;iIn+=4,pszIn+=4,pbOut+=3) { + b1=Base64CharToInt(pszIn[0]); + b2=Base64CharToInt(pszIn[1]); + b3=Base64CharToInt(pszIn[2]); + b4=Base64CharToInt(pszIn[3]); + if(b1==255 || b1==64 || b2==255 || b2==64 || b3==255 || b4==255) { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + pbOut[0]=(b1<<2)|(b2>>4); + if(b3==64) {nlb64->cbDecoded-=2; break;} + pbOut[1]=(b2<<4)|(b3>>2); + if(b4==64) {nlb64->cbDecoded--; break;} + pbOut[2]=b4|(b3<<6); + } + return 1; +} + +void UnloadNetlibModule(void) +{ + if (!bModuleInitialized) return; + + if (hConnectionHeaderMutex != NULL) + { + int i; + + NetlibUnloadIeProxy(); + NetlibSecurityDestroy(); + NetlibUPnPDestroy(); + NetlibLogShutdown(); + + DestroyHookableEvent(hRecvEvent); hRecvEvent = NULL; + DestroyHookableEvent(hSendEvent); hSendEvent = NULL; + + for (i = netlibUser.getCount(); i > 0; i--) + NetlibCloseHandle((WPARAM)netlibUser[i-1], 0); + + netlibUser.destroy(); + + CloseHandle(hConnectionHeaderMutex); + if (hConnectionOpenMutex) CloseHandle(hConnectionOpenMutex); + DeleteCriticalSection(&csNetlibUser); + WSACleanup(); + } +} + +int LoadNetlibModule(void) +{ + WSADATA wsadata; + + bModuleInitialized = TRUE; + + WSAStartup(MAKEWORD(2,2), &wsadata); + + HookEvent(ME_OPT_INITIALISE,NetlibOptInitialise); + + InitializeCriticalSection(&csNetlibUser); + hConnectionHeaderMutex=CreateMutex(NULL,FALSE,NULL); + NetlibLogInit(); + + connectionTimeout = 0; + + OSVERSIONINFOEX osvi = {0}; + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (GetVersionEx((LPOSVERSIONINFO)&osvi)) + { + // Connection limiting was introduced in Windows XP SP2 and later and set to 10 / sec + if (osvi.dwMajorVersion == 5 && ((osvi.dwMinorVersion == 1 && osvi.wServicePackMajor >= 2) || osvi.dwMinorVersion > 1)) + connectionTimeout = 150; + // Connection limiting has limits based on addition Windows Vista pre SP2 + else if (osvi.dwMajorVersion == 6 && osvi.wServicePackMajor < 2) + { + DWORD dwType = 0; + tGetProductInfo pGetProductInfo = (tGetProductInfo) GetProcAddress(GetModuleHandleA("kernel32"), "GetProductInfo"); + if (pGetProductInfo != NULL) pGetProductInfo(6, 0, 0, 0, &dwType); + switch( dwType ) + { + case 0x01: // Vista Ultimate edition have connection limit of 25 / sec - plenty for Miranda + case 0x1c: + break; + + case 0x02: // Vista Home Basic edition have connection limit of 2 / sec + case 0x05: + connectionTimeout = 1000; + break; + + default: // all other editions have connection limit of 10 / sec + connectionTimeout = 150; + break; + } + } + // Connection limiting is disabled by default and is controlled by registry setting in Windows Vista SP2 and later + else if (osvi.dwMajorVersion >= 6) + { + static const char keyn[] = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"; + static const char valn[] = "EnableConnectionRateLimiting"; + + HKEY hSettings; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyn, 0, KEY_QUERY_VALUE, &hSettings) == ERROR_SUCCESS) + { + DWORD tValueLen, enabled; + tValueLen = sizeof(enabled); + if (RegQueryValueExA(hSettings, valn, NULL, NULL, (BYTE*)&enabled, &tValueLen) == ERROR_SUCCESS && enabled) + connectionTimeout = 150; // if enabled limit is set to 10 / sec + RegCloseKey(hSettings); + } + + } + } + + hConnectionOpenMutex = connectionTimeout ? CreateMutex(NULL,FALSE,NULL) : NULL; + g_LastConnectionTick = GetTickCount(); + + CreateServiceFunction(MS_NETLIB_REGISTERUSER,NetlibRegisterUser); + CreateServiceFunction(MS_NETLIB_GETUSERSETTINGS,NetlibGetUserSettings); + CreateServiceFunction(MS_NETLIB_SETUSERSETTINGS,NetlibSetUserSettings); + CreateServiceFunction(MS_NETLIB_CLOSEHANDLE,NetlibCloseHandle); + CreateServiceFunction(MS_NETLIB_BINDPORT,NetlibBindPort); + CreateServiceFunction(MS_NETLIB_OPENCONNECTION,NetlibOpenConnection); + CreateServiceFunction(MS_NETLIB_SETHTTPPROXYINFO,NetlibHttpGatewaySetInfo); + CreateServiceFunction(MS_NETLIB_SETSTICKYHEADERS,NetlibHttpSetSticky); + CreateServiceFunction(MS_NETLIB_GETSOCKET,NetlibGetSocket); + CreateServiceFunction(MS_NETLIB_URLENCODE,NetlibHttpUrlEncode); + CreateServiceFunction(MS_NETLIB_BASE64ENCODE,NetlibBase64Encode); + CreateServiceFunction(MS_NETLIB_BASE64DECODE,NetlibBase64Decode); + CreateServiceFunction(MS_NETLIB_SENDHTTPREQUEST,NetlibHttpSendRequest); + CreateServiceFunction(MS_NETLIB_RECVHTTPHEADERS,NetlibHttpRecvHeaders); + CreateServiceFunction(MS_NETLIB_FREEHTTPREQUESTSTRUCT,NetlibHttpFreeRequestStruct); + CreateServiceFunction(MS_NETLIB_HTTPTRANSACTION,NetlibHttpTransaction); + CreateServiceFunction(MS_NETLIB_SEND,NetlibSend); + CreateServiceFunction(MS_NETLIB_RECV,NetlibRecv); + CreateServiceFunction(MS_NETLIB_SELECT,NetlibSelect); + CreateServiceFunction(MS_NETLIB_SELECTEX,NetlibSelectEx); + CreateServiceFunction(MS_NETLIB_SHUTDOWN,NetlibShutdown); + CreateServiceFunction(MS_NETLIB_CREATEPACKETRECVER,NetlibPacketRecverCreate); + CreateServiceFunction(MS_NETLIB_GETMOREPACKETS,NetlibPacketRecverGetMore); + CreateServiceFunction(MS_NETLIB_SETPOLLINGTIMEOUT,NetlibHttpSetPollingTimeout); + CreateServiceFunction(MS_NETLIB_STARTSSL,NetlibStartSsl); + + hRecvEvent = CreateHookableEvent(ME_NETLIB_FASTRECV); + hSendEvent = CreateHookableEvent(ME_NETLIB_FASTSEND); + + NetlibUPnPInit(); + NetlibSecurityInit(); + NetlibLoadIeProxy(); + + return 0; +} + +void NetlibInitSsl(void) +{ + mir_getSI(&si); +} diff --git a/src/modules/netlib/netlib.h b/src/modules/netlib/netlib.h new file mode 100644 index 0000000000..575e77f18c --- /dev/null +++ b/src/modules/netlib/netlib.h @@ -0,0 +1,209 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ +#define GetNetlibHandleType(h) (h?*(int*)h:NLH_INVALID) +#define NLH_INVALID 0 +#define NLH_USER 'USER' +#define NLH_CONNECTION 'CONN' +#define NLH_BOUNDPORT 'BIND' +#define NLH_PACKETRECVER 'PCKT' + +struct NetlibUser +{ + int handleType; + NETLIBUSER user; + NETLIBUSERSETTINGS settings; + char * szStickyHeaders; + int toLog; + int inportnum; + int outportnum; +}; + +struct NetlibNestedCriticalSection +{ + HANDLE hMutex; + DWORD dwOwningThreadId; + int lockCount; +}; + +struct NetlibHTTPProxyPacketQueue +{ + struct NetlibHTTPProxyPacketQueue *next; + PBYTE dataBuffer; + int dataBufferLen; +}; + +struct NetlibConnection +{ + int handleType; + SOCKET s, s2; + bool usingHttpGateway; + bool usingDirectHttpGateway; + bool proxyAuthNeeded; + bool dnsThroughProxy; + bool termRequested; + struct NetlibUser *nlu; + NETLIBHTTPPROXYINFO nlhpi; + PBYTE dataBuffer; + int dataBufferLen; + CRITICAL_SECTION csHttpSequenceNums; + HANDLE hOkToCloseEvent; + LONG dontCloseNow; + struct NetlibNestedCriticalSection ncsSend,ncsRecv; + HSSL hSsl; + struct NetlibHTTPProxyPacketQueue * pHttpProxyPacketQueue; + char *szNewUrl; + char *szProxyServer; + WORD wProxyPort; + int proxyType; + int pollingTimeout; + unsigned lastPost; + NETLIBOPENCONNECTION nloc; +}; + +struct NetlibBoundPort { + int handleType; + SOCKET s; + WORD wPort; + WORD wExPort; + struct NetlibUser *nlu; + NETLIBNEWCONNECTIONPROC_V2 pfnNewConnectionV2; + HANDLE hThread; + void *pExtra; +}; + +struct NetlibPacketRecver { + int handleType; + struct NetlibConnection *nlc; + NETLIBPACKETRECVER packetRecver; +}; + +//netlib.c +void NetlibFreeUserSettingsStruct(NETLIBUSERSETTINGS *settings); +void NetlibDoClose(NetlibConnection *nlc, bool noShutdown = false); +INT_PTR NetlibCloseHandle(WPARAM wParam,LPARAM lParam); +void NetlibInitializeNestedCS(struct NetlibNestedCriticalSection *nlncs); +void NetlibDeleteNestedCS(struct NetlibNestedCriticalSection *nlncs); +#define NLNCS_SEND 0 +#define NLNCS_RECV 1 +int NetlibEnterNestedCS(struct NetlibConnection *nlc,int which); +void NetlibLeaveNestedCS(struct NetlibNestedCriticalSection *nlncs); +INT_PTR NetlibBase64Encode(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibBase64Decode(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpUrlEncode(WPARAM wParam,LPARAM lParam); + +extern CRITICAL_SECTION csNetlibUser; +extern LIST netlibUser; + +//netlibautoproxy.c +void NetlibLoadIeProxy(void); +void NetlibUnloadIeProxy(void); +char* NetlibGetIeProxy(char *szUrl); +bool NetlibGetIeProxyConn(NetlibConnection *nlc, bool forceHttps); + +//netlibbind.c +int NetlibFreeBoundPort(struct NetlibBoundPort *nlbp); +INT_PTR NetlibBindPort(WPARAM wParam,LPARAM lParam); +bool BindSocketToPort(const char *szPorts, SOCKET s, int* portn); + +//netlibhttp.c +INT_PTR NetlibHttpSendRequest(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpRecvHeaders(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpFreeRequestStruct(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpTransaction(WPARAM wParam,LPARAM lParam); +void NetlibHttpSetLastErrorUsingHttpResult(int result); +NETLIBHTTPREQUEST* NetlibHttpRecv(NetlibConnection* nlc, DWORD hflags, DWORD dflags, bool isConnect = false); +void NetlibConnFromUrl(const char* szUrl, bool secur, NETLIBOPENCONNECTION &nloc); + +//netlibhttpproxy.c +int NetlibInitHttpConnection(struct NetlibConnection *nlc,struct NetlibUser *nlu,NETLIBOPENCONNECTION *nloc); +int NetlibHttpGatewayRecv(struct NetlibConnection *nlc,char *buf,int len,int flags); +int NetlibHttpGatewayPost(struct NetlibConnection *nlc,const char *buf,int len,int flags); +void HttpGatewayRemovePacket(NetlibConnection *nlc, int pck); + +INT_PTR NetlibHttpGatewaySetInfo(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpSetPollingTimeout(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpSetSticky(WPARAM wParam, LPARAM lParam); + +//netliblog.c +void NetlibLogShowOptions(void); +void NetlibDumpData(struct NetlibConnection *nlc,PBYTE buf,int len,int sent,int flags); +void NetlibLogf(NetlibUser* nlu, const char *fmt, ...); +void NetlibLogInit(void); +void NetlibLogShutdown(void); + +//netlibopenconn.c +DWORD DnsLookup(struct NetlibUser *nlu,const char *szHost); +int WaitUntilReadable(SOCKET s,DWORD dwTimeout, bool check = false); +int WaitUntilWritable(SOCKET s,DWORD dwTimeout); +bool NetlibDoConnect(NetlibConnection *nlc); +bool NetlibReconnect(NetlibConnection *nlc); +INT_PTR NetlibOpenConnection(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibStartSsl(WPARAM wParam, LPARAM lParam); + +//netlibopts.c +int NetlibOptInitialise(WPARAM wParam,LPARAM lParam); +void NetlibSaveUserSettingsStruct(const char *szSettingsModule,NETLIBUSERSETTINGS *settings); + +//netlibpktrecver.c +INT_PTR NetlibPacketRecverCreate(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibPacketRecverGetMore(WPARAM wParam,LPARAM lParam); + +//netlibsock.c +#define NL_SELECT_READ 0x0001 +#define NL_SELECT_WRITE 0x0002 +#define NL_SELECT_ALL (NL_SELECT_READ+NL_SELECT_WRITE) + +INT_PTR NetlibSend(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibRecv(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibSelect(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibSelectEx(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibShutdown(WPARAM wParam,LPARAM lParam); + +//netlibupnp.c +bool NetlibUPnPAddPortMapping(WORD intport, char *proto, + WORD *extport, DWORD *extip, bool search); +void NetlibUPnPDeletePortMapping(WORD extport, char* proto); +void NetlibUPnPCleanup(void*); +void NetlibUPnPInit(void); +void NetlibUPnPDestroy(void); + +//netlibsecurity.c +void NetlibSecurityInit(void); +void NetlibSecurityDestroy(void); +void NetlibDestroySecurityProvider(HANDLE hSecurity); +HANDLE NetlibInitSecurityProvider(const TCHAR* szProvider, const TCHAR* szPrincipal); +#ifdef UNICODE +HANDLE NetlibInitSecurityProvider(const char* szProvider, const char* szPrincipal); +#endif +char* NtlmCreateResponseFromChallenge(HANDLE hSecurity, const char *szChallenge, const TCHAR* login, const TCHAR* psw, + bool http, unsigned& complete); + + +static __inline INT_PTR NLSend(struct NetlibConnection *nlc,const char *buf,int len,int flags) { + NETLIBBUFFER nlb={(char*)buf,len,flags}; + return NetlibSend((WPARAM)nlc,(LPARAM)&nlb); +} +static __inline INT_PTR NLRecv(struct NetlibConnection *nlc,char *buf,int len,int flags) { + NETLIBBUFFER nlb={buf,len,flags}; + return NetlibRecv((WPARAM)nlc,(LPARAM)&nlb); +} diff --git a/src/modules/netlib/netlibautoproxy.cpp b/src/modules/netlib/netlibautoproxy.cpp new file mode 100644 index 0000000000..e169cf9c89 --- /dev/null +++ b/src/modules/netlib/netlibautoproxy.cpp @@ -0,0 +1,460 @@ +/* +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2010-2011 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +#include +/* +///////////////////////////////////////////////////////////////////// +// ResolveHostName (a helper function) +///////////////////////////////////////////////////////////////////// +DWORD __stdcall ResolveHostName(LPSTR lpszHostName, + LPSTR lpszIPAddress, LPDWORD lpdwIPAddressSize) +{ + if (*lpdwIPAddressSize < 17 || lpszIPAddress == NULL) + { + *lpdwIPAddressSize = 17; + return ERROR_INSUFFICIENT_BUFFER; + } + + IN_ADDR ip; + ip.s_addr = inet_addr(lpszHostName); + if (ip.s_addr == INADDR_NONE) + { + PHOSTENT myhost = gethostbyname(lpszHostName); + if (myhost != NULL) + ip = *(PIN_ADDR)myhost->h_addr; + else + return SOCKET_ERROR; + } + mir_snprintf(lpszIPAddress, *lpdwIPAddressSize, "%u.%u.%u.%u", + ip.s_net, ip.s_host, ip.s_lh, ip.s_impno); + + return 0; +} + +///////////////////////////////////////////////////////////////////// +// IsResolvable (a helper function) +///////////////////////////////////////////////////////////////////// +BOOL __stdcall IsResolvable(LPSTR lpszHost) +{ + char szDummy[255]; + DWORD dwDummySize = sizeof (szDummy) - 1; + + if (ResolveHostName(lpszHost, szDummy, &dwDummySize)) + return FALSE; + return TRUE; +} + +///////////////////////////////////////////////////////////////////// +// GetIPAddress (a helper function) +///////////////////////////////////////////////////////////////////// +DWORD __stdcall GetIPAddress(LPSTR lpszIPAddress, LPDWORD lpdwIPAddressSize) +{ + char szHostBuffer[255]; + + if (gethostname(szHostBuffer, sizeof (szHostBuffer) - 1) != ERROR_SUCCESS) + return (ERROR_INTERNET_INTERNAL_ERROR); + return (ResolveHostName(szHostBuffer, lpszIPAddress, lpdwIPAddressSize)); +} + +///////////////////////////////////////////////////////////////////// +// IsInNet (a helper function) +///////////////////////////////////////////////////////////////////// +BOOL __stdcall IsInNet(LPSTR lpszIPAddress, LPSTR lpszDest, LPSTR lpszMask) +{ + DWORD dwDest; + DWORD dwIpAddr; + DWORD dwMask; + + dwIpAddr = inet_addr(lpszIPAddress); + dwDest = inet_addr(lpszDest); + dwMask = inet_addr(lpszMask); + + if ((dwDest == INADDR_NONE) || + (dwIpAddr == INADDR_NONE) || ((dwIpAddr & dwMask) != dwDest)) + return (FALSE); + + return (TRUE); +} + +static const AutoProxyHelperVtbl OurVtbl = +{ + IsResolvable, + GetIPAddress, + ResolveHostName, + IsInNet, + NULL, + NULL, + NULL, + NULL +}; + +static AutoProxyHelperFunctions HelperFunctions = { &OurVtbl }; +*/ + +static char *szProxyHost[3]; +static LIST proxyBypass(5); + +static HMODULE hModJS; + +static pfnInternetInitializeAutoProxyDll pInternetInitializeAutoProxyDll; +static pfnInternetDeInitializeAutoProxyDll pInternetDeInitializeAutoProxyDll; +static pfnInternetGetProxyInfo pInternetGetProxyInfo; + +static bool bEnabled, bOneProxy; + +static void GetFile(char* szUrl, AUTO_PROXY_SCRIPT_BUFFER &buf) +{ + NetlibUser nlu = {0}; + NETLIBHTTPREQUEST nlhr = {0}; + + nlu.handleType = NLH_USER; + nlu.user.flags = NUF_OUTGOING | NUF_HTTPCONNS; + nlu.user.szSettingsModule = "(NULL)"; + nlu.toLog = 1; + + // initialize the netlib request + nlhr.cbSize = sizeof(nlhr); + nlhr.requestType = REQUEST_GET; + nlhr.flags = NLHRF_HTTP11 | NLHRF_DUMPASTEXT | NLHRF_REDIRECT; + nlhr.szUrl = szUrl; + + // download the page + NETLIBHTTPREQUEST *nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpTransaction((WPARAM)&nlu, (LPARAM)&nlhr); + + if (nlhrReply) + { + if (nlhrReply->resultCode == 200) + { + buf.lpszScriptBuffer = nlhrReply->pData; + buf.dwScriptBufferSize = nlhrReply->dataLength + 1; + + nlhrReply->dataLength = 0; + nlhrReply->pData = NULL; + } + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply); + } +} + +bool NetlibGetIeProxyConn(NetlibConnection *nlc, bool forceHttps) +{ + bool noHttp = false; + bool usingSsl = false; + char szUrl[256] = ""; + + if ((nlc->nloc.flags & (NLOCF_HTTP | NLOCF_HTTPGATEWAY) && nlc->nloc.flags & NLOCF_SSL) || + nlc->nloc.wPort == 443 || forceHttps) + { + mir_snprintf(szUrl, sizeof(szUrl), "https://%s", nlc->nloc.szHost); + usingSsl = true; + } + else if (nlc->nloc.flags & (NLOCF_HTTPGATEWAY | NLOCF_HTTP) || nlc->usingHttpGateway) + mir_snprintf(szUrl, sizeof(szUrl), "http://%s", nlc->nloc.szHost); + else + { + mir_snprintf(szUrl, sizeof(szUrl), "%s", nlc->nloc.szHost); + noHttp = true; + } + + mir_free(nlc->szProxyServer); nlc->szProxyServer = NULL; + nlc->wProxyPort = 0; + nlc->proxyType = 0; + + char *mt = NetlibGetIeProxy(szUrl); + char *m = NEWSTR_ALLOCA(mt); + mir_free(mt); + + if (m == NULL) return false; + + // if multiple servers, use the first one + char *c = strchr(m, ';'); if (c) *c = 0; + + // if 'direct' no proxy + if (_stricmp(lrtrim(m), "direct") == 0) return false; + + // find proxy address + char *h = strchr(m, ' '); + if (h) { *h = 0; ++h; } else return false; + + // find proxy port + char *p = strchr(h, ':'); + if (p) { *p = 0; ++p; } + + lrtrim(h); ltrim(p); + if (_stricmp(m, "proxy") == 0 && h[0]) + { + nlc->proxyType = (usingSsl || noHttp) ? PROXYTYPE_HTTPS : PROXYTYPE_HTTP; + nlc->wProxyPort = p ? atol(p) : 8080; + nlc->szProxyServer = mir_strdup(h); + } + else if (_stricmp(m, "socks") == 0 && h[0]) + { + nlc->proxyType = PROXYTYPE_SOCKS4; + nlc->wProxyPort = p ? atol(p) : 1080; + nlc->szProxyServer = mir_strdup(h); + } + else if (_stricmp(m, "socks5") == 0 && h[0]) + { + nlc->proxyType = PROXYTYPE_SOCKS5; + nlc->wProxyPort = p ? atol(p) : 1080; + nlc->szProxyServer = mir_strdup(h); + } + else + return false; + + return true; +} + +static char szAutoUrlStr[MAX_PATH] = ""; +static AUTO_PROXY_SCRIPT_BUFFER abuf = {0}; +static HANDLE hIeProxyMutex; +static bool bAutoProxyInit; + +static void NetlibInitAutoProxy(void) +{ + if (bAutoProxyInit) return; + + if (!hModJS) + { + if (!(hModJS = LoadLibraryA("jsproxy.dll"))) + return; + + pInternetInitializeAutoProxyDll = (pfnInternetInitializeAutoProxyDll) + GetProcAddress(hModJS, "InternetInitializeAutoProxyDll"); + + pInternetDeInitializeAutoProxyDll = (pfnInternetDeInitializeAutoProxyDll) + GetProcAddress(hModJS, "InternetDeInitializeAutoProxyDll"); + + pInternetGetProxyInfo = (pfnInternetGetProxyInfo) + GetProcAddress(hModJS, "InternetGetProxyInfo"); + } + + if (strstr(szAutoUrlStr, "file://") == NULL && strstr(szAutoUrlStr, "://") != NULL) + { + abuf.dwStructSize = sizeof(abuf); + GetFile(szAutoUrlStr, abuf); + } + bAutoProxyInit = true; +} + +struct IeProxyParam +{ + char *szUrl; + char *szHost; + char *szProxy; +}; + +static unsigned __stdcall NetlibIeProxyThread(void * arg) +{ + IeProxyParam *param = (IeProxyParam*)arg; + param->szProxy = NULL; + + if (!bAutoProxyInit) + { + WaitForSingleObject(hIeProxyMutex, INFINITE); + NetlibInitAutoProxy(); + ReleaseMutex(hIeProxyMutex); + } + + BOOL res; + char *loc = strstr(szAutoUrlStr, "file://"); + if (loc || strstr(szAutoUrlStr, "://") == NULL) + { + NetlibLogf(NULL, "Autoproxy Init file: %s", loc); + loc = loc ? loc + 7 : szAutoUrlStr; + res = pInternetInitializeAutoProxyDll(0, loc, NULL, NULL /*&HelperFunctions*/, NULL); + } + else + { + NetlibLogf(NULL, "Autoproxy Init %d", abuf.dwScriptBufferSize); + if (abuf.dwScriptBufferSize) + res = pInternetInitializeAutoProxyDll(0, NULL, NULL, NULL /*&HelperFunctions*/, &abuf); + else + res = false; + } + + if (res) + { + char proxyBuffer[1024]; + char *proxy = proxyBuffer; + DWORD dwProxyLen = sizeof(proxyBuffer); + + if (pInternetGetProxyInfo(param->szUrl, (DWORD)strlen(param->szUrl), + param->szHost, (DWORD)strlen(param->szHost), &proxy, &dwProxyLen)) + param->szProxy = mir_strdup(lrtrim(proxy)); + + NetlibLogf(NULL, "Autoproxy got response %s, Param: %s %s", param->szProxy, param->szUrl, param->szHost); + pInternetDeInitializeAutoProxyDll(NULL, 0); + } + else + NetlibLogf(NULL, "Autoproxy init failed"); + + return 0; +} + +char* NetlibGetIeProxy(char *szUrl) +{ + char *res = NULL; + char* p = strstr(szUrl, "://"); + if (p) p += 3; else p = szUrl; + + char *szHost = NEWSTR_ALLOCA(p); + p = strchr(szHost, '/'); if (p) *p = 0; + p = strchr(szHost, ':'); if (p) *p = 0; + _strlwr(szHost); + + if (bEnabled) + { + for (int i = 0; i < proxyBypass.getCount(); ++i) + { + if (strcmp(proxyBypass[i], "") == 0) + { + if (strchr(szHost, '.') == NULL) return NULL; + } + else if (wildcmp(szHost, proxyBypass[i])) return NULL; + } + + int ind = -1; + if (strstr(szUrl, "http://")) + ind = szProxyHost[0] ? 0 : 2; + else if (strstr(szUrl, "https://")) + ind = bOneProxy ? 0 : (szProxyHost[1] ? 1 : 2); + else + ind = szProxyHost[2] ? 2 : (bOneProxy ? 0 : (szProxyHost[1] ? 1 : 2)); + + if (ind < 0 || !szProxyHost[ind]) return NULL; + + size_t len = strlen(szHost) + 20; + res = (char*)mir_alloc(len); + mir_snprintf(res, len, "%s %s", ind == 2 ? "SOCKS" : "PROXY", szProxyHost[ind]); + return res; + } + + if (szAutoUrlStr[0]) + { + unsigned dwThreadId; + IeProxyParam param = { szUrl, szHost, NULL }; + HANDLE hThread = (HANDLE)forkthreadex(NULL, 0, NetlibIeProxyThread, 0, ¶m, &dwThreadId); + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + res = param.szProxy; + } + return res; +} + +void NetlibLoadIeProxy(void) +{ + HKEY hSettings; + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + 0, KEY_QUERY_VALUE, &hSettings)) + return; + + DWORD tValueLen, enabled = 0; + char szHostStr[256] = "", szProxyBypassStr[4096] = ""; + + tValueLen = sizeof(enabled); + int tResult = RegQueryValueExA(hSettings, "ProxyEnable", NULL, NULL, (BYTE*)&enabled, &tValueLen); + bEnabled = enabled && tResult == ERROR_SUCCESS; + + tValueLen = SIZEOF(szHostStr); + tResult = RegQueryValueExA(hSettings, "ProxyServer", NULL, NULL, (BYTE*)szHostStr, &tValueLen); + bEnabled = bEnabled && tResult == ERROR_SUCCESS; + + tValueLen = SIZEOF(szAutoUrlStr); + tResult = RegQueryValueExA(hSettings, "AutoConfigUrl", NULL, NULL, (BYTE*)szAutoUrlStr, &tValueLen); + + tValueLen = SIZEOF(szProxyBypassStr); + tResult = RegQueryValueExA(hSettings, "ProxyOverride", NULL, NULL, (BYTE*)szProxyBypassStr, &tValueLen); + + RegCloseKey(hSettings); + + if (bEnabled) + { + char* szProxy = ltrim(szHostStr); + if (szProxy[0] == 0) { enabled = false; return; } + + for (;;) + { + char *szProxyEnd = strchr(szProxy, ';'); + if (szProxyEnd) *szProxyEnd = 0; + + int ind = -1; + if (strncmp(szProxy, "http=", 5) == 0) { ind = 0; szProxy += 5; } + else if (strncmp(szProxy, "https=", 6) == 0) { ind = 1; szProxy += 6; } + else if (strncmp(szProxy, "socks=", 6) == 0) { ind = 2; szProxy += 6; } + else if (strchr(szProxy, '=')) ind = -2; + + if (ind != -2) + { + bOneProxy = ind < 0; if (ind < 0) ind = 0; + + lrtrim(szProxy); + + if (strchr(szProxy, ':')) + szProxyHost[ind] = mir_strdup(szProxy); + else + { + size_t len = strlen(szProxy) + 10; + szProxyHost[ind] = (char*)mir_alloc(len); + mir_snprintf(szProxyHost[ind], len, "%s:%u", szProxy, ind == 2 ? 1080 : 8080); + } + if (bOneProxy) break; + } + if (szProxyEnd == NULL) break; + szProxy = szProxyEnd + 1; + } + + char* szProxyBypass = szProxyBypassStr; + for(;;) + { + char *szProxyBypassEnd = strchr(szProxyBypass, ';'); + if (szProxyBypassEnd) *szProxyBypassEnd = 0; + + lrtrim(szProxyBypass); + + proxyBypass.insert(_strlwr(mir_strdup(szProxyBypass))); + if (szProxyBypassEnd == NULL) break; + + szProxyBypass = szProxyBypassEnd + 1; + } + } + + if (bEnabled || szAutoUrlStr[0]) + hIeProxyMutex = CreateMutex(NULL, FALSE, NULL); +} + +void NetlibUnloadIeProxy(void) +{ + int i; + + for (i = 0; i < 3; ++i) + mir_free(szProxyHost[i]); + + for (i = 0; i < proxyBypass.getCount(); ++i) + mir_free(proxyBypass[i]); + + proxyBypass.destroy(); + mir_free(abuf.lpszScriptBuffer); + + CloseHandle(hIeProxyMutex); +} diff --git a/src/modules/netlib/netlibbind.cpp b/src/modules/netlib/netlibbind.cpp new file mode 100644 index 0000000000..6c0c4c19e9 --- /dev/null +++ b/src/modules/netlib/netlibbind.cpp @@ -0,0 +1,287 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +bool BindSocketToPort(const char *szPorts, SOCKET s, int* portn) +{ + SOCKADDR_IN sin = {0}; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + EnterCriticalSection(&csNetlibUser); + + if (--*portn < 0 && s != INVALID_SOCKET) + { + BindSocketToPort(szPorts, INVALID_SOCKET, portn); + if (*portn == 0) + { + LeaveCriticalSection(&csNetlibUser); + return false; + } + WORD num; + CallService(MS_UTILS_GETRANDOM, sizeof(WORD), (LPARAM)&num); + *portn = num % *portn; + } + + bool before=false; + for (;;) + { + const char *psz; + char *pszEnd; + int portMin, portMax, port, portnum = 0; + + for(psz=szPorts;*psz;) + { + while (*psz == ' ' || *psz == ',') psz++; + portMin = strtol(psz, &pszEnd, 0); + if (pszEnd == psz) break; + while (*pszEnd == ' ') pszEnd++; + if(*pszEnd == '-') + { + psz = pszEnd + 1; + portMax = strtol(psz, &pszEnd, 0); + if (pszEnd == psz) portMax = 65535; + if (portMin > portMax) + { + port = portMin; + portMin = portMax; + portMax = port; + } + } + else portMax = portMin; + if (portMax >= 1) + { + if (portMin <= 0) portMin = 1; + for (port = portMin; port <= portMax; port++) + { + if (port > 65535) break; + + ++portnum; + + if (s == INVALID_SOCKET) continue; + if (!before && portnum <= *portn) continue; + if (before && portnum >= *portn) + { + LeaveCriticalSection(&csNetlibUser); + return false; + } + + sin.sin_port = htons((WORD)port); + if (bind(s, (SOCKADDR*)&sin, sizeof(sin)) == 0) + { + LeaveCriticalSection(&csNetlibUser); + *portn = portnum + 1; + return true; + } + } + } + psz = pszEnd; + } + if (*portn < 0) + { + *portn = portnum; + LeaveCriticalSection(&csNetlibUser); + return true; + } + else if (*portn >= portnum) + *portn = 0; + else + before = true; + } +} + + +int NetlibFreeBoundPort(struct NetlibBoundPort *nlbp) +{ + closesocket(nlbp->s); + WaitForSingleObject(nlbp->hThread,INFINITE); + CloseHandle(nlbp->hThread); + NetlibLogf(nlbp->nlu, "(%u) Port %u closed for incoming connections", nlbp->s, nlbp->wPort); + mir_free(nlbp); + return 1; +} + +static unsigned __stdcall NetlibBindAcceptThread(void* param) +{ + SOCKET s; + SOCKADDR_IN sin; + int sinLen; + struct NetlibConnection *nlc; + struct NetlibBoundPort *nlbp = (NetlibBoundPort*)param; + + NetlibLogf(nlbp->nlu, "(%u) Port %u opened for incoming connections", nlbp->s, nlbp->wPort); + for(;;) + { + sinLen = sizeof(sin); + s = accept(nlbp->s, (struct sockaddr*)&sin, &sinLen); + if (s == INVALID_SOCKET) break; + NetlibLogf(nlbp->nlu, "New incoming connection on port %u from %s (%d)", nlbp->wPort, inet_ntoa(sin.sin_addr), s); + nlc = (NetlibConnection*)mir_calloc(sizeof(NetlibConnection)); + nlc->handleType = NLH_CONNECTION; + nlc->nlu = nlbp->nlu; + nlc->s = s; + nlc->s2 = INVALID_SOCKET; + InitializeCriticalSection(&nlc->csHttpSequenceNums); + nlc->hOkToCloseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + nlc->dontCloseNow = 0; + NetlibInitializeNestedCS(&nlc->ncsSend); + NetlibInitializeNestedCS(&nlc->ncsRecv); + nlbp->pfnNewConnectionV2((HANDLE)nlc,ntohl(sin.sin_addr.S_un.S_addr), nlbp->pExtra); + } + NetlibUPnPDeletePortMapping(nlbp->wExPort, "TCP"); + return 0; +} + +INT_PTR NetlibBindPort(WPARAM wParam,LPARAM lParam) +{ + NETLIBBIND *nlb = (NETLIBBIND*)lParam; + struct NetlibUser *nlu = (struct NetlibUser*)wParam; + struct NetlibBoundPort *nlbp; + SOCKADDR_IN sin; + int foundPort = 0; + UINT dwThreadId; + + if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_INCOMING) || + nlb == NULL || nlb->pfnNewConnection == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if (nlb->cbSize != sizeof(NETLIBBIND) && + nlb->cbSize != NETLIBBIND_SIZEOF_V2 && + nlb->cbSize != NETLIBBIND_SIZEOF_V1) + { + return 0; + } + nlbp = (NetlibBoundPort*)mir_calloc(sizeof(NetlibBoundPort)); + nlbp->handleType = NLH_BOUNDPORT; + nlbp->nlu = nlu; + nlbp->pfnNewConnectionV2 = nlb->pfnNewConnectionV2; + nlbp->s = socket(AF_INET, SOCK_STREAM, 0); + nlbp->pExtra = (nlb->cbSize != NETLIBBIND_SIZEOF_V1) ? nlb->pExtra : NULL; + if (nlbp->s == INVALID_SOCKET) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"socket",WSAGetLastError()); + mir_free(nlbp); + return 0; + } + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = 0; + + /* if the netlib user wanted a free port given in the range, then + they better have given wPort==0, let's hope so */ + if (nlu->settings.specifyIncomingPorts && nlu->settings.szIncomingPorts && nlb->wPort == 0) + { + if (!BindSocketToPort(nlu->settings.szIncomingPorts, nlbp->s, &nlu->outportnum)) + { + NetlibLogf(nlu, "Netlib bind: Not enough ports for incoming connections specified"); + SetLastError(WSAEADDRINUSE); + } + else + foundPort = 1; + } + else + { + /* if ->wPort==0 then they'll get any free port, otherwise they'll + be asking for whatever was in nlb->wPort*/ + if (nlb->wPort != 0) + { + NetlibLogf(nlu,"%s %d: trying to bind port %d, this 'feature' can be abused, please be sure you want to allow it.",__FILE__,__LINE__,nlb->wPort); + sin.sin_port = htons(nlb->wPort); + } + if (bind(nlbp->s, (PSOCKADDR)&sin, sizeof(sin)) == 0) + foundPort = 1; + } + if (!foundPort) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"bind",WSAGetLastError()); + closesocket(nlbp->s); + mir_free(nlbp); + return 0; + } + + if (listen(nlbp->s, 5)) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"listen",WSAGetLastError()); + closesocket(nlbp->s); + mir_free(nlbp); + return 0; + } + + { int len; + DWORD extIP; + + ZeroMemory(&sin,sizeof(sin)); + len = sizeof(sin); + if (getsockname(nlbp->s,(SOCKADDR *)&sin,&len)) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"getsockname",WSAGetLastError()); + closesocket(nlbp->s); + mir_free(nlbp); + return 0; + } + nlb->wPort = ntohs(sin.sin_port); + nlbp->wPort = nlb->wPort; + nlb->dwInternalIP = ntohl(sin.sin_addr.S_un.S_addr); + + if (nlb->dwInternalIP == 0) + { + char hostname[64]; + struct hostent *he; + + gethostname(hostname, SIZEOF(hostname)); + he = gethostbyname(hostname); + if (he && he->h_addr_list[0]) + nlb->dwInternalIP = ntohl(*(PDWORD)he->h_addr_list[0]); + } + if (nlu->settings.enableUPnP && + NetlibUPnPAddPortMapping(nlb->wPort, "TCP", &nlbp->wExPort, &extIP, nlb->cbSize > NETLIBBIND_SIZEOF_V2)) + { + NetlibLogf(NULL, "UPnP port mapping succeeded. Internal Port: %u External Port: %u\n", + nlb->wPort, nlbp->wExPort); + if (nlb->cbSize > NETLIBBIND_SIZEOF_V2) + { + nlb->wExPort = nlbp->wExPort; + nlb->dwExternalIP = extIP; + } + } + else + { + if (nlu->settings.enableUPnP) + NetlibLogf(NULL, "UPnP port mapping failed. Internal Port: %u\n", nlb->wPort); + else + NetlibLogf(NULL, "UPnP disabled. Internal Port: %u\n", nlb->wPort); + + nlbp->wExPort = 0; + if (nlb->cbSize > NETLIBBIND_SIZEOF_V2) + { + nlb->wExPort = nlb->wPort; + nlb->dwExternalIP = nlb->dwInternalIP; + } + } + } + nlbp->hThread = (HANDLE)forkthreadex(NULL, 0, NetlibBindAcceptThread, 0, nlbp, &dwThreadId); + return (INT_PTR)nlbp; +} diff --git a/src/modules/netlib/netlibhttp.cpp b/src/modules/netlib/netlibhttp.cpp new file mode 100644 index 0000000000..970a28cb10 --- /dev/null +++ b/src/modules/netlib/netlibhttp.cpp @@ -0,0 +1,1331 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "../plugins/zlib/zlib.h" +#include "netlib.h" + +#define HTTPRECVHEADERSTIMEOUT 30000 //in ms +#define HTTPRECVDATATIMEOUT 20000 + +struct ResizableCharBuffer +{ + char *sz; + int iEnd, cbAlloced; +}; + +struct ProxyAuth +{ + char *szServer; + char *szMethod; +// char *szUserName; +// char *szPassword; + + ProxyAuth(const char *pszServer, const char *pszMethod) + { + szServer = mir_strdup(pszServer); + szMethod = mir_strdup(pszMethod); + } + ~ProxyAuth() + { + mir_free(szServer); + mir_free(szMethod); + } + static int Compare(const ProxyAuth* p1, const ProxyAuth* p2 ) + { return lstrcmpiA(p1->szServer, p2->szServer); } +}; + +struct ProxyAuthList : OBJLIST +{ + ProxyAuthList() : OBJLIST(2, ProxyAuth::Compare) {} + + void add(const char *szServer, const char *szMethod) + { + if (szServer == NULL) return; + int i = getIndex((ProxyAuth*)&szServer); + if (i >= 0) + { + ProxyAuth &rec = (*this)[i]; + if (szMethod == NULL) + remove(i); + else if (_stricmp(rec.szMethod, szMethod)) + { + mir_free(rec.szMethod); + rec.szMethod = mir_strdup(szMethod); + } + } + else + insert(new ProxyAuth(szServer, szMethod)); + } + + const char* find(const char *szServer) + { + ProxyAuth * rec = szServer ? OBJLIST::find((ProxyAuth*)&szServer) : NULL; + return rec ? rec->szMethod : NULL; + } +}; + +ProxyAuthList proxyAuthList; + +static void AppendToCharBuffer(struct ResizableCharBuffer *rcb, const char *fmt, ...) +{ + va_list va; + int charsDone; + + if (rcb->cbAlloced == 0) + { + rcb->cbAlloced = 512; + rcb->sz = (char*)mir_alloc(rcb->cbAlloced); + } + va_start(va, fmt); + for (;;) + { + charsDone = mir_vsnprintf(rcb->sz + rcb->iEnd, rcb->cbAlloced-rcb->iEnd, fmt, va); + if(charsDone >= 0) break; + rcb->cbAlloced += 512; + rcb->sz = (char*)mir_realloc(rcb->sz, rcb->cbAlloced); + } + va_end(va); + rcb->iEnd += charsDone; +} + +static int RecvWithTimeoutTime(struct NetlibConnection *nlc, unsigned dwTimeoutTime, char *buf, int len, int flags) +{ + DWORD dwTimeNow; + + if (!si.pending(nlc->hSsl)) + { + while ((dwTimeNow = GetTickCount()) < dwTimeoutTime) + { + unsigned dwDeltaTime = min(dwTimeoutTime - dwTimeNow, 1000); + int res = WaitUntilReadable(nlc->s, dwDeltaTime); + + switch (res) + { + case SOCKET_ERROR: + return SOCKET_ERROR; + + case 1: + return NLRecv(nlc, buf, len, flags); + } + + if (nlc->termRequested || Miranda_Terminated()) return 0; + } + SetLastError(ERROR_TIMEOUT); + return SOCKET_ERROR; + } + return NLRecv(nlc, buf, len, flags); +} + +static char* NetlibHttpFindHeader(NETLIBHTTPREQUEST *nlhrReply, const char *hdr) +{ + for (int i = 0; i < nlhrReply->headersCount; i++) + { + if (_stricmp(nlhrReply->headers[i].szName, hdr) == 0) + { + return nlhrReply->headers[i].szValue; + } + } + return NULL; +} + +static char* NetlibHttpFindAuthHeader(NETLIBHTTPREQUEST *nlhrReply, const char *hdr, const char *szProvider) +{ + char *szBasicHdr = NULL; + char *szNegoHdr = NULL; + char *szNtlmHdr = NULL; + + for (int i = 0; i < nlhrReply->headersCount; i++) + { + if (_stricmp(nlhrReply->headers[i].szName, hdr) == 0) + { + if (_strnicmp(nlhrReply->headers[i].szValue, "Negotiate", 9) == 0) + szNegoHdr = nlhrReply->headers[i].szValue; + else if (_strnicmp(nlhrReply->headers[i].szValue, "NTLM", 4) == 0) + szNtlmHdr = nlhrReply->headers[i].szValue; + else if (_strnicmp(nlhrReply->headers[i].szValue, "Basic", 5) == 0) + szBasicHdr = nlhrReply->headers[i].szValue; + } + } + + if (szNegoHdr && (!szProvider || !_stricmp(szProvider, "Negotiate"))) return szNegoHdr; + if (szNtlmHdr && (!szProvider || !_stricmp(szProvider, "NTLM"))) return szNtlmHdr; + if (!szProvider || !_stricmp(szProvider, "Basic")) return szBasicHdr; + return NULL; +} + +void NetlibConnFromUrl(const char* szUrl, bool secur, NETLIBOPENCONNECTION &nloc) +{ + secur = secur || _strnicmp(szUrl, "https", 5) == 0; + const char* phost = strstr(szUrl, "://"); + + char* szHost = mir_strdup(phost ? phost + 3 : szUrl); + + char* ppath = strchr(szHost, '/'); + if (ppath) *ppath = '\0'; + + memset(&nloc, 0, sizeof(nloc)); + nloc.cbSize = sizeof(nloc); + nloc.szHost = szHost; + + char* pcolon = strrchr(szHost, ':'); + if (pcolon) + { + *pcolon = '\0'; + nloc.wPort = (WORD)strtol(pcolon+1, NULL, 10); + } + else nloc.wPort = secur ? 443 : 80; + nloc.flags = (secur ? NLOCF_SSL : 0); +} + +static NetlibConnection* NetlibHttpProcessUrl(NETLIBHTTPREQUEST *nlhr, NetlibUser *nlu, NetlibConnection* nlc, + const char* szUrl = NULL) +{ + NETLIBOPENCONNECTION nloc; + + if (szUrl == NULL) + NetlibConnFromUrl(nlhr->szUrl, (nlhr->flags & NLHRF_SSL) != 0, nloc); + else + NetlibConnFromUrl(szUrl, false, nloc); + + nloc.flags |= NLOCF_HTTP; + if (nloc.flags & NLOCF_SSL) nlhr->flags |= NLHRF_SSL; else nlhr->flags &= ~NLHRF_SSL; + + if (nlc != NULL) + { + bool httpProxy = !(nloc.flags & NLOCF_SSL) && nlc->proxyType == PROXYTYPE_HTTP; + bool sameHost = lstrcmpA(nlc->nloc.szHost, nloc.szHost) == 0 && nlc->nloc.wPort == nloc.wPort; + + if (!httpProxy && !sameHost) + { + NetlibDoClose(nlc); + + mir_free((char*)nlc->nloc.szHost); + nlc->nloc = nloc; + return NetlibDoConnect(nlc) ? nlc : NULL; + } + } + else + nlc = (NetlibConnection*)NetlibOpenConnection((WPARAM)nlu, (LPARAM)&nloc); + + mir_free((char*)nloc.szHost); + + return nlc; +} + +struct HttpSecurityContext +{ + HANDLE m_hNtlmSecurity; + char *m_szHost; + char *m_szProvider; + + HttpSecurityContext() + { m_hNtlmSecurity = NULL; m_szHost = NULL; m_szProvider = NULL; } + + ~HttpSecurityContext() { Destroy(); } + + void Destroy(void) + { + if (!m_hNtlmSecurity) return; + + NetlibDestroySecurityProvider(m_hNtlmSecurity); + m_hNtlmSecurity = NULL; + mir_free(m_szHost); m_szHost = NULL; + mir_free(m_szProvider); m_szProvider = NULL; + } + + bool TryBasic(void) + { + return m_hNtlmSecurity && m_szProvider && _stricmp(m_szProvider, "Basic"); + } + + char* Execute(NetlibConnection *nlc, char* szHost, const char* szProvider, + const char* szChallenge, unsigned& complete) + { + char* szAuthHdr = NULL; + bool justCreated = false; + + if (m_hNtlmSecurity) + { + bool newAuth = !m_szProvider || !szProvider || _stricmp(m_szProvider, szProvider); + newAuth = newAuth || (m_szHost != szHost && (!m_szHost || !szHost || _stricmp(m_szHost, szHost))); + if (newAuth) + Destroy(); + } + + if (m_hNtlmSecurity == NULL) + { + char szSpnStr[256] = ""; + if (szHost && _stricmp(szProvider, "Basic")) + { + unsigned long ip = inet_addr(szHost); + PHOSTENT host = (ip == INADDR_NONE) ? gethostbyname(szHost) : gethostbyaddr((char*)&ip, 4, AF_INET); + mir_snprintf(szSpnStr, SIZEOF(szSpnStr), "HTTP/%s", host && host->h_name ? host->h_name : szHost); + _strlwr(szSpnStr + 5); + NetlibLogf(nlc->nlu, "Host SPN: %s", szSpnStr); + } + m_hNtlmSecurity = NetlibInitSecurityProvider(szProvider, szSpnStr[0] ? szSpnStr : NULL); + if (m_hNtlmSecurity) + { + m_szProvider = mir_strdup(szProvider); + m_szHost = mir_strdup(szHost); + justCreated = true; + } + } + + if (m_hNtlmSecurity) + { + TCHAR *szLogin = NULL, *szPassw = NULL; + + if (nlc->nlu->settings.useProxyAuth) + { + EnterCriticalSection(&csNetlibUser); + szLogin = mir_a2t(nlc->nlu->settings.szProxyAuthUser); + szPassw = mir_a2t(nlc->nlu->settings.szProxyAuthPassword); + LeaveCriticalSection(&csNetlibUser); + } + + szAuthHdr = NtlmCreateResponseFromChallenge(m_hNtlmSecurity, + szChallenge, szLogin, szPassw, true, complete); + + if (!szAuthHdr) + { + NetlibLogf(NULL, "Security login %s failed, user: " TCHAR_STR_PARAM " pssw: " TCHAR_STR_PARAM, + szProvider, szLogin ? szLogin : _T("(no user)"), szPassw ? _T("(exist)") : _T("(no psw)")); + } + else if (justCreated) + proxyAuthList.add(m_szHost, m_szProvider); + + mir_free(szLogin); + mir_free(szPassw); + } + else + complete = 1; + + return szAuthHdr; + } +}; + +static int HttpPeekFirstResponseLine(NetlibConnection *nlc, DWORD dwTimeoutTime, + DWORD recvFlags, int *resultCode, + char **ppszResultDescr, int *length) +{ + int bytesPeeked; + char buffer[2048]; + char *peol; + + for(;;) + { + bytesPeeked = RecvWithTimeoutTime(nlc, dwTimeoutTime, buffer, SIZEOF(buffer) - 1, + MSG_PEEK | recvFlags); + + if (bytesPeeked == 0) + { + SetLastError(ERROR_HANDLE_EOF); + return 0; + } + if (bytesPeeked == SOCKET_ERROR) + return 0; + + buffer[bytesPeeked] = '\0'; + peol = strchr(buffer, '\n'); + if (peol == NULL) + { + if ((int)strlen(buffer) < bytesPeeked) + { + SetLastError(ERROR_BAD_FORMAT); + return 0; + } + if (bytesPeeked == SIZEOF(buffer) - 1) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return 0; + } + if (Miranda_Terminated()) return 0; + Sleep(10); + } + else + break; + } + + if (peol == buffer) + { + SetLastError(ERROR_BAD_FORMAT); + return 0; + } + + *peol = '\0'; + + if (_strnicmp(buffer, "HTTP/", 5)) + { + SetLastError(ERROR_BAD_FORMAT); + return 0; + } + + size_t off = strcspn(buffer, " \t"); + if (off >= (unsigned)bytesPeeked) + return 0; + + char* pResultCode = buffer + off; + *(pResultCode++) = 0; + + char* pResultDescr; + *resultCode = strtol(pResultCode, &pResultDescr, 10); + + if (ppszResultDescr) + *ppszResultDescr = mir_strdup(lrtrimp(pResultDescr)); + + if (length) *length = peol - buffer + 1; + return 1; +} + +static int SendHttpRequestAndData(struct NetlibConnection *nlc,struct ResizableCharBuffer *httpRequest,NETLIBHTTPREQUEST *nlhr,int sendContentLengthHeader) +{ + bool sendData = (nlhr->requestType==REQUEST_POST || nlhr->requestType==REQUEST_PUT); + + if(sendContentLengthHeader && sendData) + AppendToCharBuffer(httpRequest,"Content-Length: %d\r\n\r\n", nlhr->dataLength); + else + AppendToCharBuffer(httpRequest,"\r\n"); + + DWORD hflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) | + (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND | NLHRF_NODUMPHEADERS) ? + MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + int bytesSent = NLSend(nlc, httpRequest->sz, httpRequest->iEnd, hflags); + if (bytesSent != SOCKET_ERROR && sendData && nlhr->dataLength) + { + DWORD sflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) | + (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND) ? + MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + int sendResult = NLSend(nlc,nlhr->pData,nlhr->dataLength, sflags); + + bytesSent = sendResult != SOCKET_ERROR ? bytesSent + sendResult : SOCKET_ERROR; + } + mir_free(httpRequest->sz); + memset(httpRequest, 0, sizeof(*httpRequest)); + + return bytesSent; +} + +INT_PTR NetlibHttpSendRequest(WPARAM wParam, LPARAM lParam) +{ + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + NETLIBHTTPREQUEST *nlhr=(NETLIBHTTPREQUEST*)lParam; + NETLIBHTTPREQUEST *nlhrReply = NULL; + HttpSecurityContext httpSecurity; + + struct ResizableCharBuffer httpRequest={0}; + const char *pszRequest, *pszUrl, *pszFullUrl; + char *szHost = NULL, *szNewUrl = NULL; + char *pszProxyAuthHdr = NULL, *pszAuthHdr = NULL; + int i, doneHostHeader, doneContentLengthHeader, doneProxyAuthHeader, doneAuthHeader; + int bytesSent; + bool lastFirstLineFail = false; + + if (nlhr == NULL || nlhr->cbSize < NETLIBHTTPREQUEST_V1_SIZE || nlhr->szUrl == NULL || nlhr->szUrl[0] == '\0') + { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + int hdrTimeout = nlhr->cbSize > NETLIBHTTPREQUEST_V1_SIZE && nlhr->timeout ? nlhr->timeout : HTTPRECVHEADERSTIMEOUT; + + switch(nlhr->requestType) + { + case REQUEST_GET: pszRequest = "GET"; break; + case REQUEST_POST: pszRequest = "POST"; break; + case REQUEST_CONNECT: pszRequest = "CONNECT"; break; + case REQUEST_HEAD: pszRequest = "HEAD"; break; + case REQUEST_PUT: pszRequest = "PUT"; break; + case REQUEST_DELETE: pszRequest = "DELETE"; break; + default: + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + if (!nlc->usingHttpGateway) + { + if (!NetlibEnterNestedCS(nlc, NLNCS_SEND)) + return SOCKET_ERROR; + } + + pszFullUrl = nlhr->szUrl; + pszUrl = NULL; + + unsigned complete = false; + int count = 11; + while (--count) + { + if (!NetlibReconnect(nlc)) + { + bytesSent = SOCKET_ERROR; + break; + } + + if (!pszUrl) + { + pszUrl = pszFullUrl; + if (nlhr->flags & (NLHRF_SMARTREMOVEHOST | NLHRF_REMOVEHOST | NLHRF_GENERATEHOST)) + { + bool usingProxy = nlc->proxyType == PROXYTYPE_HTTP && !(nlhr->flags & NLHRF_SSL); + + mir_free(szHost); + szHost = NULL; + + const char *ppath, *phost; + phost = strstr(pszUrl, "://"); + if (phost == NULL) phost = pszUrl; + else phost += 3; + ppath = strchr(phost, '/'); + if (ppath == phost) phost = NULL; + + if (nlhr->flags & NLHRF_GENERATEHOST) + { + szHost = mir_strdup(phost); + if (ppath && phost) szHost[ppath - phost] = 0; + } + + if (nlhr->flags & NLHRF_REMOVEHOST || (nlhr->flags & NLHRF_SMARTREMOVEHOST && !usingProxy)) + { + pszUrl = ppath ? ppath : "/"; + } + + if (usingProxy && phost && !nlc->dnsThroughProxy) + { + char* tszHost = mir_strdup(phost); + if (ppath && phost) tszHost[ppath - phost] = 0; + char* cln = strchr(tszHost, ':'); if (cln) *cln = 0; + + if (inet_addr(tszHost) == INADDR_NONE) + { + DWORD ip = DnsLookup(nlc->nlu, tszHost); + if (ip && szHost) + { + mir_free(szHost); + szHost = (char*)mir_alloc(30); + if (cln) *cln = ':'; + mir_snprintf(szHost, 30, "%s%s", inet_ntoa(*(PIN_ADDR)&ip), cln ? cln : ""); + } +/* + if (ip && pszUrl[0] != '/') + { + mir_free(szNewUrl); + szNewUrl = (char*)mir_alloc(strlen(pszUrl) + 60); + szNewUrl[0] = 0; + + phost = strstr(pszUrl, "://"); + if (phost) + { + phost += 3; + size_t len = phost - pszUrl; + memcpy(szNewUrl, pszUrl, len); + szNewUrl[len] = 0; + } + strcat(szNewUrl, inet_ntoa(*(PIN_ADDR)&ip)); + ppath = strchr(phost, '/'); + if (ppath) strcat(szNewUrl, ppath); + pszUrl = szNewUrl; + } +*/ + } + mir_free(tszHost); + } + } + } + + if (nlc->proxyAuthNeeded && proxyAuthList.getCount()) + { + if (httpSecurity.m_szProvider == NULL && nlc->szProxyServer) + { + const char* szAuthMethodNlu = proxyAuthList.find(nlc->szProxyServer); + + if (szAuthMethodNlu) + { + mir_free(pszProxyAuthHdr); + pszProxyAuthHdr = httpSecurity.Execute(nlc, nlc->szProxyServer, szAuthMethodNlu, "", complete); + } + } + } + nlc->proxyAuthNeeded = false; + + AppendToCharBuffer(&httpRequest, "%s %s HTTP/1.%d\r\n", pszRequest, pszUrl, (nlhr->flags & NLHRF_HTTP11) != 0); + + //HTTP headers + doneHostHeader = doneContentLengthHeader = doneProxyAuthHeader = doneAuthHeader = 0; + for (i=0; i < nlhr->headersCount; i++) + { + if (!lstrcmpiA(nlhr->headers[i].szName, "Host")) doneHostHeader = 1; + else if (!lstrcmpiA(nlhr->headers[i].szName, "Content-Length")) doneContentLengthHeader = 1; + else if (!lstrcmpiA(nlhr->headers[i].szName, "Proxy-Authorization")) doneProxyAuthHeader = 1; + else if (!lstrcmpiA(nlhr->headers[i].szName, "Authorization")) doneAuthHeader = 1; + else if (!lstrcmpiA(nlhr->headers[i].szName, "Connection")) continue; + if (nlhr->headers[i].szValue == NULL) continue; + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", nlhr->headers[i].szName, nlhr->headers[i].szValue); + } + if (szHost && !doneHostHeader) + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Host", szHost); + if (pszProxyAuthHdr && !doneProxyAuthHeader) + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Proxy-Authorization", pszProxyAuthHdr); + if (pszAuthHdr && !doneAuthHeader) + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Authorization", pszAuthHdr); + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Connection", "Keep-Alive"); + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Proxy-Connection", "Keep-Alive"); + + // Add Sticky Headers + if (nlc->nlu->szStickyHeaders != NULL) + AppendToCharBuffer(&httpRequest, "%s\r\n", nlc->nlu->szStickyHeaders); + + //send it + bytesSent = SendHttpRequestAndData(nlc, &httpRequest, nlhr, !doneContentLengthHeader); + if (bytesSent == SOCKET_ERROR) break; + + //ntlm reply + if (!doneContentLengthHeader || nlhr->requestType == REQUEST_HEAD) + { + int resultCode = 0; + + DWORD fflags = MSG_PEEK | MSG_NODUMP | ((nlhr->flags & NLHRF_NOPROXY) ? MSG_RAW : 0); + DWORD dwTimeOutTime = hdrTimeout < 0 ? -1 : GetTickCount() + hdrTimeout; + if (!HttpPeekFirstResponseLine(nlc, dwTimeOutTime, fflags, &resultCode, NULL, NULL)) + { + NetlibLogf(nlc->nlu, "%s %d: %s Failed (%u %u)",__FILE__,__LINE__,"HttpPeekFirstResponseLine",GetLastError(), count); + DWORD err = GetLastError(); + if (err == ERROR_TIMEOUT || err == ERROR_BAD_FORMAT || err == ERROR_BUFFER_OVERFLOW || + lastFirstLineFail || nlc->termRequested || nlhr->requestType == REQUEST_CONNECT) + { + bytesSent = SOCKET_ERROR; + break; + } + else + { + lastFirstLineFail = true; + continue; + } + } + lastFirstLineFail = false; + + DWORD hflags = (nlhr->flags & (NLHRF_NODUMP|NLHRF_NODUMPHEADERS|NLHRF_NODUMPSEND) ? + MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + DWORD dflags = (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND) ? + MSG_NODUMP : MSG_DUMPASTEXT | MSG_DUMPPROXY) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0) | MSG_NODUMP; + + if (resultCode == 100) + { + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + } + else if (resultCode == 307 || ((resultCode == 301 || resultCode == 302) // redirect + && (nlhr->flags & NLHRF_REDIRECT))) + { + pszUrl = NULL; + + if (nlhr->requestType == REQUEST_HEAD) + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + else + nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); + + if (nlhrReply) + { + char* tmpUrl = NetlibHttpFindHeader(nlhrReply, "Location"); + if (tmpUrl) + { + size_t rlen = 0; + if (tmpUrl[0] == '/') + { + const char *ppath, *phost; + phost = strstr(pszFullUrl, "://"); + phost = phost ? phost + 3 : pszFullUrl; + ppath = strchr(phost, '/'); + rlen = ppath ? ppath - pszFullUrl : strlen(pszFullUrl); + } + + nlc->szNewUrl = (char*)mir_realloc(nlc->szNewUrl, rlen + strlen(tmpUrl) * 3 + 1); + + strncpy(nlc->szNewUrl, pszFullUrl, rlen); + strcpy(nlc->szNewUrl + rlen, tmpUrl); + pszFullUrl = nlc->szNewUrl; + pszUrl = NULL; + + if (NetlibHttpProcessUrl(nlhr, nlc->nlu, nlc, pszFullUrl) == NULL) + { + bytesSent = SOCKET_ERROR; + break; + } + } + else + { + NetlibHttpSetLastErrorUsingHttpResult(resultCode); + bytesSent = SOCKET_ERROR; + break; + } + } + else + { + NetlibHttpSetLastErrorUsingHttpResult(resultCode); + bytesSent = SOCKET_ERROR; + break; + } + } + else if (resultCode == 401 && !doneAuthHeader) //auth required + { + if (nlhr->requestType == REQUEST_HEAD) + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + else + nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); + + mir_free(pszAuthHdr); pszAuthHdr = NULL; + if (nlhrReply) + { + char *szAuthStr = NULL; + if (!complete) + { + szAuthStr = NetlibHttpFindAuthHeader(nlhrReply, "WWW-Authenticate", + httpSecurity.m_szProvider); + if (szAuthStr) + { + char *szChallenge = strchr(szAuthStr, ' '); + if (!szChallenge || !*lrtrimp(szChallenge)) complete = true; + } + } + if (complete && httpSecurity.m_hNtlmSecurity) + { + szAuthStr = httpSecurity.TryBasic() ? + NetlibHttpFindAuthHeader(nlhrReply, "WWW-Authenticate", "Basic") : NULL; + } + + if (szAuthStr) + { + char *szChallenge = strchr(szAuthStr, ' '); + if (szChallenge) { *szChallenge = 0; szChallenge = lrtrimp(szChallenge + 1); } + + pszAuthHdr = httpSecurity.Execute(nlc, szHost, szAuthStr, szChallenge, complete); + } + } + if (pszAuthHdr == NULL) + { + proxyAuthList.add(szHost, NULL); + NetlibHttpSetLastErrorUsingHttpResult(resultCode); + bytesSent = SOCKET_ERROR; + break; + } + } + else if (resultCode == 407 && !doneProxyAuthHeader) //proxy auth required + { + if (nlhr->requestType == REQUEST_HEAD) + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + else + nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); + + mir_free(pszProxyAuthHdr); pszProxyAuthHdr = NULL; + if (nlhrReply) + { + char *szAuthStr = NULL; + if (!complete) + { + szAuthStr = NetlibHttpFindAuthHeader(nlhrReply, "Proxy-Authenticate", + httpSecurity.m_szProvider); + if (szAuthStr) + { + char *szChallenge = strchr(szAuthStr, ' '); + if (!szChallenge || !*lrtrimp(szChallenge + 1)) complete = true; + } + } + if (complete && httpSecurity.m_hNtlmSecurity) + { + szAuthStr = httpSecurity.TryBasic() ? + NetlibHttpFindAuthHeader(nlhrReply, "Proxy-Authenticate", "Basic") : NULL; + } + + if (szAuthStr) + { + char *szChallenge = strchr(szAuthStr, ' '); + if (szChallenge) { *szChallenge = 0; szChallenge = lrtrimp(szChallenge + 1); } + + pszProxyAuthHdr = httpSecurity.Execute(nlc, nlc->szProxyServer, szAuthStr, szChallenge, complete); + } + } + if (pszProxyAuthHdr == NULL) + { + proxyAuthList.add(nlc->szProxyServer, NULL); + NetlibHttpSetLastErrorUsingHttpResult(resultCode); + bytesSent = SOCKET_ERROR; + break; + } + } + else + break; + + if (pszProxyAuthHdr && resultCode != 407 && !doneProxyAuthHeader) + { + mir_free(pszProxyAuthHdr); pszProxyAuthHdr = NULL; + } + if (pszAuthHdr && resultCode != 401 && !doneAuthHeader) + { + mir_free(pszAuthHdr); pszAuthHdr = NULL; + } + + if (nlhrReply) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + nlhrReply = NULL; + } + } + else + break; + } + if (count == 0) bytesSent = SOCKET_ERROR; + if (nlhrReply) NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + + //clean up + mir_free(pszProxyAuthHdr); + mir_free(pszAuthHdr); + mir_free(szHost); + mir_free(szNewUrl); + + if (!nlc->usingHttpGateway) + NetlibLeaveNestedCS(&nlc->ncsSend); + + return bytesSent; +} + +INT_PTR NetlibHttpFreeRequestStruct(WPARAM, LPARAM lParam) +{ + NETLIBHTTPREQUEST *nlhr=(NETLIBHTTPREQUEST*)lParam; + + if (nlhr == NULL || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) || nlhr->requestType != REQUEST_RESPONSE) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if(nlhr->headers) + { + int i; + for(i=0; iheadersCount; i++) + { + mir_free(nlhr->headers[i].szName); + mir_free(nlhr->headers[i].szValue); + } + mir_free(nlhr->headers); + } + mir_free(nlhr->pData); + mir_free(nlhr->szResultDescr); + mir_free(nlhr->szUrl); + mir_free(nlhr); + return 1; +} + +INT_PTR NetlibHttpRecvHeaders(WPARAM wParam,LPARAM lParam) +{ + struct NetlibConnection *nlc = (struct NetlibConnection*)wParam; + NETLIBHTTPREQUEST *nlhr; + char *peol, *pbuffer; + char *buffer = NULL; + DWORD dwRequestTimeoutTime; + int bytesPeeked, firstLineLength = 0; + int headersCount = 0, bufferSize = 8192; + bool headersCompleted = false; + + if(!NetlibEnterNestedCS(nlc,NLNCS_RECV)) + return 0; + + dwRequestTimeoutTime = GetTickCount() + HTTPRECVDATATIMEOUT; + nlhr = (NETLIBHTTPREQUEST*)mir_calloc(sizeof(NETLIBHTTPREQUEST)); + nlhr->cbSize = sizeof(NETLIBHTTPREQUEST); + nlhr->nlc = nlc; // Needed to id connection in the protocol HTTP gateway wrapper functions + nlhr->requestType = REQUEST_RESPONSE; + + if (!HttpPeekFirstResponseLine(nlc, dwRequestTimeoutTime, lParam | MSG_PEEK, + &nlhr->resultCode, &nlhr->szResultDescr, &firstLineLength)) + { + NetlibLeaveNestedCS(&nlc->ncsRecv); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr); + return 0; + } + + buffer = (char*)mir_alloc(bufferSize + 1); + bytesPeeked = NLRecv(nlc, buffer, min(firstLineLength, bufferSize), lParam | MSG_DUMPASTEXT); + if (bytesPeeked != firstLineLength) + { + NetlibLeaveNestedCS(&nlc->ncsRecv); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr); + if (bytesPeeked != SOCKET_ERROR) SetLastError(ERROR_HANDLE_EOF); + mir_free(buffer); + return 0; + } + + // Make sure all headers arrived + bytesPeeked = 0; + while (!headersCompleted) + { + if (bytesPeeked >= bufferSize) + { + bufferSize += 8192; + mir_free(buffer); + if (bufferSize > 32 * 1024) + { + bytesPeeked = 0; + break; + } + buffer = (char*)mir_alloc(bufferSize + 1); + } + + bytesPeeked = RecvWithTimeoutTime(nlc, dwRequestTimeoutTime, buffer, bufferSize, + MSG_PEEK | MSG_NODUMP | lParam); + if (bytesPeeked == 0) break; + + if (bytesPeeked == SOCKET_ERROR) + { + bytesPeeked = 0; + break; + } + buffer[bytesPeeked] = 0; + + for (pbuffer = buffer, headersCount = 0; ; pbuffer = peol + 1, ++headersCount) + { + peol = strchr(pbuffer, '\n'); + if (peol == NULL) break; + if (peol == pbuffer || (peol == (pbuffer + 1) && *pbuffer == '\r')) + { + bytesPeeked = peol - buffer + 1; + headersCompleted = true; + break; + } + } + } + + // Recieve headers + if (bytesPeeked > 0) + bytesPeeked = NLRecv(nlc, buffer, bytesPeeked, lParam | MSG_DUMPASTEXT); + if (bytesPeeked <= 0) + { + NetlibLeaveNestedCS(&nlc->ncsRecv); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr); + mir_free(buffer); + return 0; + } + buffer[bytesPeeked] = 0; + + nlhr->headersCount = headersCount; + nlhr->headers = (NETLIBHTTPHEADER*)mir_calloc(sizeof(NETLIBHTTPHEADER) * headersCount); + + for (pbuffer = buffer, headersCount = 0; ; pbuffer = peol + 1, ++headersCount) + { + peol = strchr(pbuffer, '\n'); + if (peol == NULL || peol == pbuffer || (peol == (pbuffer + 1) && *pbuffer == '\r')) break; + *peol = 0; + + char *pColon = strchr(pbuffer, ':'); + if (pColon == NULL) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr); nlhr = NULL; + SetLastError(ERROR_INVALID_DATA); + break; + } + + *(pColon++) = 0; + nlhr->headers[headersCount].szName = mir_strdup(rtrim(pbuffer)); + nlhr->headers[headersCount].szValue = mir_strdup(lrtrimp(pColon)); + } + + NetlibLeaveNestedCS(&nlc->ncsRecv); + mir_free(buffer); + return (INT_PTR)nlhr; +} + +INT_PTR NetlibHttpTransaction(WPARAM wParam, LPARAM lParam) +{ + NetlibUser *nlu = (NetlibUser*)wParam; + NETLIBHTTPREQUEST *nlhr = (NETLIBHTTPREQUEST*)lParam, *nlhrReply; + DWORD dflags, hflags; + + if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING) || + nlhr == NULL || nlhr->cbSize < NETLIBHTTPREQUEST_V1_SIZE || + nlhr->szUrl == NULL || nlhr->szUrl[0] == 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + + NetlibConnection* nlc = NetlibHttpProcessUrl(nlhr, nlu, (NetlibConnection*)nlhr->nlc); + if (nlc == NULL) return 0; + + { + NETLIBHTTPREQUEST nlhrSend; + char szUserAgent[64]; + + nlhrSend = *nlhr; + nlhrSend.flags &= ~NLHRF_REMOVEHOST; + nlhrSend.flags |= NLHRF_GENERATEHOST | NLHRF_SMARTREMOVEHOST | NLHRF_SMARTAUTHHEADER; + + bool doneUserAgentHeader = NetlibHttpFindHeader(nlhr, "User-Agent") != NULL; + bool doneAcceptEncoding = NetlibHttpFindHeader(nlhr, "Accept-Encoding") != NULL; + + if (!doneUserAgentHeader || !doneAcceptEncoding) + { + nlhrSend.headers = (NETLIBHTTPHEADER*)mir_alloc(sizeof(NETLIBHTTPHEADER) * (nlhrSend.headersCount + 2)); + memcpy(nlhrSend.headers, nlhr->headers, sizeof(NETLIBHTTPHEADER) * nlhr->headersCount); + } + if (!doneUserAgentHeader) + { + char *pspace,szMirandaVer[64]; + + nlhrSend.headers[nlhrSend.headersCount].szName = "User-Agent"; + nlhrSend.headers[nlhrSend.headersCount].szValue = szUserAgent; + ++nlhrSend.headersCount; + CallService(MS_SYSTEM_GETVERSIONTEXT,SIZEOF(szMirandaVer),(LPARAM)szMirandaVer); + pspace=strchr(szMirandaVer,' '); + if(pspace) + { + *pspace++ = '\0'; + mir_snprintf(szUserAgent, SIZEOF(szUserAgent), "Miranda/%s (%s)", szMirandaVer, pspace); + } + else + mir_snprintf(szUserAgent, SIZEOF(szUserAgent), "Miranda/%s", szMirandaVer); + } + if (!doneAcceptEncoding) + { + nlhrSend.headers[nlhrSend.headersCount].szName = "Accept-Encoding"; + nlhrSend.headers[nlhrSend.headersCount].szValue = "deflate, gzip"; + ++nlhrSend.headersCount; + } + if (NetlibHttpSendRequest((WPARAM)nlc, (LPARAM)&nlhrSend) == SOCKET_ERROR) + { + if (!doneUserAgentHeader || !doneAcceptEncoding) mir_free(nlhrSend.headers); + NetlibCloseHandle((WPARAM)nlc, 0); + return 0; + } + if (!doneUserAgentHeader || !doneAcceptEncoding) mir_free(nlhrSend.headers); + } + + dflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT:0) | + (nlhr->flags & NLHRF_NODUMP ? MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + hflags = + (nlhr->flags & NLHRF_NODUMP ? MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + + if (nlhr->requestType == REQUEST_HEAD) + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, 0); + else + nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); + + if (nlhrReply) + { + nlhrReply->szUrl = nlc->szNewUrl; + nlc->szNewUrl = NULL; + } + + if ((nlhr->flags & NLHRF_PERSISTENT) == 0 || nlhrReply == NULL) + { + NetlibCloseHandle((WPARAM)nlc, 0); + if (nlhrReply) nlhrReply->nlc = NULL; + } + else + nlhrReply->nlc = nlc; + + return (INT_PTR)nlhrReply; +} + +void NetlibHttpSetLastErrorUsingHttpResult(int result) +{ + if (result >= 200 && result < 300) + { + SetLastError(ERROR_SUCCESS); + return; + } + switch(result) + { + case 400: SetLastError(ERROR_BAD_FORMAT); break; + case 401: + case 402: + case 403: + case 407: SetLastError(ERROR_ACCESS_DENIED); break; + case 404: SetLastError(ERROR_FILE_NOT_FOUND); break; + case 405: + case 406: SetLastError(ERROR_INVALID_FUNCTION); break; + case 408: SetLastError(ERROR_TIMEOUT); break; + default: SetLastError(ERROR_GEN_FAILURE); break; + } +} + +char* gzip_decode(char *gzip_data, int *len_ptr, int window) +{ + if (*len_ptr == 0) return NULL; + + int gzip_len = *len_ptr * 5; + char* output_data = NULL; + + int gzip_err; + z_stream zstr; + + do + { + output_data = (char*)mir_realloc(output_data, gzip_len+1); + + zstr.next_in = (Bytef*)gzip_data; + zstr.avail_in = *len_ptr; + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = Z_NULL; + inflateInit2_(&zstr, window, ZLIB_VERSION, sizeof(z_stream)); + + zstr.next_out = (Bytef*)output_data; + zstr.avail_out = gzip_len; + + gzip_err = inflate(&zstr, Z_FINISH); + + inflateEnd(&zstr); + gzip_len *= 2; + } + while (gzip_err == Z_BUF_ERROR); + + gzip_len = gzip_err == Z_STREAM_END ? zstr.total_out : -1; + + if (gzip_len <= 0) + { + mir_free(output_data); + output_data = NULL; + } + else + output_data[gzip_len] = 0; + + *len_ptr = gzip_len; + return output_data; +} + +static int NetlibHttpRecvChunkHeader(NetlibConnection* nlc, bool first, DWORD flags) +{ + char data[64], *peol1; + + for (;;) + { + int recvResult = NLRecv(nlc, data, 31, MSG_RAW | MSG_PEEK); + if (recvResult <= 0) return SOCKET_ERROR; + + data[recvResult] = 0; + + peol1 = strchr(data, '\n'); + if (peol1 != NULL) + { + char *peol2 = first ? peol1 : strchr(peol1 + 1, '\n'); + if (peol2 != NULL) + { + int sz = peol2 - data + 1; + int r = strtol(first ? data : peol1 + 1, NULL, 16); + if (r == 0) + { + char *peol3 = strchr(peol2 + 1, '\n'); + if (peol3 == NULL) continue; + sz = peol3 - data + 1; + } + NLRecv(nlc, data, sz, MSG_RAW | flags); + return r; + } + else + if (recvResult >= 31) return SOCKET_ERROR; + } + } +} + +NETLIBHTTPREQUEST* NetlibHttpRecv(NetlibConnection* nlc, DWORD hflags, DWORD dflags, bool isConnect) +{ + int dataLen = -1, i, chunkhdr = 0; + bool chunked = false; + int cenc = 0, cenctype = 0, close = 0; + +next: + NETLIBHTTPREQUEST *nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + if (nlhrReply == NULL) + return NULL; + + if (nlhrReply->resultCode == 100) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + goto next; + } + + for (i=0; iheadersCount; i++) + { + if (!lstrcmpiA(nlhrReply->headers[i].szName, "Content-Length")) + dataLen = atoi(nlhrReply->headers[i].szValue); + + if (!lstrcmpiA(nlhrReply->headers[i].szName, "Content-Encoding")) + { + cenc = i; + if (strstr(nlhrReply->headers[i].szValue, "gzip")) + cenctype = 1; + else if (strstr(nlhrReply->headers[i].szValue, "deflate")) + cenctype = 2; + } + + if (!lstrcmpiA(nlhrReply->headers[i].szName, "Connection")) + close = !lstrcmpiA(nlhrReply->headers[i].szValue, "close"); + + if (!lstrcmpiA(nlhrReply->headers[i].szName, "Transfer-Encoding") && + !lstrcmpiA(nlhrReply->headers[i].szValue, "chunked")) + { + chunked = true; + chunkhdr = i; + dataLen = -1; + } + } + + if (nlhrReply->resultCode >= 200 && (dataLen > 0 || (!isConnect && dataLen < 0))) + { + int recvResult, chunksz = -1; + int dataBufferAlloced; + + if (chunked) + { + chunksz = NetlibHttpRecvChunkHeader(nlc, true, dflags); + if (chunksz == SOCKET_ERROR) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return NULL; + } + dataLen = chunksz; + } + dataBufferAlloced = dataLen < 0 ? 2048 : dataLen + 1; + nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); + + while (chunksz != 0) + { + for(;;) + { + recvResult = RecvWithTimeoutTime(nlc, GetTickCount() + HTTPRECVDATATIMEOUT, + nlhrReply->pData + nlhrReply->dataLength, + dataBufferAlloced - nlhrReply->dataLength - 1, + dflags | (cenctype ? MSG_NODUMP : 0)); + + if (recvResult == 0) break; + if (recvResult == SOCKET_ERROR) + { + NetlibHttpFreeRequestStruct(0,(LPARAM)nlhrReply); + return NULL; + } + nlhrReply->dataLength += recvResult; + + if (dataLen >= 0) + { + if (nlhrReply->dataLength >= dataLen) break; + } + else + { + if ((dataBufferAlloced - nlhrReply->dataLength) < 256) + { + dataBufferAlloced += 2048; + nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); + if(nlhrReply->pData == NULL) + { + SetLastError(ERROR_OUTOFMEMORY); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return NULL; + } + } + } + Sleep(10); + } + + if (chunked) + { + chunksz = NetlibHttpRecvChunkHeader(nlc, false, dflags); + if (chunksz == SOCKET_ERROR) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return NULL; + } + dataLen += chunksz; + dataBufferAlloced += chunksz; + + nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); + } + else + break; + } + + nlhrReply->pData[nlhrReply->dataLength] = '\0'; + } + + if (chunked) + { + nlhrReply->headers[chunkhdr].szName = ( char* )mir_realloc(nlhrReply->headers[chunkhdr].szName, 16); + lstrcpyA(nlhrReply->headers[chunkhdr].szName, "Content-Length"); + + nlhrReply->headers[chunkhdr].szValue = ( char* )mir_realloc(nlhrReply->headers[chunkhdr].szValue, 16); + mir_snprintf(nlhrReply->headers[chunkhdr].szValue, 16, "%u", nlhrReply->dataLength); + } + + if (cenctype) + { + int bufsz = nlhrReply->dataLength; + char* szData = NULL; + + switch (cenctype) + { + case 1: + szData = gzip_decode(nlhrReply->pData, &bufsz, 0x10 | MAX_WBITS); + break; + + case 2: + szData = gzip_decode(nlhrReply->pData, &bufsz, -MAX_WBITS); + if (bufsz < 0) + { + bufsz = nlhrReply->dataLength; + szData = gzip_decode(nlhrReply->pData, &bufsz, MAX_WBITS); + } + break; + } + + if (bufsz > 0) + { + NetlibDumpData(nlc, (PBYTE)szData, bufsz, 0, dflags); + mir_free(nlhrReply->pData); + nlhrReply->pData = szData; + nlhrReply->dataLength = bufsz; + + mir_free(nlhrReply->headers[cenc].szName); + mir_free(nlhrReply->headers[cenc].szValue); + memmove(&nlhrReply->headers[cenc], &nlhrReply->headers[cenc+1], (--nlhrReply->headersCount-cenc)*sizeof(nlhrReply->headers[0])); + } + else if (bufsz == 0) + { + mir_free(nlhrReply->pData); + nlhrReply->pData = NULL; + nlhrReply->dataLength = 0; + } + } + + if (close && + (nlc->proxyType != PROXYTYPE_HTTP || nlc->nloc.flags & NLOCF_SSL) && + (!isConnect || nlhrReply->resultCode != 200)) + NetlibDoClose(nlc); + + return nlhrReply; +} diff --git a/src/modules/netlib/netlibhttpproxy.cpp b/src/modules/netlib/netlibhttpproxy.cpp new file mode 100644 index 0000000000..153d53660f --- /dev/null +++ b/src/modules/netlib/netlibhttpproxy.cpp @@ -0,0 +1,522 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +typedef enum +{ + reqHelloGet, + reqOldGet, + reqOldPost, + reqNewPost, +} +RequestType; + + +static int HttpGatewayReadSetResult(NetlibConnection *nlc, char *buf, int num, int peek) +{ + if (nlc->dataBufferLen == 0) return 0; + + int bytes = min(num, nlc->dataBufferLen); + int rbytes = nlc->dataBufferLen - bytes; + + memcpy(buf, nlc->dataBuffer, bytes); + if (!peek) + { + memmove(nlc->dataBuffer, nlc->dataBuffer + bytes, rbytes); + nlc->dataBufferLen = rbytes; + } + + return bytes; +} + +void HttpGatewayRemovePacket(NetlibConnection *nlc, int pck) +{ + EnterCriticalSection(&nlc->csHttpSequenceNums); + while (pck-- && nlc->pHttpProxyPacketQueue != NULL) + { + NetlibHTTPProxyPacketQueue *p = nlc->pHttpProxyPacketQueue; + nlc->pHttpProxyPacketQueue = nlc->pHttpProxyPacketQueue->next; + + mir_free(p->dataBuffer); + mir_free(p); + } + LeaveCriticalSection(&nlc->csHttpSequenceNums); +} + + +static bool NetlibHttpGatewaySend(struct NetlibConnection *nlc, RequestType reqType, const char *buf, int len) +{ + NETLIBHTTPREQUEST nlhrSend = {0}; + char szUrl[512]; + + nlhrSend.cbSize = sizeof(nlhrSend); + nlhrSend.nlc = nlc; + + nlhrSend.pData = (char*)buf; + nlhrSend.dataLength = len; + + nlhrSend.flags = NLHRF_GENERATEHOST | NLHRF_DUMPPROXY | NLHRF_SMARTAUTHHEADER | NLHRF_NOPROXY | NLHRF_REDIRECT; + if (nlc->nlhpi.flags & NLHPIF_HTTP11) nlhrSend.flags |= NLHRF_HTTP11; + + switch (reqType) + { + case reqHelloGet: + nlhrSend.requestType = REQUEST_GET; + nlhrSend.szUrl=nlc->nlu->user.szHttpGatewayHello; + break; + + case reqOldGet: + nlhrSend.requestType = REQUEST_GET; + nlhrSend.timeout = -1; + if ((nlc->nlhpi.flags & NLHPIF_USEGETSEQUENCE) && (nlc->nlhpi.szHttpGetUrl != NULL)) + { + EnterCriticalSection(&nlc->csHttpSequenceNums); + + mir_snprintf(szUrl, SIZEOF(szUrl), "%s%u", nlc->nlhpi.szHttpGetUrl, nlc->nlhpi.firstGetSequence++); + if (nlc->nlhpi.flags & NLHPIF_GETPOSTSAMESEQUENCE) nlc->nlhpi.firstPostSequence++; + + LeaveCriticalSection(&nlc->csHttpSequenceNums); + nlhrSend.szUrl = szUrl; + } + else + nlhrSend.szUrl = nlc->nlhpi.szHttpGetUrl; + break; + + case reqOldPost: + nlhrSend.requestType = REQUEST_POST; + if ((nlc->nlhpi.flags & NLHPIF_USEPOSTSEQUENCE) && (nlc->nlhpi.szHttpPostUrl != NULL)) + { + mir_snprintf(szUrl, SIZEOF(szUrl), "%s%u", nlc->nlhpi.szHttpPostUrl, nlc->nlhpi.firstPostSequence); + nlhrSend.szUrl = szUrl; + } + else + nlhrSend.szUrl = nlc->nlhpi.szHttpPostUrl; + break; + + case reqNewPost: + nlhrSend.requestType = REQUEST_POST; + nlhrSend.szUrl = nlc->nlhpi.szHttpPostUrl; + break; + } + + if (nlc->usingDirectHttpGateway) + { + NETLIBOPENCONNECTION nloc; + NetlibConnFromUrl(nlhrSend.szUrl, false, nloc); + + bool sameHost = lstrcmpA(nlc->nloc.szHost, nloc.szHost) == 0 && nlc->nloc.wPort == nloc.wPort; + + if (!sameHost) + { + NetlibDoClose(nlc); + + mir_free((char*)nlc->nloc.szHost); + nlc->nloc = nloc; + if (!NetlibDoConnect(nlc)) + return false; + } + else + mir_free((char*)nloc.szHost); + } + + nlhrSend.headersCount = 3; + nlhrSend.headers = (NETLIBHTTPHEADER*)alloca(sizeof(NETLIBHTTPHEADER) * nlhrSend.headersCount); + nlhrSend.headers[0].szName = "User-Agent"; + nlhrSend.headers[0].szValue = nlc->nlu->user.szHttpGatewayUserAgent; + nlhrSend.headers[1].szName = "Cache-Control"; + nlhrSend.headers[1].szValue = "no-cache, no-store "; + nlhrSend.headers[2].szName = "Pragma"; + nlhrSend.headers[2].szValue = "no-cache"; +// nlhrSend.headers[3].szName = "Accept-Encoding"; +// nlhrSend.headers[3].szValue = "deflate, gzip"; + + return NetlibHttpSendRequest((WPARAM)nlc,(LPARAM)&nlhrSend) != SOCKET_ERROR; +} + +static bool NetlibHttpGatewayStdPost(NetlibConnection *nlc, int& numPackets) +{ + int np = 0, len = 0; + + EnterCriticalSection(&nlc->csHttpSequenceNums); + + NetlibHTTPProxyPacketQueue *p = nlc->pHttpProxyPacketQueue; + while (p != NULL && np < nlc->nlhpi.combinePackets) { ++np; len += p->dataBufferLen; p = p->next;} + + char *buf = (char*)alloca(len); + + numPackets = np; + int dlen = 0; + + p = nlc->pHttpProxyPacketQueue; + while (np--) + { + memcpy(buf + dlen, p->dataBuffer, p->dataBufferLen); + dlen += p->dataBufferLen; + p = p->next; + } + + LeaveCriticalSection(&nlc->csHttpSequenceNums); + + return NetlibHttpGatewaySend(nlc, reqNewPost, buf, len); +} + +static bool NetlibHttpGatewayOscarPost(NetlibConnection *nlc, const char *buf, int len, int flags) +{ + NETLIBHTTPREQUEST *nlhrReply = NULL; + NetlibConnection nlcSend = {0}; + + nlcSend.handleType = NLH_CONNECTION; + nlcSend.nlu = nlc->nlu; + nlcSend.nlhpi = nlc->nlhpi; + nlcSend.s = nlc->s2; + nlcSend.usingHttpGateway = nlc->usingHttpGateway; + nlcSend.szProxyServer = nlc->szProxyServer; + nlcSend.wProxyPort = nlc->wProxyPort; + nlcSend.proxyType = nlc->proxyType; + + if (!NetlibReconnect(&nlcSend)) return false; + nlc->s2 = nlcSend.s; + + nlcSend.hOkToCloseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + NetlibInitializeNestedCS(&nlcSend.ncsRecv); + NetlibInitializeNestedCS(&nlcSend.ncsSend); + + bool res = NetlibHttpGatewaySend(&nlcSend, reqOldPost, buf, len); + if (res) + { + NETLIBHTTPREQUEST *nlhrReply = NetlibHttpRecv(&nlcSend, flags | MSG_RAW | MSG_DUMPPROXY, MSG_RAW | MSG_DUMPPROXY); + if (nlhrReply != NULL) + { + if (nlhrReply->resultCode != 200) + { + NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode); + res = false; + } + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + } + else + res = false; + } + + NetlibDeleteNestedCS(&nlcSend.ncsSend); + NetlibDeleteNestedCS(&nlcSend.ncsRecv); + CloseHandle(nlcSend.hOkToCloseEvent); + + nlc->s2 = nlcSend.s; + mir_free((char*)nlcSend.nloc.szHost); + + EnterCriticalSection(&nlc->csHttpSequenceNums); + + nlc->nlhpi.firstPostSequence++; + if (nlc->nlhpi.flags & NLHPIF_GETPOSTSAMESEQUENCE) nlc->nlhpi.firstGetSequence++; + + LeaveCriticalSection(&nlc->csHttpSequenceNums); + + return res; +} + + int NetlibHttpGatewayPost(struct NetlibConnection *nlc, const char *buf, int len, int flags) +{ + struct NetlibHTTPProxyPacketQueue *p; + + if (nlc->nlhpi.szHttpGetUrl != NULL) + { + return NetlibHttpGatewayOscarPost(nlc, buf, len, flags) ? len : SOCKET_ERROR; + } + + /* + * Gena01 - many changes here, do compare against the other version. + * + * Change #1: simplify to use similar code to GET + * Change #2: we need to allow to parse POST reply if szHttpGetUrl is NULL + * Change #3: Keep connection open if we need to. + * + * Impact: NONE! Since currently miranda doesn't allow szHttpGetUrl to be NULL, it will not connect + * with the new plugins that use this code. + */ + + p = ( NetlibHTTPProxyPacketQueue* )mir_alloc(sizeof(struct NetlibHTTPProxyPacketQueue)); + p->dataBuffer = ( PBYTE )mir_alloc(len); + memcpy(p->dataBuffer, buf, len); + p->dataBufferLen = len; + p->next = NULL; + + /* + * Now check to see where to insert this in our queue + */ + EnterCriticalSection(&nlc->csHttpSequenceNums); + if (nlc->pHttpProxyPacketQueue == NULL) + { + nlc->pHttpProxyPacketQueue = p; + } + else + { + struct NetlibHTTPProxyPacketQueue *t = nlc->pHttpProxyPacketQueue; + + while (t->next != NULL) t = t->next; + t->next = p; + } + LeaveCriticalSection(&nlc->csHttpSequenceNums); + + /* + * Gena01 - fake a Send!! tell 'em all is ok. We catch errors in Recv. + */ + return len; +} + +#define NETLIBHTTP_RETRYCOUNT 3 +#define NETLIBHTTP_RETRYTIMEOUT 2000 + +int NetlibHttpGatewayRecv(struct NetlibConnection *nlc, char *buf, int len, int flags) +{ + bool peek = (flags & MSG_PEEK) != 0; + + if (nlc->dataBufferLen != 0 && (!peek || nlc->dataBufferLen >= len)) + { + return HttpGatewayReadSetResult(nlc, buf, len, peek); + } + + for (int retryCount = 0; retryCount < NETLIBHTTP_RETRYCOUNT; ) + { + if (nlc->nlhpi.szHttpGetUrl == NULL && retryCount == 0) + { + if (nlc->pollingTimeout == 0) nlc->pollingTimeout = 30; + + /* We Need to sleep/wait for the data to send before we do receive */ + for (int pollCount = nlc->pollingTimeout; pollCount--; ) + { + if (nlc->pHttpProxyPacketQueue != NULL && GetTickCount() - nlc->lastPost > 1000) + break; + + if (nlc->termRequested || (SleepEx(1000, TRUE) && Miranda_Terminated())) + return SOCKET_ERROR; + } + + nlc->lastPost = GetTickCount(); + if (nlc->pHttpProxyPacketQueue == NULL && nlc->nlu->user.pfnHttpGatewayWrapSend != NULL) + { + if (nlc->nlu->user.pfnHttpGatewayWrapSend(nlc, (PBYTE)"", 0, MSG_NOHTTPGATEWAYWRAP, NetlibSend) == SOCKET_ERROR) + return SOCKET_ERROR; + } + } + + int numPackets = 0; + if (nlc->nlhpi.szHttpGetUrl) + { + if (!NetlibHttpGatewaySend(nlc, reqOldGet, NULL, 0)) + { + if (GetLastError() == ERROR_ACCESS_DENIED || nlc->termRequested) + break; + + ++retryCount; + continue; + } + } + else + { + if (!NetlibHttpGatewayStdPost(nlc, numPackets)) + { + if (GetLastError() == ERROR_ACCESS_DENIED || nlc->termRequested) + break; + + ++retryCount; + continue; + } + } + NETLIBHTTPREQUEST *nlhrReply = NetlibHttpRecv(nlc, flags | MSG_RAW | MSG_DUMPPROXY, MSG_RAW | MSG_DUMPPROXY); + if (nlhrReply == NULL) return SOCKET_ERROR; + + if (nlc->nlu->user.pfnHttpGatewayUnwrapRecv && !(flags & MSG_NOHTTPGATEWAYWRAP)) + { + nlhrReply->pData = (char*)nlc->nlu->user.pfnHttpGatewayUnwrapRecv(nlhrReply, + (PBYTE)nlhrReply->pData, nlhrReply->dataLength, &nlhrReply->dataLength, mir_realloc); +/* + if (newBuffer == NULL) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return SOCKET_ERROR; + } + else + nlhrReply->pData = (char*)newBuffer; +*/ + } + + if (nlhrReply->resultCode >= 300) + { + int resultCode = nlhrReply->resultCode; + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + + if (nlc->nlhpi.szHttpGetUrl && resultCode != 404) + { + NetlibLogf(nlc->nlu, "Error received from proxy, retrying"); + continue; + } + else + { + NetlibLogf(nlc->nlu, "Error received from proxy, retry attempts exceeded (%u)", retryCount); + SetLastError(ERROR_GEN_FAILURE); + return SOCKET_ERROR; + } + } + else + { + retryCount = 0; + HttpGatewayRemovePacket(nlc, numPackets); + } + + if (nlhrReply->dataLength) + { + if (peek) + { + int rbytes = nlc->dataBufferLen + nlhrReply->dataLength; + + nlc->dataBuffer = (PBYTE)mir_realloc(nlc->dataBuffer, rbytes); + memcpy(nlc->dataBuffer + nlc->dataBufferLen, nlhrReply->pData, nlhrReply->dataLength); + nlc->dataBufferLen = rbytes; + + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + + return HttpGatewayReadSetResult(nlc, buf, len, peek); + } + else + { + int bytes = min(len, nlhrReply->dataLength); + int rbytes = nlhrReply->dataLength - bytes; + + memcpy(buf, nlhrReply->pData, bytes); + + nlc->dataBuffer = (PBYTE)mir_realloc(nlc->dataBuffer, rbytes); + if (rbytes) memcpy(nlc->dataBuffer, nlhrReply->pData + bytes, rbytes); + nlc->dataBufferLen = rbytes; + + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return bytes; + } + } + else + { + if ((peek && nlc->dataBufferLen != 0) || nlhrReply->pData) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return HttpGatewayReadSetResult(nlc, buf, len, peek); + } + } + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + } + + SetLastError(ERROR_GEN_FAILURE); + return SOCKET_ERROR; +} + +int NetlibInitHttpConnection(struct NetlibConnection *nlc, struct NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ + NETLIBHTTPREQUEST *nlhrReply = NULL; + + nlc->nlhpi.firstGetSequence = 1; + nlc->nlhpi.firstPostSequence = 1; + + if (nlu->user.szHttpGatewayHello != NULL) + { + nlc->usingHttpGateway = true; + if (NetlibHttpGatewaySend(nlc, reqHelloGet, NULL, 0)) + nlhrReply = NetlibHttpRecv(nlc, MSG_DUMPPROXY | MSG_RAW, MSG_DUMPPROXY | MSG_RAW); + nlc->usingHttpGateway = false; + if (nlhrReply == NULL) return 0; + + if (nlhrReply->resultCode != 200) + { + NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return 0; + } + } + if (!nlu->user.pfnHttpGatewayInit(nlc, nloc, nlhrReply)) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return 0; + } + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + + /* + * Gena01 - Ok, we should be able to use just POST. Needed for Yahoo, NO GET requests + */ + if(nlc->nlhpi.szHttpPostUrl == NULL) + { + SetLastError(ERROR_BAD_FORMAT); + return 0; + } + + nlc->usingHttpGateway = true; + + //now properly connected + if (nlu->user.pfnHttpGatewayBegin && !nlu->user.pfnHttpGatewayBegin(nlc, nloc)) + return 0; + + return 1; +} + +INT_PTR NetlibHttpGatewaySetInfo(WPARAM wParam,LPARAM lParam) +{ + NETLIBHTTPPROXYINFO *nlhpi=(NETLIBHTTPPROXYINFO*)lParam; + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + + if (GetNetlibHandleType(nlc) != NLH_CONNECTION || nlhpi == NULL || + nlhpi->cbSize < (sizeof(NETLIBHTTPPROXYINFO) - sizeof(int)) || + nlhpi->szHttpPostUrl == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + + mir_free(nlc->nlhpi.szHttpGetUrl); + mir_free(nlc->nlhpi.szHttpPostUrl); + + nlc->nlhpi.combinePackets = 1; + memcpy(&nlc->nlhpi, nlhpi, min(nlhpi->cbSize, sizeof(*nlhpi))); + if (nlc->nlhpi.combinePackets == 0) nlc->nlhpi.combinePackets = 1; + + nlc->nlhpi.szHttpGetUrl = mir_strdup(nlc->nlhpi.szHttpGetUrl); + nlc->nlhpi.szHttpPostUrl = mir_strdup(nlc->nlhpi.szHttpPostUrl); + + return 1; +} + +INT_PTR NetlibHttpSetSticky(WPARAM wParam, LPARAM lParam) +{ + struct NetlibUser * nu = (struct NetlibUser*)wParam; + if (GetNetlibHandleType(nu)!=NLH_USER) return ERROR_INVALID_PARAMETER; + mir_free(nu->szStickyHeaders); + nu->szStickyHeaders = mir_strdup((char*)lParam); // pointer is ours + return 0; +} + +INT_PTR NetlibHttpSetPollingTimeout(WPARAM wParam, LPARAM lParam) +{ + int oldTimeout; + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + if (GetNetlibHandleType(nlc)!=NLH_CONNECTION) return -1; + oldTimeout = nlc->pollingTimeout; + nlc->pollingTimeout = lParam; + return oldTimeout; +} diff --git a/src/modules/netlib/netliblog.cpp b/src/modules/netlib/netliblog.cpp new file mode 100644 index 0000000000..fb3fe1d5f2 --- /dev/null +++ b/src/modules/netlib/netliblog.cpp @@ -0,0 +1,637 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" +#include "../srfile/file.h" + +#define MS_NETLIB_LOGWIN "Netlib/Log/Win" + +extern HANDLE hConnectionHeaderMutex; + +#define TIMEFORMAT_NONE 0 +#define TIMEFORMAT_HHMMSS 1 +#define TIMEFORMAT_MILLISECONDS 2 +#define TIMEFORMAT_MICROSECONDS 3 +struct { + HWND hwndOpts; + int toOutputDebugString; + int toFile; + int toLog; + TCHAR* szFile; + TCHAR* szUserFile; + int timeFormat; + int showUser; + int dumpSent,dumpRecv,dumpProxy,dumpSsl; + int textDumps,autoDetectText; + CRITICAL_SECTION cs; + int save; +} logOptions = {0}; + +typedef struct { + const char* pszHead; + const char* pszMsg; +} LOGMSG; + +static __int64 mirandaStartTime,perfCounterFreq; +static int bIsActive = TRUE; +static HANDLE hLogEvent = NULL; + +static const TCHAR* szTimeFormats[] = +{ + _T( "No times" ), + _T( "Standard hh:mm:ss times" ), + _T( "Times in milliseconds" ), + _T( "Times in microseconds" ) +}; + +static INT_PTR CALLBACK LogOptionsDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + switch(message) { + case WM_INITDIALOG: + logOptions.hwndOpts=hwndDlg; + TranslateDialogDefault(hwndDlg); + CheckDlgButton(hwndDlg,IDC_DUMPRECV,logOptions.dumpRecv?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_DUMPSENT,logOptions.dumpSent?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_DUMPPROXY,logOptions.dumpProxy?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_DUMPSSL,logOptions.dumpSsl?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_TEXTDUMPS,logOptions.textDumps?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_AUTODETECTTEXT,logOptions.autoDetectText?BST_CHECKED:BST_UNCHECKED); + { int i; + for( i=0; i < SIZEOF(szTimeFormats); i++ ) + SendDlgItemMessage(hwndDlg,IDC_TIMEFORMAT,CB_ADDSTRING,0,(LPARAM)TranslateTS( szTimeFormats[i] )); + } + SendDlgItemMessage(hwndDlg,IDC_TIMEFORMAT,CB_SETCURSEL,logOptions.timeFormat,0); + CheckDlgButton(hwndDlg,IDC_SHOWNAMES,logOptions.showUser?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_TOOUTPUTDEBUGSTRING,logOptions.toOutputDebugString?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_TOFILE,logOptions.toFile?BST_CHECKED:BST_UNCHECKED); + SetDlgItemText(hwndDlg,IDC_FILENAME,logOptions.szUserFile); + SetDlgItemText(hwndDlg,IDC_PATH,logOptions.szFile); + CheckDlgButton(hwndDlg,IDC_SHOWTHISDLGATSTART,DBGetContactSettingByte(NULL, "Netlib", "ShowLogOptsAtStart",0)?BST_CHECKED:BST_UNCHECKED); + { DBVARIANT dbv; + if(!DBGetContactSettingString(NULL, "Netlib", "RunAtStart",&dbv)) { + SetDlgItemTextA(hwndDlg,IDC_RUNATSTART,dbv.pszVal); + DBFreeVariant(&dbv); + } + } + logOptions.save = 0; + { + TVINSERTSTRUCT tvis = {0}; + int i; + HWND hwndFilter = GetDlgItem(hwndDlg,IDC_FILTER); + + SetWindowLongPtr(hwndFilter, GWL_STYLE, GetWindowLongPtr(hwndFilter, GWL_STYLE) | (TVS_NOHSCROLL | TVS_CHECKBOXES)); + + tvis.hParent=NULL; + tvis.hInsertAfter=TVI_SORT; + tvis.item.mask=TVIF_PARAM|TVIF_TEXT|TVIF_STATE; + tvis.item.stateMask=TVIS_STATEIMAGEMASK; + + for (i = 0; i < netlibUser.getCount(); ++i) + { + tvis.item.pszText=netlibUser[i]->user.ptszDescriptiveName; + tvis.item.lParam=i; + tvis.item.state=INDEXTOSTATEIMAGEMASK( (netlibUser[i]->toLog) ? 2 : 1 ); + TreeView_InsertItem(hwndFilter, &tvis); + } + tvis.item.lParam=-1; + tvis.item.pszText=TranslateT("(Miranda Core Logging)"); + tvis.item.state=INDEXTOSTATEIMAGEMASK( (logOptions.toLog) ? 2 : 1 ); + TreeView_InsertItem(hwndFilter, &tvis); + } + return TRUE; + case WM_COMMAND: + switch(LOWORD(wParam)) { +/* + case IDC_DUMPRECV: + case IDC_DUMPSENT: + case IDC_DUMPPROXY: + case IDC_TEXTDUMPS: + case IDC_AUTODETECTTEXT: + case IDC_TIMEFORMAT: + case IDC_SHOWNAMES: + case IDC_TOOUTPUTDEBUGSTRING: + case IDC_TOFILE: + case IDC_SHOWTHISDLGATSTART: + case IDC_RUNATSTART: + break; +*/ + case IDC_FILENAME: + if(HIWORD(wParam)!=EN_CHANGE) break; + if((HWND)lParam==GetFocus()) + CheckDlgButton(hwndDlg,IDC_TOFILE,BST_CHECKED); + + { + TCHAR path[MAX_PATH]; + GetWindowText((HWND)lParam, path, MAX_PATH); + + TCHAR *pszNewPath = Utils_ReplaceVarsT(path); + pathToAbsoluteT(pszNewPath, path, NULL); + SetDlgItemText(hwndDlg, IDC_PATH, path); + mir_free(pszNewPath); + } + break; + case IDC_FILENAMEBROWSE: + case IDC_RUNATSTARTBROWSE: + { TCHAR str[MAX_PATH+2]; + OPENFILENAME ofn={0}; + TCHAR filter[512],*pfilter; + + GetWindowText(GetWindow((HWND)lParam,GW_HWNDPREV),str,SIZEOF(str)); + ofn.lStructSize=OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner=hwndDlg; + ofn.Flags=OFN_HIDEREADONLY | OFN_DONTADDTORECENT; + if (LOWORD(wParam)==IDC_FILENAMEBROWSE) { + ofn.lpstrTitle=TranslateT("Select where log file will be created"); + } else { + ofn.Flags|=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST; + ofn.lpstrTitle=TranslateT("Select program to be run"); + } + _tcscpy(filter,TranslateT("All Files")); + _tcscat(filter,_T(" (*)")); + pfilter=filter+lstrlen(filter)+1; + _tcscpy(pfilter,_T("*")); + pfilter=pfilter+lstrlen(pfilter)+1; + *pfilter='\0'; + ofn.lpstrFilter=filter; + ofn.lpstrFile=str; + ofn.nMaxFile=SIZEOF(str)-2; + ofn.nMaxFileTitle=MAX_PATH; + if (LOWORD(wParam)==IDC_FILENAMEBROWSE) { + if(!GetSaveFileName(&ofn)) return 1; + } else { + if(!GetOpenFileName(&ofn)) return 1; + } + if(LOWORD(wParam)==IDC_RUNATSTARTBROWSE && _tcschr(str,' ')!=NULL) { + MoveMemory(str+1,str,SIZEOF(str)-2); + str[0]='"'; + lstrcat(str,_T("\"")); + } + SetWindowText(GetWindow((HWND)lParam,GW_HWNDPREV),str); + break; + } + case IDC_RUNNOW: + { TCHAR str[MAX_PATH+1]; + STARTUPINFO si={0}; + PROCESS_INFORMATION pi; + GetDlgItemText(hwndDlg,IDC_RUNATSTART,str,MAX_PATH); + si.cb=sizeof(si); + if(str[0]) CreateProcess(NULL,str,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi); + } + break; + case IDC_SAVE: + logOptions.save = 1; + // + case IDOK: + { + TCHAR str[MAX_PATH]; + + GetDlgItemText(hwndDlg, IDC_RUNATSTART, str, MAX_PATH); + DBWriteContactSettingTString(NULL, "Netlib", "RunAtStart",str); + DBWriteContactSettingByte(NULL, "Netlib", "ShowLogOptsAtStart",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_SHOWTHISDLGATSTART)); + + EnterCriticalSection(&logOptions.cs); + + mir_free(logOptions.szUserFile); + GetWindowText(GetDlgItem(hwndDlg,IDC_FILENAME), str, MAX_PATH ); + logOptions.szUserFile = mir_tstrdup(str); + + mir_free(logOptions.szFile); + GetWindowText(GetDlgItem(hwndDlg,IDC_PATH), str, MAX_PATH ); + logOptions.szFile = mir_tstrdup(str); + + logOptions.dumpRecv=IsDlgButtonChecked(hwndDlg,IDC_DUMPRECV); + logOptions.dumpSent=IsDlgButtonChecked(hwndDlg,IDC_DUMPSENT); + logOptions.dumpProxy=IsDlgButtonChecked(hwndDlg,IDC_DUMPPROXY); + logOptions.dumpSsl=IsDlgButtonChecked(hwndDlg,IDC_DUMPSSL); + logOptions.textDumps=IsDlgButtonChecked(hwndDlg,IDC_TEXTDUMPS); + logOptions.autoDetectText=IsDlgButtonChecked(hwndDlg,IDC_AUTODETECTTEXT); + logOptions.timeFormat=SendDlgItemMessage(hwndDlg,IDC_TIMEFORMAT,CB_GETCURSEL,0,0); + logOptions.showUser=IsDlgButtonChecked(hwndDlg,IDC_SHOWNAMES); + logOptions.toOutputDebugString=IsDlgButtonChecked(hwndDlg,IDC_TOOUTPUTDEBUGSTRING); + logOptions.toFile=IsDlgButtonChecked(hwndDlg,IDC_TOFILE); + + LeaveCriticalSection(&logOptions.cs); + } + { + HWND hwndFilter = GetDlgItem(logOptions.hwndOpts, IDC_FILTER); + TVITEM tvi={0}; + BOOL checked; + + tvi.mask=TVIF_HANDLE|TVIF_PARAM|TVIF_STATE|TVIF_TEXT; + tvi.hItem=TreeView_GetRoot(hwndFilter); + + while(tvi.hItem) + { + TreeView_GetItem(hwndFilter,&tvi); + checked = ((tvi.state&TVIS_STATEIMAGEMASK)>>12==2); + + if (tvi.lParam == -1) { + logOptions.toLog = checked; + if ( logOptions.save ) + DBWriteContactSettingDword(NULL, "Netlib", "NLlog",checked); + } + else + if (tvi.lParam < netlibUser.getCount()) { + netlibUser[tvi.lParam]->toLog = checked; + if ( logOptions.save ) + DBWriteContactSettingDword(NULL,netlibUser[tvi.lParam]->user.szSettingsModule,"NLlog",checked); + } + + tvi.hItem=TreeView_GetNextSibling(hwndFilter,tvi.hItem); + } + } + + if ( logOptions.save ) { + DBWriteContactSettingByte(NULL, "Netlib", "DumpRecv",(BYTE)logOptions.dumpRecv); + DBWriteContactSettingByte(NULL, "Netlib", "DumpSent",(BYTE)logOptions.dumpSent); + DBWriteContactSettingByte(NULL, "Netlib", "DumpProxy",(BYTE)logOptions.dumpProxy); + DBWriteContactSettingByte(NULL, "Netlib", "DumpSsl",(BYTE)logOptions.dumpSsl); + DBWriteContactSettingByte(NULL, "Netlib", "TextDumps",(BYTE)logOptions.textDumps); + DBWriteContactSettingByte(NULL, "Netlib", "AutoDetectText",(BYTE)logOptions.autoDetectText); + DBWriteContactSettingByte(NULL, "Netlib", "TimeFormat",(BYTE)logOptions.timeFormat); + DBWriteContactSettingByte(NULL, "Netlib", "ShowUser",(BYTE)logOptions.showUser); + DBWriteContactSettingByte(NULL, "Netlib", "ToOutputDebugString",(BYTE)logOptions.toOutputDebugString); + DBWriteContactSettingByte(NULL, "Netlib", "ToFile",(BYTE)logOptions.toFile); + DBWriteContactSettingTString(NULL, "Netlib", "File", logOptions.szFile ? logOptions.szUserFile: _T("")); + logOptions.save = 0; + } + else + DestroyWindow(hwndDlg); + + break; + case IDCANCEL: + DestroyWindow(hwndDlg); + break; + } + break; + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + case WM_DESTROY: + ImageList_Destroy(TreeView_GetImageList(GetDlgItem(hwndDlg, IDC_FILTER), TVSIL_STATE)); + logOptions.hwndOpts=NULL; + break; + } + return FALSE; +} + +void NetlibLogShowOptions(void) +{ + if(logOptions.hwndOpts==NULL) + logOptions.hwndOpts=CreateDialog(hMirandaInst,MAKEINTRESOURCE(IDD_NETLIBLOGOPTS),NULL,LogOptionsDlgProc); + SetForegroundWindow(logOptions.hwndOpts); +} + +static INT_PTR ShowOptions(WPARAM, LPARAM) +{ + NetlibLogShowOptions(); + return 0; +} + +static INT_PTR NetlibLog(WPARAM wParam, LPARAM lParam) +{ + struct NetlibUser *nlu = (struct NetlibUser*)wParam; + struct NetlibUser nludummy; + const char *pszMsg = (const char*)lParam; + char szTime[32], szHead[128]; + LARGE_INTEGER liTimeNow; + DWORD dwOriginalLastError; + + if (!bIsActive) + return 0; + + if ((nlu != NULL && GetNetlibHandleType(nlu) != NLH_USER) || pszMsg == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if (nlu == NULL) /* if the Netlib user handle is NULL, just pretend its not */ + { + if (!logOptions.toLog) + return 1; + nlu = &nludummy; + nlu->user.szSettingsModule = "(NULL)"; + } + else if (!nlu->toLog) + return 1; + + dwOriginalLastError = GetLastError(); + switch (logOptions.timeFormat) + { + case TIMEFORMAT_HHMMSS: + GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, + NULL, NULL, szTime, SIZEOF(szTime)); + break; + + case TIMEFORMAT_MILLISECONDS: + QueryPerformanceCounter(&liTimeNow); + liTimeNow.QuadPart -= mirandaStartTime; + mir_snprintf(szTime, SIZEOF(szTime), "%I64u.%03I64u", liTimeNow.QuadPart / perfCounterFreq, + 1000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq); + break; + + case TIMEFORMAT_MICROSECONDS: + QueryPerformanceCounter(&liTimeNow); + liTimeNow.QuadPart -= mirandaStartTime; + mir_snprintf(szTime, SIZEOF(szTime), "%I64u.%06I64u", liTimeNow.QuadPart / perfCounterFreq, + 1000000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq); + break; + + default: + szTime[0] = '\0'; + break; + } + if(logOptions.timeFormat || logOptions.showUser) + mir_snprintf(szHead, SIZEOF(szHead) - 1, "[%s%s%s] ", szTime, + (logOptions.showUser && logOptions.timeFormat) ? " " : "", + logOptions.showUser ? nlu->user.szSettingsModule : ""); + else + szHead[0]=0; + + if(logOptions.toOutputDebugString) + { + if (szHead[0]) + OutputDebugStringA(szHead); + OutputDebugStringA(pszMsg); + OutputDebugStringA("\n"); + } + + if (logOptions.toFile && logOptions.szFile[0]) + { + EnterCriticalSection(&logOptions.cs); + + FILE *fp; + fp = _tfopen(logOptions.szFile, _T("ab")); + if (!fp) + { + CreatePathToFileT(logOptions.szFile); + fp = _tfopen(logOptions.szFile, _T("at")); + } + if (fp) + { + size_t len = strlen(pszMsg); + fprintf(fp,"%s%s%s", szHead, pszMsg, pszMsg[len-1] == '\n' ? "" : "\r\n"); + fclose(fp); + } + LeaveCriticalSection(&logOptions.cs); + } + + if (((THook*)hLogEvent)->subscriberCount) + { + LOGMSG logMsg = { szHead, pszMsg }; + CallHookSubscribers(hLogEvent, (WPARAM)nlu, (LPARAM)&logMsg); + } + + SetLastError(dwOriginalLastError); + return 1; +} + +static INT_PTR NetlibLogW(WPARAM wParam, LPARAM lParam) +{ + const wchar_t *pszMsg = (const wchar_t*)lParam; + char* szMsg = Utf8EncodeUcs2(pszMsg); + INT_PTR res = NetlibLog(wParam, (LPARAM)szMsg); + mir_free(szMsg); + return res; +} + +void NetlibLogf(NetlibUser* nlu, const char *fmt, ...) +{ + if (nlu == NULL) + { + if (!logOptions.toLog) + return; + } + else if (!nlu->toLog) + return; + + va_list va; + char szText[1024]; + + va_start(va,fmt); + mir_vsnprintf(szText, sizeof(szText), fmt, va); + va_end(va); + + NetlibLog((WPARAM)nlu, (LPARAM)szText); +} + + +void NetlibDumpData(struct NetlibConnection *nlc,PBYTE buf,int len,int sent,int flags) +{ + int isText=1; + char szTitleLine[128]; + char *szBuf; + int titleLineLen; + struct NetlibUser *nlu; + bool useStack = false; + + // This section checks a number of conditions and aborts + // the dump if the data should not be written to the log + + // Check packet flags + if (flags & (MSG_PEEK | MSG_NODUMP)) + return; + + // Check user's log settings + if (!(logOptions.toOutputDebugString || + ((THook*)hLogEvent)->subscriberCount || + (logOptions.toFile && logOptions.szFile[0]))) + return; + if ((sent && !logOptions.dumpSent) || + (!sent && !logOptions.dumpRecv)) + return; + if ((flags & MSG_DUMPPROXY) && !logOptions.dumpProxy) + return; + if ((flags & MSG_DUMPSSL) && !logOptions.dumpSsl) + return; + + WaitForSingleObject(hConnectionHeaderMutex, INFINITE); + nlu = nlc ? nlc->nlu : NULL; + titleLineLen = mir_snprintf(szTitleLine, SIZEOF(szTitleLine), "(%p:%u) Data %s%s\r\n", + nlc, nlc ? nlc->s : 0, sent ? "sent" : "received", flags & MSG_DUMPPROXY ? " (proxy)" : ""); + ReleaseMutex(hConnectionHeaderMutex); + + // check filter settings + if (nlu == NULL) + { + if (!logOptions.toLog) + return; + } + else if (!nlu->toLog) + return; + + if (!logOptions.textDumps) + isText = 0; + else if (!(flags&MSG_DUMPASTEXT)) + { + if (logOptions.autoDetectText) + { + int i; + for(i = 0; i=0x80) + { + isText = 0; + break; + } + } + } + else + isText = 0; + } + + // Text data + if ( isText ) { + int sz = titleLineLen + len + 1; + useStack = sz <= 8192; + szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz)); + CopyMemory( szBuf, szTitleLine, titleLineLen ); + CopyMemory( szBuf + titleLineLen, (const char*)buf, len ); + szBuf[titleLineLen + len] = '\0'; + } + // Binary data + else { + int line, col, colsInLine; + char *pszBuf; + int sz = titleLineLen + ((len+16)>>4) * 78 + 1; + useStack = sz <= 8192; + + szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz)); + CopyMemory(szBuf, szTitleLine, titleLineLen); + pszBuf = szBuf + titleLineLen; + for ( line = 0; ; line += 16 ) { + colsInLine = min(16, len - line); + + if (colsInLine == 16) { + PBYTE p = buf + line; + pszBuf += wsprintfA( + pszBuf, "%08X: %02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X ", + line, p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14],p[15] ); + } + else { + pszBuf += wsprintfA(pszBuf, "%08X: ", line); + // Dump data as hex + for (col = 0; col < colsInLine; col++) + pszBuf += wsprintfA(pszBuf, "%02X%c", buf[line + col], ((col&3)==3 && col != 15)?'-':' '); + // Fill out last line with blanks + for ( ; col<16; col++) + { + lstrcpyA(pszBuf, " "); + pszBuf += 3; + } + *pszBuf++ = ' '; + } + + for (col = 0; col < colsInLine; col++) + *pszBuf++ = buf[line+col]<' '?'.':(char)buf[line+col]; + + if (len-line<=16) + break; + + *pszBuf++ = '\r'; // End each line with a break + *pszBuf++ = '\n'; // End each line with a break + } + *pszBuf = '\0'; + } + + NetlibLog((WPARAM)nlu,(LPARAM)szBuf); + if (!useStack) mir_free(szBuf); +} + +void NetlibLogInit(void) +{ + DBVARIANT dbv; + LARGE_INTEGER li; + + QueryPerformanceFrequency( &li ); + perfCounterFreq = li.QuadPart; + QueryPerformanceCounter( &li ); + mirandaStartTime = li.QuadPart; + + CreateServiceFunction( MS_NETLIB_LOGWIN, ShowOptions ); + CreateServiceFunction( MS_NETLIB_LOG, NetlibLog ); + CreateServiceFunction( MS_NETLIB_LOGW, NetlibLogW ); + hLogEvent = CreateHookableEvent( ME_NETLIB_FASTDUMP ); + + InitializeCriticalSection(&logOptions.cs); + logOptions.dumpRecv = DBGetContactSettingByte( NULL, "Netlib", "DumpRecv", 1 ); + logOptions.dumpSent = DBGetContactSettingByte( NULL, "Netlib", "DumpSent", 1 ); + logOptions.dumpProxy = DBGetContactSettingByte( NULL, "Netlib", "DumpProxy", 1 ); + logOptions.dumpSsl = DBGetContactSettingByte( NULL, "Netlib", "DumpSsl", 0 ); + logOptions.textDumps = DBGetContactSettingByte( NULL, "Netlib", "TextDumps", 1 ); + logOptions.autoDetectText = DBGetContactSettingByte( NULL, "Netlib", "AutoDetectText", 1 ); + logOptions.timeFormat = DBGetContactSettingByte( NULL, "Netlib", "TimeFormat", TIMEFORMAT_HHMMSS ); + logOptions.showUser = DBGetContactSettingByte( NULL, "Netlib", "ShowUser", 1 ); + logOptions.toOutputDebugString = DBGetContactSettingByte( NULL, "Netlib", "ToOutputDebugString", 0 ); + logOptions.toFile = DBGetContactSettingByte( NULL, "Netlib", "ToFile", 0 ); + logOptions.toLog = DBGetContactSettingDword( NULL, "Netlib", "NLlog", 1 ); + + if (!DBGetContactSettingTString(NULL, "Netlib", "File", &dbv)) + { + logOptions.szUserFile = mir_tstrdup(dbv.ptszVal); + TCHAR *pszNewPath = Utils_ReplaceVarsT(dbv.ptszVal); + + TCHAR path[MAX_PATH]; + pathToAbsoluteT(pszNewPath, path, NULL); + logOptions.szFile = mir_tstrdup(path); + + mir_free(pszNewPath); + DBFreeVariant(&dbv); + } + else + { + logOptions.szUserFile = mir_tstrdup(_T("%miranda_logpath%\\netlog.txt")); + logOptions.szFile = Utils_ReplaceVarsT(logOptions.szUserFile); + } + + if ( logOptions.toFile && logOptions.szFile[0] ) { + FILE *fp; + fp = _tfopen( logOptions.szFile, _T("wt")); + if ( fp ) + fclose(fp); + } + + if ( DBGetContactSettingByte( NULL, "Netlib", "ShowLogOptsAtStart", 0 )) + NetlibLogShowOptions(); + + if ( !DBGetContactSettingTString( NULL, "Netlib", "RunAtStart", &dbv )) { + STARTUPINFO si = { 0 }; + PROCESS_INFORMATION pi; + si.cb = sizeof( si ); + if ( dbv.ptszVal[0] ) + CreateProcess( NULL, dbv.ptszVal, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ); + DBFreeVariant( &dbv ); + } +} + +void NetlibLogShutdown(void) +{ + bIsActive = FALSE; + DestroyHookableEvent( hLogEvent ); hLogEvent = NULL; + if ( IsWindow( logOptions.hwndOpts )) + DestroyWindow( logOptions.hwndOpts ); + DeleteCriticalSection( &logOptions.cs ); + mir_free( logOptions.szFile ); + mir_free( logOptions.szUserFile ); +} diff --git a/src/modules/netlib/netlibopenconn.cpp b/src/modules/netlib/netlibopenconn.cpp new file mode 100644 index 0000000000..b9d4753b9a --- /dev/null +++ b/src/modules/netlib/netlibopenconn.cpp @@ -0,0 +1,944 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +extern CRITICAL_SECTION csNetlibUser; +extern HANDLE hConnectionOpenMutex; +extern DWORD g_LastConnectionTick; +extern int connectionTimeout; +static int iUPnPCleanup = 0; + +#define RECV_DEFAULT_TIMEOUT 60000 + +//returns in network byte order +DWORD DnsLookup(struct NetlibUser *nlu,const char *szHost) +{ + HOSTENT* host; + DWORD ip = inet_addr(szHost); + if (ip != INADDR_NONE) + return ip; + + __try + { + host = gethostbyname(szHost); + if ( host ) + return *(u_long*)host->h_addr_list[0]; + + NetlibLogf(nlu,"%s %d: %s() for host %s failed (%u)",__FILE__,__LINE__,"gethostbyname", szHost, WSAGetLastError()); + } + __except(EXCEPTION_EXECUTE_HANDLER) {} + + return 0; +} + +int WaitUntilReadable(SOCKET s, DWORD dwTimeout, bool check) +{ + fd_set readfd; + TIMEVAL tv; + + if (s == INVALID_SOCKET) return SOCKET_ERROR; + + tv.tv_sec = dwTimeout / 1000; + tv.tv_usec = (dwTimeout % 1000) * 1000; + + FD_ZERO(&readfd); + FD_SET(s, &readfd); + + int result = select(0, &readfd, 0, 0, &tv); + if (result == 0 && !check) SetLastError(ERROR_TIMEOUT); + return result; +} + +int WaitUntilWritable(SOCKET s,DWORD dwTimeout) +{ + fd_set writefd; + TIMEVAL tv; + + tv.tv_sec = dwTimeout / 1000; + tv.tv_usec = (dwTimeout % 1000) * 1000; + + FD_ZERO(&writefd); + FD_SET(s, &writefd); + + switch(select(0, 0, &writefd, 0, &tv)) + { + case 0: + SetLastError(ERROR_TIMEOUT); + case SOCKET_ERROR: + return 0; + } + return 1; +} + +bool RecvUntilTimeout(struct NetlibConnection *nlc, char *buf, int len, int flags, DWORD dwTimeout) +{ + int nReceived = 0; + DWORD dwTimeNow, dwCompleteTime = GetTickCount() + dwTimeout; + + while ((dwTimeNow = GetTickCount()) < dwCompleteTime) + { + if (WaitUntilReadable(nlc->s, dwCompleteTime - dwTimeNow) <= 0) return false; + nReceived = NLRecv(nlc, buf, len, flags); + if (nReceived <= 0) return false; + + buf += nReceived; + len -= nReceived; + if (len <= 0) return true; + } + SetLastError( ERROR_TIMEOUT ); + return false; +} + +static int NetlibInitSocks4Connection(NetlibConnection *nlc, NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ + // http://www.socks.nec.com/protocol/socks4.protocol and http://www.socks.nec.com/protocol/socks4a.protocol + if (!nloc->szHost || !nloc->szHost[0]) return 0; + + size_t nHostLen = strlen(nloc->szHost) + 1; + size_t nUserLen = nlu->settings.szProxyAuthUser ? strlen(nlu->settings.szProxyAuthUser) + 1 : 1; + size_t len = 8 + nUserLen; + + char* pInit = (char*)alloca(len + nHostLen); + pInit[0] = 4; // SOCKS4 + pInit[1] = 1; //connect + *(PWORD)&pInit[2] = htons(nloc->wPort); + + if (nUserLen <= 1) pInit[8] = 0; + else memcpy(&pInit[8], nlu->settings.szProxyAuthUser, nUserLen); + + //if cannot resolve host, try resolving through proxy (requires SOCKS4a) + DWORD ip = DnsLookup(nlu, nloc->szHost); + *(PDWORD)&pInit[4] = ip ? ip : 0x01000000; + if (!ip) + { + memcpy(&pInit[len], nloc->szHost, nHostLen); + len += nHostLen; + } + + if (NLSend(nlc, pInit, (int)len, MSG_DUMPPROXY) == SOCKET_ERROR) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLSend",GetLastError()); + return 0; + } + + char reply[8]; + if (!RecvUntilTimeout(nlc, reply, sizeof(reply), MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + + switch ((BYTE)reply[1]) + { + case 90: return 1; + case 91: SetLastError(ERROR_ACCESS_DENIED); break; + case 92: SetLastError(ERROR_CONNECTION_UNAVAIL); break; + case 93: SetLastError(ERROR_INVALID_ACCESS); break; + default: SetLastError(ERROR_INVALID_DATA); break; + } + NetlibLogf(nlu,"%s %d: Proxy connection failed (%x %u)",__FILE__,__LINE__, (BYTE)reply[1], GetLastError()); + return 0; +} + +static int NetlibInitSocks5Connection(struct NetlibConnection *nlc,struct NetlibUser *nlu,NETLIBOPENCONNECTION *nloc) +{ //rfc1928 + BYTE buf[258]; + + buf[0]=5; //yep, socks5 + buf[1]=1; //one auth method + buf[2]=nlu->settings.useProxyAuth?2:0; + if(NLSend(nlc,(char*)buf,3,MSG_DUMPPROXY)==SOCKET_ERROR) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLSend",GetLastError()); + return 0; + } + + //confirmation of auth method + if (!RecvUntilTimeout(nlc,(char*)buf,2,MSG_DUMPPROXY,RECV_DEFAULT_TIMEOUT)) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + if((buf[1]!=0 && buf[1]!=2)) { + SetLastError(ERROR_INVALID_ID_AUTHORITY); + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLRecv",GetLastError()); + return 0; + } + + if(buf[1]==2) { //rfc1929 + int nUserLen,nPassLen; + PBYTE pAuthBuf; + + nUserLen=lstrlenA(nlu->settings.szProxyAuthUser); + nPassLen=lstrlenA(nlu->settings.szProxyAuthPassword); + pAuthBuf=(PBYTE)mir_alloc(3+nUserLen+nPassLen); + pAuthBuf[0]=1; //auth version + pAuthBuf[1]=nUserLen; + memcpy(pAuthBuf+2,nlu->settings.szProxyAuthUser,nUserLen); + pAuthBuf[2+nUserLen]=nPassLen; + memcpy(pAuthBuf+3+nUserLen,nlu->settings.szProxyAuthPassword,nPassLen); + if(NLSend(nlc,(char*)pAuthBuf,3+nUserLen+nPassLen,MSG_DUMPPROXY)==SOCKET_ERROR) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLSend",GetLastError()); + mir_free(pAuthBuf); + return 0; + } + mir_free(pAuthBuf); + + if (!RecvUntilTimeout(nlc,(char*)buf,2,MSG_DUMPPROXY,RECV_DEFAULT_TIMEOUT)) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + if(buf[1]) { + SetLastError(ERROR_ACCESS_DENIED); + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + } + + { + PBYTE pInit; + int nHostLen; + DWORD hostIP; + + if(nlc->dnsThroughProxy) { + if((hostIP=inet_addr(nloc->szHost))==INADDR_NONE) + nHostLen=lstrlenA(nloc->szHost)+1; + else nHostLen=4; + } + else { + if((hostIP=DnsLookup(nlu,nloc->szHost))==0) + return 0; + nHostLen=4; + } + pInit=(PBYTE)mir_alloc(6+nHostLen); + pInit[0]=5; //SOCKS5 + pInit[1]= nloc->flags & NLOCF_UDP ? 3 : 1; //connect or UDP + pInit[2]=0; //reserved + if(hostIP==INADDR_NONE) { //DNS lookup through proxy + pInit[3]=3; + pInit[4]=nHostLen-1; + memcpy(pInit+5,nloc->szHost,nHostLen-1); + } + else { + pInit[3]=1; + *(PDWORD)(pInit+4)=hostIP; + } + *(PWORD)(pInit+4+nHostLen)=htons(nloc->wPort); + if(NLSend(nlc,(char*)pInit,6+nHostLen,MSG_DUMPPROXY)==SOCKET_ERROR) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLSend",GetLastError()); + mir_free(pInit); + return 0; + } + mir_free(pInit); + } + + if (!RecvUntilTimeout(nlc,(char*)buf,5,MSG_DUMPPROXY,RECV_DEFAULT_TIMEOUT)) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + + if ( buf[0]!=5 || buf[1] ) { + const char* err = "Unknown response"; + if ( buf[0] != 5 ) + SetLastError(ERROR_BAD_FORMAT); + else + { + switch(buf[1]) + { + case 1: SetLastError(ERROR_GEN_FAILURE); err = "General failure"; break; + case 2: SetLastError(ERROR_ACCESS_DENIED); err = "Connection not allowed by ruleset"; break; + case 3: SetLastError(WSAENETUNREACH); err = "Network unreachable"; break; + case 4: SetLastError(WSAEHOSTUNREACH); err = "Host unreachable"; break; + case 5: SetLastError(WSAECONNREFUSED); err = "Connection refused by destination host"; break; + case 6: SetLastError(WSAETIMEDOUT); err = "TTL expired"; break; + case 7: SetLastError(ERROR_CALL_NOT_IMPLEMENTED); err = "Command not supported / protocol error"; break; + case 8: SetLastError(ERROR_INVALID_ADDRESS); err = "Address type not supported"; break; + default: SetLastError(ERROR_INVALID_DATA); break; + } + } + NetlibLogf(nlu,"%s %d: Proxy conection failed. %s.",__FILE__,__LINE__, err); + return 0; + } + { + int nRecvSize = 0; + switch( buf[3] ) { + case 1:// ipv4 addr + nRecvSize = 5; + break; + case 3:// dns name addr + nRecvSize = buf[4] + 2; + break; + case 4:// ipv6 addr + nRecvSize = 17; + break; + default: + NetlibLogf(nlu,"%s %d: %s() unknown address type (%u)",__FILE__,__LINE__,"NetlibInitSocks5Connection",(int)buf[3]); + return 0; + } + if (!RecvUntilTimeout(nlc,(char*)buf,nRecvSize,MSG_DUMPPROXY,RECV_DEFAULT_TIMEOUT)) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + } + + //connected + return 1; +} + +static bool NetlibInitHttpsConnection(struct NetlibConnection *nlc, struct NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ //rfc2817 + NETLIBHTTPREQUEST nlhrSend = {0}, *nlhrReply; + char szUrl[512]; + + nlhrSend.cbSize = sizeof(nlhrSend); + nlhrSend.requestType = REQUEST_CONNECT; + nlhrSend.flags = NLHRF_GENERATEHOST | NLHRF_DUMPPROXY | NLHRF_SMARTAUTHHEADER | NLHRF_HTTP11 | NLHRF_NOPROXY | NLHRF_REDIRECT; + if (nlc->dnsThroughProxy) + { + mir_snprintf(szUrl, SIZEOF(szUrl), "%s:%u", nloc->szHost, nloc->wPort); + } + else + { + DWORD ip = DnsLookup(nlu, nloc->szHost); + if (ip == 0) return false; + mir_snprintf(szUrl, SIZEOF(szUrl), "%s:%u", inet_ntoa(*(PIN_ADDR)&ip), nloc->wPort); + } + nlhrSend.szUrl = szUrl; + + nlc->usingHttpGateway = true; + + if (NetlibHttpSendRequest((WPARAM)nlc, (LPARAM)&nlhrSend) == SOCKET_ERROR) + { + nlc->usingHttpGateway = false; + return 0; + } + nlhrReply = NetlibHttpRecv(nlc, MSG_DUMPPROXY | MSG_RAW, MSG_DUMPPROXY | MSG_RAW, true); + nlc->usingHttpGateway = false; + if (nlhrReply == NULL) return false; + if (nlhrReply->resultCode < 200 || nlhrReply->resultCode >= 300) + { + if (nlhrReply->resultCode == 403 && nlc->dnsThroughProxy) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + nlc->dnsThroughProxy = 0; + return NetlibInitHttpsConnection(nlc, nlu, nloc); + } + + NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode); + NetlibLogf(nlu,"%s %d: %s request failed (%u %s)",__FILE__,__LINE__,nlu->settings.proxyType==PROXYTYPE_HTTP?"HTTP":"HTTPS",nlhrReply->resultCode,nlhrReply->szResultDescr); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return 0; + } + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + //connected + return true; +} + +static void FreePartiallyInitedConnection(struct NetlibConnection *nlc) +{ + DWORD dwOriginalLastError=GetLastError(); + + if (nlc->s!=INVALID_SOCKET) closesocket(nlc->s); + mir_free(nlc->nlhpi.szHttpPostUrl); + mir_free(nlc->nlhpi.szHttpGetUrl); + mir_free((char*)nlc->nloc.szHost); + mir_free(nlc->szProxyServer); + NetlibDeleteNestedCS(&nlc->ncsSend); + NetlibDeleteNestedCS(&nlc->ncsRecv); + CloseHandle(nlc->hOkToCloseEvent); + DeleteCriticalSection(&nlc->csHttpSequenceNums); + mir_free(nlc); + SetLastError(dwOriginalLastError); +} + +static bool my_connectIPv4(NetlibConnection *nlc, NETLIBOPENCONNECTION * nloc) +{ + int rc = 0, retrycnt = 0; + u_long notblocking = 1; + DWORD lasterr = 0; + static const TIMEVAL tv = { 1, 0 }; + + unsigned int dwTimeout = (nloc->cbSize == sizeof(NETLIBOPENCONNECTION) && nloc->flags & NLOCF_V2) ? nloc->timeout : 0; + // if dwTimeout is zero then its an old style connection or new with a 0 timeout, select() will error quicker anyway + if (dwTimeout == 0) dwTimeout = 30; + + // this is for XP SP2 where there is a default connection attempt limit of 10/second + if (connectionTimeout) + { + WaitForSingleObject(hConnectionOpenMutex, 10000); + int waitdiff = GetTickCount() - g_LastConnectionTick; + if (waitdiff < connectionTimeout) SleepEx(connectionTimeout, TRUE); + g_LastConnectionTick = GetTickCount(); + ReleaseMutex(hConnectionOpenMutex); + + // might of died in between the wait + if (Miranda_Terminated()) return false; + } + + SOCKADDR_IN sin = {0}; + sin.sin_family = AF_INET; + + if (nlc->proxyType) + { + if (!nlc->szProxyServer) return false; + + if (nloc) + NetlibLogf(nlc->nlu,"(%p) Connecting to proxy %s:%d for %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort, nloc->szHost, nloc->wPort); + else + NetlibLogf(nlc->nlu,"(%p) Connecting to proxy %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort); + + sin.sin_port = htons(nlc->wProxyPort); + sin.sin_addr.s_addr = DnsLookup(nlc->nlu, nlc->szProxyServer); + } + else + { + if (!nloc || !nloc->szHost) return false; + NetlibLogf(nlc->nlu,"(%p) Connecting to server %s:%d....", nlc, nloc->szHost, nloc->wPort); + + sin.sin_port = htons(nloc->wPort); + sin.sin_addr.s_addr = DnsLookup(nlc->nlu, nloc->szHost); + } + +retry: + nlc->s = socket(AF_INET,nloc->flags & NLOCF_UDP ? SOCK_DGRAM : SOCK_STREAM, 0); + if (nlc->s == INVALID_SOCKET) return false; + + // return the socket to non blocking + if (ioctlsocket(nlc->s, FIONBIO, ¬blocking) != 0) return false; + + if (nlc->nlu->settings.specifyOutgoingPorts && nlc->nlu->settings.szOutgoingPorts && nlc->nlu->settings.szOutgoingPorts[0]) + { + if (!BindSocketToPort(nlc->nlu->settings.szOutgoingPorts, nlc->s, &nlc->nlu->inportnum)) + NetlibLogf(nlc->nlu,"Netlib connect: Not enough ports for outgoing connections specified"); + } + + // try a connect + if (connect(nlc->s, (LPSOCKADDR)&sin, sizeof(sin)) == 0) + { + goto unblock; + } + + // didn't work, was it cos of nonblocking? + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + rc = SOCKET_ERROR; + goto unblock; + } + + for (;;) + { + fd_set r, w, e; + FD_ZERO(&r); FD_ZERO(&w); FD_ZERO(&e); + FD_SET(nlc->s, &r); + FD_SET(nlc->s, &w); + FD_SET(nlc->s, &e); + if ((rc = select(0, &r, &w, &e, &tv)) == SOCKET_ERROR) + break; + + if (rc > 0) + { + if (FD_ISSET(nlc->s, &w)) + { + // connection was successful + rc = 0; + } + if (FD_ISSET(nlc->s, &r)) + { + // connection was closed + rc = SOCKET_ERROR; + lasterr = WSAECONNRESET; + } + if (FD_ISSET(nlc->s, &e)) + { + // connection failed. + int len = sizeof(lasterr); + rc = SOCKET_ERROR; + getsockopt(nlc->s, SOL_SOCKET, SO_ERROR, (char*)&lasterr, &len); + if (lasterr == WSAEADDRINUSE && ++retrycnt <= 2) + { + closesocket(nlc->s); + goto retry; + } + } + break; + } + else if (Miranda_Terminated()) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + else if (nloc->cbSize == sizeof(NETLIBOPENCONNECTION) && nloc->flags & NLOCF_V2 && + nloc->waitcallback != NULL && nloc->waitcallback(&dwTimeout) == 0) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + if (--dwTimeout == 0) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + } + +unblock: + notblocking = 0; + ioctlsocket(nlc->s, FIONBIO, ¬blocking); + if (lasterr) SetLastError(lasterr); + return rc == 0; +} + +static bool my_connectIPv6(NetlibConnection *nlc, NETLIBOPENCONNECTION * nloc) +{ + int rc = SOCKET_ERROR, retrycnt = 0; + u_long notblocking = 1; + DWORD lasterr = 0; + static const TIMEVAL tv = { 1, 0 }; + + unsigned int dwTimeout = (nloc->cbSize == sizeof(NETLIBOPENCONNECTION) && nloc->flags & NLOCF_V2) ? nloc->timeout : 0; + // if dwTimeout is zero then its an old style connection or new with a 0 timeout, select() will error quicker anyway + if (dwTimeout == 0) dwTimeout = 30; + + // this is for XP SP2 where there is a default connection attempt limit of 10/second + if (connectionTimeout) + { + WaitForSingleObject(hConnectionOpenMutex, 10000); + int waitdiff = GetTickCount() - g_LastConnectionTick; + if (waitdiff < connectionTimeout) SleepEx(connectionTimeout, TRUE); + g_LastConnectionTick = GetTickCount(); + ReleaseMutex(hConnectionOpenMutex); + + // might of died in between the wait + if (Miranda_Terminated()) return false; + } + + char szPort[6]; + addrinfo *air = NULL, *ai, hints = {0}; + + hints.ai_family = AF_UNSPEC; + + if (nloc->flags & NLOCF_UDP) + { + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + } + else + { + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + } + + if (nlc->proxyType) + { + if (!nlc->szProxyServer) return false; + + if (nloc) + NetlibLogf(nlc->nlu,"(%p) Connecting to proxy %s:%d for %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort, nloc->szHost, nloc->wPort); + else + NetlibLogf(nlc->nlu,"(%p) Connecting to proxy %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort); + + _itoa(nlc->wProxyPort, szPort, 10); + if (MyGetaddrinfo(nlc->szProxyServer, szPort, &hints, &air)) + { + NetlibLogf(nlc->nlu,"%s %d: %s() for host %s failed (%u)",__FILE__,__LINE__,"getaddrinfo", nlc->szProxyServer, WSAGetLastError()); + return false; + } + } + else + { + if (!nloc || !nloc->szHost) return false; + NetlibLogf(nlc->nlu,"(%p) Connecting to server %s:%d....", nlc, nloc->szHost, nloc->wPort); + + _itoa(nlc->nloc.wPort, szPort, 10); + + if (MyGetaddrinfo(nlc->nloc.szHost, szPort, &hints, &air)) + { + NetlibLogf(nlc->nlu,"%s %d: %s() for host %s failed (%u)",__FILE__,__LINE__,"getaddrinfo", nlc->nloc.szHost, WSAGetLastError()); + return false; + } + } + + for (ai = air; ai && !Miranda_Terminated(); ai = ai->ai_next) + { +retry: + nlc->s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (nlc->s == INVALID_SOCKET) return false; + + // return the socket to non blocking + if (ioctlsocket(nlc->s, FIONBIO, ¬blocking) != 0) return false; + + if (nlc->nlu->settings.specifyOutgoingPorts && nlc->nlu->settings.szOutgoingPorts && nlc->nlu->settings.szOutgoingPorts[0]) + { + if (!BindSocketToPort(nlc->nlu->settings.szOutgoingPorts, nlc->s, &nlc->nlu->inportnum)) + NetlibLogf(nlc->nlu,"Netlib connect: Not enough ports for outgoing connections specified"); + } + + // try a connect + if (connect(nlc->s, ai->ai_addr, (int)ai->ai_addrlen) == 0) + { + rc = 0; + break; + } + + // didn't work, was it cos of nonblocking? + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + rc = SOCKET_ERROR; + break; + } + + for (;;) // timeout loop + { + fd_set r, w, e; + FD_ZERO(&r); FD_ZERO(&w); FD_ZERO(&e); + FD_SET(nlc->s, &r); + FD_SET(nlc->s, &w); + FD_SET(nlc->s, &e); + if ((rc = select(0, &r, &w, &e, &tv)) == SOCKET_ERROR) + break; + + if (rc > 0) + { + if (FD_ISSET(nlc->s, &w)) + { + // connection was successful + rc = 0; + lasterr = 0; + } + if (FD_ISSET(nlc->s, &r)) + { + // connection was closed + rc = SOCKET_ERROR; + lasterr = WSAECONNRESET; + } + if (FD_ISSET(nlc->s, &e)) + { + // connection failed. + int len = sizeof(lasterr); + rc = SOCKET_ERROR; + getsockopt(nlc->s, SOL_SOCKET, SO_ERROR, (char*)&lasterr, &len); + if (lasterr == WSAEADDRINUSE && ++retrycnt <= 2) + { + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; + goto retry; + } + } + break; + } + else if (Miranda_Terminated()) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + else if (nloc->cbSize == sizeof(NETLIBOPENCONNECTION) && nloc->flags & NLOCF_V2 && + nloc->waitcallback != NULL && nloc->waitcallback(&dwTimeout) == 0) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + if (--dwTimeout == 0) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + } + + if (rc == 0) break; + + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; + } + + MyFreeaddrinfo(air); + + notblocking = 0; + if (nlc->s != INVALID_SOCKET) ioctlsocket(nlc->s, FIONBIO, ¬blocking); + if (rc && lasterr) SetLastError(lasterr); + return rc == 0; +} + +static bool my_connect(NetlibConnection *nlc, NETLIBOPENCONNECTION * nloc) +{ + return MyGetaddrinfo && MyFreeaddrinfo ? my_connectIPv6(nlc, nloc) : my_connectIPv4(nlc, nloc); +} + +static int NetlibHttpFallbackToDirect(struct NetlibConnection *nlc, struct NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ + NetlibDoClose(nlc, true); + + NetlibLogf(nlu,"Fallback to direct connection"); + + nlc->proxyAuthNeeded = false; + nlc->proxyType = 0; + mir_free(nlc->szProxyServer); nlc->szProxyServer = NULL; + if (!my_connect(nlc, nloc)) + { + NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError()); + return false; + } + return true; +} + +bool NetlibDoConnect(NetlibConnection *nlc) +{ + NETLIBOPENCONNECTION *nloc = &nlc->nloc; + NetlibUser *nlu = nlc->nlu; + + mir_free(nlc->szProxyServer); nlc->szProxyServer = NULL; + + bool usingProxy = false, forceHttps = false; + if (nlu->settings.useProxy) + { + if (nlu->settings.proxyType == PROXYTYPE_IE) + { + usingProxy = NetlibGetIeProxyConn(nlc, false); + } + else + { + if (nlu->settings.szProxyServer && nlu->settings.szProxyServer[0]) + { + nlc->szProxyServer = mir_strdup(nlu->settings.szProxyServer); + nlc->wProxyPort = nlu->settings.wProxyPort; + nlc->proxyType = nlu->settings.proxyType; + usingProxy = true; + } + } + } + +retry: + if (usingProxy) + { + if (!my_connect(nlc, nloc)) + { + usingProxy = false; + nlc->proxyType = 0; + } + } + if (!usingProxy) + { + my_connect(nlc, nloc); + } + + if (nlc->s == INVALID_SOCKET) + { + if (usingProxy && (nlc->proxyType == PROXYTYPE_HTTPS || nlc->proxyType == PROXYTYPE_HTTP)) + { + usingProxy = false; + if (!NetlibHttpFallbackToDirect(nlc, nlu, nloc)) + { + NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError()); + return false; + } + } + else + { + if (nlu->settings.useProxy && !usingProxy && nlu->settings.proxyType == PROXYTYPE_IE && !forceHttps) + { + forceHttps = true; + usingProxy = NetlibGetIeProxyConn(nlc, true); + if (usingProxy) goto retry; + } + NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError()); + return false; + } + } + + if (usingProxy && !((nloc->flags & (NLOCF_HTTP | NLOCF_SSL)) == NLOCF_HTTP && + (nlc->proxyType == PROXYTYPE_HTTP || nlc->proxyType == PROXYTYPE_HTTPS))) + { + if (!WaitUntilWritable(nlc->s, 30000)) return false; + + switch (nlc->proxyType) + { + case PROXYTYPE_SOCKS4: + if (!NetlibInitSocks4Connection(nlc, nlu, nloc)) return false; + break; + + case PROXYTYPE_SOCKS5: + if (!NetlibInitSocks5Connection(nlc, nlu, nloc)) return false; + break; + + case PROXYTYPE_HTTPS: + nlc->proxyAuthNeeded = true; + if (!NetlibInitHttpsConnection(nlc, nlu, nloc)) + { + usingProxy = false; + if (!NetlibHttpFallbackToDirect(nlc, nlu, nloc)) + return false; + } + break; + + case PROXYTYPE_HTTP: + nlc->proxyAuthNeeded = true; + if (!(nlu->user.flags & NUF_HTTPGATEWAY || nloc->flags & NLOCF_HTTPGATEWAY) || nloc->flags & NLOCF_SSL) + { + //NLOCF_HTTP not specified and no HTTP gateway available: try HTTPS + if (!NetlibInitHttpsConnection(nlc, nlu, nloc)) + { + //can't do HTTPS: try direct + usingProxy = false; + if (!NetlibHttpFallbackToDirect(nlc, nlu, nloc)) + return false; + } + } + else + { + if (!NetlibInitHttpConnection(nlc, nlu, nloc)) return false; + } + break; + + default: + SetLastError(ERROR_INVALID_PARAMETER); + FreePartiallyInitedConnection(nlc); + return false; + } + } + else if (nloc->flags & NLOCF_HTTPGATEWAY) + { + if (!NetlibInitHttpConnection(nlc, nlu, nloc)) return false; + nlc->usingDirectHttpGateway = true; + } + + NetlibLogf(nlu,"(%d) Connected to %s:%d", nlc->s, nloc->szHost, nloc->wPort); + + if (NLOCF_SSL & nloc->flags) + { + return NetlibStartSsl((WPARAM)nlc, 0) != 0; + } + + return true; +} + +bool NetlibReconnect(NetlibConnection *nlc) +{ + char buf[4]; + bool opened = nlc->s != INVALID_SOCKET; + + if (opened) + { + switch (WaitUntilReadable(nlc->s, 0, true)) + { + case SOCKET_ERROR: + opened = false; + break; + + case 0: + opened = true; + break; + + case 1: + opened = recv(nlc->s, buf, 1, MSG_PEEK) > 0; + break; + } + + if (!opened) + NetlibDoClose(nlc, true); + } + + if (!opened) + { + if (Miranda_Terminated()) return false; + if (nlc->usingHttpGateway) + { + nlc->proxyAuthNeeded = true; + return my_connect(nlc, &nlc->nloc); + } + else + return NetlibDoConnect(nlc); + } + return true; +} + +INT_PTR NetlibOpenConnection(WPARAM wParam,LPARAM lParam) +{ + NETLIBOPENCONNECTION *nloc = (NETLIBOPENCONNECTION*)lParam; + struct NetlibUser *nlu = (struct NetlibUser*)wParam; + struct NetlibConnection *nlc; + + NetlibLogf(nlu,"Connection request to %s:%d (Flags %x)....", nloc->szHost, nloc->wPort, nloc->flags); + + if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING) || nloc == NULL || + (nloc->cbSize != NETLIBOPENCONNECTION_V1_SIZE && nloc->cbSize != sizeof(NETLIBOPENCONNECTION)) || + nloc->szHost == NULL || nloc->wPort == 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + nlc = (struct NetlibConnection*)mir_calloc(sizeof(struct NetlibConnection)); + nlc->handleType = NLH_CONNECTION; + nlc->nlu = nlu; + nlc->nloc = *nloc; + nlc->nloc.szHost = mir_strdup(nloc->szHost); + nlc->s = INVALID_SOCKET; + nlc->s2 = INVALID_SOCKET; + nlc->dnsThroughProxy = nlu->settings.dnsThroughProxy != 0; + + InitializeCriticalSection(&nlc->csHttpSequenceNums); + nlc->hOkToCloseEvent = CreateEvent(NULL,TRUE,TRUE,NULL); + nlc->dontCloseNow = 0; + NetlibInitializeNestedCS(&nlc->ncsSend); + NetlibInitializeNestedCS(&nlc->ncsRecv); + + if (!NetlibDoConnect(nlc)) + { + FreePartiallyInitedConnection(nlc); + return 0; + } + + if (iUPnPCleanup == 0) + { + EnterCriticalSection(&csNetlibUser); + if (iUPnPCleanup == 0) + { + iUPnPCleanup = 1; + forkthread(NetlibUPnPCleanup, 0, NULL); + } + LeaveCriticalSection(&csNetlibUser); + } + + return (INT_PTR)nlc; +} + +INT_PTR NetlibStartSsl(WPARAM wParam, LPARAM lParam) +{ + NetlibConnection *nlc = (NetlibConnection*)wParam; + if (nlc == NULL) return 0; + + NETLIBSSL *sp = (NETLIBSSL*)lParam; + const char *szHost = sp ? sp->host : nlc->nloc.szHost; + + NetlibLogf(nlc->nlu, "(%d %s) Starting SSL negotiation", nlc->s, szHost); + nlc->hSsl = si.connect(nlc->s, szHost, nlc->nlu->settings.validateSSL); + + if (nlc->hSsl == NULL) + NetlibLogf(nlc->nlu,"(%d %s) Failure to negotiate SSL connection", nlc->s, szHost); + else + NetlibLogf(nlc->nlu, "(%d %s) SSL negotiation successful", nlc->s, szHost); + + return nlc->hSsl != NULL; +} diff --git a/src/modules/netlib/netlibopts.cpp b/src/modules/netlib/netlibopts.cpp new file mode 100644 index 0000000000..0c562a5cff --- /dev/null +++ b/src/modules/netlib/netlibopts.cpp @@ -0,0 +1,556 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +struct NetlibTempSettings +{ + DWORD flags; + char *szSettingsModule; + NETLIBUSERSETTINGS settings; +}; + +static LIST tempSettings(5); + +static const UINT outgoingConnectionsControls[] = +{ + IDC_STATIC12, + IDC_USEPROXY, + IDC_STATIC21,IDC_PROXYTYPE, + IDC_STATIC22,IDC_PROXYHOST,IDC_STATIC23,IDC_PROXYPORT,IDC_STOFTENPORT, + IDC_PROXYAUTH, + IDC_STATIC31,IDC_PROXYUSER,IDC_STATIC32,IDC_PROXYPASS, + IDC_PROXYDNS, + IDC_SPECIFYPORTSO, + IDC_PORTSRANGEO, + IDC_STATIC54, + IDC_VALIDATESSL}; +static const UINT useProxyControls[]={ + IDC_STATIC21,IDC_PROXYTYPE, + IDC_STATIC22,IDC_PROXYHOST,IDC_STATIC23,IDC_PROXYPORT,IDC_STOFTENPORT, + IDC_PROXYAUTH, + IDC_STATIC31,IDC_PROXYUSER,IDC_STATIC32,IDC_PROXYPASS, + IDC_PROXYDNS}; +static const UINT specifyOPortsControls[]={ + IDC_PORTSRANGEO, + IDC_STATIC54 +}; +static const UINT incomingConnectionsControls[]={ + IDC_STATIC43, + IDC_SPECIFYPORTS, + IDC_PORTSRANGE, + IDC_STATIC52, + IDC_ENABLEUPNP}; +static const UINT specifyPortsControls[]={ + IDC_PORTSRANGE, + IDC_STATIC52}; + +static const TCHAR* szProxyTypes[]={_T(""),_T("SOCKS4"),_T("SOCKS5"),_T("HTTP"),_T("HTTPS"),_T("Internet Explorer")}; +static const WORD oftenProxyPorts[]={1080,1080,1080,8080,8080,8080}; + +#define M_REFRESHALL (WM_USER+100) +#define M_REFRESHENABLING (WM_USER+101) + +static void ShowMultipleControls(HWND hwndDlg,const UINT *controls,int cControls,int state) +{ + int i; + for(i=0;iszIncomingPorts) dest->szIncomingPorts=mir_strdup(dest->szIncomingPorts); + if(dest->szOutgoingPorts) dest->szOutgoingPorts=mir_strdup(dest->szOutgoingPorts); + if(dest->szProxyAuthPassword) dest->szProxyAuthPassword=mir_strdup(dest->szProxyAuthPassword); + if(dest->szProxyAuthUser) dest->szProxyAuthUser=mir_strdup(dest->szProxyAuthUser); + if(dest->szProxyServer) dest->szProxyServer=mir_strdup(dest->szProxyServer); +} + +static void CombineSettingsStrings(char **dest,char **source) +{ + if(*dest!=NULL && (*source==NULL || lstrcmpiA(*dest,*source))) {mir_free(*dest); *dest=NULL;} +} + +static void CombineSettingsStructs(NETLIBUSERSETTINGS *dest,DWORD *destFlags,NETLIBUSERSETTINGS *source,DWORD sourceFlags) +{ + if(sourceFlags&NUF_OUTGOING) { + if(*destFlags&NUF_OUTGOING) { + if(dest->validateSSL!=source->validateSSL) dest->validateSSL=2; + if(dest->useProxy!=source->useProxy) dest->useProxy=2; + if(dest->proxyType!=source->proxyType) dest->proxyType=0; + CombineSettingsStrings(&dest->szProxyServer,&source->szProxyServer); + if(dest->wProxyPort!=source->wProxyPort) dest->wProxyPort=0; + if(dest->useProxyAuth!=source->useProxyAuth) dest->useProxyAuth=2; + CombineSettingsStrings(&dest->szProxyAuthUser,&source->szProxyAuthUser); + CombineSettingsStrings(&dest->szProxyAuthPassword,&source->szProxyAuthPassword); + if(dest->dnsThroughProxy!=source->dnsThroughProxy) dest->dnsThroughProxy=2; + if(dest->specifyOutgoingPorts!=source->specifyOutgoingPorts) dest->specifyOutgoingPorts=2; + CombineSettingsStrings(&dest->szOutgoingPorts,&source->szOutgoingPorts); + } + else { + dest->validateSSL=source->validateSSL; + dest->useProxy=source->useProxy; + dest->proxyType=source->proxyType; + dest->szProxyServer=source->szProxyServer; + if(dest->szProxyServer) dest->szProxyServer=mir_strdup(dest->szProxyServer); + dest->wProxyPort=source->wProxyPort; + dest->useProxyAuth=source->useProxyAuth; + dest->szProxyAuthUser=source->szProxyAuthUser; + if(dest->szProxyAuthUser) dest->szProxyAuthUser=mir_strdup(dest->szProxyAuthUser); + dest->szProxyAuthPassword=source->szProxyAuthPassword; + if(dest->szProxyAuthPassword) dest->szProxyAuthPassword=mir_strdup(dest->szProxyAuthPassword); + dest->dnsThroughProxy=source->dnsThroughProxy; + dest->specifyOutgoingPorts=source->specifyOutgoingPorts; + dest->szOutgoingPorts=source->szOutgoingPorts; + if (dest->szOutgoingPorts) dest->szOutgoingPorts=mir_strdup(dest->szOutgoingPorts); + } + } + if(sourceFlags&NUF_INCOMING) { + if(*destFlags&NUF_INCOMING) { + if(dest->enableUPnP!=source->enableUPnP) dest->enableUPnP=2; + if(dest->specifyIncomingPorts!=source->specifyIncomingPorts) dest->specifyIncomingPorts=2; + CombineSettingsStrings(&dest->szIncomingPorts,&source->szIncomingPorts); + } + else { + dest->enableUPnP=source->enableUPnP; + dest->specifyIncomingPorts=source->specifyIncomingPorts; + dest->szIncomingPorts=source->szIncomingPorts; + if(dest->szIncomingPorts) dest->szIncomingPorts=mir_strdup(dest->szIncomingPorts); + } + } + if((*destFlags&NUF_NOHTTPSOPTION)!=(sourceFlags&NUF_NOHTTPSOPTION)) + *destFlags=(*destFlags|sourceFlags)&~NUF_NOHTTPSOPTION; + else *destFlags|=sourceFlags; +} + +static void ChangeSettingIntByCheckbox(HWND hwndDlg,UINT ctrlId,int iUser,int memberOffset) +{ + int newValue,i; + + newValue=IsDlgButtonChecked(hwndDlg,ctrlId)!=BST_CHECKED; + CheckDlgButton(hwndDlg,ctrlId,newValue?BST_CHECKED:BST_UNCHECKED); + if (iUser == -1) + { + for (i=0; iflags & NUF_NOOPTIONS)) + *(int*)(((PBYTE)&tempSettings[i]->settings) + memberOffset) = newValue; + } + else *(int*)(((PBYTE)&tempSettings[iUser]->settings) + memberOffset)=newValue; + SendMessage(hwndDlg,M_REFRESHENABLING,0,0); +} + +static void ChangeSettingStringByEdit(HWND hwndDlg,UINT ctrlId,int iUser,int memberOffset) +{ + int i,newValueLen; + char *szNewValue,**ppszNew; + + newValueLen=GetWindowTextLength(GetDlgItem(hwndDlg,ctrlId)); + szNewValue=(char*)mir_alloc(newValueLen+1); + GetDlgItemTextA(hwndDlg,ctrlId,szNewValue,newValueLen+1); + if (iUser == -1) + { + for (i=0; iflags & NUF_NOOPTIONS)) + { + ppszNew=(char**)(((PBYTE)&tempSettings[i]->settings)+memberOffset); + if(*ppszNew) mir_free(*ppszNew); + *ppszNew=mir_strdup(szNewValue); + } + mir_free(szNewValue); + } + else { + ppszNew=(char**)(((PBYTE)&tempSettings[iUser]->settings)+memberOffset); + if(*ppszNew) mir_free(*ppszNew); + *ppszNew=szNewValue; + } +} + +static void WriteSettingsStructToDb(const char *szSettingsModule,NETLIBUSERSETTINGS *settings,DWORD flags) +{ + if(flags&NUF_OUTGOING) { + char szEncodedPassword[512]; + DBWriteContactSettingByte(NULL,szSettingsModule,"NLValidateSSL",(BYTE)settings->validateSSL); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLUseProxy",(BYTE)settings->useProxy); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLProxyType",(BYTE)settings->proxyType); + DBWriteContactSettingString(NULL,szSettingsModule,"NLProxyServer",settings->szProxyServer?settings->szProxyServer:""); + DBWriteContactSettingWord(NULL,szSettingsModule,"NLProxyPort",(WORD)settings->wProxyPort); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLUseProxyAuth",(BYTE)settings->useProxyAuth); + DBWriteContactSettingString(NULL,szSettingsModule,"NLProxyAuthUser",settings->szProxyAuthUser?settings->szProxyAuthUser:""); + lstrcpynA(szEncodedPassword,settings->szProxyAuthPassword?settings->szProxyAuthPassword:"",SIZEOF(szEncodedPassword)); + CallService(MS_DB_CRYPT_ENCODESTRING,SIZEOF(szEncodedPassword),(LPARAM)szEncodedPassword); + DBWriteContactSettingString(NULL,szSettingsModule,"NLProxyAuthPassword",szEncodedPassword); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLDnsThroughProxy",(BYTE)settings->dnsThroughProxy); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLSpecifyOutgoingPorts",(BYTE)settings->specifyOutgoingPorts); + DBWriteContactSettingString(NULL,szSettingsModule,"NLOutgoingPorts",settings->szOutgoingPorts?settings->szOutgoingPorts:""); + } + if(flags&NUF_INCOMING) { + DBWriteContactSettingByte(NULL,szSettingsModule,"NLEnableUPnP",(BYTE)settings->enableUPnP); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLSpecifyIncomingPorts",(BYTE)settings->specifyIncomingPorts); + DBWriteContactSettingString(NULL,szSettingsModule,"NLIncomingPorts",settings->szIncomingPorts?settings->szIncomingPorts:""); + } +} + +void NetlibSaveUserSettingsStruct(const char *szSettingsModule,NETLIBUSERSETTINGS *settings) +{ + int i; + NETLIBUSERSETTINGS combinedSettings={0}; + DWORD flags; + + EnterCriticalSection(&csNetlibUser); + + NetlibUser *thisUser, tUser; + tUser.user.szSettingsModule = (char*)szSettingsModule; + thisUser = netlibUser.find(&tUser); + + if (thisUser == NULL) + { + LeaveCriticalSection(&csNetlibUser); + return; + } + + NetlibFreeUserSettingsStruct(&thisUser->settings); + CopySettingsStruct(&thisUser->settings, settings); + WriteSettingsStructToDb(thisUser->user.szSettingsModule, &thisUser->settings, thisUser->user.flags); + combinedSettings.cbSize = sizeof(combinedSettings); + for (i=0, flags=0; i < netlibUser.getCount(); ++i) + { + if (thisUser->user.flags & NUF_NOOPTIONS) continue; + CombineSettingsStructs(&combinedSettings, &flags, &thisUser->settings, thisUser->user.flags); + } + if(combinedSettings.validateSSL==2) combinedSettings.validateSSL=0; + if(combinedSettings.useProxy==2) combinedSettings.useProxy=0; + if(combinedSettings.proxyType==0) combinedSettings.proxyType=PROXYTYPE_SOCKS5; + if(combinedSettings.useProxyAuth==2) combinedSettings.useProxyAuth=0; + if(combinedSettings.dnsThroughProxy==2) combinedSettings.dnsThroughProxy=1; + if(combinedSettings.enableUPnP==2) combinedSettings.enableUPnP=1; + if(combinedSettings.specifyIncomingPorts==2) combinedSettings.specifyIncomingPorts=0; + WriteSettingsStructToDb("Netlib",&combinedSettings,flags); + NetlibFreeUserSettingsStruct(&combinedSettings); + LeaveCriticalSection(&csNetlibUser); +} + +static INT_PTR CALLBACK DlgProcNetlibOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_INITDIALOG: + { int iUser,iItem; + + TranslateDialogDefault(hwndDlg); + iItem=SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_ADDSTRING,0,(LPARAM)TranslateT("")); + SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_SETITEMDATA,iItem,(LPARAM)-1); + SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_SETCURSEL,iItem,0); + + EnterCriticalSection(&csNetlibUser); + for (iUser = 0; iUser < netlibUser.getCount(); ++iUser) + { + NetlibTempSettings *thisSettings = (NetlibTempSettings*)mir_calloc(sizeof(NetlibTempSettings)); + thisSettings->flags = netlibUser[iUser]->user.flags; + thisSettings->szSettingsModule = mir_strdup(netlibUser[iUser]->user.szSettingsModule); + CopySettingsStruct(&thisSettings->settings, &netlibUser[iUser]->settings); + tempSettings.insert(thisSettings); + + if (netlibUser[iUser]->user.flags & NUF_NOOPTIONS) continue; + iItem = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_ADDSTRING, 0, + (LPARAM)netlibUser[iUser]->user.ptszDescriptiveName); + SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS,CB_SETITEMDATA, iItem, iUser); + } + LeaveCriticalSection(&csNetlibUser); + + SendMessage(hwndDlg,M_REFRESHALL,0,0); + return TRUE; + } + case M_REFRESHALL: + { int iUser=SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_GETCURSEL,0,0),0); + NETLIBUSERSETTINGS settings = {0}; + DWORD flags; + + if (iUser == -1) + { + int i; + settings.cbSize=sizeof(settings); + for (i = 0, flags = 0; i < tempSettings.getCount(); ++i) + { + if (tempSettings[i]->flags & NUF_NOOPTIONS) continue; + CombineSettingsStructs(&settings, &flags, &tempSettings[i]->settings, tempSettings[i]->flags); + } + } + else + { + NetlibFreeUserSettingsStruct(&settings); + CopySettingsStruct(&settings, &tempSettings[iUser]->settings); + flags = tempSettings[iUser]->flags; + } + ShowMultipleControls(hwndDlg,outgoingConnectionsControls,SIZEOF(outgoingConnectionsControls),flags&NUF_OUTGOING?SW_SHOW:SW_HIDE); + CheckDlgButton(hwndDlg,IDC_USEPROXY,settings.useProxy); + SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_RESETCONTENT,0,0); + if (settings.proxyType == 0) AddProxyTypeItem(hwndDlg,0,settings.proxyType); + AddProxyTypeItem(hwndDlg, PROXYTYPE_SOCKS4, settings.proxyType); + AddProxyTypeItem(hwndDlg, PROXYTYPE_SOCKS5, settings.proxyType); + if (flags & (NUF_HTTPCONNS | NUF_HTTPGATEWAY)) AddProxyTypeItem(hwndDlg,PROXYTYPE_HTTP,settings.proxyType); + if (!(flags & NUF_NOHTTPSOPTION)) AddProxyTypeItem(hwndDlg,PROXYTYPE_HTTPS,settings.proxyType); + if (flags & (NUF_HTTPCONNS | NUF_HTTPGATEWAY) || !(flags & NUF_NOHTTPSOPTION)) + AddProxyTypeItem(hwndDlg,PROXYTYPE_IE,settings.proxyType); + SetDlgItemTextA(hwndDlg,IDC_PROXYHOST,settings.szProxyServer?settings.szProxyServer:""); + if (settings.wProxyPort) SetDlgItemInt(hwndDlg,IDC_PROXYPORT,settings.wProxyPort,FALSE); + else SetDlgItemTextA(hwndDlg,IDC_PROXYPORT,""); + CheckDlgButton(hwndDlg,IDC_PROXYAUTH,settings.useProxyAuth); + SetDlgItemTextA(hwndDlg,IDC_PROXYUSER,settings.szProxyAuthUser?settings.szProxyAuthUser:""); + SetDlgItemTextA(hwndDlg,IDC_PROXYPASS,settings.szProxyAuthPassword?settings.szProxyAuthPassword:""); + CheckDlgButton(hwndDlg,IDC_PROXYDNS,settings.dnsThroughProxy); + CheckDlgButton(hwndDlg,IDC_VALIDATESSL,settings.validateSSL); + + ShowMultipleControls(hwndDlg,incomingConnectionsControls,SIZEOF(incomingConnectionsControls),flags&NUF_INCOMING?SW_SHOW:SW_HIDE); + CheckDlgButton(hwndDlg,IDC_SPECIFYPORTS,settings.specifyIncomingPorts); + SetDlgItemTextA(hwndDlg,IDC_PORTSRANGE,settings.szIncomingPorts?settings.szIncomingPorts:""); + + CheckDlgButton(hwndDlg,IDC_SPECIFYPORTSO,settings.specifyOutgoingPorts); + SetDlgItemTextA(hwndDlg,IDC_PORTSRANGEO,settings.szOutgoingPorts?settings.szOutgoingPorts:""); + + CheckDlgButton(hwndDlg,IDC_ENABLEUPNP,settings.enableUPnP); + + NetlibFreeUserSettingsStruct(&settings); + SendMessage(hwndDlg,M_REFRESHENABLING,0,0); + break; + } + case M_REFRESHENABLING: + { int selectedProxyType; + TCHAR str[80]; + + selectedProxyType=SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_GETCURSEL,0,0),0); + mir_sntprintf(str, SIZEOF(str), TranslateT("(often %d)"),oftenProxyPorts[selectedProxyType]); + SetDlgItemText(hwndDlg,IDC_STOFTENPORT,str); + if (IsDlgButtonChecked(hwndDlg,IDC_USEPROXY) != BST_UNCHECKED) + { + int enableAuth = 0, enableUser = 0, enablePass = 0, enableServer = 1; + EnableMultipleControls(hwndDlg, useProxyControls, SIZEOF(useProxyControls), TRUE); + if (selectedProxyType == 0) + { + int i; + for (i = 0; i < tempSettings.getCount(); ++i) + { + if (!tempSettings[i]->settings.useProxy || + tempSettings[i]->flags & NUF_NOOPTIONS || !(tempSettings[i]->flags & NUF_OUTGOING)) + continue; + + if (tempSettings[i]->settings.proxyType==PROXYTYPE_SOCKS4) enableUser=1; + else + { + enableAuth=1; + if (tempSettings[i]->settings.useProxyAuth) + { + enableUser=enablePass=1; + } + } + } + } + else + { + if (selectedProxyType == PROXYTYPE_SOCKS4) enableUser=1; + else + { + if (selectedProxyType == PROXYTYPE_IE) enableServer=0; + enableAuth=1; + if (IsDlgButtonChecked(hwndDlg,IDC_PROXYAUTH) != BST_UNCHECKED) + enableUser = enablePass = 1; + } + } + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYAUTH), enableAuth); + EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC31), enableUser); + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYUSER), enableUser); + EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC32), enablePass); + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYPASS), enablePass); + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYHOST), enableServer); + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYPORT), enableServer); + } + else EnableMultipleControls(hwndDlg,useProxyControls,SIZEOF(useProxyControls),FALSE); + EnableMultipleControls(hwndDlg,specifyPortsControls,SIZEOF(specifyPortsControls),IsDlgButtonChecked(hwndDlg,IDC_SPECIFYPORTS)!=BST_UNCHECKED); + EnableMultipleControls(hwndDlg,specifyOPortsControls,SIZEOF(specifyOPortsControls),IsDlgButtonChecked(hwndDlg,IDC_SPECIFYPORTSO)!=BST_UNCHECKED); + break; + } + case WM_COMMAND: + { int iUser=SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_GETCURSEL,0,0),0); + switch(LOWORD(wParam)) + { + case IDC_NETLIBUSERS: + if(HIWORD(wParam)==CBN_SELCHANGE) SendMessage(hwndDlg,M_REFRESHALL,0,0); + return 0; + + case IDC_LOGOPTIONS: + NetlibLogShowOptions(); + return 0; + + case IDC_PROXYTYPE: + if (HIWORD(wParam) != CBN_SELCHANGE) return 0; + { int newValue,i; + newValue = SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_GETCURSEL,0,0),0); + if (iUser == -1) + { + if (newValue == 0) return 0; + for (i = 0; i < tempSettings.getCount(); ++i) + { + if (tempSettings[i]->flags & NUF_NOOPTIONS) continue; + if (newValue == PROXYTYPE_HTTP && !(tempSettings[i]->flags & (NUF_HTTPCONNS|NUF_HTTPGATEWAY))) + tempSettings[i]->settings.proxyType = PROXYTYPE_HTTPS; + else if (newValue == PROXYTYPE_HTTPS && tempSettings[i]->flags & NUF_NOHTTPSOPTION) + tempSettings[i]->settings.proxyType = PROXYTYPE_HTTP; + else tempSettings[i]->settings.proxyType = newValue; + } + SendMessage(hwndDlg, M_REFRESHALL, 0, 0); + } + else + { + tempSettings[iUser]->settings.proxyType = newValue; + SendMessage(hwndDlg,M_REFRESHENABLING,0,0); + } + } + break; + case IDC_USEPROXY: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,useProxy)); + break; + case IDC_PROXYAUTH: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,useProxyAuth)); + break; + case IDC_PROXYDNS: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,dnsThroughProxy)); + break; + case IDC_SPECIFYPORTS: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,specifyIncomingPorts)); + break; + case IDC_SPECIFYPORTSO: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,specifyOutgoingPorts)); + break; + case IDC_ENABLEUPNP: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,enableUPnP)); + break; + case IDC_VALIDATESSL: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,validateSSL)); + break; + case IDC_PROXYHOST: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szProxyServer)); + break; + case IDC_PROXYPORT: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + { int newValue,i; + newValue=GetDlgItemInt(hwndDlg,LOWORD(wParam),NULL,FALSE); + if (iUser == -1) + { + for (i = 0; i < tempSettings.getCount(); ++i) + if (!(tempSettings[i]->flags & NUF_NOOPTIONS)) + tempSettings[i]->settings.wProxyPort = newValue; + } + else tempSettings[iUser]->settings.wProxyPort = newValue; + } + break; + case IDC_PROXYUSER: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szProxyAuthUser)); + break; + case IDC_PROXYPASS: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szProxyAuthPassword)); + break; + case IDC_PORTSRANGE: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szIncomingPorts)); + break; + case IDC_PORTSRANGEO: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szOutgoingPorts)); + break; + } + ShowWindow(GetDlgItem(hwndDlg,IDC_RECONNECTREQD),SW_SHOW); + SendMessage(GetParent(hwndDlg),PSM_CHANGED,0,0); + break; + } + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) + { + case PSN_APPLY: + { int iUser; + for (iUser = 0; iUser < tempSettings.getCount(); iUser++) + NetlibSaveUserSettingsStruct(tempSettings[iUser]->szSettingsModule, + &tempSettings[iUser]->settings); + return TRUE; + } + } + break; + } + break; + case WM_DESTROY: + { int iUser; + for (iUser = 0; iUser < tempSettings.getCount(); ++iUser) + { + mir_free(tempSettings[iUser]->szSettingsModule); + NetlibFreeUserSettingsStruct(&tempSettings[iUser]->settings); + mir_free(tempSettings[iUser]); + } + tempSettings.destroy(); + break; + } + } + return FALSE; +} + +static UINT expertOnlyControls[]={IDC_LOGOPTIONS}; +int NetlibOptInitialise(WPARAM wParam,LPARAM) +{ + int optionsCount = 0; + EnterCriticalSection(&csNetlibUser); + for (int i = 0; i < netlibUser.getCount(); ++i) + if (!(netlibUser[i]->user.flags & NUF_NOOPTIONS)) ++optionsCount; + LeaveCriticalSection(&csNetlibUser); + if (optionsCount == 0) return 0; + + OPTIONSDIALOGPAGE odp = { 0 }; + + odp.cbSize = sizeof(odp); + odp.position = 900000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_NETLIB); + odp.pszTitle = LPGEN("Network"); + odp.pfnDlgProc = DlgProcNetlibOpts; + odp.flags = ODPF_BOLDGROUPS; + odp.expertOnlyControls = expertOnlyControls; + odp.nExpertOnlyControls = SIZEOF( expertOnlyControls ); + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} diff --git a/src/modules/netlib/netlibpktrecver.cpp b/src/modules/netlib/netlibpktrecver.cpp new file mode 100644 index 0000000000..9722324a4f --- /dev/null +++ b/src/modules/netlib/netlibpktrecver.cpp @@ -0,0 +1,85 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +INT_PTR NetlibPacketRecverCreate(WPARAM wParam,LPARAM lParam) +{ + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + struct NetlibPacketRecver *nlpr; + + if(GetNetlibHandleType(nlc)!=NLH_CONNECTION || lParam==0) { + SetLastError(ERROR_INVALID_PARAMETER); + return (INT_PTR)(struct NetlibPacketRecver*)NULL; + } + nlpr=(struct NetlibPacketRecver*)mir_calloc(sizeof(struct NetlibPacketRecver)); + if(nlpr==NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return (INT_PTR)(struct NetlibPacketRecver*)NULL; + } + nlpr->handleType=NLH_PACKETRECVER; + nlpr->nlc=nlc; + nlpr->packetRecver.cbSize=sizeof(nlpr->packetRecver); + nlpr->packetRecver.bufferSize=lParam; + nlpr->packetRecver.buffer=(PBYTE)mir_alloc(nlpr->packetRecver.bufferSize); + nlpr->packetRecver.bytesUsed=0; + nlpr->packetRecver.bytesAvailable=0; + return (INT_PTR)nlpr; +} + +INT_PTR NetlibPacketRecverGetMore(WPARAM wParam,LPARAM lParam) +{ + struct NetlibPacketRecver *nlpr=(struct NetlibPacketRecver*)wParam; + NETLIBPACKETRECVER *nlprParam=(NETLIBPACKETRECVER*)lParam; + INT_PTR recvResult; + + if(GetNetlibHandleType(nlpr)!=NLH_PACKETRECVER || nlprParam==NULL || nlprParam->cbSize!=sizeof(NETLIBPACKETRECVER) || nlprParam->bytesUsed>nlpr->packetRecver.bytesAvailable) { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + if (Miranda_Terminated()) { /* HACK: Lame, break while loops of protocols that can't kill their while loops, (cough, ICQ, cough) */ + SetLastError(ERROR_TIMEOUT); + return SOCKET_ERROR; + } + nlpr->packetRecver.dwTimeout=nlprParam->dwTimeout; + if(nlprParam->bytesUsed==0) { + if(nlpr->packetRecver.bytesAvailable==nlpr->packetRecver.bufferSize) { + nlpr->packetRecver.bytesAvailable=0; + NetlibLogf(nlpr->nlc->nlu,"Packet recver: packet overflowed buffer, ditching"); + } + } + else { + MoveMemory(nlpr->packetRecver.buffer,nlpr->packetRecver.buffer+nlprParam->bytesUsed,nlpr->packetRecver.bytesAvailable-nlprParam->bytesUsed); + nlpr->packetRecver.bytesAvailable-=nlprParam->bytesUsed; + } + if(nlprParam->dwTimeout!=INFINITE) { + if(!si.pending(nlpr->nlc->hSsl) && WaitUntilReadable(nlpr->nlc->s,nlprParam->dwTimeout) <= 0) { + *nlprParam=nlpr->packetRecver; + return SOCKET_ERROR; + } + } + recvResult=NLRecv(nlpr->nlc, (char*)nlpr->packetRecver.buffer+nlpr->packetRecver.bytesAvailable,nlpr->packetRecver.bufferSize-nlpr->packetRecver.bytesAvailable,0); + if(recvResult>0) nlpr->packetRecver.bytesAvailable+=recvResult; + *nlprParam=nlpr->packetRecver; + return recvResult; +} diff --git a/src/modules/netlib/netlibsecurity.cpp b/src/modules/netlib/netlibsecurity.cpp new file mode 100644 index 0000000000..c20dcf10a1 --- /dev/null +++ b/src/modules/netlib/netlibsecurity.cpp @@ -0,0 +1,570 @@ +/* +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +#define SECURITY_WIN32 +#include +#include + +static HMODULE g_hSecurity = NULL; +static PSecurityFunctionTable g_pSSPI = NULL; + +typedef struct +{ + CtxtHandle hClientContext; + CredHandle hClientCredential; + TCHAR* szProvider; + TCHAR* szPrincipal; + unsigned cbMaxToken; + bool hasDomain; +} + NtlmHandleType; + +typedef struct +{ + WORD len; + WORD allocedSpace; + DWORD offset; +} + NTLM_String; + +typedef struct +{ + char sign[8]; + DWORD type; // == 2 + NTLM_String targetName; + DWORD flags; + BYTE challenge[8]; + BYTE context[8]; + NTLM_String targetInfo; +} + NtlmType2packet; + +static unsigned secCnt = 0, ntlmCnt = 0; +static HANDLE hSecMutex; + +static void ReportSecError(SECURITY_STATUS scRet, int line) +{ + char szMsgBuf[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, scRet, LANG_USER_DEFAULT, szMsgBuf, SIZEOF(szMsgBuf), NULL); + + char *p = strchr(szMsgBuf, 13); if (p) *p = 0; + + NetlibLogf(NULL, "Security error 0x%x on line %u (%s)", scRet, line, szMsgBuf); +} + +static void LoadSecurityLibrary(void) +{ + INIT_SECURITY_INTERFACE pInitSecurityInterface; + + g_hSecurity = LoadLibraryA("secur32.dll"); + if (g_hSecurity == NULL) + g_hSecurity = LoadLibraryA("security.dll"); + + if (g_hSecurity == NULL) + return; + + pInitSecurityInterface = (INIT_SECURITY_INTERFACE)GetProcAddress(g_hSecurity, SECURITY_ENTRYPOINT_ANSI); + if (pInitSecurityInterface != NULL) + { + g_pSSPI = pInitSecurityInterface(); + } + + if (g_pSSPI == NULL) + { + FreeLibrary(g_hSecurity); + g_hSecurity = NULL; + } +} + +static void FreeSecurityLibrary(void) +{ + FreeLibrary(g_hSecurity); + g_hSecurity = NULL; + g_pSSPI = NULL; +} + +HANDLE NetlibInitSecurityProvider(const TCHAR* szProvider, const TCHAR* szPrincipal) +{ + HANDLE hSecurity = NULL; + + if (_tcsicmp(szProvider, _T("Basic")) == 0) + { + NtlmHandleType* hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType)); + hNtlm->szProvider = mir_tstrdup(szProvider); + SecInvalidateHandle(&hNtlm->hClientContext); + SecInvalidateHandle(&hNtlm->hClientCredential); + ntlmCnt++; + + return hNtlm; + } + + WaitForSingleObject(hSecMutex, INFINITE); + + if (secCnt == 0 ) + { + LoadSecurityLibrary(); + secCnt += g_hSecurity != NULL; + } + else secCnt++; + + if (g_pSSPI != NULL) + { + PSecPkgInfo ntlmSecurityPackageInfo; + bool isGSSAPI = _tcsicmp(szProvider, _T("GSSAPI")) == 0; + const TCHAR *szProviderC = isGSSAPI ? _T("Kerberos") : szProvider; + SECURITY_STATUS sc = g_pSSPI->QuerySecurityPackageInfo((LPTSTR)szProviderC, &ntlmSecurityPackageInfo); + if (sc == SEC_E_OK) + { + NtlmHandleType* hNtlm; + + hSecurity = hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType)); + hNtlm->cbMaxToken = ntlmSecurityPackageInfo->cbMaxToken; + g_pSSPI->FreeContextBuffer(ntlmSecurityPackageInfo); + + hNtlm->szProvider = mir_tstrdup(szProvider); + hNtlm->szPrincipal = mir_tstrdup(szPrincipal ? szPrincipal : _T("")); + SecInvalidateHandle(&hNtlm->hClientContext); + SecInvalidateHandle(&hNtlm->hClientCredential); + ntlmCnt++; + } + } + + ReleaseMutex(hSecMutex); + return hSecurity; +} + +#ifdef UNICODE +HANDLE NetlibInitSecurityProvider(const char* szProvider, const char* szPrincipal) +{ + return NetlibInitSecurityProvider(StrConvT(szProvider), StrConvT(szPrincipal)); +} +#endif + +void NetlibDestroySecurityProvider(HANDLE hSecurity) +{ + if (hSecurity == NULL) return; + + WaitForSingleObject(hSecMutex, INFINITE); + + if (ntlmCnt != 0) + { + NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity; + if (SecIsValidHandle(&hNtlm->hClientContext)) g_pSSPI->DeleteSecurityContext(&hNtlm->hClientContext); + if (SecIsValidHandle(&hNtlm->hClientCredential)) g_pSSPI->FreeCredentialsHandle(&hNtlm->hClientCredential); + mir_free(hNtlm->szProvider); + mir_free(hNtlm->szPrincipal); + + --ntlmCnt; + + mir_free(hNtlm); + } + + if (secCnt && --secCnt == 0) + FreeSecurityLibrary(); + + ReleaseMutex(hSecMutex); +} + +char* CompleteGssapi(HANDLE hSecurity, unsigned char *szChallenge, unsigned chlsz) +{ + if (!szChallenge || !szChallenge[0]) return NULL; + + NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity; + unsigned char inDataBuffer[1024]; + + SecBuffer inBuffers[2] = + { + { sizeof(inDataBuffer), SECBUFFER_DATA, inDataBuffer }, + { chlsz, SECBUFFER_STREAM, szChallenge }, + }; + + SecBufferDesc inBuffersDesc = { SECBUFFER_VERSION, 2, inBuffers }; + + unsigned long qop = 0; + SECURITY_STATUS sc = g_pSSPI->DecryptMessage(&hNtlm->hClientContext, &inBuffersDesc, 0, &qop); + if (sc != SEC_E_OK) + { + ReportSecError(sc, __LINE__); + return NULL; + } + + unsigned char LayerMask = inDataBuffer[0]; + unsigned int MaxMessageSize = htonl(*(unsigned*)&inDataBuffer[1]); + + SecPkgContext_Sizes sizes; + sc = g_pSSPI->QueryContextAttributes(&hNtlm->hClientContext, SECPKG_ATTR_SIZES, &sizes); + if (sc != SEC_E_OK) + { + ReportSecError(sc, __LINE__); + return NULL; + } + + unsigned char *tokenBuffer = (unsigned char*)alloca(sizes.cbSecurityTrailer); + unsigned char *paddingBuffer = (unsigned char*)alloca(sizes.cbBlockSize); + + unsigned char outDataBuffer[4] = { 1, 0, 16, 0 }; + + SecBuffer outBuffers[3] = + { + { sizes.cbSecurityTrailer, SECBUFFER_TOKEN, tokenBuffer }, + { sizeof(outDataBuffer), SECBUFFER_DATA, outDataBuffer }, + { sizes.cbBlockSize, SECBUFFER_PADDING, paddingBuffer } + }; + SecBufferDesc outBuffersDesc = { SECBUFFER_VERSION, 3, outBuffers }; + + sc = g_pSSPI->EncryptMessage(&hNtlm->hClientContext, SECQOP_WRAP_NO_ENCRYPT, &outBuffersDesc, 0); + if (sc != SEC_E_OK) + { + ReportSecError(sc, __LINE__); + return NULL; + } + + unsigned i, ressz = 0; + for (i = 0; i < outBuffersDesc.cBuffers; i++) + ressz += outBuffersDesc.pBuffers[i].cbBuffer; + + + unsigned char *response = (unsigned char*)alloca(ressz), *p = response; + for (i = 0; i < outBuffersDesc.cBuffers; i++) + { + memcpy(p, outBuffersDesc.pBuffers[i].pvBuffer, outBuffersDesc.pBuffers[i].cbBuffer); + p += outBuffersDesc.pBuffers[i].cbBuffer; + } + + NETLIBBASE64 nlb64; + nlb64.cbDecoded = ressz; + nlb64.pbDecoded = response; + nlb64.cchEncoded = Netlib_GetBase64EncodedBufferSize(nlb64.cbDecoded); + nlb64.pszEncoded = (char*)alloca(nlb64.cchEncoded); + if (!NetlibBase64Encode(0,(LPARAM)&nlb64)) return NULL; + + return mir_strdup(nlb64.pszEncoded); +} + +char* NtlmCreateResponseFromChallenge(HANDLE hSecurity, const char *szChallenge, const TCHAR* login, const TCHAR* psw, + bool http, unsigned& complete) +{ + SECURITY_STATUS sc; + SecBufferDesc outputBufferDescriptor,inputBufferDescriptor; + SecBuffer outputSecurityToken,inputSecurityToken; + TimeStamp tokenExpiration; + ULONG contextAttributes; + NETLIBBASE64 nlb64 = { 0 }; + + NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity; + + if (hSecurity == NULL || ntlmCnt == 0) return NULL; + + if (_tcsicmp(hNtlm->szProvider, _T("Basic"))) + { + bool isGSSAPI = _tcsicmp(hNtlm->szProvider, _T("GSSAPI")) == 0; + TCHAR *szProvider = isGSSAPI ? _T("Kerberos") : hNtlm->szProvider; + bool hasChallenge = szChallenge != NULL && szChallenge[0] != '\0'; + if (hasChallenge) + { + nlb64.cchEncoded = lstrlenA(szChallenge); + nlb64.pszEncoded = (char*)szChallenge; + nlb64.cbDecoded = Netlib_GetBase64DecodedBufferSize(nlb64.cchEncoded); + nlb64.pbDecoded = (PBYTE)alloca(nlb64.cbDecoded); + if (!NetlibBase64Decode(0, (LPARAM)&nlb64)) return NULL; + + if (isGSSAPI && complete) + return CompleteGssapi(hSecurity, nlb64.pbDecoded, nlb64.cbDecoded); + + inputBufferDescriptor.cBuffers = 1; + inputBufferDescriptor.pBuffers = &inputSecurityToken; + inputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + inputSecurityToken.BufferType = SECBUFFER_TOKEN; + inputSecurityToken.cbBuffer = nlb64.cbDecoded; + inputSecurityToken.pvBuffer = nlb64.pbDecoded; + + // try to decode the domain name from the NTLM challenge + if (login != NULL && login[0] != '\0' && !hNtlm->hasDomain) + { + NtlmType2packet* pkt = ( NtlmType2packet* )nlb64.pbDecoded; + if (!strncmp(pkt->sign, "NTLMSSP", 8) && pkt->type == 2) + { +#ifdef UNICODE + wchar_t* domainName = (wchar_t*)&nlb64.pbDecoded[pkt->targetName.offset]; + int domainLen = pkt->targetName.len; + + // Negotiate ANSI? if yes, convert the ANSI name to unicode + if ((pkt->flags & 1) == 0) + { + int bufsz = MultiByteToWideChar(CP_ACP, 0, (char*)domainName, domainLen, NULL, 0); + wchar_t* buf = (wchar_t*)alloca(bufsz * sizeof(wchar_t)); + domainLen = MultiByteToWideChar(CP_ACP, 0, (char*)domainName, domainLen, buf, bufsz) - 1; + domainName = buf; + } + else + domainLen /= sizeof(wchar_t); +#else + char* domainName = (char*)&nlb64.pbDecoded[pkt->targetName.offset]; + int domainLen = pkt->targetName.len; + + // Negotiate Unicode? if yes, convert the unicode name to ANSI + if (pkt->flags & 1) + { + int bufsz = WideCharToMultiByte(CP_ACP, 0, (WCHAR*)domainName, domainLen, NULL, 0, NULL, NULL); + char* buf = (char*)alloca(bufsz); + domainLen = WideCharToMultiByte(CP_ACP, 0, (WCHAR*)domainName, domainLen, buf, bufsz, NULL, NULL) - 1; + domainName = buf; + } +#endif + + if (domainLen) + { + size_t newLoginLen = _tcslen(login) + domainLen + 1; + TCHAR *newLogin = (TCHAR*)alloca(newLoginLen * sizeof(TCHAR)); + + _tcsncpy(newLogin, domainName, domainLen); + newLogin[domainLen] = '\\'; + _tcscpy(newLogin + domainLen + 1, login); + + char* szChl = NtlmCreateResponseFromChallenge(hSecurity, NULL, newLogin, psw, http, complete); + mir_free(szChl); + } + } + } + } + else + { + if (SecIsValidHandle(&hNtlm->hClientContext)) g_pSSPI->DeleteSecurityContext(&hNtlm->hClientContext); + if (SecIsValidHandle(&hNtlm->hClientCredential)) g_pSSPI->FreeCredentialsHandle(&hNtlm->hClientCredential); + + SEC_WINNT_AUTH_IDENTITY auth; + + if (login != NULL && login[0] != '\0') + { + memset(&auth, 0, sizeof(auth)); +#ifdef _UNICODE + NetlibLogf(NULL, "Security login requested, user: %S pssw: %s", login, psw ? "(exist)" : "(no psw)"); +#else + NetlibLogf(NULL, "Security login requested, user: %s pssw: %s", login, psw ? "(exist)" : "(no psw)"); +#endif + + const TCHAR* loginName = login; + const TCHAR* domainName = _tcschr(login, '\\'); + int domainLen = 0; + int loginLen = lstrlen(loginName); + if (domainName != NULL) + { + loginName = domainName + 1; + loginLen = lstrlen(loginName); + domainLen = domainName - login; + domainName = login; + } + else if ((domainName = _tcschr(login, '@')) != NULL) + { + loginName = login; + loginLen = domainName - login; + domainLen = lstrlen(++domainName); + } + +#ifdef UNICODE + auth.User = (PWORD)loginName; + auth.UserLength = loginLen; + auth.Password = (PWORD)psw; + auth.PasswordLength = lstrlen(psw); + auth.Domain = (PWORD)domainName; + auth.DomainLength = domainLen; + auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; +#else + auth.User = (PBYTE)loginName; + auth.UserLength = loginLen; + auth.Password = (PBYTE)psw; + auth.PasswordLength = lstrlen(psw); + auth.Domain = (PBYTE)domainName; + auth.DomainLength = domainLen; + auth.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; +#endif + + hNtlm->hasDomain = domainLen != 0; + } + + sc = g_pSSPI->AcquireCredentialsHandle(NULL, szProvider, + SECPKG_CRED_OUTBOUND, NULL, hNtlm->hasDomain ? &auth : NULL, NULL, NULL, + &hNtlm->hClientCredential, &tokenExpiration); + if (sc != SEC_E_OK) + { + ReportSecError(sc, __LINE__); + return NULL; + } + } + + outputBufferDescriptor.cBuffers = 1; + outputBufferDescriptor.pBuffers = &outputSecurityToken; + outputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + outputSecurityToken.BufferType = SECBUFFER_TOKEN; + outputSecurityToken.cbBuffer = hNtlm->cbMaxToken; + outputSecurityToken.pvBuffer = alloca(outputSecurityToken.cbBuffer); + + sc = g_pSSPI->InitializeSecurityContext(&hNtlm->hClientCredential, + hasChallenge ? &hNtlm->hClientContext : NULL, + hNtlm->szPrincipal, isGSSAPI ? ISC_REQ_MUTUAL_AUTH | ISC_REQ_STREAM : 0, 0, SECURITY_NATIVE_DREP, + hasChallenge ? &inputBufferDescriptor : NULL, 0, &hNtlm->hClientContext, + &outputBufferDescriptor, &contextAttributes, &tokenExpiration); + + complete = (sc != SEC_I_COMPLETE_AND_CONTINUE && sc != SEC_I_CONTINUE_NEEDED); + + if (sc == SEC_I_COMPLETE_NEEDED || sc == SEC_I_COMPLETE_AND_CONTINUE) + { + sc = g_pSSPI->CompleteAuthToken(&hNtlm->hClientContext, &outputBufferDescriptor); + } + + if (sc != SEC_E_OK && sc != SEC_I_CONTINUE_NEEDED) + { + ReportSecError(sc, __LINE__); + return NULL; + } + + nlb64.cbDecoded = outputSecurityToken.cbBuffer; + nlb64.pbDecoded = (PBYTE)outputSecurityToken.pvBuffer; + } + else + { + if (!login || !psw) return NULL; + + char *szLogin = mir_t2a(login); + char *szPassw = mir_t2a(psw); + + size_t authLen = strlen(szLogin) + strlen(szPassw) + 5; + char *szAuth = (char*)alloca(authLen); + + nlb64.cbDecoded = mir_snprintf(szAuth, authLen,"%s:%s", szLogin, szPassw); + nlb64.pbDecoded=(PBYTE)szAuth; + complete = true; + + mir_free(szPassw); + mir_free(szLogin); + } + + nlb64.cchEncoded = Netlib_GetBase64EncodedBufferSize(nlb64.cbDecoded); + nlb64.pszEncoded = (char*)alloca(nlb64.cchEncoded); + if (!NetlibBase64Encode(0,(LPARAM)&nlb64)) return NULL; + + char* result; + if (http) + { + char* szProvider = mir_t2a(hNtlm->szProvider); + nlb64.cchEncoded += (int)strlen(szProvider) + 10; + result = (char*)mir_alloc(nlb64.cchEncoded); + mir_snprintf(result, nlb64.cchEncoded, "%s %s", szProvider, nlb64.pszEncoded); + mir_free(szProvider); + } + else + result = mir_strdup(nlb64.pszEncoded); + + return result; +} + +/////////////////////////////////////////////////////////////////////////////// + +static INT_PTR InitSecurityProviderService(WPARAM, LPARAM lParam) +{ + HANDLE hSecurity = NetlibInitSecurityProvider((char*)lParam, NULL); + return (INT_PTR)hSecurity; +} + +static INT_PTR InitSecurityProviderService2(WPARAM, LPARAM lParam) +{ + NETLIBNTLMINIT2 *req = ( NETLIBNTLMINIT2* )lParam; + if (req->cbSize < sizeof(*req)) return 0; + + HANDLE hSecurity; + +#ifdef UNICODE + if (req->flags & NNR_UNICODE) + hSecurity = NetlibInitSecurityProvider(req->szProviderName, req->szPrincipal); + else +#endif + hSecurity = NetlibInitSecurityProvider((char*)req->szProviderName, (char*)req->szPrincipal); + + return (INT_PTR)hSecurity; +} + +static INT_PTR DestroySecurityProviderService( WPARAM, LPARAM lParam ) +{ + NetlibDestroySecurityProvider(( HANDLE )lParam ); + return 0; +} + +static INT_PTR NtlmCreateResponseService( WPARAM wParam, LPARAM lParam ) +{ + NETLIBNTLMREQUEST* req = ( NETLIBNTLMREQUEST* )lParam; + unsigned complete; + + char* response = NtlmCreateResponseFromChallenge(( HANDLE )wParam, req->szChallenge, + StrConvT(req->userName), StrConvT(req->password), false, complete ); + + return (INT_PTR)response; +} + +static INT_PTR NtlmCreateResponseService2( WPARAM wParam, LPARAM lParam ) +{ + NETLIBNTLMREQUEST2* req = ( NETLIBNTLMREQUEST2* )lParam; + if (req->cbSize < sizeof(*req)) return 0; + + char* response; + +#ifdef UNICODE + if (req->flags & NNR_UNICODE) + { + response = NtlmCreateResponseFromChallenge(( HANDLE )wParam, req->szChallenge, + req->szUserName, req->szPassword, false, req->complete ); + } + else + { + TCHAR *szLogin = mir_a2t((char*)req->szUserName); + TCHAR *szPassw = mir_a2t((char*)req->szPassword); + response = NtlmCreateResponseFromChallenge(( HANDLE )wParam, req->szChallenge, + szLogin, szPassw, false, req->complete ); + mir_free(szLogin); + mir_free(szPassw); + } +#else + response = NtlmCreateResponseFromChallenge(( HANDLE )wParam, req->szChallenge, + req->szUserName, req->szPassword, false, req->complete ); +#endif + + return (INT_PTR)response; +} + +void NetlibSecurityInit(void) +{ + hSecMutex = CreateMutex(NULL, FALSE, NULL); + + CreateServiceFunction( MS_NETLIB_INITSECURITYPROVIDER, InitSecurityProviderService ); + CreateServiceFunction( MS_NETLIB_INITSECURITYPROVIDER2, InitSecurityProviderService2 ); + CreateServiceFunction( MS_NETLIB_DESTROYSECURITYPROVIDER, DestroySecurityProviderService ); + CreateServiceFunction( MS_NETLIB_NTLMCREATERESPONSE, NtlmCreateResponseService ); + CreateServiceFunction( MS_NETLIB_NTLMCREATERESPONSE2, NtlmCreateResponseService2 ); +} + +void NetlibSecurityDestroy(void) +{ + CloseHandle(hSecMutex); +} \ No newline at end of file diff --git a/src/modules/netlib/netlibsock.cpp b/src/modules/netlib/netlibsock.cpp new file mode 100644 index 0000000000..875c47b0a1 --- /dev/null +++ b/src/modules/netlib/netlibsock.cpp @@ -0,0 +1,203 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +extern HANDLE hConnectionHeaderMutex,hSendEvent,hRecvEvent; + +INT_PTR NetlibSend(WPARAM wParam,LPARAM lParam) +{ + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + NETLIBBUFFER *nlb=(NETLIBBUFFER*)lParam; + INT_PTR result; + + if ( nlb == NULL ) { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + if ( !NetlibEnterNestedCS( nlc, NLNCS_SEND )) + return SOCKET_ERROR; + + if ( nlc->usingHttpGateway && !( nlb->flags & MSG_RAW )) { + if ( !( nlb->flags & MSG_NOHTTPGATEWAYWRAP ) && nlc->nlu->user.pfnHttpGatewayWrapSend ) { + NetlibDumpData( nlc, ( PBYTE )nlb->buf, nlb->len, 1, nlb->flags ); + result = nlc->nlu->user.pfnHttpGatewayWrapSend(( HANDLE )nlc, ( PBYTE )nlb->buf, nlb->len, nlb->flags | MSG_NOHTTPGATEWAYWRAP, NetlibSend ); + } + else result = NetlibHttpGatewayPost( nlc, nlb->buf, nlb->len, nlb->flags ); + } + else { + NetlibDumpData( nlc, ( PBYTE )nlb->buf, nlb->len, 1, nlb->flags ); + if (nlc->hSsl) + result = si.write( nlc->hSsl, nlb->buf, nlb->len ); + else + result = send( nlc->s, nlb->buf, nlb->len, nlb->flags & 0xFFFF ); + } + NetlibLeaveNestedCS( &nlc->ncsSend ); + + if ((( THook* )hSendEvent)->subscriberCount ) { + NETLIBNOTIFY nln = { nlb, result }; + CallHookSubscribers( hSendEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user ); + } + return result; +} + +INT_PTR NetlibRecv(WPARAM wParam,LPARAM lParam) +{ + struct NetlibConnection *nlc = (struct NetlibConnection*)wParam; + NETLIBBUFFER* nlb = ( NETLIBBUFFER* )lParam; + int recvResult; + + if ( nlb == NULL ) { + SetLastError( ERROR_INVALID_PARAMETER ); + return SOCKET_ERROR; + } + + if ( !NetlibEnterNestedCS( nlc, NLNCS_RECV )) + return SOCKET_ERROR; + + if ( nlc->usingHttpGateway && !( nlb->flags & MSG_RAW )) + recvResult = NetlibHttpGatewayRecv( nlc, nlb->buf, nlb->len, nlb->flags ); + else + { + if (nlc->hSsl) + recvResult = si.read( nlc->hSsl, nlb->buf, nlb->len, (nlb->flags & MSG_PEEK) != 0 ); + else + recvResult = recv( nlc->s, nlb->buf, nlb->len, nlb->flags & 0xFFFF ); + } + NetlibLeaveNestedCS( &nlc->ncsRecv ); + if (recvResult <= 0) + return recvResult; + + NetlibDumpData(nlc, (PBYTE)nlb->buf, recvResult, 0, nlb->flags); + + if ((nlb->flags & MSG_PEEK) == 0 && ((THook*)hRecvEvent)->subscriberCount) + { + NETLIBNOTIFY nln = { nlb, recvResult }; + CallHookSubscribers(hRecvEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user); + } + return recvResult; +} + +static int ConnectionListToSocketList(HANDLE *hConns, fd_set *fd, int& pending) +{ + struct NetlibConnection *nlcCheck; + int i; + + FD_ZERO(fd); + for(i=0;hConns[i] && hConns[i]!=INVALID_HANDLE_VALUE && ihandleType!=NLH_CONNECTION && nlcCheck->handleType!=NLH_BOUNDPORT) { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + FD_SET(nlcCheck->s,fd); + if ( si.pending( nlcCheck->hSsl )) + pending++; + } + return 1; +} + +INT_PTR NetlibSelect(WPARAM,LPARAM lParam) +{ + NETLIBSELECT *nls=(NETLIBSELECT*)lParam; + if (nls==NULL || nls->cbSize!=sizeof(NETLIBSELECT)) { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + TIMEVAL tv; + tv.tv_sec=nls->dwTimeout/1000; + tv.tv_usec=(nls->dwTimeout%1000)*1000; + + int pending = 0; + fd_set readfd, writefd, exceptfd; + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + if (!ConnectionListToSocketList(nls->hReadConns,&readfd,pending) + || !ConnectionListToSocketList(nls->hWriteConns,&writefd,pending) + || !ConnectionListToSocketList(nls->hExceptConns,&exceptfd,pending)) { + ReleaseMutex(hConnectionHeaderMutex); + return SOCKET_ERROR; + } + ReleaseMutex(hConnectionHeaderMutex); + if (pending) + return 1; + + return select(0,&readfd,&writefd,&exceptfd,nls->dwTimeout==INFINITE?NULL:&tv); +} + +INT_PTR NetlibSelectEx(WPARAM, LPARAM lParam) +{ + NETLIBSELECTEX *nls=(NETLIBSELECTEX*)lParam; + if (nls==NULL || nls->cbSize!=sizeof(NETLIBSELECTEX)) { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + TIMEVAL tv; + tv.tv_sec=nls->dwTimeout/1000; + tv.tv_usec=(nls->dwTimeout%1000)*1000; + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + + int pending = 0; + fd_set readfd,writefd,exceptfd; + if (!ConnectionListToSocketList(nls->hReadConns,&readfd,pending) + || !ConnectionListToSocketList(nls->hWriteConns,&writefd,pending) + || !ConnectionListToSocketList(nls->hExceptConns,&exceptfd,pending)) { + ReleaseMutex(hConnectionHeaderMutex); + return SOCKET_ERROR; + } + ReleaseMutex(hConnectionHeaderMutex); + + int rc = (pending) ? pending : select(0,&readfd,&writefd,&exceptfd,nls->dwTimeout==INFINITE?NULL:&tv); + + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + /* go thru each passed HCONN array and grab its socket handle, then give it to FD_ISSET() + to see if an event happened for that socket, if it has it will be returned as TRUE (otherwise not) + This happens for read/write/except */ + struct NetlibConnection *conn=NULL; + int j; + for (j=0; jhReadConns[j]; + if (conn==NULL || conn==INVALID_HANDLE_VALUE) break; + + if (si.pending(conn->hSsl)) + nls->hReadStatus[j] = TRUE; + if (conn->usingHttpGateway && conn->nlhpi.szHttpGetUrl == NULL && conn->dataBuffer == NULL) + nls->hReadStatus[j] = (conn->pHttpProxyPacketQueue != NULL); + else + nls->hReadStatus[j] = FD_ISSET(conn->s,&readfd); + } + for (j=0; jhWriteConns[j]; + if (conn==NULL || conn==INVALID_HANDLE_VALUE) break; + nls->hWriteStatus[j] = FD_ISSET(conn->s,&writefd); + } + for (j=0; jhExceptConns[j]; + if (conn==NULL || conn==INVALID_HANDLE_VALUE) break; + nls->hExceptStatus[j] = FD_ISSET(conn->s,&exceptfd); + } + ReleaseMutex(hConnectionHeaderMutex); + return rc; +} diff --git a/src/modules/netlib/netlibssl.cpp b/src/modules/netlib/netlibssl.cpp new file mode 100644 index 0000000000..2db769a3da --- /dev/null +++ b/src/modules/netlib/netlibssl.cpp @@ -0,0 +1,981 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include +#include "netlib.h" + +#define SECURITY_WIN32 +#include +#include + +//#include + +typedef BOOL (* SSL_EMPTY_CACHE_FN_M)(VOID); + +static HMODULE g_hSchannel; +static PSecurityFunctionTableA g_pSSPI; +static HANDLE g_hSslMutex; +static SSL_EMPTY_CACHE_FN_M MySslEmptyCache; +static CredHandle hCreds; +static bool bSslInitDone; + +typedef BOOL (WINAPI *pfnCertGetCertificateChain)(HCERTCHAINENGINE, PCCERT_CONTEXT, LPFILETIME, HCERTSTORE, PCERT_CHAIN_PARA, DWORD, LPVOID, PCCERT_CHAIN_CONTEXT*); +static pfnCertGetCertificateChain fnCertGetCertificateChain; + +typedef VOID (WINAPI *pfnCertFreeCertificateChain)(PCCERT_CHAIN_CONTEXT); +static pfnCertFreeCertificateChain fnCertFreeCertificateChain; + +typedef BOOL (WINAPI *pfnCertFreeCertificateContext)(PCCERT_CONTEXT); +static pfnCertFreeCertificateContext fnCertFreeCertificateContext; + +typedef BOOL (WINAPI *pfnCertVerifyCertificateChainPolicy)(LPCSTR, PCCERT_CHAIN_CONTEXT, PCERT_CHAIN_POLICY_PARA, PCERT_CHAIN_POLICY_STATUS); +static pfnCertVerifyCertificateChainPolicy fnCertVerifyCertificateChainPolicy; + +typedef enum +{ + sockOpen, + sockClosed, + sockError +} SocketState; + + +struct SslHandle +{ + SOCKET s; + + CtxtHandle hContext; + + BYTE *pbRecDataBuf; + int cbRecDataBuf; + int sbRecDataBuf; + + BYTE *pbIoBuffer; + int cbIoBuffer; + int sbIoBuffer; + + SocketState state; +}; + +static void ReportSslError(SECURITY_STATUS scRet, int line, bool showPopup = false) +{ + TCHAR szMsgBuf[256]; + switch (scRet) + { + case 0: + case ERROR_NOT_READY: + return; + + case SEC_E_INVALID_TOKEN: + _tcscpy(szMsgBuf, TranslateT("Client cannot decode host message. Possible causes: Host does not support SSL or requires not existing security package")); + break; + + case CERT_E_CN_NO_MATCH: + case SEC_E_WRONG_PRINCIPAL: + _tcscpy(szMsgBuf, TranslateT("Host we are connecting to is not the one certificate was issued for")); + break; + + default: + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, scRet, LANG_USER_DEFAULT, szMsgBuf, SIZEOF(szMsgBuf), NULL); + } + + TCHAR szMsgBuf2[512]; + mir_sntprintf(szMsgBuf2, SIZEOF(szMsgBuf2), _T("SSL connection failure (%x %u): %s"), scRet, line, szMsgBuf); + + char* szMsg = Utf8EncodeT(szMsgBuf2); + NetlibLogf(NULL, szMsg); + mir_free(szMsg); + + SetLastError(scRet); + PUShowMessageT(szMsgBuf2, SM_WARNING); +} + +static bool AcquireCredentials(void) +{ + SCHANNEL_CRED SchannelCred; + TimeStamp tsExpiry; + SECURITY_STATUS scRet; + + ZeroMemory(&SchannelCred, sizeof(SchannelCred)); + + SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; + SchannelCred.grbitEnabledProtocols = SP_PROT_SSL3TLS1_CLIENTS /*| 0xA00 TLS1.1 & 1.2*/; + + SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION; + + // Create an SSPI credential. + scRet = g_pSSPI->AcquireCredentialsHandleA( + NULL, // Name of principal + UNISP_NAME_A, // Name of package + SECPKG_CRED_OUTBOUND, // Flags indicating use + NULL, // Pointer to logon ID + &SchannelCred, // Package specific data + NULL, // Pointer to GetKey() func + NULL, // Value to pass to GetKey() + &hCreds, // (out) Cred Handle + &tsExpiry); // (out) Lifetime (optional) + + ReportSslError(scRet, __LINE__); + return scRet == SEC_E_OK; +} + +static bool SSL_library_init(void) +{ + if (bSslInitDone) return true; + + WaitForSingleObject(g_hSslMutex, INFINITE); + + if (!bSslInitDone) + { + g_hSchannel = LoadLibraryA("schannel.dll"); + if (g_hSchannel) + { + INIT_SECURITY_INTERFACE_A pInitSecurityInterface; + pInitSecurityInterface = (INIT_SECURITY_INTERFACE_A)GetProcAddress(g_hSchannel, SECURITY_ENTRYPOINT_ANSIA); + if (pInitSecurityInterface != NULL) + g_pSSPI = pInitSecurityInterface(); + + if (g_pSSPI) + { + HINSTANCE hCrypt = LoadLibraryA("crypt32.dll"); + if (hCrypt) + { + fnCertGetCertificateChain = (pfnCertGetCertificateChain)GetProcAddress(hCrypt, "CertGetCertificateChain"); + fnCertFreeCertificateChain = (pfnCertFreeCertificateChain)GetProcAddress(hCrypt, "CertFreeCertificateChain"); + fnCertFreeCertificateContext = (pfnCertFreeCertificateContext)GetProcAddress(hCrypt, "CertFreeCertificateContext"); + fnCertVerifyCertificateChainPolicy = (pfnCertVerifyCertificateChainPolicy)GetProcAddress(hCrypt, "CertVerifyCertificateChainPolicy"); + } + + MySslEmptyCache = (SSL_EMPTY_CACHE_FN_M)GetProcAddress(g_hSchannel, "SslEmptyCache"); + AcquireCredentials(); + bSslInitDone = true; + } + else + { + FreeLibrary(g_hSchannel); + g_hSchannel = NULL; + } + } + } + + ReleaseMutex(g_hSslMutex); + return bSslInitDone; +} + +void NetlibSslFree(SslHandle *ssl) +{ + if (ssl == NULL) return; + + g_pSSPI->DeleteSecurityContext(&ssl->hContext); + + mir_free(ssl->pbRecDataBuf); + mir_free(ssl->pbIoBuffer); + memset(ssl, 0, sizeof(SslHandle)); + mir_free(ssl); +} + +BOOL NetlibSslPending(SslHandle *ssl) +{ + return ssl != NULL && ( ssl->cbRecDataBuf != 0 || ssl->cbIoBuffer != 0 ); +} + +static bool VerifyCertificate(SslHandle *ssl, PCSTR pszServerName, DWORD dwCertFlags) +{ + if (!fnCertGetCertificateChain) + return true; + + static LPSTR rgszUsages[] = + { + szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE + }; + + CERT_CHAIN_PARA ChainPara = {0}; + HTTPSPolicyCallbackData polHttps = {0}; + CERT_CHAIN_POLICY_PARA PolicyPara = {0}; + CERT_CHAIN_POLICY_STATUS PolicyStatus = {0}; + PCCERT_CHAIN_CONTEXT pChainContext = NULL; + PCCERT_CONTEXT pServerCert = NULL; + DWORD scRet; + + PWSTR pwszServerName = mir_a2u(pszServerName); + + scRet = g_pSSPI->QueryContextAttributesA(&ssl->hContext, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pServerCert); + if (scRet != SEC_E_OK) + goto cleanup; + + if (pServerCert == NULL) + { + scRet = SEC_E_WRONG_PRINCIPAL; + goto cleanup; + } + + ChainPara.cbSize = sizeof(ChainPara); + ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + ChainPara.RequestedUsage.Usage.cUsageIdentifier = SIZEOF(rgszUsages); + ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; + + if (!fnCertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, + &ChainPara, 0, NULL, &pChainContext)) + { + scRet = GetLastError(); + goto cleanup; + } + + polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); + polHttps.dwAuthType = AUTHTYPE_SERVER; + polHttps.fdwChecks = dwCertFlags; + polHttps.pwszServerName = pwszServerName; + + PolicyPara.cbSize = sizeof(PolicyPara); + PolicyPara.pvExtraPolicyPara = &polHttps; + + PolicyStatus.cbSize = sizeof(PolicyStatus); + + if (!fnCertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, + &PolicyPara, &PolicyStatus)) + { + scRet = GetLastError(); + goto cleanup; + } + + if (PolicyStatus.dwError) + { + scRet = PolicyStatus.dwError; + goto cleanup; + } + + scRet = SEC_E_OK; + +cleanup: + if (pChainContext) + fnCertFreeCertificateChain(pChainContext); + if (pServerCert) + fnCertFreeCertificateContext(pServerCert); + mir_free(pwszServerName); + + ReportSslError(scRet, __LINE__, true); + return scRet == SEC_E_OK; +} + +static SECURITY_STATUS ClientHandshakeLoop(SslHandle *ssl, BOOL fDoInitialRead) +{ + SecBufferDesc InBuffer; + SecBuffer InBuffers[2]; + SecBufferDesc OutBuffer; + SecBuffer OutBuffers[1]; + DWORD dwSSPIFlags; + DWORD dwSSPIOutFlags; + TimeStamp tsExpiry; + SECURITY_STATUS scRet; + DWORD cbData; + + BOOL fDoRead; + + dwSSPIFlags = + ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + ssl->cbIoBuffer = 0; + + fDoRead = fDoInitialRead; + + scRet = SEC_I_CONTINUE_NEEDED; + + // Loop until the handshake is finished or an error occurs. + while (scRet == SEC_I_CONTINUE_NEEDED || scRet == SEC_E_INCOMPLETE_MESSAGE || scRet == SEC_I_INCOMPLETE_CREDENTIALS) + { + // Read server data + if (0 == ssl->cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) + { + if (fDoRead) + { + static const TIMEVAL tv = {6, 0}; + fd_set fd; + + // If buffer not large enough reallocate buffer + if (ssl->sbIoBuffer <= ssl->cbIoBuffer) + { + ssl->sbIoBuffer += 4096; + ssl->pbIoBuffer = (PUCHAR)mir_realloc(ssl->pbIoBuffer, ssl->sbIoBuffer); + } + + FD_ZERO(&fd); + FD_SET(ssl->s, &fd); + if (select(1, &fd, NULL, NULL, &tv) != 1) + { + NetlibLogf(NULL, "SSL Negotiation failure recieving data (timeout) (bytes %u)", ssl->cbIoBuffer); + scRet = ERROR_NOT_READY; + break; + } + + cbData = recv(ssl->s, (char*)ssl->pbIoBuffer + ssl->cbIoBuffer, ssl->sbIoBuffer - ssl->cbIoBuffer, 0); + if (cbData == SOCKET_ERROR) + { + NetlibLogf(NULL, "SSL Negotiation failure recieving data (%d)", WSAGetLastError()); + scRet = ERROR_NOT_READY; + break; + } + if (cbData == 0) + { + NetlibLogf(NULL, "SSL Negotiation connection gracefully closed"); + scRet = ERROR_NOT_READY; + break; + } + + NetlibDumpData(NULL, ssl->pbIoBuffer + ssl->cbIoBuffer, cbData, 0, MSG_DUMPSSL); + ssl->cbIoBuffer += cbData; + } + else fDoRead = TRUE; + } + + // Set up the input buffers. Buffer 0 is used to pass in data + // received from the server. Schannel will consume some or all + // of this. Leftover data (if any) will be placed in buffer 1 and + // given a buffer type of SECBUFFER_EXTRA. + + InBuffers[0].pvBuffer = ssl->pbIoBuffer; + InBuffers[0].cbBuffer = ssl->cbIoBuffer; + InBuffers[0].BufferType = SECBUFFER_TOKEN; + + InBuffers[1].pvBuffer = NULL; + InBuffers[1].cbBuffer = 0; + InBuffers[1].BufferType = SECBUFFER_EMPTY; + + InBuffer.cBuffers = 2; + InBuffer.pBuffers = InBuffers; + InBuffer.ulVersion = SECBUFFER_VERSION; + + // Set up the output buffers. These are initialized to NULL + // so as to make it less likely we'll attempt to free random + // garbage later. + + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType= SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + scRet = g_pSSPI->InitializeSecurityContextA( + &hCreds, + &ssl->hContext, + NULL, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + &InBuffer, + 0, + NULL, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry); + + // If success (or if the error was one of the special extended ones), + // send the contents of the output buffer to the server. + if (scRet == SEC_E_OK || + scRet == SEC_I_CONTINUE_NEEDED || + (FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))) + { + if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) + { + NetlibDumpData(NULL, (unsigned char*)(OutBuffers[0].pvBuffer), OutBuffers[0].cbBuffer, 1, MSG_DUMPSSL); + cbData = send(ssl->s, (char*)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0); + if (cbData == SOCKET_ERROR || cbData == 0) + { + NetlibLogf(NULL, "SSL Negotiation failure sending data (%d)", WSAGetLastError()); + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + return SEC_E_INTERNAL_ERROR; + } + + // Free output buffer. + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + OutBuffers[0].pvBuffer = NULL; + } + } + + // we need to read more data from the server and try again. + if (scRet == SEC_E_INCOMPLETE_MESSAGE) continue; + + // handshake completed successfully. + if (scRet == SEC_E_OK) + { + // Store remaining data for further use + if (InBuffers[1].BufferType == SECBUFFER_EXTRA) + { + memmove(ssl->pbIoBuffer, + ssl->pbIoBuffer + (ssl->cbIoBuffer - InBuffers[1].cbBuffer), + InBuffers[1].cbBuffer); + ssl->cbIoBuffer = InBuffers[1].cbBuffer; + } + else + ssl->cbIoBuffer = 0; + break; + } + + // Check for fatal error. + if (FAILED(scRet)) break; + + // server just requested client authentication. + if (scRet == SEC_I_INCOMPLETE_CREDENTIALS) + { + // Server has requested client authentication and + // GetNewClientCredentials(ssl); + + // Go around again. + fDoRead = FALSE; + scRet = SEC_I_CONTINUE_NEEDED; + continue; + } + + + // Copy any leftover data from the buffer, and go around again. + if (InBuffers[1].BufferType == SECBUFFER_EXTRA) + { + memmove(ssl->pbIoBuffer, + ssl->pbIoBuffer + (ssl->cbIoBuffer - InBuffers[1].cbBuffer), + InBuffers[1].cbBuffer); + + ssl->cbIoBuffer = InBuffers[1].cbBuffer; + } + else ssl->cbIoBuffer = 0; + } + + // Delete the security context in the case of a fatal error. + ReportSslError(scRet, __LINE__); + + if (ssl->cbIoBuffer == 0) + { + mir_free(ssl->pbIoBuffer); + ssl->pbIoBuffer = NULL; + ssl->sbIoBuffer = 0; + } + + return scRet; +} + +static bool ClientConnect(SslHandle *ssl, const char *host) +{ + SecBufferDesc OutBuffer; + SecBuffer OutBuffers[1]; + DWORD dwSSPIFlags; + DWORD dwSSPIOutFlags; + TimeStamp tsExpiry; + SECURITY_STATUS scRet; + DWORD cbData; + + if (SecIsValidHandle(&ssl->hContext)) + { + g_pSSPI->DeleteSecurityContext(&ssl->hContext); + SecInvalidateHandle(&ssl->hContext); + } + + if (MySslEmptyCache) MySslEmptyCache(); + + dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + // Initiate a ClientHello message and generate a token. + + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType = SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + scRet = g_pSSPI->InitializeSecurityContextA( + &hCreds, + NULL, + (SEC_CHAR*)host, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + NULL, + 0, + &ssl->hContext, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry); + + if (scRet != SEC_I_CONTINUE_NEEDED) + { + ReportSslError(scRet, __LINE__); + return 0; + } + + // Send response to server if there is one. + if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) + { + NetlibDumpData(NULL, (unsigned char*)(OutBuffers[0].pvBuffer), OutBuffers[0].cbBuffer, 1, MSG_DUMPSSL); + cbData = send(ssl->s, (char*)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0); + if (cbData == SOCKET_ERROR || cbData == 0) + { + NetlibLogf(NULL, "SSL failure sending connection data (%d %d)", ssl->s, WSAGetLastError()); + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + return 0; + } + + // Free output buffer. + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + OutBuffers[0].pvBuffer = NULL; + } + + return ClientHandshakeLoop(ssl, TRUE) == SEC_E_OK; +} + + +SslHandle *NetlibSslConnect(SOCKET s, const char* host, int verify) +{ + SslHandle *ssl = (SslHandle*)mir_calloc(sizeof(SslHandle)); + ssl->s = s; + + SecInvalidateHandle(&ssl->hContext); + + DWORD dwFlags = 0; + + if (!host || inet_addr(host) != INADDR_NONE) + dwFlags |= 0x00001000; + + bool res = SSL_library_init(); + + if (res) res = ClientConnect(ssl, host); + if (res && verify) res = VerifyCertificate(ssl, host, dwFlags); + + if (!res) + { + NetlibSslFree(ssl); + ssl = NULL; + } + return ssl; +} + + +void NetlibSslShutdown(SslHandle *ssl) +{ + DWORD dwType; + + SecBufferDesc OutBuffer; + SecBuffer OutBuffers[1]; + DWORD dwSSPIFlags; + DWORD dwSSPIOutFlags; + TimeStamp tsExpiry; + DWORD scRet; + + if (ssl == NULL || !SecIsValidHandle(&ssl->hContext)) + return; + + dwType = SCHANNEL_SHUTDOWN; + + OutBuffers[0].pvBuffer = &dwType; + OutBuffers[0].BufferType = SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = sizeof(dwType); + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + scRet = g_pSSPI->ApplyControlToken(&ssl->hContext, &OutBuffer); + if (FAILED(scRet)) return; + + // + // Build an SSL close notify message. + // + + dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_RET_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType = SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + scRet = g_pSSPI->InitializeSecurityContextA( + &hCreds, + &ssl->hContext, + NULL, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + NULL, + 0, + &ssl->hContext, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry); + + if (FAILED(scRet)) return; + + // Send the close notify message to the server. + if (OutBuffers[0].pvBuffer != NULL && OutBuffers[0].cbBuffer != 0) + { + NetlibDumpData(NULL, (unsigned char*)(OutBuffers[0].pvBuffer), OutBuffers[0].cbBuffer, 1, MSG_DUMPSSL); + send(ssl->s, (char*)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0); + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + } +} + +static int NetlibSslReadSetResult(SslHandle *ssl, char *buf, int num, int peek) +{ + if (ssl->cbRecDataBuf == 0) + { + return (ssl->state == sockClosed ? 0: SOCKET_ERROR); + } + + int bytes = min(num, ssl->cbRecDataBuf); + int rbytes = ssl->cbRecDataBuf - bytes; + + memcpy(buf, ssl->pbRecDataBuf, bytes); + if (!peek) + { + memmove(ssl->pbRecDataBuf, ssl->pbRecDataBuf + bytes, rbytes); + ssl->cbRecDataBuf = rbytes; + } + + return bytes; +} + +int NetlibSslRead(SslHandle *ssl, char *buf, int num, int peek) +{ + SECURITY_STATUS scRet; + DWORD cbData; + DWORD resNum = 0; + int i; + + SecBufferDesc Message; + SecBuffer Buffers[4]; + SecBuffer * pDataBuffer; + SecBuffer * pExtraBuffer; + + if (ssl == NULL) return SOCKET_ERROR; + + if (num <= 0) return 0; + + if (ssl->state != sockOpen || (ssl->cbRecDataBuf != 0 && (!peek || ssl->cbRecDataBuf >= num))) + { + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + scRet = SEC_E_OK; + + for (;;) + { + if (0 == ssl->cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) + { + if (ssl->sbIoBuffer <= ssl->cbIoBuffer) + { + ssl->sbIoBuffer += 2048; + ssl->pbIoBuffer = (PUCHAR)mir_realloc(ssl->pbIoBuffer, ssl->sbIoBuffer); + } + + if (peek) + { + static const TIMEVAL tv = {0}; + fd_set fd; + FD_ZERO(&fd); + FD_SET(ssl->s, &fd); + + cbData = select(1, &fd, NULL, NULL, &tv); + if (cbData == SOCKET_ERROR) + { + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + if (cbData == 0 && ssl->cbRecDataBuf) + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + cbData = recv(ssl->s, (char*)ssl->pbIoBuffer + ssl->cbIoBuffer, ssl->sbIoBuffer - ssl->cbIoBuffer, 0); + if (cbData == SOCKET_ERROR) + { + NetlibLogf(NULL, "SSL failure recieving data (%d)", WSAGetLastError()); + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + if (cbData == 0) + { + NetlibLogf(NULL, "SSL connection gracefully closed"); + if (peek && ssl->cbRecDataBuf) + { + ssl->state = sockClosed; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + // Server disconnected. + if (ssl->cbIoBuffer) + { + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + return 0; + } + else + { + NetlibDumpData(NULL, ssl->pbIoBuffer + ssl->cbIoBuffer, cbData, 0, MSG_DUMPSSL); + ssl->cbIoBuffer += cbData; + } + } + + // Attempt to decrypt the received data. + Buffers[0].pvBuffer = ssl->pbIoBuffer; + Buffers[0].cbBuffer = ssl->cbIoBuffer; + Buffers[0].BufferType = SECBUFFER_DATA; + + Buffers[1].BufferType = SECBUFFER_EMPTY; + Buffers[2].BufferType = SECBUFFER_EMPTY; + Buffers[3].BufferType = SECBUFFER_EMPTY; + + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + + if (g_pSSPI->DecryptMessage != NULL && g_pSSPI->DecryptMessage != PVOID(0x80000000)) + scRet = g_pSSPI->DecryptMessage(&ssl->hContext, &Message, 0, NULL); + else + scRet = ((DECRYPT_MESSAGE_FN)g_pSSPI->Reserved4)(&ssl->hContext, &Message, 0, NULL); + + // The input buffer contains only a fragment of an + // encrypted record. Loop around and read some more + // data. + if (scRet == SEC_E_INCOMPLETE_MESSAGE) + continue; + + if ( scRet != SEC_E_OK && scRet != SEC_I_RENEGOTIATE && scRet != SEC_I_CONTEXT_EXPIRED) + { + ReportSslError(scRet, __LINE__); + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + // Locate data and (optional) extra buffers. + pDataBuffer = NULL; + pExtraBuffer = NULL; + for(i = 1; i < 4; i++) + { + if (pDataBuffer == NULL && Buffers[i].BufferType == SECBUFFER_DATA) + pDataBuffer = &Buffers[i]; + + if (pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA) + pExtraBuffer = &Buffers[i]; + } + + // Return decrypted data. + if (pDataBuffer) + { + DWORD bytes, rbytes; + + bytes = peek ? 0 : min((DWORD)num, pDataBuffer->cbBuffer); + rbytes = pDataBuffer->cbBuffer - bytes; + + NetlibDumpData(NULL, (PBYTE)pDataBuffer->pvBuffer, pDataBuffer->cbBuffer, 0, MSG_DUMPSSL); + + if (rbytes > 0) + { + int nbytes = ssl->cbRecDataBuf + rbytes; + if (ssl->sbRecDataBuf < nbytes) + { + ssl->sbRecDataBuf = nbytes; + ssl->pbRecDataBuf = (PUCHAR)mir_realloc(ssl->pbRecDataBuf, nbytes); + } + memcpy(ssl->pbRecDataBuf + ssl->cbRecDataBuf, (char*)pDataBuffer->pvBuffer + bytes, rbytes); + ssl->cbRecDataBuf = nbytes; + } + + if (peek) + { + resNum = bytes = min(num, ssl->cbRecDataBuf); + memcpy(buf, ssl->pbRecDataBuf, bytes); + } + else + { + resNum = bytes; + memcpy(buf, pDataBuffer->pvBuffer, bytes); + } + } + + // Move any "extra" data to the input buffer. + if (pExtraBuffer) + { + memmove(ssl->pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer); + ssl->cbIoBuffer = pExtraBuffer->cbBuffer; + } + else ssl->cbIoBuffer = 0; + + if (pDataBuffer && resNum) + return resNum; + + // Server signaled end of session + if (scRet == SEC_I_CONTEXT_EXPIRED) + { + NetlibLogf(NULL, "SSL Server signaled SSL Shutdown"); + ssl->state = sockClosed; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + if (scRet == SEC_I_RENEGOTIATE) + { + // The server wants to perform another handshake + // sequence. + + scRet = ClientHandshakeLoop(ssl, FALSE); + if (scRet != SEC_E_OK) + { + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + } + } +} + +int NetlibSslWrite(SslHandle *ssl, const char *buf, int num) +{ + SecPkgContext_StreamSizes Sizes; + SECURITY_STATUS scRet; + DWORD cbData; + + SecBufferDesc Message; + SecBuffer Buffers[4] = {0}; + + PUCHAR pbDataBuffer; + + PUCHAR pbMessage; + DWORD cbMessage; + + DWORD sendOff = 0; + + if (ssl == NULL) return SOCKET_ERROR; + + scRet = g_pSSPI->QueryContextAttributesA(&ssl->hContext, SECPKG_ATTR_STREAM_SIZES, &Sizes); + if (scRet != SEC_E_OK) return scRet; + + pbDataBuffer = (PUCHAR)mir_calloc(Sizes.cbMaximumMessage + Sizes.cbHeader + Sizes.cbTrailer); + + pbMessage = pbDataBuffer + Sizes.cbHeader; + + while (sendOff < (DWORD)num) + { + cbMessage = min(Sizes.cbMaximumMessage, (DWORD)num - sendOff); + CopyMemory(pbMessage, buf+sendOff, cbMessage); + + Buffers[0].pvBuffer = pbDataBuffer; + Buffers[0].cbBuffer = Sizes.cbHeader; + Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; + + Buffers[1].pvBuffer = pbMessage; + Buffers[1].cbBuffer = cbMessage; + Buffers[1].BufferType = SECBUFFER_DATA; + + Buffers[2].pvBuffer = pbMessage + cbMessage; + Buffers[2].cbBuffer = Sizes.cbTrailer; + Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + + Buffers[3].BufferType = SECBUFFER_EMPTY; + + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + + if (g_pSSPI->EncryptMessage != NULL) + scRet = g_pSSPI->EncryptMessage(&ssl->hContext, 0, &Message, 0); + else + scRet = ((ENCRYPT_MESSAGE_FN)g_pSSPI->Reserved3)(&ssl->hContext, 0, &Message, 0); + + if (FAILED(scRet)) break; + + // Calculate encrypted packet size + cbData = Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer; + + // Send the encrypted data to the server. + NetlibDumpData(NULL, pbDataBuffer, cbData, 1, MSG_DUMPSSL); + cbData = send(ssl->s, (char*)pbDataBuffer, cbData, 0); + if (cbData == SOCKET_ERROR || cbData == 0) + { + NetlibLogf(NULL, "SSL failure sending data (%d)", WSAGetLastError()); + scRet = SEC_E_INTERNAL_ERROR; + break; + } + + sendOff += cbMessage; + } + + mir_free(pbDataBuffer); + return scRet == SEC_E_OK ? num : SOCKET_ERROR; +} + +static INT_PTR GetSslApi(WPARAM, LPARAM lParam) +{ + SSL_API* si = (SSL_API*)lParam; + if (si == NULL) return FALSE; + + if (si->cbSize != sizeof(SSL_API)) + return FALSE; + + si->connect = (HSSL (__cdecl *)(SOCKET,const char *,int))NetlibSslConnect; + si->pending = (BOOL (__cdecl *)(HSSL))NetlibSslPending; + si->read = (int (__cdecl *)(HSSL,char *,int,int))NetlibSslRead; + si->write = (int (__cdecl *)(HSSL,const char *,int))NetlibSslWrite; + si->shutdown = (void (__cdecl *)(HSSL))NetlibSslShutdown; + si->sfree = (void (__cdecl *)(HSSL))NetlibSslFree; + + return TRUE; +} + +int LoadSslModule(void) +{ + CreateServiceFunction(MS_SYSTEM_GET_SI, GetSslApi); + g_hSslMutex = CreateMutex(NULL, FALSE, NULL); + SecInvalidateHandle(&hCreds); + + return 0; +} + +void UnloadSslModule(void) +{ + if (g_pSSPI && SecIsValidHandle(&hCreds)) + g_pSSPI->FreeCredentialsHandle(&hCreds); + CloseHandle(g_hSslMutex); + if (g_hSchannel) FreeLibrary(g_hSchannel); +} diff --git a/src/modules/netlib/netlibupnp.cpp b/src/modules/netlib/netlibupnp.cpp new file mode 100644 index 0000000000..935801d330 --- /dev/null +++ b/src/modules/netlib/netlibupnp.cpp @@ -0,0 +1,885 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +static const char search_request_msg[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: 1\r\n" + "ST: urn:schemas-upnp-org:service:%s\r\n" + "\r\n"; + +static const char xml_get_hdr[] = + "GET %s HTTP/1.1\r\n" + "HOST: %s:%u\r\n" + "ACCEPT-LANGUAGE: *\r\n\r\n"; + +static const char soap_post_hdr[] = + "POST %s HTTP/1.1\r\n" + "HOST: %s:%u\r\n" + "CONTENT-LENGTH: %u\r\n" + "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n" + "SOAPACTION: \"%s#%s\"\r\n\r\n" + "%s"; + +static const char soap_post_hdr_m[] = + "M-POST %s URL HTTP/1.1\r\n" + "HOST: %s:%u\r\n" + "CONTENT-LENGTH: %u\r\n" + "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n" + "MAN: \"http://schemas.xmlsoap.org/soap/envelope/\"; ns=01\r\n" + "01-SOAPACTION: \"%s#%s\"\r\n\r\n" + "%s"; + +static const char search_device[] = + "%s"; + +static const char soap_action[] = + "\r\n" + "\r\n" + " \r\n" + " \r\n" + "%s" + " \r\n" + " \r\n" + "\r\n"; + +static const char soap_query[] = + "\r\n" + " \r\n" + " \r\n" + " %s\r\n" + " \r\n" + " \r\n" + "\r\n"; + +static const char add_port_mapping[] = + " \r\n" + " %i\r\n" + " %s\r\n" + " %i\r\n" + " %s\r\n" + " 1\r\n" + " Miranda\r\n" + " 0\r\n"; + +static const char delete_port_mapping[] = + " \r\n" + " %i\r\n" + " %s\r\n"; + +static const char get_port_mapping[] = + " %i\r\n"; + +static bool gatewayFound; +static SOCKADDR_IN locIP; +static time_t lastDiscTime; +static int expireTime = 120; + +static int retryCount; +static SOCKET sock = INVALID_SOCKET; +static char szConnHost[256]; +static unsigned short sConnPort; + +static WORD *portList; +static unsigned numports, numportsAlloc; +static HANDLE portListMutex; + +static char szCtlUrl[256], szDev[256]; + +typedef enum +{ + DeviceGetReq, + ControlAction, + ControlQuery +} ReqType; + +static bool txtParseParam(char* szData, char* presearch, + char* start, char* finish, char* param, size_t size) +{ + char *cp, *cp1; + size_t len; + + *param = 0; + + if (presearch != NULL) { + cp1 = strstr(szData, presearch); + if (cp1 == NULL) return false; + } + else + cp1 = szData; + + cp = strstr(cp1, start); + if (cp == NULL) return false; + cp += strlen(start); + while (*cp == ' ') ++cp; + + cp1 = strstr(cp, finish); + if (cp1 == NULL) return false; + while (*(cp1-1) == ' ' && cp1 > cp) --cp1; + + len = min((size_t)(cp1 - cp), size-1); + strncpy(param, cp, len); + param[len] = 0; + + return true; +} + +void parseURL(char* szUrl, char* szHost, unsigned short* sPort, char* szPath) +{ + char *ppath, *phost, *pport; + int sz; + + phost = strstr(szUrl,"://"); + if (phost == NULL) phost = szUrl; + else phost += 3; + + ppath = strchr(phost,'/'); + if (ppath == NULL) ppath = phost + strlen(phost); + + pport = strchr(phost,':'); + if (pport == NULL) pport = ppath; + + if (szHost != NULL) + { + sz = pport - phost + 1; + if (sz>256) sz = 256; + strncpy(szHost, phost, sz); + szHost[sz-1] = 0; + } + + if (sPort != NULL) + { + if (pport < ppath) + { + long prt = atol(pport+1); + *sPort = prt != 0 ? (unsigned short)prt : 80; + } + else + *sPort = 80; + } + + if (szPath != NULL) + { + strncpy(szPath, ppath, 256); + szPath[255] = 0; + } +} + + +static void LongLog(char* szData) +{ + CallService(MS_NETLIB_LOG, 0, (LPARAM)szData); +} + +static void closeRouterConnection(void) +{ + if (sock != INVALID_SOCKET) + { + closesocket(sock); + sock = INVALID_SOCKET; + } +} + +static void validateSocket(void) +{ + static const TIMEVAL tv = { 0, 0 }; + fd_set rfd; + char buf[4]; + bool opened; + + if (sock == INVALID_SOCKET) + return; + + FD_ZERO(&rfd); + FD_SET(sock, &rfd); + + switch (select(1, &rfd, NULL, NULL, &tv)) + { + case SOCKET_ERROR: + opened = false; + break; + + case 0: + opened = true; + break; + + case 1: + opened = recv(sock, buf, 1, MSG_PEEK) > 0; + break; + } + + if (!opened) + closeRouterConnection(); +} + +static int httpTransact(char* szUrl, char* szResult, int resSize, char* szActionName, ReqType reqtype) +{ + // Parse URL + char szHost[256], szPath[256], szRes[16]; + int sz = 0, res = 0; + unsigned short sPort; + bool needClose; + + const char* szPostHdr = soap_post_hdr; + char* szData = ( char* )mir_alloc(4096); + char* szReq = NULL; + + parseURL(szUrl, szHost, &sPort, szPath); + + if (sPort != sConnPort || _stricmp(szHost, szConnHost)) + closeRouterConnection(); + else + validateSocket(); + + for (;;) + { + retryCount = 0; + switch(reqtype) + { + case DeviceGetReq: + sz = mir_snprintf (szData, 4096, xml_get_hdr, szPath, szHost, sPort); + break; + + case ControlAction: + { + char szData1[1024]; + + szReq = mir_strdup(szResult); + sz = mir_snprintf (szData1, sizeof(szData1), + soap_action, szActionName, szDev, szReq, szActionName); + + sz = mir_snprintf (szData, 4096, + szPostHdr, szPath, szHost, sPort, + sz, szDev, szActionName, szData1); + } + break; + + case ControlQuery: + { + char szData1[1024]; + + sz = mir_snprintf (szData1, sizeof(szData1), + soap_query, szActionName); + + sz = mir_snprintf (szData, 4096, + szPostHdr, szPath, szHost, sPort, + sz, "urn:schemas-upnp-org:control-1-0", "QueryStateVariable", szData1); + } + break; + } + szResult[0] = 0; + { + static const TIMEVAL tv = { 6, 0 }; + static unsigned ttl = 4; + static u_long mode = 1; + fd_set rfd, wfd, efd; + SOCKADDR_IN enetaddr; + +retrycon: + if (sock == INVALID_SOCKET) + { + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + enetaddr.sin_family = AF_INET; + enetaddr.sin_port = htons(sPort); + enetaddr.sin_addr.s_addr = inet_addr(szHost); + + // Resolve host name if needed + if (enetaddr.sin_addr.s_addr == INADDR_NONE) + { + PHOSTENT he = gethostbyname(szHost); + if (he) + enetaddr.sin_addr.s_addr = *(unsigned*)he->h_addr_list[0]; + } + + NetlibLogf(NULL, "UPnP HTTP connection Host: %s Port: %u", szHost, sPort); + + FD_ZERO(&rfd); FD_ZERO(&wfd); FD_ZERO(&efd); + FD_SET(sock, &rfd); FD_SET(sock, &wfd); FD_SET(sock, &efd); + + // Limit the scope of the connection (does not work for + setsockopt(sock, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(unsigned)); + + // Put socket into non-blocking mode for timeout on connect + ioctlsocket(sock, FIONBIO, &mode); + + // Connect to the remote host + if (connect(sock, (SOCKADDR*)&enetaddr, sizeof(enetaddr)) == SOCKET_ERROR) + { + int err = WSAGetLastError(); + + // Socket connection failed + if (err != WSAEWOULDBLOCK) + { + closeRouterConnection(); + NetlibLogf(NULL, "UPnP connect failed %d", err); + break; + } + // Wait for socket to connect + else if (select(1, &rfd, &wfd, &efd, &tv) != 1) + { + closeRouterConnection(); + NetlibLogf(NULL, "UPnP connect timeout"); + break; + } + else if (!FD_ISSET(sock, &wfd)) + { + closeRouterConnection(); + NetlibLogf(NULL, "UPnP connect failed"); + break; + } + } + strcpy(szConnHost, szHost); sConnPort = sPort; + } + + if (send(sock, szData, sz, 0) != SOCKET_ERROR) + { + char *hdrend = NULL; + int acksz = 0, pktsz = 0; + + if (szActionName == NULL) + { + int len = sizeof(locIP); + getsockname(sock, (SOCKADDR*)&locIP, &len); + if (locIP.sin_addr.S_un.S_addr == 0x0100007f) + { + struct hostent *he; + + gethostname(szPath, sizeof(szPath)); + he = gethostbyname(szPath); + if (he != NULL) + locIP.sin_addr.S_un.S_addr = *(PDWORD)he->h_addr_list[0]; + } + } + + LongLog(szData); + sz = 0; + for(;;) + { + int bytesRecv; + + FD_ZERO(&rfd); + FD_SET(sock, &rfd); + + // Wait for the next packet + if (select(1, &rfd, NULL, NULL, &tv) != 1) + { + closeRouterConnection(); + NetlibLogf(NULL, "UPnP recieve timeout"); + break; + } + + // + bytesRecv = recv(sock, &szResult[sz], resSize-sz, 0); + + // Connection closed or aborted, all data received + if (bytesRecv == 0 || bytesRecv == SOCKET_ERROR) + { + closeRouterConnection(); + if ((bytesRecv == SOCKET_ERROR || sz == 0) && retryCount < 2) + { + ++retryCount; + goto retrycon; + } + break; + } + + sz += bytesRecv; + + // Insert null terminator to use string functions + if (sz >= (resSize-1)) + { + szResult[resSize-1] = 0; + break; + } + else + szResult[sz] = 0; + + // HTTP header found? + if (hdrend == NULL) + { + // Find HTTP header end + hdrend = strstr(szResult, "\r\n\r\n"); + if (hdrend == NULL) + { + hdrend = strstr(szResult, "\n\n"); + if (hdrend) hdrend += 2; + } + + else + hdrend += 4; + + if (hdrend != NULL) + { + // Get packet size if provided + if (txtParseParam(szResult, NULL, "Content-Length:", "\n", szRes, sizeof(szRes)) || + txtParseParam(szResult, NULL, "CONTENT-LENGTH:", "\n", szRes, sizeof(szRes))) + { + // Add size of HTTP header to the packet size to compute full transmission size + pktsz = atol(ltrimp(szRes)) + (hdrend - szResult); + } + // Get encoding type if provided + else if (txtParseParam(szResult, NULL, "Transfer-Encoding:", "\n", szRes, sizeof(szRes))) + { + if (_stricmp(lrtrimp(szRes), "Chunked") == 0) + acksz = hdrend - szResult; + } + if (txtParseParam(szResult, NULL, "Connection:", "\n", szRes, sizeof(szRes))) + { + needClose = (_stricmp(lrtrimp(szRes), "close") == 0); + } + } + } + + // Content-Length bytes reached, all data received + if (sz >= pktsz && pktsz != 0) + { + szResult[pktsz] = 0; + break; + } + + // Chunked encoding processing + if (sz > acksz && acksz != 0) + { +retry: + // Parse out chunk size + char* data = szResult + acksz; + char* peol1 = data == hdrend ? data - 1 : strchr(data, '\n'); + if (peol1 != NULL) + { + char *peol2 = strchr(++peol1, '\n'); + if (peol2 != NULL) + { + // Get chunk size + int chunkBytes = strtol(peol1, NULL, 16); + acksz += chunkBytes; + peol2++; + + memmove(data, peol2, strlen(peol2) + 1); + sz -= peol2 - data; + + // Last chunk, all data received + if (chunkBytes == 0) break; + if (sz > acksz) goto retry; + } + } + } + } + LongLog(szResult); + } + else + { + if (retryCount < 2) + { + closeRouterConnection(); + ++retryCount; + goto retrycon; + } + else + NetlibLogf(NULL, "UPnP send failed %d", WSAGetLastError()); + } + } + txtParseParam(szResult, "HTTP", " ", " ", szRes, sizeof(szRes)); + res = atol(szRes); + if (szActionName != NULL && res == 405 && szPostHdr == soap_post_hdr) + szPostHdr = soap_post_hdr_m; + else + break; + } + + if (needClose) + closeRouterConnection(); + + mir_free(szData); + mir_free(szReq); + return res; +} + +static unsigned getExtIP(void) +{ + char szExtIP[30]; + char* szData = (char*)mir_alloc(4096); szData[0] = 0; + + unsigned extip = 0; + int res = httpTransact(szCtlUrl, szData, 4096, "GetExternalIPAddress", ControlAction); + if (res == 200 && txtParseParam(szData, "", "<", szExtIP, sizeof(szExtIP))) + extip = ntohl(inet_addr(szExtIP)); + + mir_free(szData); + return extip; +} + +static bool getUPnPURLs(char* szUrl, size_t sizeUrl) +{ + char* szData = (char*)mir_alloc(8192); + + gatewayFound = httpTransact(szUrl, szData, 8192, NULL, DeviceGetReq) == 200; + if (gatewayFound) + { + char szTemp[256], *rpth; + size_t ctlLen; + + txtParseParam(szData, NULL, "", "", szTemp, sizeof(szTemp)); + strncpy(szCtlUrl, szTemp[0] ? szTemp : szUrl, sizeof(szCtlUrl)); + szCtlUrl[sizeof(szCtlUrl)-1] = 0; + + mir_snprintf(szTemp, sizeof(szTemp), search_device, szDev); + txtParseParam(szData, szTemp, "", "", szUrl, sizeUrl); + + // URL combining per RFC 2396 + if ( szUrl[0] != 0 ) + { + if (strstr(szUrl, "://") != NULL) // absolute URI + rpth = szCtlUrl; + else if (strncmp(szUrl, "//", 2) == 0) // relative URI net_path + { + rpth = strstr(szCtlUrl, "//"); + if (rpth == NULL) rpth = szCtlUrl; + } + else if (szUrl[0] == '/') // relative URI abs_path + { + rpth = strstr(szCtlUrl, "//"); + rpth = rpth ? rpth + 2 : szCtlUrl; + + rpth = strchr(rpth, '/'); + if (rpth == NULL) rpth = szCtlUrl + strlen(szCtlUrl); + } + else + { // relative URI rel_path + size_t ctlCLen = strlen(szCtlUrl); + rpth = szCtlUrl + ctlCLen; + if (ctlCLen != 0 && *(rpth-1) != '/') + strncpy(rpth++, "/", sizeof(szCtlUrl) - ctlCLen); + } + + ctlLen = sizeof(szCtlUrl) - (rpth - szCtlUrl); + strncpy(rpth, szUrl, ctlLen); + szCtlUrl[sizeof(szCtlUrl)-1] = 0; + } + else + { + szCtlUrl[0] = 0; + gatewayFound = false; + } + } + mir_free(szData); + + return gatewayFound; +} + + +static void discoverUPnP(void) +{ + char* buf; + int buflen; + unsigned i, j, nip = 0; + unsigned* ips = NULL; + + static const unsigned any = INADDR_ANY; + static const TIMEVAL tv = { 1, 600000 }; + + char szUrl[256] = ""; + char hostname[256]; + PHOSTENT he; + fd_set readfd; + + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + SOCKADDR_IN enetaddr; + enetaddr.sin_family = AF_INET; + enetaddr.sin_port = htons(1900); + enetaddr.sin_addr.s_addr = inet_addr("239.255.255.250"); + + gethostname(hostname, sizeof(hostname)); + he = gethostbyname(hostname); + + if (he) + { + while(he->h_addr_list[nip]) ++nip; + + ips = ( unsigned* )mir_alloc(nip * sizeof(unsigned)); + + for (j = 0; j < nip; ++j) + ips[j] = *(unsigned*)he->h_addr_list[j]; + } + + buf = (char*)mir_alloc(1500); + + for(i = 3; --i && szUrl[0] == 0;) + { + for (j = 0; j < nip; ++j) + { + if (ips) + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&ips[j], sizeof(unsigned)); + + buflen = mir_snprintf(buf, 1500, search_request_msg, "WANIPConnection:1"); + sendto(sock, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr)); + LongLog(buf); + + buflen = mir_snprintf(buf, 1500, search_request_msg, "WANPPPConnection:1"); + sendto(sock, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr)); + LongLog(buf); + } + + if (Miranda_Terminated()) break; + + FD_ZERO(&readfd); + FD_SET(sock, &readfd); + + while (select(1, &readfd, NULL, NULL, &tv) >= 1) + { + buflen = recv(sock, buf, 1500, 0); + if (buflen != SOCKET_ERROR) + { + buf[buflen] = 0; + LongLog(buf); + + if (txtParseParam(buf, NULL, "LOCATION:", "\n", szUrl, sizeof(szUrl)) || + txtParseParam(buf, NULL, "Location:", "\n", szUrl, sizeof(szUrl))) + { + char age[30]; + char szHostNew[256], szHostExist[256]; + + lrtrim(szUrl); + + parseURL(szUrl, szHostNew, NULL, NULL); + parseURL(szCtlUrl, szHostExist, NULL, NULL); + if (strcmp(szHostNew, szHostExist) == 0) + { + gatewayFound = true; + break; + } + + txtParseParam(buf, NULL, "ST:", "\n", szDev, sizeof(szDev)); + txtParseParam(buf, "max-age", "=", "\n", age, sizeof(age)); + expireTime = atoi(lrtrimp(age)); + lrtrim(szDev); + + if (getUPnPURLs(szUrl, sizeof(szUrl))) + { + gatewayFound = getExtIP() != 0; + if (gatewayFound) break; + } + } + } + FD_ZERO(&readfd); + FD_SET(sock, &readfd); + } + } + + mir_free(buf); + mir_free(ips); + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&any, sizeof(unsigned)); + closesocket(sock); +} + + +static bool findUPnPGateway(void) +{ + if ((time(NULL) - lastDiscTime) >= expireTime) + { + WaitForSingleObject(portListMutex, INFINITE); + + time_t curTime = time(NULL); + + if ((curTime - lastDiscTime) >= expireTime) + { + gatewayFound = false; + + discoverUPnP(); + lastDiscTime = curTime; + + NetlibLogf(NULL, "UPnP Gateway detected %d, Control URL: %s", gatewayFound, szCtlUrl); + } + + ReleaseMutex(portListMutex); + } + + return gatewayFound; +} + +bool NetlibUPnPAddPortMapping(WORD intport, char *proto, WORD *extport, DWORD *extip, bool search) +{ + int res = 0, i = 5; + + if (findUPnPGateway()) + { + char* szData = (char*)mir_alloc(4096); + char szExtIP[30]; + + *extport = intport - 1; + *extip = ntohl(locIP.sin_addr.S_un.S_addr); + + WaitForSingleObject(portListMutex, INFINITE); + + do + { + ++*extport; + mir_snprintf(szData, 4096, add_port_mapping, + *extport, proto, intport, inet_ntoa(locIP.sin_addr)); + res = httpTransact(szCtlUrl, szData, 4096, "AddPortMapping", ControlAction); + txtParseParam(szData, NULL, "", "", szExtIP, sizeof(szExtIP)); + + } + while (search && res == 500 && atol(szExtIP) == 718 && --i); + + mir_free(szData); + + if (res == 200) + { + unsigned ip = getExtIP(); + if (ip) *extip = ip; + + if (numports >= numportsAlloc) + mir_realloc(portList, sizeof(WORD)*(numportsAlloc += 10)); + portList[numports++] = *extport; + } + + ReleaseMutex(portListMutex); + } + + return res == 200; +} + +void NetlibUPnPDeletePortMapping(WORD extport, char* proto) +{ + if (extport == 0) + return; + + // findUPnPGateway(); + + if (gatewayFound) + { + unsigned i; + char* szData = (char*)mir_alloc(4096); + + WaitForSingleObject(portListMutex, INFINITE); + mir_snprintf(szData, 4096, delete_port_mapping, extport, proto); + httpTransact(szCtlUrl, szData, 4096, "DeletePortMapping", ControlAction); + + for (i = 0; i < numports; ++i) + if (portList[i] == extport && --numports > 0) + memmove(&portList[i], &portList[i+1], (numports - i) * sizeof(WORD)); + + mir_free(szData); + ReleaseMutex(portListMutex); + } +} + +void NetlibUPnPCleanup(void*) +{ + if (DBGetContactSettingByte(NULL,"Netlib","NLEnableUPnP",1)==0) + // upnp is disabled globally, no need for a cleanup + return; + + { + int i, incoming = 0; + EnterCriticalSection(&csNetlibUser); + for (i = 0; i < netlibUser.getCount(); ++i) + { + if (netlibUser[i]->user.flags & NUF_INCOMING) + { + incoming = 1; + break; + } + } + LeaveCriticalSection(&csNetlibUser); + if (!incoming) return; + } + + if (findUPnPGateway()) + { + char* szData = (char*)alloca(4096); + char buf[50], lip[50]; + unsigned i, j = 0, k, num = 100; + + WORD ports[30]; + + strcpy(lip, inet_ntoa(locIP.sin_addr)); + + WaitForSingleObject(portListMutex, INFINITE); + + if (httpTransact(szCtlUrl, szData, 4096, "PortMappingNumberOfEntries", ControlQuery) == 200 && + txtParseParam(szData, "QueryStateVariableResponse", "", "<", buf, sizeof(buf))) + num = atol(buf); + + for (i=0; i", "<", buf, sizeof(buf)) || strcmp(buf, "Miranda") != 0) + continue; + + if (!txtParseParam(szData, "", "<", buf, sizeof(buf)) || strcmp(buf, lip) != 0) + continue; + + if (txtParseParam(szData, "", "<", buf, sizeof(buf))) + { + WORD mport = (WORD)atol(buf); + + if (j >= SIZEOF(ports)) + break; + + for (k=0; k= numports) + ports[j++] = mport; + } + } + + ReleaseMutex(portListMutex); + + for (i=0; i