context: 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 Sytem 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.
                  systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), new List<string>() { "SeTcbPrivilege" });
                  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
              {
  
gt:                 if (becomeSid == "S-1-5-18")