I have a C# console application that needs to read a shared file on a machine in another domain. When the application tries to access the file an exception occurs as the local user does not have permission to access the shared resource.
Currently I overcome this problem manually by open the shared folder from the run and put the username and password into the windows authentication dialog then run the application.
How can I do it programmatically?
a) p/invoke LogonUser
with LOGON32_LOGON_NEW_CREDENTIALS
and create a new WindowsIdentity
with the new token, then use normal file access.
b) p/invoke WNetAddConnection3. Be advised that this makes your remote share accessible to every other process on your machine.
c) WMI via System.Management
and CIM_DataFile
; you won't even need p/invoke. System.Management
lets you specify credentials for remote machine.
I used the point "a" as "Anton" suggested, I developed two versions for one class, first one using Win32 APIs and second uses WindowsIdentity class
Version 1:
class UserImpersonation:IDisposable { [DllImport("advapi32.dll")] public static extern int LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool RevertToSelf(); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern bool CloseHandle(IntPtr handle); const int LOGON32_PROVIDER_DEFAULT = 0; const int LOGON32_LOGON_INTERACTIVE = 2; WindowsImpersonationContext wic; string _userName; string _domain; string _passWord; public UserImpersonation(string userName, string domain, string passWord) { _userName = userName; _domain = domain; _passWord = passWord; } public bool ImpersonateValidUser() { WindowsIdentity wi; IntPtr token = IntPtr.Zero; IntPtr tokenDuplicate = IntPtr.Zero; if (RevertToSelf()) { if (LogonUser(_userName, _domain, _passWord, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0) { if (DuplicateToken(token, 2, ref tokenDuplicate) != 0) { wi = new WindowsIdentity(tokenDuplicate); wic = wi.Impersonate(); if (wic != null) { CloseHandle(token); CloseHandle(tokenDuplicate); return true; } } } } if (token != IntPtr.Zero) CloseHandle(token); if (tokenDuplicate != IntPtr.Zero) CloseHandle(tokenDuplicate); return false; } #region IDisposable Members public void Dispose() { if(wic != null) wic.Dispose(); RevertToSelf(); } #endregion }
Version2 (from MSDN with small changes)
class UserImpersonation2:IDisposable { [DllImport("advapi32.dll")] public static extern bool LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern bool CloseHandle(IntPtr handle); WindowsImpersonationContext wic; IntPtr tokenHandle; string _userName; string _domain; string _passWord; public UserImpersonation2(string userName, string domain, string passWord) { _userName = userName; _domain = domain; _passWord = passWord; } const int LOGON32_PROVIDER_DEFAULT = 0; const int LOGON32_LOGON_INTERACTIVE = 2; public bool ImpersonateValidUser() { bool returnValue = LogonUser(_userName, _domain, _passWord, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle); Console.WriteLine("LogonUser called."); if (false == returnValue) { int ret = Marshal.GetLastWin32Error(); Console.WriteLine("LogonUser failed with error code : {0}", ret); return false; } Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No")); Console.WriteLine("Value of Windows NT token: " + tokenHandle); // Check the identity. Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name); // Use the token handle returned by LogonUser. WindowsIdentity newId = new WindowsIdentity(tokenHandle); wic = newId.Impersonate(); // Check the identity. Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name); return true; } #region IDisposable Members public void Dispose() { if(wic!=null) wic.Undo(); if (tokenHandle != IntPtr.Zero) CloseHandle(tokenHandle); } #endregion }
How to use (both are the same)
const string file = @"\\machine\test\file.txt"; using (UserImpersonation user = new UserImpersonation("user", "domain", "password")) { if (user.ImpersonateValidUser()) { StreamReader reader = new StreamReader(file); Console.WriteLine(reader.ReadToEnd()); reader.Close(); } }
From memory you'll need to use a Windows API call and login as a user on the other domain. See this link for an example.
Another idea could be to use the RunAs command line argument to read the file and save it into a file on your local domain/server.