Geeks With Blogs

The Life and Times of a Dev Yes, we're really that weird

We have this app that dynamically loads assemblies based on the latest versions that have been published.  Prior to today, we've just been putting out an MSI and then using that to install.  I finally got around to implementing the dynamic update, even though it's been in place for a while.  We're not doing anything fancy, just checking an xml file in the location of the assemblies and if the version there is newer that the version on the machine, we copy out everything in mass.  This would be bad if we cared about bandwidth, but since theres a limited number of users, I'm not worried.  Ideally, it would only copy out the changed dlls.  To do that, you'd have to create a new AppDomain, and then using shadow copy, load the local assembly, load the remote assembly and compare the version.  If newer, you'd then have to copy the file down from the server to the local machine.  After the copy, you'd unload the appdomain and then proceed with loading the assembly as needed.  Too much work, but it would scale to a large number of users better.

So I implemented this fancy new copying scheme, and the first user that tries it, it breaks. :(  Turns out that regular users don't have the permissions necessary to copy assemblies into the program files directory.  Bummer.  I could make them all admins on their box, but since we have a pretty good churn, that adds a lot of work to the sysadmin when installing stuff.

The solution (bright lights and drum roll please):  Impersonation--i.e. logging on with a user that does have permissions to copy the files and then copying the files.  Only problem was I had users that were failing and needed the fix NOW.  Don't even start with the testing thing--I'd love to do testing, but I'm afraid that our owner just doesn't think it's important enough to throw money (i.e. bodies) at it.

So, I do a quick search on the internet and come up with this on MS: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemSecurityPrincipalWindowsIdentityClassImpersonateTopic.asp.

Looks good, except that it's a console application.  However, the solution does work.  Note that since this is unsafe code, we have to be sure to clean up our handles, which is why there's an out parameter.  I could create an impersonation object that implemented dispose so that we could always be sure to clean up the handles.  I'll have to think about it--I was in a rush.  Here's the end result:

            [DllImport("advapi32.dll", SetLastError=true)]
            public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
                  int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

            [DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
            private unsafe static extern int FormatMessage(int dwFlags, ref IntPtr lpSource,
                  int dwMessageId, int dwLanguageId, ref String lpBuffer, int nSize, IntPtr *Arguments);

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

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

            // GetErrorMessage formats and returns an error message
            // corresponding to the input errorCode.
            public unsafe static string GetErrorMessage(int errorCode)
            {
                  int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
                  int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
                  int FORMAT_MESSAGE_FROM_SYSTEM  = 0x00001000;

                  //int errorCode = 0x5; //ERROR_ACCESS_DENIED
                  //throw new System.ComponentModel.Win32Exception(errorCode);

                  int messageSize = 255;

                  String lpMsgBuf = "";

                  int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;

                  IntPtr ptrlpSource = IntPtr.Zero;
                  IntPtr prtArguments = IntPtr.Zero;

                  int retVal = FormatMessage(dwFlags, ref ptrlpSource, errorCode, 0, ref lpMsgBuf, messageSize, &prtArguments);

                  if (0 == retVal)
                  {
                        throw new Exception("Failed to format message for error code " + errorCode + ". ");
                  }

                  return lpMsgBuf;
            }

            private WindowsImpersonationContext Impersonate(out IntPtr token, out IntPtr dupeToken)
            {
                  IntPtr tokenHandle = new IntPtr(0);
                  IntPtr dupeTokenHandle = new IntPtr(0);
                  WindowsImpersonationContext impersonatedUser=null;

                  try
                  {
                        string userName;
                        string domainName;
                        string password;

                        // Get the user token for the specified user, domain, and password using the
                        // unmanaged LogonUser method. 
                        // The local machine name can be used for the domain name to impersonate a user on this machine.
                        domainName = ”MyDomain”;
                        userName = “TheUserName”;
                        password= “ThePassword”;

                        const int LOGON32_PROVIDER_DEFAULT = 0;
                        //This parameter causes LogonUser to create a primary token.
                        const int LOGON32_LOGON_INTERACTIVE = 2;
                        const int SecurityImpersonation = 2;
                        tokenHandle = IntPtr.Zero;
                        dupeTokenHandle = IntPtr.Zero;

                        // Call LogonUser to obtain a handle to an access token.
                        bool returnValue = LogonUser(userName, domainName, password,
                              LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                              ref tokenHandle);

                        if (false == returnValue)
                        {
                              int ret = Marshal.GetLastWin32Error();
                              log.Error("LogonUser failed with error code : " + ret.ToString());
                              log.Error("\nError: [" + ret.ToString() + "] " + GetErrorMessage(ret) + "\n");
                              int errorCode = 0x5; //ERROR_ACCESS_DENIED
                              throw new System.ComponentModel.Win32Exception(errorCode);
                        }
                        bool retVal = DuplicateToken(tokenHandle, SecurityImpersonation, ref dupeTokenHandle);
                        if (false == retVal)
                        {
                              CloseHandle(tokenHandle);
                              throw new System.Exception("Exception thrown in trying to duplicate token.");
                        }
 
                        // The token that is passed to the following constructor must
                        // be a primary token in order to use it for impersonation.
                        WindowsIdentity newId = new WindowsIdentity(dupeTokenHandle);
                        impersonatedUser = newId.Impersonate();
       
                  }
                  catch(Exception ex)
                  {
                        log.Error("Exception occurred.",ex);
                        throw;
                  }
                  token=tokenHandle;
                  dupeToken=dupeTokenHandle;
                  return impersonatedUser;
            }
            private void UndoImpersonation(WindowsImpersonationContext user, IntPtr tokenHandle,IntPtr dupeTokenHandle)
            {
                  try
                  {
                        // Stop impersonating the user.
                        if (user != null)
                        {
                              user.Undo();
                        }

                        // Free the tokens.
                        if (tokenHandle != IntPtr.Zero)
                              CloseHandle(tokenHandle);
                        if (dupeTokenHandle != IntPtr.Zero)
                              CloseHandle(dupeTokenHandle);
                  }
                  catch (System.Exception ex)
                  {

                  }

            }

Posted on Tuesday, December 13, 2005 10:14 AM Work | Back to top


Comments on this post: Impersonation and Program Files

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Robert May | Powered by: GeeksWithBlogs.net