I am trying to connect to a DCOM object on a remote machine which is logged in as a different user from a C# application. I have gotten my code working to connect to the same DCOM object while it is running on the same computer, like so:
MyDCOMType dcomObject;
Type remoteSessionContextType = Type.GetTypeFromProgID("ServerApp.MyDCOMType");
dcomObject = (MyDCOMType)Activator.CreateInstance(remoteSessionContextType);
string version = dcomObject.GetVersion();
MyDCOMType is a type implemented in the DCOM application (a VB6 app) that I added as a reference in my project. I have been able to create an instance of it in the C# code and call all of the methods with the expected results. Now I am trying to get it to connect to the same object on the remote machine. I am attempting to impersonate the user on the remote system like so (variable definitions and error handling omitted):
if (LogonUser(
userName,
domain,
password,
LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_WINNT50,
out token) != false)
{
if (DuplicateToken(token, (int)_SECURITY_IMPERSONATION_LEVEL.SecurityDelegation, out tokenDuplicate) != false)
{
impersonationContext = WindowsIdentity.Impersonate(tokenDuplicate.DangerousGetHandle());
}
}
Which is then followed by similar code to create the object:
MyDCOMType dcomObject;
Type remoteSessionContextType = Type.GetTypeFromProgID("ServerApp.MyDCOMType", ipAddress);
dcomObject = (MyDCOMType)Activator.CreateInstance(remoteSessionContextType);
string version = dcomObject.GetVersion();
Except that the CreateInstance call throws an UnauthorizedAccessException error with error code 80070005 for unauthorized access.
The other thing is that I have C++ code that does the same thing that works perfectly. That code goes like so:
COAUTHINFO AuthInfo;
COAUTHIDENTITY AuthIdentity;
COSERVERINFO ServerInfo;
MULTI_QI Results;
HRESULT hr;
BSTR version;
_MyDCOMType *pSession = NULL;
AuthIdentity.Domain = (unsigned short *) w_domain;
AuthIdentity.DomainLength = wcslen( w_domain);
AuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
AuthIdentity.Password = (unsigned short *) w_password;
AuthIdentity.PasswordLength = wcslen(w_password);
AuthIdentity.User = (unsigned short *) w_username;
AuthIdentity.UserLength = wcslen(w_username);
AuthInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CALL;
AuthInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT;
AuthInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE;
AuthInfo.dwCapabilities = EOAC_NONE;
AuthInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
AuthInfo.pAuthIdentityData = &AuthIdentity;
AuthInfo.pwszServerPrincName = NULL;
ServerInfo.dwReserved1 = 0;
ServerInfo.dwReserved2 = 0;
ServerInfo.pAuthInfo = &AuthInfo;
ServerInfo.pwszName = w_nodename;
hr = CoCreateInstanceEx(clsid, NULL, CLSCTX_ALL, &ServerInfo, (ULONG) 1, &Results);
hr = CoSetProxyBlanket(Results.pItf, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, &AuthIdentity, EOAC_NONE);
pSession = (_MyDCOMType *)Results.pItf;
hr = pSession->raw_GetVersion(&version);
It connects to the remote server, creates the DCOM object, and gets data from it just fine. I'd like to figure out how to get my C# code to do the same. Any ideas? Did I mess up one of the enums going to the functions? Am I missing some critical call or doing something all wrong? As background, my computer and the computer I am trying to connect to are both logged in under domain accounts, but neither has permissions on the other. The C++ code impersonates the remote user for the purpose of the DCOM connection. I am trying to do a .NET impersonation in such a way that it will be valid for the network connection given a manually entered username and password.
Alternatively, if it just isn't possible, I suppose I could write a small C++/CLR DLL with the same C++ code to handle the DCOM connection part and reference it from the C# code, but I'm hoping to avoid the extra complexity.
I banged away on it for a while and made no progress, so I gave up and wrote a C++/CLR DLL, which works perfectly. Code is as follows:
MyDCOMTalker::MyDCOMTalker(String ^ipAddress, String ^username, String ^password, String ^domain)
{
COAUTHINFO AuthInfo;
COSERVERINFO ServerInfo;
MULTI_QI Results;
CLSID clsid;
AuthIdentity = new COAUTHIDENTITY;
long DSStatus;
BSTR umVersion;
AuthIdentity->Domain = (unsigned short *)Marshal::StringToHGlobalAuto(domain).ToPointer();
AuthIdentity->DomainLength = domain->Length;
AuthIdentity->Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
AuthIdentity->Password = (unsigned short *)Marshal::StringToHGlobalAuto(password).ToPointer();
AuthIdentity->PasswordLength = password->Length;
AuthIdentity->User = (unsigned short *)Marshal::StringToHGlobalAuto(username).ToPointer();
AuthIdentity->UserLength = username->Length;
AuthInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CALL;
AuthInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT;
AuthInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE;
AuthInfo.dwCapabilities = EOAC_NONE;
AuthInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
AuthInfo.pAuthIdentityData = AuthIdentity;
AuthInfo.pwszServerPrincName = NULL;
ServerInfo.dwReserved1 = 0;
ServerInfo.dwReserved2 = 0;
ServerInfo.pAuthInfo = &AuthInfo;
ServerInfo.pwszName = (LPWSTR)Marshal::StringToHGlobalAuto(ipAddress).ToPointer();
Results.pIID = &_uuidof(_MyDCOMType);
Results.pItf = NULL;
Results.hr = 0;
CoInitialize(NULL);
Marshal::ThrowExceptionForHR(CLSIDFromProgID(L"MyDcomDLL.MyDCOMType", &clsid));
Marshal::ThrowExceptionForHR(CoCreateInstanceEx(clsid, NULL, CLSCTX_ALL, &ServerInfo, (ULONG) 1, &Results));
Marshal::ThrowExceptionForHR(CoSetProxyBlanket(Results.pItf, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE, AuthIdentity, EOAC_NONE));
pSession = (_MyDCOMType*)Results.pItf;
Marshal::ThrowExceptionForHR(pSession->raw_GetVersion(&DSStatus, &umVersion));
if (DSStatus == 0 && umVersion != NULL)
version = Marshal::PtrToStringBSTR((System::IntPtr)umVersion);
else
version = String::Empty;
}
With class definition:
public ref class MyDCOMTalker
{
private:
_MyDCOMType *pSession;
String ^version;
COAUTHIDENTITY *AuthIdentity;
public:
MyDCOMTalker(String ^ipAddress, String ^username, String ^password, String ^domain);
};
The usual caveats apply - I chopped out the actual functionality and error checking/disposal details for simpler code, and I don't really know C++/CLR or COM and DCOM that well, so there may be things that could be done easier. If you know of any improvements or a way to do it in C#, please comment/answer as appropriate.