diff --git a/lib/ansible/plugins/shell/powershell.py b/lib/ansible/plugins/shell/powershell.py index 1c536251ab0..d131442cd14 100644 --- a/lib/ansible/plugins/shell/powershell.py +++ b/lib/ansible/plugins/shell/powershell.py @@ -204,13 +204,8 @@ namespace Ansible public IntPtr lpReserved; public IntPtr lpDesktop; public IntPtr lpTitle; - public Int32 dwX; - public Int32 dwY; - public Int32 dwXSize; - public Int32 dwYSize; - public Int32 dwXCountChars; - public Int32 dwYCountChars; - public Int32 dwFillAttribute; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)] + public byte[] _data1; public Int32 dwFlags; public Int16 wShowWindow; public Int16 cbReserved2; @@ -257,17 +252,6 @@ namespace Ansible public SID_AND_ATTRIBUTES User; } - [StructLayout(LayoutKind.Sequential)] - public struct IO_COUNTERS - { - public UInt64 ReadOperationCount; - public UInt64 WriteOperationCount; - public UInt64 OtherOperationCount; - public UInt64 ReadTransferCount; - public UInt64 WriteTransferCount; - public UInt64 OtherTransferCount; - } - [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_BASIC_LIMIT_INFORMATION { @@ -283,14 +267,13 @@ namespace Ansible } [StructLayout(LayoutKind.Sequential)] - public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION + public class JOBOBJECT_EXTENDED_LIMIT_INFORMATION { - public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; - public IO_COUNTERS IoInfo; - public UIntPtr ProcessMemoryLimit; - public UIntPtr JobMemoryLimit; - public UIntPtr PeakProcessMemoryUsed; - public UIntPtr PeakJobMemoryUsed; + public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION(); + [MarshalAs(UnmanagedType.ByValArray, SizeConst=48)] + public byte[] IO_COUNTERS_BLOB; + [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)] + public UIntPtr[] LIMIT_BLOB; } [Flags] @@ -305,8 +288,6 @@ namespace Ansible CREATE_BREAKAWAY_FROM_JOB = 0x01000000, CREATE_DEFAULT_ERROR_MODE = 0x04000000, CREATE_NEW_CONSOLE = 0x00000010, - CREATE_NEW_PROCESS_GROUP = 0x00000200, - CREATE_SEPARATE_WOW_VDM = 0x00000800, CREATE_SUSPENDED = 0x00000004, CREATE_UNICODE_ENVIRONMENT = 0x00000400, EXTENDED_STARTUPINFO_PRESENT = 0x00080000 @@ -339,9 +320,6 @@ namespace Ansible public enum LogonProvider { LOGON32_PROVIDER_DEFAULT = 0, - LOGON32_PROVIDER_WINNT35 = 1, - LOGON32_PROVIDER_WINNT40 = 2, - LOGON32_PROVIDER_WINNT50 = 3 } public enum TokenInformationClass @@ -363,28 +341,12 @@ namespace Ansible [Flags] public enum ProcessAccessFlags : uint { - PROCESS_ALL_ACCESS = 0x001F0FFF, - PROCESS_TERMINATE = 0x00000001, - PROCESS_CREATE_THREAD = 0x00000002, - PROCESS_VM_OPERATION = 0x00000008, - PROCESS_VM_READ = 0x00000010, - PROCESS_VM_WRITE = 0x00000020, - PROCESS_DUP_HANDLE = 0x00000040, - PROCESS_CREATE_PROCESS = 0x000000080, - PROCESS_SET_QUOTA = 0x00000100, - PROCESS_SET_INFORMATION = 0x00000200, PROCESS_QUERY_INFORMATION = 0x00000400, - PROCESS_SUSPEND_RESUME = 0x00000800, - PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000, - SYNCHRONIZE = 0x00100000 } public enum SECURITY_IMPERSONATION_LEVEL { - SecurityAnoynmous, - SecurityIdentification, SecurityImpersonation, - SecurityDelegation } public enum TOKEN_TYPE @@ -395,13 +357,7 @@ namespace Ansible enum JobObjectInfoType { - AssociateCompletionPortInformation = 7, - BasicLimitInformation = 2, - BasicUIRestrictions = 4, - EndOfJobTimeInformation = 6, ExtendedLimitInformation = 9, - SecurityLimitInformation = 5, - GroupInformation = 11 } [Flags] @@ -455,8 +411,8 @@ namespace Ansible private static extern bool SetInformationJobObject( IntPtr hJob, JobObjectInfoType JobObjectInfoClass, - IntPtr lpJobObjectInfo, - UInt32 cbJobObjectInfoLength); + JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo, + int cbJobObjectInfoLength); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AssignProcessToJobObject( @@ -475,17 +431,11 @@ namespace Ansible if (handle == IntPtr.Zero) throw new Win32Exception("CreateJobObject() failed"); - JOBOBJECT_BASIC_LIMIT_INFORMATION jobInfo = new JOBOBJECT_BASIC_LIMIT_INFORMATION(); - jobInfo.LimitFlags = LimitFlags.JOB_OBJECT_LIMIT_BREAKAWAY_OK | LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedJobInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); - extendedJobInfo.BasicLimitInformation = jobInfo; + // on OSs that support nested jobs, one of the jobs must allow breakaway for async to work properly under WinRM + extendedJobInfo.BasicLimitInformation.LimitFlags = LimitFlags.JOB_OBJECT_LIMIT_BREAKAWAY_OK | LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); - IntPtr pExtendedJobInfo = Marshal.AllocHGlobal(length); - Marshal.StructureToPtr(extendedJobInfo, pExtendedJobInfo, false); - - if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, pExtendedJobInfo, (UInt32)length)) + if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedJobInfo, Marshal.SizeOf(extendedJobInfo))) throw new Win32Exception("SetInformationJobObject() failed"); } @@ -628,8 +578,6 @@ namespace Ansible { SecurityIdentifier account = GetBecomeSid(username); - CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT | CreationFlags.CREATE_BREAKAWAY_FROM_JOB | CreationFlags.CREATE_SUSPENDED; - STARTUPINFOEX si = new STARTUPINFOEX(); si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES; @@ -665,6 +613,9 @@ namespace Ansible // Create the environment block if set IntPtr lpEnvironment = IntPtr.Zero; + // To support async + become, we have to do some job magic later, which requires both breakaway and starting suspended + CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT | CreationFlags.CREATE_BREAKAWAY_FROM_JOB | CreationFlags.CREATE_SUSPENDED; + PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); // Get the user tokens to try running processes with @@ -761,97 +712,109 @@ namespace Ansible GrantAccessToWindowStationAndDesktop(account); string account_sid = account.ToString(); bool impersonated = false; - IntPtr hSystemTokenDup = IntPtr.Zero; - // Try to get SYSTEM token handle so we can impersonate to get full admin token - IntPtr hSystemToken = GetSystemUserHandle(); - if (hSystemToken == IntPtr.Zero && service_sids.Contains(account_sid)) - { - // We need the SYSTEM token if we want to become one of those accounts, fail here - throw new Win32Exception("Failed to get token for NT AUTHORITY\\SYSTEM"); - } - else if (hSystemToken != IntPtr.Zero) + try { - // We have the token, need to duplicate and impersonate - bool dupResult = DuplicateTokenEx( - hSystemToken, - TokenAccessLevels.MaximumAllowed, - IntPtr.Zero, - SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, - TOKEN_TYPE.TokenPrimary, - out hSystemTokenDup); - int lastError = Marshal.GetLastWin32Error(); - CloseHandle(hSystemToken); - - if (!dupResult && service_sids.Contains(account_sid)) - throw new Win32Exception(lastError, "Failed to duplicate token for NT AUTHORITY\\SYSTEM"); - else if (dupResult && account_sid != "S-1-5-18") + IntPtr hSystemTokenDup = IntPtr.Zero; + + // Try to get SYSTEM token handle so we can impersonate to get full admin token + IntPtr hSystemToken = GetSystemUserHandle(); + if (hSystemToken == IntPtr.Zero && service_sids.Contains(account_sid)) { - if (ImpersonateLoggedOnUser(hSystemTokenDup)) - impersonated = true; - else if (service_sids.Contains(account_sid)) - throw new Win32Exception("Failed to impersonate as SYSTEM account"); + // We need the SYSTEM token if we want to become one of those accounts, fail here + throw new Win32Exception("Failed to get token for NT AUTHORITY\\SYSTEM"); } - } - - LogonType logonType; - string domain = null; - if (service_sids.Contains(account_sid)) - { - logonType = LogonType.LOGON32_LOGON_SERVICE; - domain = "NT AUTHORITY"; - password = null; - switch (account_sid) + else if (hSystemToken != IntPtr.Zero) { - case "S-1-5-18": - tokens.Add(hSystemTokenDup); - return tokens; - case "S-1-5-19": - username = "LocalService"; - break; - case "S-1-5-20": - username = "NetworkService"; - break; + // We have the token, need to duplicate and impersonate + bool dupResult = DuplicateTokenEx( + hSystemToken, + TokenAccessLevels.MaximumAllowed, + IntPtr.Zero, + SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, + TOKEN_TYPE.TokenPrimary, + out hSystemTokenDup); + int lastError = Marshal.GetLastWin32Error(); + CloseHandle(hSystemToken); + + if (!dupResult && service_sids.Contains(account_sid)) + throw new Win32Exception(lastError, "Failed to duplicate token for NT AUTHORITY\\SYSTEM"); + else if (dupResult && account_sid != "S-1-5-18") + { + if (ImpersonateLoggedOnUser(hSystemTokenDup)) + impersonated = true; + else if (service_sids.Contains(account_sid)) + throw new Win32Exception("Failed to impersonate as SYSTEM account"); + } + // If SYSTEM impersonation failed but we're trying to become a regular user, just proceed; + // might get a limited token in UAC-enabled cases, but better than nothing... } - } - else - { - // We are trying to become a local or domain account - logonType = LogonType.LOGON32_LOGON_INTERACTIVE; - if (username.Contains(@"\")) + + LogonType logonType; + string domain = null; + + if (service_sids.Contains(account_sid)) { - var user_split = username.Split(Convert.ToChar(@"\")); - domain = user_split[0]; - username = user_split[1]; + // We're using a well-known service account, do a service logon instead of interactive + logonType = LogonType.LOGON32_LOGON_SERVICE; + domain = "NT AUTHORITY"; + password = null; + switch (account_sid) + { + case "S-1-5-18": + tokens.Add(hSystemTokenDup); + return tokens; + case "S-1-5-19": + username = "LocalService"; + break; + case "S-1-5-20": + username = "NetworkService"; + break; + } } - else if (username.Contains("@")) - domain = null; else - domain = "."; - } + { + // We are trying to become a local or domain account + logonType = LogonType.LOGON32_LOGON_INTERACTIVE; + if (username.Contains(@"\")) + { + var user_split = username.Split(Convert.ToChar(@"\")); + domain = user_split[0]; + username = user_split[1]; + } + else if (username.Contains("@")) + domain = null; + else + domain = "."; + } - IntPtr hToken = IntPtr.Zero; - if (!LogonUser( - username, - domain, - password, - logonType, - LogonProvider.LOGON32_PROVIDER_DEFAULT, - out hToken)) - { - throw new Win32Exception("LogonUser failed"); - } + IntPtr hToken = IntPtr.Zero; + if (!LogonUser( + username, + domain, + password, + logonType, + LogonProvider.LOGON32_PROVIDER_DEFAULT, + out hToken)) + { + throw new Win32Exception("LogonUser failed"); + } + + if (!service_sids.Contains(account_sid)) + { + // Try and get the elevated token for local/domain account + IntPtr hTokenElevated = GetElevatedToken(hToken); + tokens.Add(hTokenElevated); + } - if (!service_sids.Contains(account_sid)) + // add the original token as a fallback + tokens.Add(hToken); + } + finally { - // Try and get the elevated token for local/domain account - IntPtr hTokenElevated = GetElevatedToken(hToken); - tokens.Add(hTokenElevated); + if (impersonated) + RevertToSelf(); } - tokens.Add(hToken); - - if (impersonated) - RevertToSelf(); return tokens; }