summaryrefslogtreecommitdiffstats
path: root/lib/ansible/module_utils/csharp/Ansible.Become.cs
blob: a6f645cabd3c15d86be6963ed8b05b4c5f656e67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using Ansible.AccessToken;
using Ansible.Process;

namespace Ansible.Become
{
    internal class NativeHelpers
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct KERB_S4U_LOGON
        {
            public UInt32 MessageType;
            public UInt32 Flags;
            public LSA_UNICODE_STRING ClientUpn;
            public LSA_UNICODE_STRING ClientRealm;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct LSA_STRING
        {
            public UInt16 Length;
            public UInt16 MaximumLength;
            [MarshalAs(UnmanagedType.LPStr)] public string Buffer;

            public static implicit operator string(LSA_STRING s)
            {
                return s.Buffer;
            }

            public static implicit operator LSA_STRING(string s)
            {
                if (s == null)
                    s = "";

                LSA_STRING lsaStr = new LSA_STRING
                {
                    Buffer = s,
                    Length = (UInt16)s.Length,
                    MaximumLength = (UInt16)(s.Length + 1),
                };
                return lsaStr;
            }
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct LSA_UNICODE_STRING
        {
            public UInt16 Length;
            public UInt16 MaximumLength;
            public IntPtr Buffer;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_LOGON_SESSION_DATA
        {
            public UInt32 Size;
            public Luid LogonId;
            public LSA_UNICODE_STRING UserName;
            public LSA_UNICODE_STRING LogonDomain;
            public LSA_UNICODE_STRING AuthenticationPackage;
            public SECURITY_LOGON_TYPE LogonType;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_SOURCE
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char[] SourceName;
            public Luid SourceIdentifier;
        }

        public enum SECURITY_LOGON_TYPE
        {
            System = 0, // Used only by the System account
            Interactive = 2,
            Network,
            Batch,
            Service,
            Proxy,
            Unlock,
            NetworkCleartext,
            NewCredentials,
            RemoteInteractive,
            CachedInteractive,
            CachedRemoteInteractive,
            CachedUnlock
        }
    }

    internal class NativeMethods
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool AllocateLocallyUniqueId(
            out Luid Luid);

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CreateProcessWithTokenW(
            SafeNativeHandle hToken,
            LogonFlags dwLogonFlags,
            [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
            StringBuilder lpCommandLine,
            Process.NativeHelpers.ProcessCreationFlags dwCreationFlags,
            Process.SafeMemoryBuffer lpEnvironment,
            [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
            Process.NativeHelpers.STARTUPINFOEX lpStartupInfo,
            out Process.NativeHelpers.PROCESS_INFORMATION lpProcessInformation);

        [DllImport("kernel32.dll")]
        public static extern UInt32 GetCurrentThreadId();

        [DllImport("user32.dll", SetLastError = true)]
        public static extern NoopSafeHandle GetProcessWindowStation();

        [DllImport("user32.dll", SetLastError = true)]
        public static extern NoopSafeHandle GetThreadDesktop(
            UInt32 dwThreadId);

        [DllImport("secur32.dll", SetLastError = true)]
        public static extern UInt32 LsaDeregisterLogonProcess(
            IntPtr LsaHandle);

        [DllImport("secur32.dll", SetLastError = true)]
        public static extern UInt32 LsaFreeReturnBuffer(
            IntPtr Buffer);

        [DllImport("secur32.dll", SetLastError = true)]
        public static extern UInt32 LsaGetLogonSessionData(
            ref Luid LogonId,
            out SafeLsaMemoryBuffer ppLogonSessionData);

        [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern UInt32 LsaLogonUser(
            SafeLsaHandle LsaHandle,
            NativeHelpers.LSA_STRING OriginName,
            LogonType LogonType,
            UInt32 AuthenticationPackage,
            IntPtr AuthenticationInformation,
            UInt32 AuthenticationInformationLength,
            IntPtr LocalGroups,
            NativeHelpers.TOKEN_SOURCE SourceContext,
            out SafeLsaMemoryBuffer ProfileBuffer,
            out UInt32 ProfileBufferLength,
            out Luid LogonId,
            out SafeNativeHandle Token,
            out IntPtr Quotas,
            out UInt32 SubStatus);

        [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern UInt32 LsaLookupAuthenticationPackage(
            SafeLsaHandle LsaHandle,
            NativeHelpers.LSA_STRING PackageName,
            out UInt32 AuthenticationPackage);

        [DllImport("advapi32.dll")]
        public static extern UInt32 LsaNtStatusToWinError(
            UInt32 Status);

        [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern UInt32 LsaRegisterLogonProcess(
            NativeHelpers.LSA_STRING LogonProcessName,
            out SafeLsaHandle LsaHandle,
            out IntPtr SecurityMode);
    }

    internal class SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        public SafeLsaHandle() : base(true) { }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        protected override bool ReleaseHandle()
        {
            UInt32 res = NativeMethods.LsaDeregisterLogonProcess(handle);
            return res == 0;
        }
    }

    internal class SafeLsaMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
    {
        public SafeLsaMemoryBuffer() : base(true) { }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        protected override bool ReleaseHandle()
        {
            UInt32 res = NativeMethods.LsaFreeReturnBuffer(handle);
            return res == 0;
        }
    }

    internal class NoopSafeHandle : SafeHandle
    {
        public NoopSafeHandle() : base(IntPtr.Zero, false) { }
        public override bool IsInvalid { get { return false; } }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        protected override bool ReleaseHandle() { return true; }
    }

    [Flags]
    public enum LogonFlags
    {
        WithProfile = 0x00000001,
        NetcredentialsOnly = 0x00000002
    }

    public class BecomeUtil
    {
        private static List<string> SERVICE_SIDS = new List<string>()
        {
            "S-1-5-18", // NT AUTHORITY\SYSTEM
            "S-1-5-19", // NT AUTHORITY\LocalService
            "S-1-5-20"  // NT AUTHORITY\NetworkService
        };
        private static int WINDOWS_STATION_ALL_ACCESS = 0x000F037F;
        private static int DESKTOP_RIGHTS_ALL_ACCESS = 0x000F01FF;

        public static Result CreateProcessAsUser(string username, string password, string command)
        {
            return CreateProcessAsUser(username, password, LogonFlags.WithProfile, LogonType.Interactive,
                 null, command, null, null, "");
        }

        public static Result CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType,
            string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment,
            string stdin)
        {
            byte[] stdinBytes;
            if (String.IsNullOrEmpty(stdin))
                stdinBytes = new byte[0];
            else
            {
                if (!stdin.EndsWith(Environment.NewLine))
                    stdin += Environment.NewLine;
                stdinBytes = new UTF8Encoding(false).GetBytes(stdin);
            }
            return CreateProcessAsUser(username, password, logonFlags, logonType, lpApplicationName, lpCommandLine,
                lpCurrentDirectory, environment, stdinBytes);
        }

        /// <summary>
        /// Creates a process as another user account. This method will attempt to run as another user with the
        /// highest possible permissions available. The main privilege required is the SeDebugPrivilege, without
        /// this privilege you can only run as a local or domain user if the username and password is specified.
        /// </summary>
        /// <param name="username">The username of the runas user</param>
        /// <param name="password">The password of the runas user</param>
        /// <param name="logonFlags">LogonFlags to control how to logon a user when the password is specified</param>
        /// <param name="logonType">Controls what type of logon is used, this only applies when the password is specified</param>
        /// <param name="lpApplicationName">The name of the executable or batch file to executable</param>
        /// <param name="lpCommandLine">The command line to execute, typically this includes lpApplication as the first argument</param>
        /// <param name="lpCurrentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param>
        /// <param name="environment">A dictionary of key/value pairs to define the new process environment</param>
        /// <param name="stdin">Bytes sent to the stdin pipe</param>
        /// <returns>Ansible.Process.Result object that contains the command output and return code</returns>
        public static Result CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType,
            string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, byte[] stdin)
        {
            // While we use STARTUPINFOEX having EXTENDED_STARTUPINFO_PRESENT causes a parameter validation error
            Process.NativeHelpers.ProcessCreationFlags creationFlags = Process.NativeHelpers.ProcessCreationFlags.CREATE_UNICODE_ENVIRONMENT;
            Process.NativeHelpers.PROCESS_INFORMATION pi = new Process.NativeHelpers.PROCESS_INFORMATION();
            Process.NativeHelpers.STARTUPINFOEX si = new Process.NativeHelpers.STARTUPINFOEX();
            si.startupInfo.dwFlags = Process.NativeHelpers.StartupInfoFlags.USESTDHANDLES;

            SafeFileHandle stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinRead, stdinWrite;
            ProcessUtil.CreateStdioPipes(si, out stdoutRead, out stdoutWrite, out stderrRead, out stderrWrite,
                out stdinRead, out stdinWrite);
            FileStream stdinStream = new FileStream(stdinWrite, FileAccess.Write);

            // $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't
            // make sense for these parameters
            if (lpApplicationName == "")
                lpApplicationName = null;

            if (lpCurrentDirectory == "")
                lpCurrentDirectory = null;

            // A user may have 2 tokens, 1 limited and 1 elevated. GetUserTokens will return both token to ensure
            // we don't close one of the pairs while the process is still running. If the process tries to retrieve
            // one of the pairs and the token handle is closed then it will fail with ERROR_NO_SUCH_LOGON_SESSION.
            List<SafeNativeHandle> userTokens = GetUserTokens(username, password, logonType);
            try
            {
                using (Process.SafeMemoryBuffer lpEnvironment = ProcessUtil.CreateEnvironmentPointer(environment))
                {
                    bool launchSuccess = false;
                    StringBuilder commandLine = new StringBuilder(lpCommandLine);
                    foreach (SafeNativeHandle token in userTokens)
                    {
                        // GetUserTokens could return null if an elevated token could not be retrieved.
                        if (token == null)
                            continue;

                        if (NativeMethods.CreateProcessWithTokenW(token, logonFlags, lpApplicationName,
                                commandLine, creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
                        {
                            launchSuccess = true;
                            break;
                        }
                    }

                    if (!launchSuccess)
                        throw new Process.Win32Exception("CreateProcessWithTokenW() failed");
                }
                return ProcessUtil.WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin,
                    pi.hProcess);
            }
            finally
            {
                userTokens.Where(t => t != null).ToList().ForEach(t => t.Dispose());
            }
        }

        private static List<SafeNativeHandle> GetUserTokens(string username, string password, LogonType logonType)
        {
            List<SafeNativeHandle> userTokens = new List<SafeNativeHandle>();

            SafeNativeHandle systemToken = null;
            bool impersonated = false;
            string becomeSid = username;
            if (logonType != LogonType.NewCredentials)
            {
                // If prefixed with .\, we are becoming a local account, strip the prefix
                if (username.StartsWith(".\\"))
                    username = username.Substring(2);

                NTAccount account = new NTAccount(username);
                becomeSid = ((SecurityIdentifier)account.Translate(typeof(SecurityIdentifier))).Value;

                // Grant access to the current Windows Station and Desktop to the become user
                GrantAccessToWindowStationAndDesktop(account);

                // Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service
                // account or have administrative rights on the become access token.
                // If we ultimately are becoming the SYSTEM account we want the token with the most privileges available.
                // https://github.com/ansible/ansible/issues/71453
                bool mostPrivileges = becomeSid == "S-1-5-18";
                systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"),
                    new List<string>() { "SeTcbPrivilege" }, mostPrivileges);
                if (systemToken != null)
                {
                    try
                    {
                        TokenUtil.ImpersonateToken(systemToken);
                        impersonated = true;
                    }
                    catch (Process.Win32Exception) { }  // We tried, just rely on current user's permissions.
                }
            }

            // We require impersonation if becoming a service sid or becoming a user without a password
            if (!impersonated && (SERVICE_SIDS.Contains(becomeSid) || String.IsNullOrEmpty(password)))
                throw new Exception("Failed to get token for NT AUTHORITY\\SYSTEM required for become as a service account or an account without a password");

            try
            {
                if (becomeSid == "S-1-5-18")
                    userTokens.Add(systemToken);
                // Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass.
                // We only use S4U if no password was defined or it was null
                else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials)
                {
                    // If no password was specified, try and duplicate an existing token for that user or use S4U to
                    // generate one without network credentials
                    SecurityIdentifier sid = new SecurityIdentifier(becomeSid);
                    SafeNativeHandle becomeToken = GetPrimaryTokenForUser(sid);
                    if (becomeToken != null)
                    {
                        userTokens.Add(GetElevatedToken(becomeToken));
                        userTokens.Add(becomeToken);
                    }
                    else
                    {
                        becomeToken = GetS4UTokenForUser(sid, logonType);
                        userTokens.Add(null);
                        userTokens.Add(becomeToken);
                    }
                }
                else
                {
                    string domain = null;
                    switch (becomeSid)
                    {
                        case "S-1-5-19":
                            logonType = LogonType.Service;
                            domain = "NT AUTHORITY";
                            username = "LocalService";
                            break;
                        case "S-1-5-20":
                            logonType = LogonType.Service;
                            domain = "NT AUTHORITY";
                            username = "NetworkService";
                            break;
                        default:
                            // Trying to become a local or domain account
                            if (username.Contains(@"\"))
                            {
                                string[] userSplit = username.Split(new char[1] { '\\' }, 2);
                                domain = userSplit[0];
                                username = userSplit[1];
                            }
                            else if (!username.Contains("@"))
                                domain = ".";
                            break;
                    }

                    SafeNativeHandle hToken = TokenUtil.LogonUser(username, domain, password, logonType,
                        LogonProvider.Default);

                    // Get the elevated token for a local/domain accounts only
                    if (!SERVICE_SIDS.Contains(becomeSid))
                        userTokens.Add(GetElevatedToken(hToken));
                    userTokens.Add(hToken);
                }
            }
            finally
            {
                if (impersonated)
                    TokenUtil.RevertToSelf();
            }

            return userTokens;
        }

        private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid,
            List<string> requiredPrivileges = null, bool mostPrivileges = false)
        {
            // According to CreateProcessWithTokenW we require a token with
            //  TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
            // Also add in TOKEN_IMPERSONATE so we can get an impersonated token
            TokenAccessLevels dwAccess = TokenAccessLevels.Query |
                TokenAccessLevels.Duplicate |
                TokenAccessLevels.AssignPrimary |
                TokenAccessLevels.Impersonate;

            SafeNativeHandle userToken = null;
            int privilegeCount = 0;

            foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess))
            {
                // Filter out any Network logon tokens, using become with that is useless when S4U
                // can give us a Batch logon
                NativeHelpers.SECURITY_LOGON_TYPE tokenLogonType = GetTokenLogonType(hToken);
                if (tokenLogonType == NativeHelpers.SECURITY_LOGON_TYPE.Network)
                    continue;

                List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList();

                // If the token has less or the same number of privileges than the current token, skip it.
                if (mostPrivileges && privilegeCount >= actualPrivileges.Count)
                    continue;

                // Check that the required privileges are on the token
                if (requiredPrivileges != null)
                {
                    int missing = requiredPrivileges.Where(x => !actualPrivileges.Contains(x)).Count();
                    if (missing > 0)
                        continue;
                }

                // Duplicate the token to convert it to a primary token with the access level required.
                try
                {
                    userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed,
                        SecurityImpersonationLevel.Anonymous, TokenType.Primary);
                    privilegeCount = actualPrivileges.Count;
                }
                catch (Process.Win32Exception)
                {
                    continue;
                }

                // If we don't care about getting the token with the most privileges, escape the loop as we already
                // have a token.
                if (!mostPrivileges)
                    break;
            }

            return userToken;
        }

        private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)
        {
            NTAccount becomeAccount = (NTAccount)sid.Translate(typeof(NTAccount));
            string[] userSplit = becomeAccount.Value.Split(new char[1] { '\\' }, 2);
            string domainName = userSplit[0];
            string username = userSplit[1];
            bool domainUser = domainName.ToLowerInvariant() != Environment.MachineName.ToLowerInvariant();

            NativeHelpers.LSA_STRING logonProcessName = "ansible";
            SafeLsaHandle lsaHandle;
            IntPtr securityMode;
            UInt32 res = NativeMethods.LsaRegisterLogonProcess(logonProcessName, out lsaHandle, out securityMode);
            if (res != 0)
                throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaRegisterLogonProcess() failed");

            using (lsaHandle)
            {
                NativeHelpers.LSA_STRING packageName = domainUser ? "Kerberos" : "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0";
                UInt32 authPackage;
                res = NativeMethods.LsaLookupAuthenticationPackage(lsaHandle, packageName, out authPackage);
                if (res != 0)
                    throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
                        String.Format("LsaLookupAuthenticationPackage({0}) failed", (string)packageName));

                int usernameLength = username.Length * sizeof(char);
                int domainLength = domainName.Length * sizeof(char);
                int authInfoLength = (Marshal.SizeOf(typeof(NativeHelpers.KERB_S4U_LOGON)) + usernameLength + domainLength);
                IntPtr authInfo = Marshal.AllocHGlobal((int)authInfoLength);
                try
                {
                    IntPtr usernamePtr = IntPtr.Add(authInfo, Marshal.SizeOf(typeof(NativeHelpers.KERB_S4U_LOGON)));
                    IntPtr domainPtr = IntPtr.Add(usernamePtr, usernameLength);

                    // KERB_S4U_LOGON has the same structure as MSV1_0_S4U_LOGON (local accounts)
                    NativeHelpers.KERB_S4U_LOGON s4uLogon = new NativeHelpers.KERB_S4U_LOGON
                    {
                        MessageType = 12,  // KerbS4ULogon
                        Flags = 0,
                        ClientUpn = new NativeHelpers.LSA_UNICODE_STRING
                        {
                            Length = (UInt16)usernameLength,
                            MaximumLength = (UInt16)usernameLength,
                            Buffer = usernamePtr,
                        },
                        ClientRealm = new NativeHelpers.LSA_UNICODE_STRING
                        {
                            Length = (UInt16)domainLength,
                            MaximumLength = (UInt16)domainLength,
                            Buffer = domainPtr,
                        },
                    };
                    Marshal.StructureToPtr(s4uLogon, authInfo, false);
                    Marshal.Copy(username.ToCharArray(), 0, usernamePtr, username.Length);
                    Marshal.Copy(domainName.ToCharArray(), 0, domainPtr, domainName.Length);

                    Luid sourceLuid;
                    if (!NativeMethods.AllocateLocallyUniqueId(out sourceLuid))
                        throw new Process.Win32Exception("AllocateLocallyUniqueId() failed");

                    NativeHelpers.TOKEN_SOURCE tokenSource = new NativeHelpers.TOKEN_SOURCE
                    {
                        SourceName = "ansible\0".ToCharArray(),
                        SourceIdentifier = sourceLuid,
                    };

                    // Only Batch or Network will work with S4U, prefer Batch but use Network if asked
                    LogonType lsaLogonType = logonType == LogonType.Network
                        ? LogonType.Network
                        : LogonType.Batch;
                    SafeLsaMemoryBuffer profileBuffer;
                    UInt32 profileBufferLength;
                    Luid logonId;
                    SafeNativeHandle hToken;
                    IntPtr quotas;
                    UInt32 subStatus;

                    res = NativeMethods.LsaLogonUser(lsaHandle, logonProcessName, lsaLogonType, authPackage,
                        authInfo, (UInt32)authInfoLength, IntPtr.Zero, tokenSource, out profileBuffer, out profileBufferLength,
                        out logonId, out hToken, out quotas, out subStatus);
                    if (res != 0)
                        throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
                            String.Format("LsaLogonUser() failed with substatus {0}", subStatus));

                    profileBuffer.Dispose();
                    return hToken;
                }
                finally
                {
                    Marshal.FreeHGlobal(authInfo);
                }
            }
        }

        private static SafeNativeHandle GetElevatedToken(SafeNativeHandle hToken)
        {
            TokenElevationType tet = TokenUtil.GetTokenElevationType(hToken);
            // We already have the best token we can get, no linked token is really available.
            if (tet != TokenElevationType.Limited)
                return null;

            SafeNativeHandle linkedToken = TokenUtil.GetTokenLinkedToken(hToken);
            TokenStatistics tokenStats = TokenUtil.GetTokenStatistics(linkedToken);

            // We can only use a token if it's a primary one (we had the SeTcbPrivilege set)
            if (tokenStats.TokenType == TokenType.Primary)
                return linkedToken;
            else
                return null;
        }

        private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken)
        {
            TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken);

            SafeLsaMemoryBuffer sessionDataPtr;
            UInt32 res = NativeMethods.LsaGetLogonSessionData(ref stats.AuthenticationId, out sessionDataPtr);
            if (res != 0)
                // Default to Network, if we weren't able to get the actual type treat it as an error and assume
                // we don't want to run a process with the token
                return NativeHelpers.SECURITY_LOGON_TYPE.Network;

            using (sessionDataPtr)
            {
                NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(
                    sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA));
                return sessionData.LogonType;
            }
        }

        private static void GrantAccessToWindowStationAndDesktop(IdentityReference account)
        {
            GrantAccess(account, NativeMethods.GetProcessWindowStation(), WINDOWS_STATION_ALL_ACCESS);
            GrantAccess(account, NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId()), DESKTOP_RIGHTS_ALL_ACCESS);
        }

        private static void GrantAccess(IdentityReference account, NoopSafeHandle handle, int accessMask)
        {
            GenericSecurity security = new GenericSecurity(false, ResourceType.WindowObject, handle, AccessControlSections.Access);
            security.AddAccessRule(new GenericAccessRule(account, accessMask, AccessControlType.Allow));
            security.Persist(handle, AccessControlSections.Access);
        }

        private class GenericSecurity : NativeObjectSecurity
        {
            public GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested)
                : base(isContainer, resType, objectHandle, sectionsRequested) { }
            public new void Persist(SafeHandle handle, AccessControlSections includeSections) { base.Persist(handle, includeSections); }
            public new void AddAccessRule(AccessRule rule) { base.AddAccessRule(rule); }
            public override Type AccessRightType { get { throw new NotImplementedException(); } }
            public override AccessRule AccessRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
                InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
            { throw new NotImplementedException(); }
            public override Type AccessRuleType { get { return typeof(AccessRule); } }
            public override AuditRule AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
                InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags)
            { throw new NotImplementedException(); }
            public override Type AuditRuleType { get { return typeof(AuditRule); } }
        }

        private class GenericAccessRule : AccessRule
        {
            public GenericAccessRule(IdentityReference identity, int accessMask, AccessControlType type) :
                base(identity, accessMask, false, InheritanceFlags.None, PropagationFlags.None, type)
            { }
        }
    }
}