/*++ Copyright (C) 1998-2001 Microsoft Corporation Module Name: COMTRANS.CPP Abstract: Connects via COM History: a-davj 13-Jan-98 Created. --*/ #include "precomp.h" #include #include #include #include #include #include #include "wbemprox.h" #include "comtrans.h" #include #include #include #include #include #include #include // The following should not be changed since 9x boxes to not support privacy. #define AUTH_LEVEL RPC_C_AUTHN_LEVEL_DEFAULT class CSocketInit { private: bool m_bInitDone; public: CSocketInit() : m_bInitDone(false){}; int Init(); ~CSocketInit(){if(m_bInitDone) WSACleanup ();}; }; int CSocketInit::Init() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 2 ); int iRet = WSAStartup (wVersionRequested, & wsaData); if(iRet == 0) m_bInitDone = true; return iRet; } BOOL bGotDot(char * pTest) { if(pTest == NULL) return FALSE; for(;*pTest && *pTest != '.'; pTest++); // intentional semi if(*pTest == '.') return TRUE; else return FALSE; } struct hostent * GetFQDN(WCHAR * pServer) { SIZE_T Len = wcslen(pServer); SIZE_T LenAnsi = 4*Len; wmilib::auto_buffer pAnsiServerName(new CHAR[LenAnsi+1]); ULONG BytesCopyed = 0; // // Use the same routine that RPCRT4 uses // NTSTATUS Status = RtlUnicodeToMultiByteN(pAnsiServerName.get(),LenAnsi,&BytesCopyed,pServer,Len*sizeof(WCHAR)); if (0 != Status) return NULL; pAnsiServerName[BytesCopyed] = 0; // if it is an ip string long lIP = inet_addr(pAnsiServerName.get()); if(lIP != INADDR_NONE) { struct hostent * pRet = gethostbyaddr((char *)&lIP, 4, AF_INET ); if(pRet && pRet->h_name) { // search the returned name for at least one dot. Sometimes, gethostbyaddr will just return // the lanman name and not the fqdn. if(bGotDot(pRet->h_name)) return pRet; // normal case, all is well! // try passing the short name to get the fqdn version DWORD dwLen = lstrlenA(pRet->h_name) + 1; char * pNew = new char[dwLen]; if(pNew == NULL) return NULL; CVectorDeleteMe dm2(pNew); StringCchCopyA(pNew, dwLen, pRet->h_name); pRet = gethostbyname(pNew); if(pRet && bGotDot(pRet->h_name)) return pRet; // normal case, all is well! } } return gethostbyname(pAnsiServerName.get()); } #define PREFIXSTR L"RPCSS/" HRESULT BuildReturnString(WCHAR * pFQDN, WCHAR ** ppResult) { if(pFQDN == NULL) return WBEM_E_INVALID_PARAMETER; DWORD dwBuffLen = wcslen(pFQDN) + wcslen(PREFIXSTR) + 1; *ppResult = new WCHAR[dwBuffLen]; if(*ppResult == NULL) return WBEM_E_OUT_OF_MEMORY; StringCchCopy(*ppResult, dwBuffLen, PREFIXSTR); StringCchCat(*ppResult, dwBuffLen, pFQDN); return S_OK; } HRESULT GetPrincipal(WCHAR * pServerMachine, WCHAR ** ppResult, BOOL &bLocal, CSocketInit & sock) { DWORD dwLocalFQDNLen = 0; DWORD dwBuffLen; WCHAR * pwsCurrentCompFQDN = NULL; bLocal = FALSE; *ppResult = NULL; // Get the current computer name in FQDN format BOOL bRet = GetComputerNameEx(ComputerNameDnsFullyQualified, NULL, &dwLocalFQDNLen); if(bRet || GetLastError() != ERROR_MORE_DATA) return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32,GetLastError()); dwLocalFQDNLen++; // add one for the null pwsCurrentCompFQDN = new WCHAR[dwLocalFQDNLen]; if(pwsCurrentCompFQDN == NULL) return WBEM_E_OUT_OF_MEMORY; CVectorDeleteMe dm(pwsCurrentCompFQDN); bRet = GetComputerNameEx(ComputerNameDnsFullyQualified, pwsCurrentCompFQDN, &dwLocalFQDNLen); if(!bRet) return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32,GetLastError()); // if the name is "." or equal to the current machine, no need to do much fancy work here if(bAreWeLocal ( pServerMachine )) { bLocal = TRUE; return BuildReturnString(pwsCurrentCompFQDN, ppResult); } // probably not local. Use sockets to establish the FQDN of the server if(0 != sock.Init()) return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32,GetLastError()); struct hostent * pEnt = GetFQDN(pServerMachine); if(pEnt == NULL || pEnt->h_name == NULL) { // we failed. just return the best we can return BuildReturnString(pServerMachine, ppResult); } // all is well. Convert the host name to WCHAR. DWORD dwHostLen = lstrlenA(pEnt->h_name) + 1; WCHAR * pwsHostFQDN = new WCHAR[dwHostLen]; if(pwsHostFQDN == NULL) return WBEM_E_OUT_OF_MEMORY; CVectorDeleteMe dm2(pwsHostFQDN); mbstowcs(pwsHostFQDN, pEnt->h_name, dwHostLen); // now there is the possibility that they specified the ip of the local machine. // In that case, set the bLocal in case caller needs to know this if(wbem_wcsicmp(pwsHostFQDN, pwsCurrentCompFQDN) == 0) bLocal = TRUE; // now, make the actual string. return BuildReturnString(pwsHostFQDN, ppResult); } //*************************************************************************** // // CDCOMTrans::CDCOMTrans // // DESCRIPTION: // // Constructor. // //*************************************************************************** CDCOMTrans::CDCOMTrans() { m_cRef=0; m_pLevel1 = NULL; InterlockedIncrement(&g_cObj); m_bInitialized = TRUE; } //*************************************************************************** // // CDCOMTrans::~CDCOMTrans // // DESCRIPTION: // // Destructor. // //*************************************************************************** CDCOMTrans::~CDCOMTrans(void) { if(m_pLevel1) m_pLevel1->Release(); InterlockedDecrement(&g_cObj); } //*************************************************************************** // HRESULT CDCOMTrans::QueryInterface // long CDCOMTrans::AddRef // long CDCOMTrans::Release // // DESCRIPTION: // // Standard Com IUNKNOWN functions. // //*************************************************************************** STDMETHODIMP CDCOMTrans::QueryInterface ( IN REFIID riid, OUT PPVOID ppv ) { *ppv=NULL; if (m_bInitialized && (IID_IUnknown==riid || riid == IID_IWbemClientTransport)) *ppv=(IWbemClientTransport *)this; if (NULL!=*ppv) { ((LPUNKNOWN)*ppv)->AddRef(); return NOERROR; } return ResultFromScode(E_NOINTERFACE); } bool IsImpersonating(SECURITY_IMPERSONATION_LEVEL &impLevel) { HANDLE hThreadToken; bool bImpersonating = false; if(OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken)) { DWORD dwBytesReturned = 0; if(GetTokenInformation( hThreadToken, TokenImpersonationLevel, &impLevel, sizeof(DWORD), &dwBytesReturned ) && ((SecurityImpersonation == impLevel) || (SecurityDelegation == impLevel))) bImpersonating = true; CloseHandle(hThreadToken); } return bImpersonating; } //*************************************************************************** // // IsLocalConnection(IWbemLevel1Login * pLogin) // // DESCRIPTION: // // Querries the server to see if this is a local connection. This is done // by creating a event and asking the server to set it. This will only work // if the server is the same box. // // RETURN VALUE: // // true if the server is the same box. // //*************************************************************************** BOOL IsLocalConnection(IUnknown * pInterface) { IRpcOptions *pRpcOpt = NULL; ULONG_PTR dwProperty = 0; HRESULT hr = pInterface->QueryInterface(IID_IRpcOptions, (void**)&pRpcOpt); //DbgPrintfA(0,"QueryInterface(IID_IRpcOptions) hr = %08x\n",hr); if (SUCCEEDED(hr)) { hr = pRpcOpt->Query(pInterface, COMBND_SERVER_LOCALITY, &dwProperty); pRpcOpt->Release(); if (SUCCEEDED(hr)) return (SERVER_LOCALITY_REMOTE == dwProperty)?FALSE:TRUE; } else if (E_NOINTERFACE == hr) // real pointer, not a proxy { return TRUE; } return FALSE; } //*************************************************************************** // // SetClientIdentity // // DESCRIPTION: // // Passes the machine name and process id to the server. Failure is not // serious since this is debugging type info in any case. // //*************************************************************************** void SetClientIdentity(IUnknown * pLogin, bool bSet, BSTR PrincipalArg, DWORD dwAuthenticationLevel, COAUTHIDENTITY *pauthident, DWORD dwCapabilities, DWORD dwAuthnSvc) { bool bRet = false; IWbemLoginClientID * pLoginHelper = NULL; SCODE sc = pLogin->QueryInterface(IID_IWbemLoginClientID, (void **)&pLoginHelper); if(sc != S_OK) return; if(bSet) sc = WbemSetProxyBlanket( pLoginHelper, dwAuthnSvc, RPC_C_AUTHZ_NONE, PrincipalArg, dwAuthenticationLevel, RPC_C_IMP_LEVEL_IMPERSONATE, pauthident, dwCapabilities); CReleaseMe rm(pLoginHelper); TCHAR tcMyName[MAX_COMPUTERNAME_LENGTH + 1]; DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1; if(!GetComputerName(tcMyName,&dwSize)) return; long lProcID = GetCurrentProcessId(); pLoginHelper->SetClientInfo(tcMyName, lProcID, 0); } SCODE CDCOMTrans::DoConnection( BSTR NetworkResource, BSTR User, BSTR Password, BSTR Locale, long lFlags, BSTR Authority, IWbemContext *pCtx, IWbemServices **pInterface) { HRESULT hr = DoActualConnection(NetworkResource, User, Password, Locale, lFlags, Authority, pCtx, pInterface); if(hr == 0x800706be) { ERRORTRACE((LOG_WBEMPROX,"Initial connection failed with 0x800706be, retrying\n")); Sleep(5000); hr = DoActualConnection(NetworkResource, User, Password, Locale, lFlags, Authority, pCtx, pInterface); } return hr; } SCODE CDCOMTrans::DoActualConnection( BSTR NetworkResource, BSTR User, BSTR Password, BSTR Locale, long lFlags, BSTR Authority, IWbemContext *pCtx, IWbemServices **pInterface) { BSTR AuthArg = NULL, UserArg = NULL; // this is the pricipal as extracted from the optional Authority argument BSTR PrincipalArg = NULL; // this is the pricipal which is calculated from the server name in the path WCHAR * pwCalculatedPrincipal = NULL; bool bAuthenticate = true; bool bSet = false; CSocketInit sock; SCODE sc = WBEM_E_FAILED; sc = DetermineLoginTypeEx(AuthArg, UserArg, PrincipalArg, Authority, User); if(sc != S_OK) { ERRORTRACE((LOG_WBEMPROX, "Cannot determine Login type, Authority = %S, User = %S\n",Authority, User)); return sc; } CSysFreeMe fm1(AuthArg); CSysFreeMe fm2(UserArg); CSysFreeMe fm3(PrincipalArg); // Determine if it is local WCHAR *t_ServerMachine = ExtractMachineName ( NetworkResource ) ; if ( t_ServerMachine == NULL ) { ERRORTRACE((LOG_WBEMPROX, "Cannot extract machine name -%S-\n", NetworkResource)); return WBEM_E_INVALID_PARAMETER ; } CVectorDeleteMe dm(t_ServerMachine); BOOL t_Local; if(PrincipalArg == NULL) { sc = GetPrincipal(t_ServerMachine, &pwCalculatedPrincipal, t_Local, sock); if(FAILED(sc)) { t_Local = bAreWeLocal(t_ServerMachine); ERRORTRACE((LOG_WBEMPROX, "GetPrincipal(%S) hr = %08x\n",t_ServerMachine,sc)); } else { DEBUGTRACE((LOG_WBEMPROX, "Using the principal -%S-\n", pwCalculatedPrincipal)); } } else t_Local = bAreWeLocal(t_ServerMachine); CVectorDeleteMe dm2(pwCalculatedPrincipal); SECURITY_IMPERSONATION_LEVEL impLevel = SecurityImpersonation; bool bImpersonatingThread = IsImpersonating (impLevel); bool bCredentialsSpecified = (UserArg || AuthArg || Password); // Setup the authentication structures COSERVERINFO si; si.pwszName = t_ServerMachine; si.dwReserved1 = 0; si.dwReserved2 = 0; si.pAuthInfo = NULL; COAUTHINFO ai; si.pAuthInfo = &ai; ai.dwAuthzSvc = RPC_C_AUTHZ_NONE; if(PrincipalArg) { ai.dwAuthnSvc = RPC_C_AUTHN_GSS_KERBEROS; ai.pwszServerPrincName = PrincipalArg; } else if (pwCalculatedPrincipal) { ai.dwAuthnSvc = RPC_C_AUTHN_GSS_NEGOTIATE; ai.pwszServerPrincName = pwCalculatedPrincipal; } else { ai.dwAuthnSvc = RPC_C_AUTHN_WINNT; ai.pwszServerPrincName = NULL; } ai.dwAuthnLevel = AUTH_LEVEL; ai.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; ai.dwCapabilities = 0; COAUTHIDENTITY authident; if(bCredentialsSpecified) { // Load up the structure. memset((void *)&authident,0,sizeof(COAUTHIDENTITY)); if(UserArg) { authident.UserLength = wcslen(UserArg); authident.User = (LPWSTR)UserArg; } if(AuthArg) { authident.DomainLength = wcslen(AuthArg); authident.Domain = (LPWSTR)AuthArg; } if(Password) { authident.PasswordLength = wcslen(Password); authident.Password = (LPWSTR)Password; } authident.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; ai.pAuthIdentityData = &authident; } else ai.pAuthIdentityData = NULL; // Get the IWbemLevel1Login pointer sc = DoCCI(&si ,t_Local, lFlags); if((sc == 0x800706d3 || sc == 0x800706ba) && !t_Local) { // If we are going to a stand alone dcom box, try again with the authentication level lowered ai.dwAuthnLevel = RPC_C_AUTHN_LEVEL_NONE; SCODE hr = DoCCI(&si ,t_Local, lFlags); if(hr == S_OK) { sc = S_OK; bAuthenticate = false; } } if(sc != S_OK) return sc; // Set the values used for CoSetProxyBlanket calls. If the principal was passed in via the Authority // argument, then it is used and kerberos is forced. Otherwise, the values will be set based on // querying the Proxy it will be either NULL (if NTLM is used) or COLE_DEFAULT_PRINCIPAL. DWORD dwAuthnSvc = RPC_C_AUTHN_WINNT; WCHAR * pwCSPBPrincipal = NULL; if(PrincipalArg) { dwAuthnSvc = RPC_C_AUTHN_GSS_KERBEROS; pwCSPBPrincipal = PrincipalArg; } else { DWORD dwQueryAuthnLevel, dwQueryImpLevel, dwQueryCapabilities; HRESULT hr = CoQueryProxyBlanket( m_pLevel1, //Location for the proxy to query &dwAuthnSvc, //Location for the current authentication service NULL, //Location for the current authorization service NULL, //Location for the current principal name &dwQueryAuthnLevel, //Location for the current authentication level &dwQueryImpLevel, //Location for the current impersonation level NULL, &dwQueryCapabilities //Location for flags indicating further capabilities of the proxy ); if(SUCCEEDED(hr) && dwAuthnSvc != RPC_C_AUTHN_WINNT) { pwCSPBPrincipal = COLE_DEFAULT_PRINCIPAL; } else { dwAuthnSvc = RPC_C_AUTHN_WINNT; pwCSPBPrincipal = NULL; } } // The authentication level is set based on having to go to a share level box or not. The // capabilities are set based on if we are an impersonating thread or not DWORD dwAuthenticationLevel, dwCapabilities; if(bAuthenticate) dwAuthenticationLevel = AUTH_LEVEL; else dwAuthenticationLevel = RPC_C_AUTHN_LEVEL_NONE; if(bImpersonatingThread && !UserArg) dwCapabilities = EOAC_STATIC_CLOAKING; else dwCapabilities = EOAC_NONE; // Do the security negotiation if(!t_Local) { // Suppress the SetBlanket call if we are on a Win2K delegation-level thread with implicit credentials if (!(bImpersonatingThread && !bCredentialsSpecified && (SecurityDelegation == impLevel))) { // Note that if we are on a Win2K impersonating thread with no user specified // we should allow DCOM to use whatever EOAC capabilities are set up for this // application. This allows remote connections with NULL User/Password but // non-NULL authority to succeed. sc = WbemSetProxyBlanket( m_pLevel1, dwAuthnSvc, RPC_C_AUTHZ_NONE, pwCSPBPrincipal, dwAuthenticationLevel, RPC_C_IMP_LEVEL_IMPERSONATE, (bCredentialsSpecified) ? &authident : NULL, dwCapabilities); bSet = true; if(sc != S_OK) { ERRORTRACE((LOG_WBEMPROX,"Error setting Level1 login interface security pointer, return code is 0x%x\n", sc)); return sc; } } } else // LOCAL case { // if impersonating set cloaking if(bImpersonatingThread) { sc = WbemSetProxyBlanket( m_pLevel1, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, dwAuthenticationLevel, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING); if(sc != S_OK && sc != 0x80004002) // no such interface is ok since you get that when // called inproc! { ERRORTRACE((LOG_WBEMPROX,"Error setting Level1 login interface security pointer, return code is 0x%x\n", sc)); return sc; } } } SetClientIdentity(m_pLevel1, bSet, PrincipalArg, dwAuthenticationLevel, &authident, dwCapabilities, dwAuthnSvc); if(bCredentialsSpecified && IsLocalConnection(m_pLevel1)) { ERRORTRACE((LOG_WBEMPROX,"Credentials were specified for a local connections\n")); return WBEM_E_LOCAL_CREDENTIALS; } // The MAX_WAIT flag only applies to CoCreateInstanceEx, get rid of it lFlags = lFlags & ~WBEM_FLAG_CONNECT_USE_MAX_WAIT; sc = m_pLevel1->NTLMLogin(NetworkResource, Locale, lFlags, pCtx,(IWbemServices**) pInterface); if(sc == 0x800706d3 && !t_Local) // RPC_S_UNKNOWN_AUTHN_SERVICE { // If we are going to a stand alone dcom box, try again with the authentication level lowered ERRORTRACE((LOG_WBEMPROX,"Attempt to connect to %S returned RPC_S_UNKNOWN_AUTHN_SERVICE\n",NetworkResource)); HRESULT hr; hr = SetInterfaceSecurityAuth(m_pLevel1, &authident, false); if (SUCCEEDED(hr)) hr = m_pLevel1->NTLMLogin(NetworkResource, Locale, lFlags, pCtx, (IWbemServices**)pInterface); if(hr == S_OK) { SetInterfaceSecurityAuth((IUnknown *)*pInterface, &authident, false); } } else if(SUCCEEDED(sc) && bAuthenticate == false && !t_Local) { // this is used to support share level boxs. The scripting code is written to expect that // the IWbemServices pointer is ready to use and so it must be lowered before returning. WbemSetProxyBlanket(*pInterface, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE); } if(FAILED(sc)) ERRORTRACE((LOG_WBEMPROX,"NTLMLogin resulted in hr = 0x%x\n", sc)); return sc; } struct WaitThreadArg { DWORD m_dwThreadId; HANDLE m_hTerminate; }; DWORD WINAPI TimeoutThreadRoutine(LPVOID lpParameter) { WaitThreadArg * pReq = (WaitThreadArg *)lpParameter; DWORD dwRet = WaitForSingleObject(pReq->m_hTerminate, 60000); if(dwRet == WAIT_TIMEOUT) { HRESULT hr = CoInitializeEx(NULL,COINIT_MULTITHREADED ); if(FAILED(hr)) return 1; ICancelMethodCalls * px = NULL; hr = CoGetCancelObject(pReq->m_dwThreadId, IID_ICancelMethodCalls, (void **)&px); if(SUCCEEDED(hr)) { hr = px->Cancel(0); px->Release(); } CoUninitialize(); } return 0; } //*************************************************************************** // // DoCCI // // DESCRIPTION: // // Connects up to WBEM via DCOM. But before making the call, a thread cancel // thread may be created to handle the case where we try to connect up // to a box which is hanging // // PARAMETERS: // // NetworkResource Namespze path // ppLogin set to Login proxy // bLocal Indicates if connection is local // lFlags Mainly used for WBEM_FLAG_CONNECT_USE_MAX_WAIT flag // // RETURN VALUE: // // S_OK all is well // else error listed in WBEMSVC.H // //*************************************************************************** SCODE CDCOMTrans::DoCCI (IN COSERVERINFO * psi, IN BOOL bLocal, long lFlags ) { if(lFlags & WBEM_FLAG_CONNECT_USE_MAX_WAIT) { // special case. we want to spawn off a thread that will kill of our // request if it takes too long WaitThreadArg arg; arg.m_hTerminate = CreateEvent(NULL, FALSE, FALSE, NULL); if(arg.m_hTerminate == NULL) return WBEM_E_OUT_OF_MEMORY; CCloseMe cm(arg.m_hTerminate); arg.m_dwThreadId = GetCurrentThreadId(); DWORD dwIDLikeIcare; HRESULT hr = CoEnableCallCancellation(NULL); if(FAILED(hr)) return hr; HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)TimeoutThreadRoutine, (LPVOID)&arg, 0, &dwIDLikeIcare); if(hThread == NULL) { CoDisableCallCancellation(NULL); return WBEM_E_OUT_OF_MEMORY; } CCloseMe cm2(hThread); hr = DoActualCCI (psi, bLocal, lFlags & ~WBEM_FLAG_CONNECT_USE_MAX_WAIT ); CoDisableCallCancellation(NULL); SetEvent(arg.m_hTerminate); WaitForSingleObject(hThread, INFINITE); return hr; } else return DoActualCCI (psi, bLocal, lFlags ); } //*************************************************************************** // // DoActualCCI // // DESCRIPTION: // // Connects up to WBEM via DCOM. // // PARAMETERS: // // NetworkResource Namespze path // ppLogin set to Login proxy // bLocal Indicates if connection is local // lFlags Not used // // RETURN VALUE: // // S_OK all is well // else error listed in WBEMSVC.H // //*************************************************************************** SCODE CDCOMTrans::DoActualCCI (IN COSERVERINFO * psi, IN BOOL bLocal, long lFlags ) { HRESULT t_Result ; MULTI_QI mqi; mqi.pIID = &IID_IWbemLevel1Login; mqi.pItf = 0; mqi.hr = 0; t_Result = CoCreateInstanceEx ( CLSID_WbemLevel1Login, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, ( bLocal ) ? NULL : psi , 1, &mqi ); if ( t_Result == S_OK ) { m_pLevel1 = (IWbemLevel1Login*) mqi.pItf ; DEBUGTRACE((LOG_WBEMPROX,"ConnectViaDCOM, CoCreateInstanceEx resulted in hr = 0x%x\n", t_Result )); } else { ERRORTRACE((LOG_WBEMPROX,"ConnectViaDCOM, CoCreateInstanceEx resulted in hr = 0x%x\n", t_Result )); } return t_Result ; }