Monday, May 11, 2009

“I am not a crook!” Impersonation

I was recently tasked with creating little web page that monitors the backup files on our servers.  Obviously I’m going to need to use particular credentials since the server hard drives are locked down.

So I set about creating a little impersonation object.  I read a number of blogs and they all seemed to be showing similar systems and then I came across a tutorial from Microsoft.  Now normally I back page as quick as possible when I end up on one of there documentation pages because they are invariably Byzantine, but this time I figured I’d give it a shot.  Well they were saying oh you don’t have to do it this ways ( the way most blogs were showing it ) if you do this and that. So I said aha!  A short cut.  Well after many errors and exceptions it occurred to me to search the code repository on my machine and see if there was anything already there.  Brilliance! Low and behold!  log4net needs to access your file system in order to write it’s logs.  And guess what?  They are doing it the same way everyone else ( except microsoft ) is doing it.  So I kinda stripped out some of the stuff they had that was specific to their use and came up with the following.

public class Impersonator
    {
        private readonly string _userName;
        private readonly string _domainName;
        private readonly string _password;
        private IntPtr _tokenHandle;
        private WindowsIdentity _identity;
        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private extern static bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);

        public Impersonator(string userName, string password, string domainName)
        {
            _userName = userName;
            _domainName = domainName;
            _password = password;
            LogonUser();
        }

        private void LogonUser()
        {
            const int LOGON32_PROVIDER_DEFAULT = 0;
            //This parameter causes LogonUser to create a primary token.
            const int LOGON32_LOGON_INTERACTIVE = 2;

            // Call LogonUser to obtain a handle to an access token.
            _tokenHandle = IntPtr.Zero;
            if (!LogonUser(_userName, _domainName, _password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref _tokenHandle))
            {
                //log4net error handler.  I'm using log4net so I figured I'd take advantage
                //NativeError error = NativeError.GetLastError();
                throw new Exception("Failed to LogonUser [" + _userName + "] in Domain [" + _domainName + "]."); // Error: " + error.ToString());
            }

            const int SecurityImpersonation = 2;
            IntPtr dupeTokenHandle = IntPtr.Zero;
            if (!DuplicateToken(_tokenHandle, SecurityImpersonation, ref dupeTokenHandle))
            {
                //log4net error handler.  I'm using log4net so I figured I'd take advantage
                //NativeError error = NativeError.GetLastError();
                if (_tokenHandle != IntPtr.Zero)
                {
                    CloseHandle(_tokenHandle);
                }
                throw new Exception("Failed to DuplicateToken after LogonUser."); // Error: " + error.ToString());
            }

            _identity = new WindowsIdentity(dupeTokenHandle);

            // Free the tokens.
            if (dupeTokenHandle != IntPtr.Zero)
            {
                CloseHandle(dupeTokenHandle);
            }
            if (_tokenHandle != IntPtr.Zero)
            {
                CloseHandle(_tokenHandle);
            }
        }

        public IDisposable Impersonate()
        {
            return _identity != null ? new DisposableImpersonationContext(_identity.Impersonate()) : null;
        }

        private sealed class DisposableImpersonationContext : IDisposable
        {
            private readonly WindowsImpersonationContext m_impersonationContext;
            public DisposableImpersonationContext(WindowsImpersonationContext impersonationContext)
            {
                m_impersonationContext = impersonationContext;
            }
            public void Dispose()
            {
                m_impersonationContext.Undo();
            }
        }
    }

 

One would then use it like so

 

  var impersonator = new Impersonator(LoginName, Password, Domain);
            using(impersonator.Impersonate())
            {
                if (!Directory.Exists(DirectoryPath)) return;
                var dir = new DirectoryInfo(DirectoryPath);
             }

Sorry about the code formatting I have to learn how to do this.

After calling the impersonator.Impersonate() method( provided your credentials are proper ) you will be operating under those credentials.  In example I’m accessing a directory on a server that I specified in code else where.

So this is the quick and dirty plus some of my bitching.  Here is a nice post by Rick Strahl who goes into more detail.

No comments: