WindowsSecurityContext.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. #region Apache License
  2. //
  3. // Licensed to the Apache Software Foundation (ASF) under one or more
  4. // contributor license agreements. See the NOTICE file distributed with
  5. // this work for additional information regarding copyright ownership.
  6. // The ASF licenses this file to you under the Apache License, Version 2.0
  7. // (the "License"); you may not use this file except in compliance with
  8. // the License. You may obtain a copy of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS,
  14. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. // See the License for the specific language governing permissions and
  16. // limitations under the License.
  17. //
  18. #endregion
  19. // .NET Compact Framework 1.0 has no support for WindowsIdentity
  20. #if !NETCF
  21. // MONO 1.0 has no support for Win32 Logon APIs
  22. #if !MONO
  23. // SSCLI 1.0 has no support for Win32 Logon APIs
  24. #if !SSCLI
  25. // We don't want framework or platform specific code in the CLI version of log4net
  26. #if !CLI_1_0
  27. using System;
  28. using System.Runtime.InteropServices;
  29. using System.Security.Principal;
  30. using System.Security.Permissions;
  31. using log4net.Core;
  32. namespace log4net.Util
  33. {
  34. /// <summary>
  35. /// Impersonate a Windows Account
  36. /// </summary>
  37. /// <remarks>
  38. /// <para>
  39. /// This <see cref="SecurityContext"/> impersonates a Windows account.
  40. /// </para>
  41. /// <para>
  42. /// How the impersonation is done depends on the value of <see cref="Impersonate"/>.
  43. /// This allows the context to either impersonate a set of user credentials specified
  44. /// using username, domain name and password or to revert to the process credentials.
  45. /// </para>
  46. /// </remarks>
  47. public class WindowsSecurityContext : SecurityContext, IOptionHandler
  48. {
  49. /// <summary>
  50. /// The impersonation modes for the <see cref="WindowsSecurityContext"/>
  51. /// </summary>
  52. /// <remarks>
  53. /// <para>
  54. /// See the <see cref="WindowsSecurityContext.Credentials"/> property for
  55. /// details.
  56. /// </para>
  57. /// </remarks>
  58. public enum ImpersonationMode
  59. {
  60. /// <summary>
  61. /// Impersonate a user using the credentials supplied
  62. /// </summary>
  63. User,
  64. /// <summary>
  65. /// Revert this the thread to the credentials of the process
  66. /// </summary>
  67. Process
  68. }
  69. #region Member Variables
  70. private ImpersonationMode m_impersonationMode = ImpersonationMode.User;
  71. private string m_userName;
  72. private string m_domainName = Environment.MachineName;
  73. private string m_password;
  74. private WindowsIdentity m_identity;
  75. #endregion
  76. #region Constructor
  77. /// <summary>
  78. /// Default constructor
  79. /// </summary>
  80. /// <remarks>
  81. /// <para>
  82. /// Default constructor
  83. /// </para>
  84. /// </remarks>
  85. public WindowsSecurityContext()
  86. {
  87. }
  88. #endregion
  89. #region Public Properties
  90. /// <summary>
  91. /// Gets or sets the impersonation mode for this security context
  92. /// </summary>
  93. /// <value>
  94. /// The impersonation mode for this security context
  95. /// </value>
  96. /// <remarks>
  97. /// <para>
  98. /// Impersonate either a user with user credentials or
  99. /// revert this thread to the credentials of the process.
  100. /// The value is one of the <see cref="ImpersonationMode"/>
  101. /// enum.
  102. /// </para>
  103. /// <para>
  104. /// The default value is <see cref="ImpersonationMode.User"/>
  105. /// </para>
  106. /// <para>
  107. /// When the mode is set to <see cref="ImpersonationMode.User"/>
  108. /// the user's credentials are established using the
  109. /// <see cref="UserName"/>, <see cref="DomainName"/> and <see cref="Password"/>
  110. /// values.
  111. /// </para>
  112. /// <para>
  113. /// When the mode is set to <see cref="ImpersonationMode.Process"/>
  114. /// no other properties need to be set. If the calling thread is
  115. /// impersonating then it will be reverted back to the process credentials.
  116. /// </para>
  117. /// </remarks>
  118. public ImpersonationMode Credentials
  119. {
  120. get { return m_impersonationMode; }
  121. set { m_impersonationMode = value; }
  122. }
  123. /// <summary>
  124. /// Gets or sets the Windows username for this security context
  125. /// </summary>
  126. /// <value>
  127. /// The Windows username for this security context
  128. /// </value>
  129. /// <remarks>
  130. /// <para>
  131. /// This property must be set if <see cref="Credentials"/>
  132. /// is set to <see cref="ImpersonationMode.User"/> (the default setting).
  133. /// </para>
  134. /// </remarks>
  135. public string UserName
  136. {
  137. get { return m_userName; }
  138. set { m_userName = value; }
  139. }
  140. /// <summary>
  141. /// Gets or sets the Windows domain name for this security context
  142. /// </summary>
  143. /// <value>
  144. /// The Windows domain name for this security context
  145. /// </value>
  146. /// <remarks>
  147. /// <para>
  148. /// The default value for <see cref="DomainName"/> is the local machine name
  149. /// taken from the <see cref="Environment.MachineName"/> property.
  150. /// </para>
  151. /// <para>
  152. /// This property must be set if <see cref="Credentials"/>
  153. /// is set to <see cref="ImpersonationMode.User"/> (the default setting).
  154. /// </para>
  155. /// </remarks>
  156. public string DomainName
  157. {
  158. get { return m_domainName; }
  159. set { m_domainName = value; }
  160. }
  161. /// <summary>
  162. /// Sets the password for the Windows account specified by the <see cref="UserName"/> and <see cref="DomainName"/> properties.
  163. /// </summary>
  164. /// <value>
  165. /// The password for the Windows account specified by the <see cref="UserName"/> and <see cref="DomainName"/> properties.
  166. /// </value>
  167. /// <remarks>
  168. /// <para>
  169. /// This property must be set if <see cref="Credentials"/>
  170. /// is set to <see cref="ImpersonationMode.User"/> (the default setting).
  171. /// </para>
  172. /// </remarks>
  173. public string Password
  174. {
  175. set { m_password = value; }
  176. }
  177. #endregion
  178. #region IOptionHandler Members
  179. /// <summary>
  180. /// Initialize the SecurityContext based on the options set.
  181. /// </summary>
  182. /// <remarks>
  183. /// <para>
  184. /// This is part of the <see cref="IOptionHandler"/> delayed object
  185. /// activation scheme. The <see cref="ActivateOptions"/> method must
  186. /// be called on this object after the configuration properties have
  187. /// been set. Until <see cref="ActivateOptions"/> is called this
  188. /// object is in an undefined state and must not be used.
  189. /// </para>
  190. /// <para>
  191. /// If any of the configuration properties are modified then
  192. /// <see cref="ActivateOptions"/> must be called again.
  193. /// </para>
  194. /// <para>
  195. /// The security context will try to Logon the specified user account and
  196. /// capture a primary token for impersonation.
  197. /// </para>
  198. /// </remarks>
  199. /// <exception cref="ArgumentNullException">The required <see cref="UserName" />,
  200. /// <see cref="DomainName" /> or <see cref="Password" /> properties were not specified.</exception>
  201. public void ActivateOptions()
  202. {
  203. if (m_impersonationMode == ImpersonationMode.User)
  204. {
  205. if (m_userName == null) throw new ArgumentNullException("m_userName");
  206. if (m_domainName == null) throw new ArgumentNullException("m_domainName");
  207. if (m_password == null) throw new ArgumentNullException("m_password");
  208. m_identity = LogonUser(m_userName, m_domainName, m_password);
  209. }
  210. }
  211. #endregion
  212. /// <summary>
  213. /// Impersonate the Windows account specified by the <see cref="UserName"/> and <see cref="DomainName"/> properties.
  214. /// </summary>
  215. /// <param name="state">caller provided state</param>
  216. /// <returns>
  217. /// An <see cref="IDisposable"/> instance that will revoke the impersonation of this SecurityContext
  218. /// </returns>
  219. /// <remarks>
  220. /// <para>
  221. /// Depending on the <see cref="Credentials"/> property either
  222. /// impersonate a user using credentials supplied or revert
  223. /// to the process credentials.
  224. /// </para>
  225. /// </remarks>
  226. public override IDisposable Impersonate(object state)
  227. {
  228. if (m_impersonationMode == ImpersonationMode.User)
  229. {
  230. if (m_identity != null)
  231. {
  232. return new DisposableImpersonationContext(m_identity.Impersonate());
  233. }
  234. }
  235. else if (m_impersonationMode == ImpersonationMode.Process)
  236. {
  237. // Impersonate(0) will revert to the process credentials
  238. return new DisposableImpersonationContext(WindowsIdentity.Impersonate(IntPtr.Zero));
  239. }
  240. return null;
  241. }
  242. /// <summary>
  243. /// Create a <see cref="WindowsIdentity"/> given the userName, domainName and password.
  244. /// </summary>
  245. /// <param name="userName">the user name</param>
  246. /// <param name="domainName">the domain name</param>
  247. /// <param name="password">the password</param>
  248. /// <returns>the <see cref="WindowsIdentity"/> for the account specified</returns>
  249. /// <remarks>
  250. /// <para>
  251. /// Uses the Windows API call LogonUser to get a principal token for the account. This
  252. /// token is used to initialize the WindowsIdentity.
  253. /// </para>
  254. /// </remarks>
  255. #if NET_4_0 || MONO_4_0
  256. [System.Security.SecuritySafeCritical]
  257. #endif
  258. [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode = true)]
  259. private static WindowsIdentity LogonUser(string userName, string domainName, string password)
  260. {
  261. const int LOGON32_PROVIDER_DEFAULT = 0;
  262. //This parameter causes LogonUser to create a primary token.
  263. const int LOGON32_LOGON_INTERACTIVE = 2;
  264. // Call LogonUser to obtain a handle to an access token.
  265. IntPtr tokenHandle = IntPtr.Zero;
  266. if(!LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle))
  267. {
  268. NativeError error = NativeError.GetLastError();
  269. throw new Exception("Failed to LogonUser ["+userName+"] in Domain ["+domainName+"]. Error: "+ error.ToString());
  270. }
  271. const int SecurityImpersonation = 2;
  272. IntPtr dupeTokenHandle = IntPtr.Zero;
  273. if(!DuplicateToken(tokenHandle, SecurityImpersonation, ref dupeTokenHandle))
  274. {
  275. NativeError error = NativeError.GetLastError();
  276. if (tokenHandle != IntPtr.Zero)
  277. {
  278. CloseHandle(tokenHandle);
  279. }
  280. throw new Exception("Failed to DuplicateToken after LogonUser. Error: " + error.ToString());
  281. }
  282. WindowsIdentity identity = new WindowsIdentity(dupeTokenHandle);
  283. // Free the tokens.
  284. if (dupeTokenHandle != IntPtr.Zero)
  285. {
  286. CloseHandle(dupeTokenHandle);
  287. }
  288. if (tokenHandle != IntPtr.Zero)
  289. {
  290. CloseHandle(tokenHandle);
  291. }
  292. return identity;
  293. }
  294. #region Native Method Stubs
  295. [DllImport("advapi32.dll", SetLastError=true)]
  296. private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
  297. [DllImport("kernel32.dll", CharSet=CharSet.Auto)]
  298. private extern static bool CloseHandle(IntPtr handle);
  299. [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
  300. private extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
  301. #endregion
  302. #region DisposableImpersonationContext class
  303. /// <summary>
  304. /// Adds <see cref="IDisposable"/> to <see cref="WindowsImpersonationContext"/>
  305. /// </summary>
  306. /// <remarks>
  307. /// <para>
  308. /// Helper class to expose the <see cref="WindowsImpersonationContext"/>
  309. /// through the <see cref="IDisposable"/> interface.
  310. /// </para>
  311. /// </remarks>
  312. private sealed class DisposableImpersonationContext : IDisposable
  313. {
  314. private readonly WindowsImpersonationContext m_impersonationContext;
  315. /// <summary>
  316. /// Constructor
  317. /// </summary>
  318. /// <param name="impersonationContext">the impersonation context being wrapped</param>
  319. /// <remarks>
  320. /// <para>
  321. /// Constructor
  322. /// </para>
  323. /// </remarks>
  324. public DisposableImpersonationContext(WindowsImpersonationContext impersonationContext)
  325. {
  326. m_impersonationContext = impersonationContext;
  327. }
  328. /// <summary>
  329. /// Revert the impersonation
  330. /// </summary>
  331. /// <remarks>
  332. /// <para>
  333. /// Revert the impersonation
  334. /// </para>
  335. /// </remarks>
  336. public void Dispose()
  337. {
  338. m_impersonationContext.Undo();
  339. }
  340. }
  341. #endregion
  342. }
  343. }
  344. #endif // !CLI_1_0
  345. #endif // !SSCLI
  346. #endif // !MONO
  347. #endif // !NETCF