1 using Microsoft.Win32.SafeHandles;
2 using System;
3 using System.Collections;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using System.Runtime.ConstrainedExecution;
8 using System.Runtime.InteropServices;
9 using System.Security.AccessControl;
10 using System.Security.Principal;
11 using System.Text;
12 using Ansible.AccessToken;
13 using Ansible.Process;
14 
15 namespace Ansible.Become
16 {
17     internal class NativeHelpers
18     {
19         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
20         public struct KERB_S4U_LOGON
21         {
22             public UInt32 MessageType;
23             public UInt32 Flags;
24             public LSA_UNICODE_STRING ClientUpn;
25             public LSA_UNICODE_STRING ClientRealm;
26         }
27 
28         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
29         public struct LSA_STRING
30         {
31             public UInt16 Length;
32             public UInt16 MaximumLength;
33             [MarshalAs(UnmanagedType.LPStr)] public string Buffer;
34 
ExitHandler(int rc)35             public static implicit operator string(LSA_STRING s)
36             {
37                 return s.Buffer;
38             }
39 
40             public static implicit operator LSA_STRING(string s)
41             {
42                 if (s == null)
43                     s = "";
44 
45                 LSA_STRING lsaStr = new LSA_STRING
46                 {
47                     Buffer = s,
48                     Length = (UInt16)s.Length,
49                     MaximumLength = (UInt16)(s.Length + 1),
50                 };
51                 return lsaStr;
52             }
53         }
54 
55         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
56         public struct LSA_UNICODE_STRING
57         {
58             public UInt16 Length;
59             public UInt16 MaximumLength;
60             public IntPtr Buffer;
61         }
62 
63         [StructLayout(LayoutKind.Sequential)]
64         public struct SECURITY_LOGON_SESSION_DATA
65         {
66             public UInt32 Size;
67             public Luid LogonId;
68             public LSA_UNICODE_STRING UserName;
69             public LSA_UNICODE_STRING LogonDomain;
70             public LSA_UNICODE_STRING AuthenticationPackage;
71             public SECURITY_LOGON_TYPE LogonType;
72         }
73 
74         [StructLayout(LayoutKind.Sequential)]
75         public struct TOKEN_SOURCE
76         {
77             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char[] SourceName;
78             public Luid SourceIdentifier;
79         }
80 
81         public enum SECURITY_LOGON_TYPE
82         {
83             System = 0, // Used only by the Sytem account
84             Interactive = 2,
85             Network,
86             Batch,
87             Service,
88             Proxy,
89             Unlock,
90             NetworkCleartext,
91             NewCredentials,
92             RemoteInteractive,
93             CachedInteractive,
94             CachedRemoteInteractive,
95             CachedUnlock
96         }
97     }
98 
99     internal class NativeMethods
100     {
101         [DllImport("advapi32.dll", SetLastError = true)]
102         public static extern bool AllocateLocallyUniqueId(
103             out Luid Luid);
104 
105         [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
106         public static extern bool CreateProcessWithTokenW(
107             SafeNativeHandle hToken,
108             LogonFlags dwLogonFlags,
109             [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
110             StringBuilder lpCommandLine,
111             Process.NativeHelpers.ProcessCreationFlags dwCreationFlags,
112             Process.SafeMemoryBuffer lpEnvironment,
113             [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
114             Process.NativeHelpers.STARTUPINFOEX lpStartupInfo,
115             out Process.NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
116 
117         [DllImport("kernel32.dll")]
118         public static extern UInt32 GetCurrentThreadId();
119 
120         [DllImport("user32.dll", SetLastError = true)]
121         public static extern NoopSafeHandle GetProcessWindowStation();
122 
123         [DllImport("user32.dll", SetLastError = true)]
124         public static extern NoopSafeHandle GetThreadDesktop(
125             UInt32 dwThreadId);
126 
127         [DllImport("secur32.dll", SetLastError = true)]
128         public static extern UInt32 LsaDeregisterLogonProcess(
129             IntPtr LsaHandle);
130 
131         [DllImport("secur32.dll", SetLastError = true)]
132         public static extern UInt32 LsaFreeReturnBuffer(
133             IntPtr Buffer);
134 
135         [DllImport("secur32.dll", SetLastError = true)]
136         public static extern UInt32 LsaGetLogonSessionData(
137             ref Luid LogonId,
138             out SafeLsaMemoryBuffer ppLogonSessionData);
139 
140         [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
141         public static extern UInt32 LsaLogonUser(
142             SafeLsaHandle LsaHandle,
143             NativeHelpers.LSA_STRING OriginName,
144             LogonType LogonType,
145             UInt32 AuthenticationPackage,
146             IntPtr AuthenticationInformation,
147             UInt32 AuthenticationInformationLength,
148             IntPtr LocalGroups,
149             NativeHelpers.TOKEN_SOURCE SourceContext,
150             out SafeLsaMemoryBuffer ProfileBuffer,
151             out UInt32 ProfileBufferLength,
152             out Luid LogonId,
153             out SafeNativeHandle Token,
154             out IntPtr Quotas,
155             out UInt32 SubStatus);
156 
157         [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
158         public static extern UInt32 LsaLookupAuthenticationPackage(
159             SafeLsaHandle LsaHandle,
160             NativeHelpers.LSA_STRING PackageName,
161             out UInt32 AuthenticationPackage);
162 
163         [DllImport("advapi32.dll")]
164         public static extern UInt32 LsaNtStatusToWinError(
165             UInt32 Status);
166 
167         [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
168         public static extern UInt32 LsaRegisterLogonProcess(
169             NativeHelpers.LSA_STRING LogonProcessName,
170             out SafeLsaHandle LsaHandle,
171             out IntPtr SecurityMode);
172     }
173 
174     internal class SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid
175     {
176         public SafeLsaHandle() : base(true) { }
177 
178         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
179         protected override bool ReleaseHandle()
180         {
181             UInt32 res = NativeMethods.LsaDeregisterLogonProcess(handle);
182             return res == 0;
183         }
184     }
185 
186     internal class SafeLsaMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
187     {
188         public SafeLsaMemoryBuffer() : base(true) { }
189 
190         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
AnsibleModule(string[] args, IDictionary argumentSpec, IDictionary[] fragments = null)191         protected override bool ReleaseHandle()
192         {
193             UInt32 res = NativeMethods.LsaFreeReturnBuffer(handle);
194             return res == 0;
195         }
196     }
197 
198     internal class NoopSafeHandle : SafeHandle
199     {
200         public NoopSafeHandle() : base(IntPtr.Zero, false) { }
201         public override bool IsInvalid { get { return false; } }
202 
203         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
204         protected override bool ReleaseHandle() { return true; }
205     }
206 
207     [Flags]
208     public enum LogonFlags
209     {
210         WithProfile = 0x00000001,
211         NetcredentialsOnly = 0x00000002
212     }
213 
214     public class BecomeUtil
215     {
216         private static List<string> SERVICE_SIDS = new List<string>()
217         {
218             "S-1-5-18", // NT AUTHORITY\SYSTEM
219             "S-1-5-19", // NT AUTHORITY\LocalService
220             "S-1-5-20"  // NT AUTHORITY\NetworkService
221         };
222         private static int WINDOWS_STATION_ALL_ACCESS = 0x000F037F;
223         private static int DESKTOP_RIGHTS_ALL_ACCESS = 0x000F01FF;
224 
225         public static Result CreateProcessAsUser(string username, string password, string command)
226         {
227             return CreateProcessAsUser(username, password, LogonFlags.WithProfile, LogonType.Interactive,
228                  null, command, null, null, "");
229         }
230 
231         public static Result CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType,
232             string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment,
233             string stdin)
234         {
235             byte[] stdinBytes;
236             if (String.IsNullOrEmpty(stdin))
237                 stdinBytes = new byte[0];
238             else
239             {
240                 if (!stdin.EndsWith(Environment.NewLine))
241                     stdin += Environment.NewLine;
242                 stdinBytes = new UTF8Encoding(false).GetBytes(stdin);
243             }
244             return CreateProcessAsUser(username, password, logonFlags, logonType, lpApplicationName, lpCommandLine,
245                 lpCurrentDirectory, environment, stdinBytes);
246         }
247 
248         /// <summary>
249         /// Creates a process as another user account. This method will attempt to run as another user with the
250         /// highest possible permissions available. The main privilege required is the SeDebugPrivilege, without
251         /// this privilege you can only run as a local or domain user if the username and password is specified.
252         /// </summary>
253         /// <param name="username">The username of the runas user</param>
254         /// <param name="password">The password of the runas user</param>
255         /// <param name="logonFlags">LogonFlags to control how to logon a user when the password is specified</param>
256         /// <param name="logonType">Controls what type of logon is used, this only applies when the password is specified</param>
257         /// <param name="lpApplicationName">The name of the executable or batch file to executable</param>
258         /// <param name="lpCommandLine">The command line to execute, typically this includes lpApplication as the first argument</param>
259         /// <param name="lpCurrentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param>
260         /// <param name="environment">A dictionary of key/value pairs to define the new process environment</param>
261         /// <param name="stdin">Bytes sent to the stdin pipe</param>
262         /// <returns>Ansible.Process.Result object that contains the command output and return code</returns>
263         public static Result CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType,
264             string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, byte[] stdin)
265         {
Create(string[] args, IDictionary argumentSpec, IDictionary[] fragments = null)266             // While we use STARTUPINFOEX having EXTENDED_STARTUPINFO_PRESENT causes a parameter validation error
267             Process.NativeHelpers.ProcessCreationFlags creationFlags = Process.NativeHelpers.ProcessCreationFlags.CREATE_UNICODE_ENVIRONMENT;
268             Process.NativeHelpers.PROCESS_INFORMATION pi = new Process.NativeHelpers.PROCESS_INFORMATION();
269             Process.NativeHelpers.STARTUPINFOEX si = new Process.NativeHelpers.STARTUPINFOEX();
270             si.startupInfo.dwFlags = Process.NativeHelpers.StartupInfoFlags.USESTDHANDLES;
Debug(string message)271 
272             SafeFileHandle stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinRead, stdinWrite;
273             ProcessUtil.CreateStdioPipes(si, out stdoutRead, out stdoutWrite, out stderrRead, out stderrWrite,
274                 out stdinRead, out stdinWrite);
275             FileStream stdinStream = new FileStream(stdinWrite, FileAccess.Write);
276 
Deprecate(string message, string version)277             // $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't
278             // make sense for these parameters
279             if (lpApplicationName == "")
280                 lpApplicationName = null;
281 
Deprecate(string message, string version, string collectionName)282             if (lpCurrentDirectory == "")
283                 lpCurrentDirectory = null;
284 
285             // A user may have 2 tokens, 1 limited and 1 elevated. GetUserTokens will return both token to ensure
286             // we don't close one of the pairs while the process is still running. If the process tries to retrieve
287             // one of the pairs and the token handle is closed then it will fail with ERROR_NO_SUCH_LOGON_SESSION.
288             List<SafeNativeHandle> userTokens = GetUserTokens(username, password, logonType);
Deprecate(string message, DateTime date)289             try
290             {
291                 using (Process.SafeMemoryBuffer lpEnvironment = ProcessUtil.CreateEnvironmentPointer(environment))
292                 {
293                     bool launchSuccess = false;
294                     StringBuilder commandLine = new StringBuilder(lpCommandLine);
295                     foreach (SafeNativeHandle token in userTokens)
296                     {
297                         // GetUserTokens could return null if an elevated token could not be retrieved.
298                         if (token == null)
299                             continue;
300 
301                         if (NativeMethods.CreateProcessWithTokenW(token, logonFlags, lpApplicationName,
302                                 commandLine, creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
303                         {
304                             launchSuccess = true;
305                             break;
306                         }
307                     }
308 
309                     if (!launchSuccess)
310                         throw new Process.Win32Exception("CreateProcessWithTokenW() failed");
311                 }
312                 return ProcessUtil.WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin,
313                     pi.hProcess);
314             }
315             finally
316             {
317                 userTokens.Where(t => t != null).ToList().ForEach(t => t.Dispose());
318             }
319         }
320 
321         private static List<SafeNativeHandle> GetUserTokens(string username, string password, LogonType logonType)
322         {
323             List<SafeNativeHandle> userTokens = new List<SafeNativeHandle>();
324 
325             SafeNativeHandle systemToken = null;
326             bool impersonated = false;
327             string becomeSid = username;
328             if (logonType != LogonType.NewCredentials)
329             {
330                 // If prefixed with .\, we are becoming a local account, strip the prefix
331                 if (username.StartsWith(".\\"))
332                     username = username.Substring(2);
333 
334                 NTAccount account = new NTAccount(username);
335                 becomeSid = ((SecurityIdentifier)account.Translate(typeof(SecurityIdentifier))).Value;
336 
LogEvent(string message, EventLogEntryType logEntryType = EventLogEntryType.Information, bool sanitise = true)337                 // Grant access to the current Windows Station and Desktop to the become user
338                 GrantAccessToWindowStationAndDesktop(account);
339 
340                 // Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service
341                 // account or have administrative rights on the become access token.
342                 // If we ultimately are becoming the SYSTEM account we want the token with the most privileges available.
343                 // https://github.com/ansible/ansible/issues/71453
344                 bool mostPrivileges = becomeSid == "S-1-5-18";
345                 systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"),
346                     new List<string>() { "SeTcbPrivilege" }, mostPrivileges);
347                 if (systemToken != null)
348                 {
349                     try
350                     {
351                         TokenUtil.ImpersonateToken(systemToken);
352                         impersonated = true;
353                     }
354                     catch (Process.Win32Exception) { }  // We tried, just rely on current user's permissions.
355                 }
356             }
357 
358             // We require impersonation if becoming a service sid or becoming a user without a password
359             if (!impersonated && (SERVICE_SIDS.Contains(becomeSid) || String.IsNullOrEmpty(password)))
360                 throw new Exception("Failed to get token for NT AUTHORITY\\SYSTEM required for become as a service account or an account without a password");
361 
362             try
363             {
364                 if (becomeSid == "S-1-5-18")
365                     userTokens.Add(systemToken);
366                 // Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass.
367                 // We only use S4U if no password was defined or it was null
368                 else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials)
369                 {
370                     // If no password was specified, try and duplicate an existing token for that user or use S4U to
371                     // generate one without network credentials
372                     SecurityIdentifier sid = new SecurityIdentifier(becomeSid);
373                     SafeNativeHandle becomeToken = GetPrimaryTokenForUser(sid);
374                     if (becomeToken != null)
375                     {
376                         userTokens.Add(GetElevatedToken(becomeToken));
377                         userTokens.Add(becomeToken);
378                     }
379                     else
380                     {
381                         becomeToken = GetS4UTokenForUser(sid, logonType);
382                         userTokens.Add(null);
Warn(string message)383                         userTokens.Add(becomeToken);
384                     }
385                 }
386                 else
387                 {
388                     string domain = null;
FromJson(string json)389                     switch (becomeSid)
390                     {
391                         case "S-1-5-19":
392                             logonType = LogonType.Service;
393                             domain = "NT AUTHORITY";
394                             username = "LocalService";
395                             break;
396                         case "S-1-5-20":
397                             logonType = LogonType.Service;
398                             domain = "NT AUTHORITY";
399                             username = "NetworkService";
400                             break;
401                         default:
402                             // Trying to become a local or domain account
403                             if (username.Contains(@"\"))
404                             {
405                                 string[] userSplit = username.Split(new char[1] { '\\' }, 2);
406                                 domain = userSplit[0];
407                                 username = userSplit[1];
408                             }
409                             else if (!username.Contains("@"))
410                                 domain = ".";
411                             break;
412                     }
413 
414                     SafeNativeHandle hToken = TokenUtil.LogonUser(username, domain, password, logonType,
415                         LogonProvider.Default);
416 
417                     // Get the elevated token for a local/domain accounts only
418                     if (!SERVICE_SIDS.Contains(becomeSid))
419                         userTokens.Add(GetElevatedToken(hToken));
420                     userTokens.Add(hToken);
421                 }
422             }
423             finally
424             {
GetParams(string[] args)425                 if (impersonated)
426                     TokenUtil.RevertToSelf();
427             }
428 
429             return userTokens;
430         }
431 
432         private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid,
433             List<string> requiredPrivileges = null, bool mostPrivileges = false)
434         {
435             // According to CreateProcessWithTokenW we require a token with
436             //  TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
437             // Also add in TOKEN_IMPERSONATE so we can get an impersonated token
438             TokenAccessLevels dwAccess = TokenAccessLevels.Query |
439                 TokenAccessLevels.Duplicate |
440                 TokenAccessLevels.AssignPrimary |
441                 TokenAccessLevels.Impersonate;
442 
ParseBool(object value)443             SafeNativeHandle userToken = null;
444             int privilegeCount = 0;
445 
446             foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess))
447             {
448                 // Filter out any Network logon tokens, using become with that is useless when S4U
449                 // can give us a Batch logon
450                 NativeHelpers.SECURITY_LOGON_TYPE tokenLogonType = GetTokenLogonType(hToken);
451                 if (tokenLogonType == NativeHelpers.SECURITY_LOGON_TYPE.Network)
452                     continue;
453 
454                 List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList();
455 
456                 // If the token has less or the same number of privileges than the current token, skip it.
457                 if (mostPrivileges && privilegeCount >= actualPrivileges.Count)
458                     continue;
459 
460                 // Check that the required privileges are on the token
461                 if (requiredPrivileges != null)
462                 {
ParseDict(object value)463                     int missing = requiredPrivileges.Where(x => !actualPrivileges.Contains(x)).Count();
464                     if (missing > 0)
465                         continue;
466                 }
467 
468                 // Duplicate the token to convert it to a primary token with the access level required.
469                 try
470                 {
471                     userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed,
472                         SecurityImpersonationLevel.Anonymous, TokenType.Primary);
473                     privilegeCount = actualPrivileges.Count;
474                 }
475                 catch (Process.Win32Exception)
476                 {
477                     continue;
478                 }
479 
480                 // If we don't care about getting the token with the most privileges, escape the loop as we already
481                 // have a token.
482                 if (!mostPrivileges)
483                     break;
484             }
485 
486             return userToken;
487         }
488 
489         private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)
490         {
491             NTAccount becomeAccount = (NTAccount)sid.Translate(typeof(NTAccount));
492             string[] userSplit = becomeAccount.Value.Split(new char[1] { '\\' }, 2);
493             string domainName = userSplit[0];
494             string username = userSplit[1];
495             bool domainUser = domainName.ToLowerInvariant() != Environment.MachineName.ToLowerInvariant();
496 
497             NativeHelpers.LSA_STRING logonProcessName = "ansible";
498             SafeLsaHandle lsaHandle;
499             IntPtr securityMode;
500             UInt32 res = NativeMethods.LsaRegisterLogonProcess(logonProcessName, out lsaHandle, out securityMode);
501             if (res != 0)
502                 throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaRegisterLogonProcess() failed");
503 
504             using (lsaHandle)
505             {
506                 NativeHelpers.LSA_STRING packageName = domainUser ? "Kerberos" : "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0";
507                 UInt32 authPackage;
508                 res = NativeMethods.LsaLookupAuthenticationPackage(lsaHandle, packageName, out authPackage);
509                 if (res != 0)
510                     throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
511                         String.Format("LsaLookupAuthenticationPackage({0}) failed", (string)packageName));
512 
513                 int usernameLength = username.Length * sizeof(char);
514                 int domainLength = domainName.Length * sizeof(char);
515                 int authInfoLength = (Marshal.SizeOf(typeof(NativeHelpers.KERB_S4U_LOGON)) + usernameLength + domainLength);
516                 IntPtr authInfo = Marshal.AllocHGlobal((int)authInfoLength);
517                 try
518                 {
519                     IntPtr usernamePtr = IntPtr.Add(authInfo, Marshal.SizeOf(typeof(NativeHelpers.KERB_S4U_LOGON)));
ParseFloat(object value)520                     IntPtr domainPtr = IntPtr.Add(usernamePtr, usernameLength);
521 
522                     // KERB_S4U_LOGON has the same structure as MSV1_0_S4U_LOGON (local accounts)
523                     NativeHelpers.KERB_S4U_LOGON s4uLogon = new NativeHelpers.KERB_S4U_LOGON
524                     {
525                         MessageType = 12,  // KerbS4ULogon
526                         Flags = 0,
527                         ClientUpn = new NativeHelpers.LSA_UNICODE_STRING
528                         {
ParseInt(object value)529                             Length = (UInt16)usernameLength,
530                             MaximumLength = (UInt16)usernameLength,
531                             Buffer = usernamePtr,
532                         },
533                         ClientRealm = new NativeHelpers.LSA_UNICODE_STRING
534                         {
535                             Length = (UInt16)domainLength,
536                             MaximumLength = (UInt16)domainLength,
537                             Buffer = domainPtr,
538                         },
539                     };
540                     Marshal.StructureToPtr(s4uLogon, authInfo, false);
541                     Marshal.Copy(username.ToCharArray(), 0, usernamePtr, username.Length);
542                     Marshal.Copy(domainName.ToCharArray(), 0, domainPtr, domainName.Length);
543 
544                     Luid sourceLuid;
545                     if (!NativeMethods.AllocateLocallyUniqueId(out sourceLuid))
546                         throw new Process.Win32Exception("AllocateLocallyUniqueId() failed");
547 
548                     NativeHelpers.TOKEN_SOURCE tokenSource = new NativeHelpers.TOKEN_SOURCE
549                     {
550                         SourceName = "ansible\0".ToCharArray(),
ParseList(object value)551                         SourceIdentifier = sourceLuid,
552                     };
553 
554                     // Only Batch or Network will work with S4U, prefer Batch but use Network if asked
555                     LogonType lsaLogonType = logonType == LogonType.Network
556                         ? LogonType.Network
557                         : LogonType.Batch;
558                     SafeLsaMemoryBuffer profileBuffer;
559                     UInt32 profileBufferLength;
560                     Luid logonId;
561                     SafeNativeHandle hToken;
562                     IntPtr quotas;
563                     UInt32 subStatus;
564 
565                     res = NativeMethods.LsaLogonUser(lsaHandle, logonProcessName, lsaLogonType, authPackage,
566                         authInfo, (UInt32)authInfoLength, IntPtr.Zero, tokenSource, out profileBuffer, out profileBufferLength,
567                         out logonId, out hToken, out quotas, out subStatus);
568                     if (res != 0)
569                         throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
570                             String.Format("LsaLogonUser() failed with substatus {0}", subStatus));
ParsePath(object value)571 
572                     profileBuffer.Dispose();
573                     return hToken;
574                 }
575                 finally
576                 {
577                     Marshal.FreeHGlobal(authInfo);
578                 }
579             }
580         }
581 
582         private static SafeNativeHandle GetElevatedToken(SafeNativeHandle hToken)
583         {
584             TokenElevationType tet = TokenUtil.GetTokenElevationType(hToken);
585             // We already have the best token we can get, no linked token is really available.
586             if (tet != TokenElevationType.Limited)
587                 return null;
588 
ParseRaw(object value)589             SafeNativeHandle linkedToken = TokenUtil.GetTokenLinkedToken(hToken);
590             TokenStatistics tokenStats = TokenUtil.GetTokenStatistics(linkedToken);
ParseSid(object value)591 
592             // We can only use a token if it's a primary one (we had the SeTcbPrivilege set)
593             if (tokenStats.TokenType == TokenType.Primary)
594                 return linkedToken;
595             else
596                 return null;
597         }
598 
599         private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken)
600         {
601             TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken);
602 
603             SafeLsaMemoryBuffer sessionDataPtr;
604             UInt32 res = NativeMethods.LsaGetLogonSessionData(ref stats.AuthenticationId, out sessionDataPtr);
ParseStr(object value)605             if (res != 0)
606                 // Default to Network, if we weren't able to get the actual type treat it as an error and assume
607                 // we don't want to run a process with the token
608                 return NativeHelpers.SECURITY_LOGON_TYPE.Network;
609 
610             using (sessionDataPtr)
611             {
612                 NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(
613                     sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA));
614                 return sessionData.LogonType;
615             }
616         }
617 
618         private static void GrantAccessToWindowStationAndDesktop(IdentityReference account)
619         {
620             GrantAccess(account, NativeMethods.GetProcessWindowStation(), WINDOWS_STATION_ALL_ACCESS);
621             GrantAccess(account, NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId()), DESKTOP_RIGHTS_ALL_ACCESS);
622         }
623 
624         private static void GrantAccess(IdentityReference account, NoopSafeHandle handle, int accessMask)
625         {
626             GenericSecurity security = new GenericSecurity(false, ResourceType.WindowObject, handle, AccessControlSections.Access);
627             security.AddAccessRule(new GenericAccessRule(account, accessMask, AccessControlType.Allow));
628             security.Persist(handle, AccessControlSections.Access);
629         }
630 
631         private class GenericSecurity : NativeObjectSecurity
632         {
633             public GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested)
634                 : base(isContainer, resType, objectHandle, sectionsRequested) { }
635             public new void Persist(SafeHandle handle, AccessControlSections includeSections) { base.Persist(handle, includeSections); }
636             public new void AddAccessRule(AccessRule rule) { base.AddAccessRule(rule); }
637             public override Type AccessRightType { get { throw new NotImplementedException(); } }
638             public override AccessRule AccessRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
639                 InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
640             { throw new NotImplementedException(); }
641             public override Type AccessRuleType { get { return typeof(AccessRule); } }
642             public override AuditRule AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
643                 InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags)
644             { throw new NotImplementedException(); }
645             public override Type AuditRuleType { get { return typeof(AuditRule); } }
646         }
647 
648         private class GenericAccessRule : AccessRule
649         {
650             public GenericAccessRule(IdentityReference identity, int accessMask, AccessControlType type) :
651                 base(identity, accessMask, false, InheritanceFlags.None, PropagationFlags.None, type)
652             { }
653         }
654     }
655 }
656