Search code examples
c#impersonationdelegation

windows service gets UnauthorizedAccessException on reading file from remote share while impersonating


I have a windows service that runs under the local machine system account. Inside this service, it tries to read a remote .ini file that is available on a remote shared folder. The code trying to read this file is wrapped in impersonation using LogonUser (a simplified version of the code is below for an idea of what it is doing). The impersonation successfully starts impersonating the user configured, however the instant it attempts to read the remote ini file found on the remote network share, an UnauthorizedAccessException is thrown. This happens even though the configured user has read/write permissions on the remote machine. When I modify the code to remove all impersonation, and instead run the windows service as the configured user, all access to the remote .ini file are successful. I would prefer to use impersonation to get access to this file rather than a hack such as running the service as the user. Can anyone see errors with the impersonation code in the example? Is there something I need to do on my Vista 64 bit box to enable this? Are there specific active directory permissions my IT co-workers need to enable?

    private WindowsImpersonationContext _impersonatedUser;
    private IntPtr _token;

    // Declare signatures for Win32 LogonUser and CloseHandle APIs
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool LogonUser(
      string principal,
      string authority,
      string password,
      LogonSessionType logonType,
      LogonProvider logonProvider,
      out IntPtr token);
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr handle);
    enum LogonSessionType : uint
    {
        Interactive = 2,
        Network,
        Batch,
        Service,
        NetworkCleartext = 8,
        NewCredentials
    }
    enum LogonProvider : uint
    {
        Default = 0, // default for platform (use this!)
        WinNT35,     // sends smoke signals to authority
        WinNT40,     // uses NTLM
        WinNT50      // negotiates Kerb or NTLM
    }
    ....

    var result = LogonUser(exchangeUserId, exchangeDomain,
                           password,
                           LogonSessionType.Network,
                           LogonProvider.Default,
                           out _token);

    var id = new WindowsIdentity(_token);
    _impersonatedUser = id.Impersonate();

    try
    {
        //Validate access to the file on the remote computer/share
        File.GetAccessControl(remoteFileAvailableInSharedFolder);
    }
    catch (UnauthorizedAccessException unauthorized)
    {
        ...
    }
    ....

    // Stop impersonation and revert to the process identity
    if (_impersonatedUser != null)
    {
       _impersonatedUser.Undo();
       _impersonatedUser = null;
    }

    if (_token != IntPtr.Zero)
    {
        CloseHandle(_token);
        _token = IntPtr.Zero;
    }

Solution

  • Doing further research I discovered the core issue to be caused by the impersonation code. I had to add the use of the api DuplicateToken and added usage of the api RevertToSelf too. On making these changes (see class below) the impersonation worked as well as the code did when the whole service was ran under my domain user. Hope the code helps others with this same issue.

    public class Impersonation : IDisposable
    {
        private WindowsImpersonationContext _impersonatedUserContext;
    
        // Declare signatures for Win32 LogonUser and CloseHandle APIs
        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool LogonUser(
          string principal,
          string authority,
          string password,
          LogonSessionType logonType,
          LogonProvider logonProvider,
          out IntPtr token);
    
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr handle);
    
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern int DuplicateToken(IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);
    
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool RevertToSelf();
    
        // ReSharper disable UnusedMember.Local
        enum LogonSessionType : uint
        {
            Interactive = 2,
            Network,
            Batch,
            Service,
            NetworkCleartext = 8,
            NewCredentials
        }
        // ReSharper disable InconsistentNaming
        enum LogonProvider : uint
        {
            Default = 0, // default for platform (use this!)
            WinNT35,     // sends smoke signals to authority
            WinNT40,     // uses NTLM
            WinNT50      // negotiates Kerb or NTLM
        }
        // ReSharper restore InconsistentNaming
        // ReSharper restore UnusedMember.Local
    
        /// <summary>
        /// Class to allow running a segment of code under a given user login context
        /// </summary>
        /// <param name="user">domain\user</param>
        /// <param name="password">user's domain password</param>
        public Impersonation(string user, string password)
        {
            var token = ValidateParametersAndGetFirstLoginToken(user, password);
    
            var duplicateToken = IntPtr.Zero;
            try
            {
                if (DuplicateToken(token, 2, ref duplicateToken) == 0)
                {
                    throw new Exception("DuplicateToken call to reset permissions for this token failed");
                }
    
                var identityForLoggedOnUser = new WindowsIdentity(duplicateToken);
                _impersonatedUserContext = identityForLoggedOnUser.Impersonate();
                if (_impersonatedUserContext == null)
                {
                    throw new Exception("WindowsIdentity.Impersonate() failed");
                }
            }
            finally
            {
                if (token != IntPtr.Zero)
                    CloseHandle(token);
                if (duplicateToken != IntPtr.Zero)
                    CloseHandle(duplicateToken);
            }
        }
    
        private static IntPtr ValidateParametersAndGetFirstLoginToken(string user, string password)
        {
            if (string.IsNullOrEmpty(user))
            {
                throw new ConfigurationErrorsException("No user passed into impersonation class");
            }
            var userHaves = user.Split('\\');
            if (userHaves.Length != 2)
            {
                throw new ConfigurationErrorsException("User must be formatted as follows: domain\\user");
            }
            if (!RevertToSelf())
            {
                throw new Exception("RevertToSelf call to remove any prior impersonations failed");
            }
    
            IntPtr token;
    
            var result = LogonUser(userHaves[1], userHaves[0],
                                   password,
                                   LogonSessionType.Interactive,
                                   LogonProvider.Default,
                                   out token);
            if (!result)
            {
                throw new ConfigurationErrorsException("Logon for user " + user + " failed.");
            }
            return token;
        }
    
        public void Dispose()
        {
            // Stop impersonation and revert to the process identity
            if (_impersonatedUserContext != null)
            {
                _impersonatedUserContext.Undo();
                _impersonatedUserContext = null;
            }
        }
    }