#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
#if NET_2_0
// .NET Compact Framework 1.0 has no support for WindowsIdentity
#if !NETCF
// MONO 1.0 has no support for Win32 Logon APIs
#if !MONO
// SSCLI 1.0 has no support for Win32 Logon APIs
#if !SSCLI
// We don't want framework or platform specific code in the CLI version of log4net
#if !CLI_1_0
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Permissions;
using log4net.Core;
namespace log4net.Util
{
///
/// Impersonate a Windows Account
///
///
///
/// This impersonates a Windows account.
///
///
/// How the impersonation is done depends on the value of .
/// This allows the context to either impersonate a set of user credentials specified
/// using username, domain name and password or to revert to the process credentials.
///
///
public class WindowsSecurityContext : SecurityContext, IOptionHandler
{
///
/// The impersonation modes for the
///
///
///
/// See the property for
/// details.
///
///
public enum ImpersonationMode
{
///
/// Impersonate a user using the credentials supplied
///
User,
///
/// Revert this the thread to the credentials of the process
///
Process
}
#region Member Variables
private ImpersonationMode m_impersonationMode = ImpersonationMode.User;
private string m_userName;
private string m_domainName = Environment.MachineName;
private string m_password;
private WindowsIdentity m_identity;
#endregion
#region Constructor
///
/// Default constructor
///
///
///
/// Default constructor
///
///
public WindowsSecurityContext()
{
}
#endregion
#region Public Properties
///
/// Gets or sets the impersonation mode for this security context
///
///
/// The impersonation mode for this security context
///
///
///
/// Impersonate either a user with user credentials or
/// revert this thread to the credentials of the process.
/// The value is one of the
/// enum.
///
///
/// The default value is
///
///
/// When the mode is set to
/// the user's credentials are established using the
/// , and
/// values.
///
///
/// When the mode is set to
/// no other properties need to be set. If the calling thread is
/// impersonating then it will be reverted back to the process credentials.
///
///
public ImpersonationMode Credentials
{
get { return m_impersonationMode; }
set { m_impersonationMode = value; }
}
///
/// Gets or sets the Windows username for this security context
///
///
/// The Windows username for this security context
///
///
///
/// This property must be set if
/// is set to (the default setting).
///
///
public string UserName
{
get { return m_userName; }
set { m_userName = value; }
}
///
/// Gets or sets the Windows domain name for this security context
///
///
/// The Windows domain name for this security context
///
///
///
/// The default value for is the local machine name
/// taken from the property.
///
///
/// This property must be set if
/// is set to (the default setting).
///
///
public string DomainName
{
get { return m_domainName; }
set { m_domainName = value; }
}
///
/// Sets the password for the Windows account specified by the and properties.
///
///
/// The password for the Windows account specified by the and properties.
///
///
///
/// This property must be set if
/// is set to (the default setting).
///
///
public string Password
{
set { m_password = value; }
}
#endregion
#region IOptionHandler Members
///
/// Initialize the SecurityContext based on the options set.
///
///
///
/// This is part of the delayed object
/// activation scheme. The method must
/// be called on this object after the configuration properties have
/// been set. Until is called this
/// object is in an undefined state and must not be used.
///
///
/// If any of the configuration properties are modified then
/// must be called again.
///
///
/// The security context will try to Logon the specified user account and
/// capture a primary token for impersonation.
///
///
/// The required ,
/// or properties were not specified.
public void ActivateOptions()
{
if (m_impersonationMode == ImpersonationMode.User)
{
if (m_userName == null) throw new ArgumentNullException("m_userName");
if (m_domainName == null) throw new ArgumentNullException("m_domainName");
if (m_password == null) throw new ArgumentNullException("m_password");
m_identity = LogonUser(m_userName, m_domainName, m_password);
}
}
#endregion
///
/// Impersonate the Windows account specified by the and properties.
///
/// caller provided state
///
/// An instance that will revoke the impersonation of this SecurityContext
///
///
///
/// Depending on the property either
/// impersonate a user using credentials supplied or revert
/// to the process credentials.
///
///
public override IDisposable Impersonate(object state)
{
if (m_impersonationMode == ImpersonationMode.User)
{
if (m_identity != null)
{
return new DisposableImpersonationContext(m_identity.Impersonate());
}
}
else if (m_impersonationMode == ImpersonationMode.Process)
{
// Impersonate(0) will revert to the process credentials
return new DisposableImpersonationContext(WindowsIdentity.Impersonate(IntPtr.Zero));
}
return null;
}
///
/// Create a given the userName, domainName and password.
///
/// the user name
/// the domain name
/// the password
/// the for the account specified
///
///
/// Uses the Windows API call LogonUser to get a principal token for the account. This
/// token is used to initialize the WindowsIdentity.
///
///
#if NET_4_0 || MONO_4_0
[System.Security.SecuritySafeCritical]
#endif
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode = true)]
private static WindowsIdentity LogonUser(string userName, string domainName, string password)
{
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.
IntPtr tokenHandle = IntPtr.Zero;
if(!LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle))
{
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))
{
NativeError error = NativeError.GetLastError();
if (tokenHandle != IntPtr.Zero)
{
CloseHandle(tokenHandle);
}
throw new Exception("Failed to DuplicateToken after LogonUser. Error: " + error.ToString());
}
WindowsIdentity identity = new WindowsIdentity(dupeTokenHandle);
// Free the tokens.
if (dupeTokenHandle != IntPtr.Zero)
{
CloseHandle(dupeTokenHandle);
}
if (tokenHandle != IntPtr.Zero)
{
CloseHandle(tokenHandle);
}
return identity;
}
#region Native Method Stubs
[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 static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
#endregion
#region DisposableImpersonationContext class
///
/// Adds to
///
///
///
/// Helper class to expose the
/// through the interface.
///
///
private sealed class DisposableImpersonationContext : IDisposable
{
private readonly WindowsImpersonationContext m_impersonationContext;
///
/// Constructor
///
/// the impersonation context being wrapped
///
///
/// Constructor
///
///
public DisposableImpersonationContext(WindowsImpersonationContext impersonationContext)
{
m_impersonationContext = impersonationContext;
}
///
/// Revert the impersonation
///
///
///
/// Revert the impersonation
///
///
public void Dispose()
{
m_impersonationContext.Undo();
}
}
#endregion
}
}
#endif // !CLI_1_0
#endif // !SSCLI
#endif // !MONO
#endif // !NETCF
#endif // NET_2_0