In C++, I can't find a way to appropriately terminate a WMI session with a remote server. Any attempt to release the IWbemServices
pointer throws an exception; the TCP connection to the server remains established until the process exits (it's open after the last CoUninitialize
call). This problem (thrown exception) does not occur when connecting to the local machine.
I've looked at a similar question asked here, but the solution from Microsoft (retrieving the IUnknown
pointer and releasing it first) didn't solve the issue.
Here's the code (error checking has been omitted for readability):
HRESULT hRes = S_OK;
IWbemLocator* pWbemLocator = NULL;
IWbemServices* pWbemServices = NULL;
// these three pointers are already initialized...
PWCHAR wcUser; // L"theuser"
PWCHAR wcPass; // L"thepassword"
PWCHAR wcAuth; // L"ntlmdomain:THEDOMAIN"
std::wstring wstrNsPath = L"\\\\remoteserver\\ROOT\\CIMV2";
hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pWbemLocator);
// this returns S_OK :)
hRes = pWbemLocator->ConnectServer((BSTR)wstrNsPath.c_str(),
(BSTR)wcUser,
(BSTR)wcPass,
NULL,
WBEM_FLAG_CONNECT_USE_MAX_WAIT,
(BSTR)wcAuth,
NULL,
&pWbemServices);
hRes = CoSetProxyBlanket(pWbemServices,
RPC_C_AUTHN_DEFAULT,
RPC_C_AUTHZ_DEFAULT,
COLE_DEFAULT_PRINCIPAL,
RPC_C_IMP_LEVEL_IMPERSONATE,
RPC_C_AUTHN_LEVEL_DEFAULT,
NULL, // domain info already specified in 'wcAuth'
EOAC_NONE);
// do some queries..
// cleanup
pWbemServices->Release(); // on remote sessions, this throws an exception
pWbemLocator->Release();
CoUninitialize();
The exception is displayed in the debug output (Visual Studio):
onecore\com\combase\dcomrem\call.cxx(1234)\combase.dll!0000ABCDEFABCDEF: (caller: 0000FEDCBAFEDCBA) ReturnHr(1) tid(4321) 80070005 Access is denied.
Is this expected behavior? Should the connection between the client and server not be terminated once the session is released?
I attempted to follow MSDN's advice and added the following after the CoSetProxyBlanket()
call in the code above. It didn't change anything.
IUnknown* pUnknown = NULL;
pWbemServices->QueryInterface(IID_IUnknown, (LPVOID*)&pUnknown);
if (pUnknown)
{
hRes = CoSetProxyBlanket(pUnknown,
RPC_C_AUTHN_DEFAULT,
RPC_C_AUTHZ_DEFAULT,
COLE_DEFAULT_PRINCIPAL,
RPC_C_IMP_LEVEL_IMPERSONATE,
RPC_C_AUTHN_LEVEL_DEFAULT,
NULL,
EOAC_NONE);
pUnknown->Release();
}
Any advice is greatly appreciated!
EDIT So after capturing session packets, it would appear that setting the proxy security with pAuthInfo == NULL
causes the request to be made by the current logged on user of the client machine. It ignores the credentials that I provided when calling ConnectServer
. I'm aware that the COAUTHIDENTITY
structure allows you to pass the correct credentials to CoSetProxyBlanket
, but I'd like to avoid having to input the domain as a separate variable. In other words, is there a way that this information can be extracted using the wcAuth
when the request is made to a remote server? If so, how could I distinguish local vs. remote requests?
Here is the output from Wireshark that led me to believe this is the problem (see packet 1617):
No. Time Source Destination Protocol Length Info
1615 162.221354 [CLIENT_IP] [SERVER_IP] DCERPC 174 Alter_context: call_id: 8, Fragment: Single, 1 context items: IRemUnknown2 V0.0 (32bit NDR), NTLMSSP_NEGOTIATE
1616 162.228517 [SERVER_IP] [CLIENT_IP] DCERPC 366 Alter_context_resp: call_id: 8, Fragment: Single, max_xmit: 5840 max_recv: 5840, 1 results: Acceptance, NTLMSSP_CHALLENGE
1617 162.229396 [CLIENT_IP] [SERVER_IP] DCERPC 612 AUTH3: call_id: 8, Fragment: Single, NTLMSSP_AUTH, User: .\[client_user]
1618 162.229495 [CLIENT_IP] [SERVER_IP] IRemUnknown2 182 RemRelease request Cnt=1 Refs=5-0
1619 162.235567 [SERVER_IP] [CLIENT_IP] TCP 60 49669 → 59905 [ACK] Seq=1606 Ack=4339 Win=64768 Len=0
1620 162.235567 [SERVER_IP] [CLIENT_IP] DCERPC 86 Fault: call_id: 8, Fragment: Single, Ctx: 0, status: nca_s_fault_access_denied
I was able to resolve the issue. If you are not using the current logged-on user token, you must specify the pAuthInfo
parameter of CoSetProxyBlanket
to a valid COAUTHIDENITY
structure pointer. The docs for IWbemLocator::ConnectServer
actually state that it's best practice to include the domain in the strUser
parameter... and that if you do so, you must pass the authority string as NULL
.
One thing to note is that if ConnectServer
succeeds, you don't have to go crazy with the sanitizing of the username string for correctness; the login either worked or it didn't (and breaks/throws an exception, depending on how you handle errors). In other words, just search the string for the domain delimiters ('\\'
or '@'
) and split them appropriately into domain name and username.