diff --git a/lib/ansible/modules/windows/win_whoami.ps1 b/lib/ansible/modules/windows/win_whoami.ps1 new file mode 100644 index 00000000000..62e48a1a946 --- /dev/null +++ b/lib/ansible/modules/windows/win_whoami.ps1 @@ -0,0 +1,836 @@ +#!powershell +# This file is part of Ansible + +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#Requires -Module Ansible.ModuleUtils.Legacy +#Requires -Module Ansible.ModuleUtils.CamelConversion + +$ErrorActionPreference = "Stop" + +$session_util = @' +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Text; + +namespace Ansible +{ + public class SessionInfo + { + // SECURITY_LOGON_SESSION_DATA + public UInt64 LogonId { get; internal set; } + public Sid Account { get; internal set; } + public string LoginDomain { get; internal set; } + public string AuthenticationPackage { get; internal set; } + public SECURITY_LOGON_TYPE LogonType { get; internal set; } + public string LoginTime { get; internal set; } + public string LogonServer { get; internal set; } + public string DnsDomainName { get; internal set; } + public string Upn { get; internal set; } + public ArrayList UserFlags { get; internal set; } + + // TOKEN_STATISTICS + public SECURITY_IMPERSONATION_LEVEL ImpersonationLevel { get; internal set; } + public TOKEN_TYPE TokenType { get; internal set; } + + // TOKEN_GROUPS + public ArrayList Groups { get; internal set; } + public Sid LogonSid { get; internal set; } + public ArrayList Rights { get; internal set; } + + // TOKEN_MANDATORY_LABEL + public Sid Label { get; internal set; } + + // TOKEN_PRIVILEGES + public Hashtable Privileges { get; internal set; } + } + + public class Win32Exception : System.ComponentModel.Win32Exception + { + private string _msg; + public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { } + public Win32Exception(int errorCode, string message) : base(errorCode) + { + _msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode); + } + public override string Message { get { return _msg; } } + public static explicit operator Win32Exception(string message) { return new Win32Exception(message); } + } + + [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 LUID + { + public UInt32 LowPart; + public Int32 HighPart; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SECURITY_LOGON_SESSION_DATA + { + public UInt32 Size; + public LUID LogonId; + public LSA_UNICODE_STRING Username; + public LSA_UNICODE_STRING LoginDomain; + public LSA_UNICODE_STRING AuthenticationPackage; + public SECURITY_LOGON_TYPE LogonType; + public UInt32 Session; + public IntPtr Sid; + public UInt64 LoginTime; + public LSA_UNICODE_STRING LogonServer; + public LSA_UNICODE_STRING DnsDomainName; + public LSA_UNICODE_STRING Upn; + public UInt32 UserFlags; + public LSA_LAST_INTER_LOGON_INFO LastLogonInfo; + public LSA_UNICODE_STRING LogonScript; + public LSA_UNICODE_STRING ProfilePath; + public LSA_UNICODE_STRING HomeDirectory; + public LSA_UNICODE_STRING HomeDirectoryDrive; + public UInt64 LogoffTime; + public UInt64 KickOffTime; + public UInt64 PasswordLastSet; + public UInt64 PasswordCanChange; + public UInt64 PasswordMustChange; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_LAST_INTER_LOGON_INFO + { + public UInt64 LastSuccessfulLogon; + public UInt64 LastFailedLogon; + public UInt32 FailedAttemptCountSinceLastSuccessfulLogon; + } + + public enum TOKEN_TYPE + { + TokenPrimary = 1, + TokenImpersonation + } + + public enum SECURITY_IMPERSONATION_LEVEL + { + SecurityAnonymous, + SecurityIdentification, + SecurityImpersonation, + SecurityDelegation + } + + 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 + } + + [Flags] + public enum TokenGroupAttributes : uint + { + SE_GROUP_ENABLED = 0x00000004, + SE_GROUP_ENABLED_BY_DEFAULT = 0x00000002, + SE_GROUP_INTEGRITY = 0x00000020, + SE_GROUP_INTEGRITY_ENABLED = 0x00000040, + SE_GROUP_LOGON_ID = 0xC0000000, + SE_GROUP_MANDATORY = 0x00000001, + SE_GROUP_OWNER = 0x00000008, + SE_GROUP_RESOURCE = 0x20000000, + SE_GROUP_USE_FOR_DENY_ONLY = 0x00000010, + } + + [Flags] + public enum UserFlags : uint + { + LOGON_OPTIMIZED = 0x4000, + LOGON_WINLOGON = 0x8000, + LOGON_PKINIT = 0x10000, + LOGON_NOT_OPTMIZED = 0x20000, + } + + [StructLayout(LayoutKind.Sequential)] + public struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + public UInt32 Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public UInt32 Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_GROUPS + { + public UInt32 GroupCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public SID_AND_ATTRIBUTES[] Groups; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_MANDATORY_LABEL + { + public SID_AND_ATTRIBUTES Label; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_STATISTICS + { + public LUID TokenId; + public LUID AuthenticationId; + public UInt64 ExpirationTime; + public TOKEN_TYPE TokenType; + public SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; + public UInt32 DynamicCharged; + public UInt32 DynamicAvailable; + public UInt32 GroupCount; + public UInt32 PrivilegeCount; + public LUID ModifiedId; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_PRIVILEGES + { + public UInt32 PrivilegeCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public LUID_AND_ATTRIBUTES[] Privileges; + } + + public class AccessToken : IDisposable + { + public enum TOKEN_INFORMATION_CLASS + { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUIAccess, + TokenMandatoryPolicy, + TokenLogonSid, + TokenIsAppContainer, + TokenCapabilities, + TokenAppContainerSid, + TokenAppContainerNumber, + TokenUserClaimAttributes, + TokenDeviceClaimAttributes, + TokenRestrictedUserClaimAttributes, + TokenRestrictedDeviceClaimAttributes, + TokenDeviceGroups, + TokenRestrictedDeviceGroups, + TokenSecurityAttributes, + TokenIsRestricted, + MaxTokenInfoClass + } + + public IntPtr hToken = IntPtr.Zero; + + [DllImport("kernel32.dll")] + private static extern IntPtr GetCurrentProcess(); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool OpenProcessToken( + IntPtr ProcessHandle, + TokenAccessLevels DesiredAccess, + out IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool GetTokenInformation( + IntPtr TokenHandle, + TOKEN_INFORMATION_CLASS TokenInformationClass, + IntPtr TokenInformation, + UInt32 TokenInformationLength, + out UInt32 ReturnLength); + + public AccessToken(TokenAccessLevels tokenAccessLevels) + { + IntPtr currentProcess = GetCurrentProcess(); + if (!OpenProcessToken(currentProcess, tokenAccessLevels, out hToken)) + throw new Win32Exception("OpenProcessToken() for current process failed"); + } + + public IntPtr GetTokenInformation(out T tokenInformation, TOKEN_INFORMATION_CLASS tokenClass) + { + UInt32 tokenLength = 0; + GetTokenInformation(hToken, tokenClass, IntPtr.Zero, 0, out tokenLength); + + IntPtr infoPtr = Marshal.AllocHGlobal((int)tokenLength); + + if (!GetTokenInformation(hToken, tokenClass, infoPtr, tokenLength, out tokenLength)) + throw new Win32Exception(String.Format("GetTokenInformation() data for {0} failed", tokenClass.ToString())); + + tokenInformation = (T)Marshal.PtrToStructure(infoPtr, typeof(T)); + return infoPtr; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + + ~AccessToken() { Dispose(); } + } + + public class LsaHandle : IDisposable + { + [Flags] + public enum DesiredAccess : uint + { + POLICY_VIEW_LOCAL_INFORMATION = 0x00000001, + POLICY_VIEW_AUDIT_INFORMATION = 0x00000002, + POLICY_GET_PRIVATE_INFORMATION = 0x00000004, + POLICY_TRUST_ADMIN = 0x00000008, + POLICY_CREATE_ACCOUNT = 0x00000010, + POLICY_CREATE_SECRET = 0x00000020, + POLICY_CREATE_PRIVILEGE = 0x00000040, + POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080, + POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100, + POLICY_AUDIT_LOG_ADMIN = 0x00000200, + POLICY_SERVER_ADMIN = 0x00000400, + POLICY_LOOKUP_NAMES = 0x00000800, + POLICY_NOTIFICATION = 0x00001000 + } + + public IntPtr handle = IntPtr.Zero; + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern uint LsaOpenPolicy( + LSA_UNICODE_STRING[] SystemName, + ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, + DesiredAccess AccessMask, + out IntPtr PolicyHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern uint LsaClose( + IntPtr ObjectHandle); + + [DllImport("advapi32.dll", SetLastError = false)] + private static extern int LsaNtStatusToWinError( + uint Status); + + [StructLayout(LayoutKind.Sequential)] + public struct LSA_OBJECT_ATTRIBUTES + { + public int Length; + public IntPtr RootDirectory; + public IntPtr ObjectName; + public int Attributes; + public IntPtr SecurityDescriptor; + public IntPtr SecurityQualityOfService; + } + + public LsaHandle(DesiredAccess desiredAccess) + { + LSA_OBJECT_ATTRIBUTES lsaAttr; + lsaAttr.RootDirectory = IntPtr.Zero; + lsaAttr.ObjectName = IntPtr.Zero; + lsaAttr.Attributes = 0; + lsaAttr.SecurityDescriptor = IntPtr.Zero; + lsaAttr.SecurityQualityOfService = IntPtr.Zero; + lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES)); + LSA_UNICODE_STRING[] system = new LSA_UNICODE_STRING[1]; + system[0].buffer = IntPtr.Zero; + + uint res = LsaOpenPolicy(system, ref lsaAttr, desiredAccess, out handle); + if (res != 0) + throw new Win32Exception(LsaNtStatusToWinError(res), "LsaOpenPolicy() failed"); + } + + public void Dispose() + { + if (handle != IntPtr.Zero) + { + LsaClose(handle); + handle = IntPtr.Zero; + } + GC.SuppressFinalize(this); + } + + ~LsaHandle() { Dispose(); } + } + + public class Sid + { + public string SidString { get; internal set; } + public string DomainName { get; internal set; } + public string AccountName { get; internal set; } + public SID_NAME_USE SidType { get; internal set; } + + public enum SID_NAME_USE + { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer, + SidTypeLabel, + SidTypeLogon, + } + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool LookupAccountSid( + string lpSystemName, + [MarshalAs(UnmanagedType.LPArray)] + byte[] Sid, + StringBuilder lpName, + ref UInt32 cchName, + StringBuilder ReferencedDomainName, + ref UInt32 cchReferencedDomainName, + out SID_NAME_USE peUse); + + public Sid(IntPtr sidPtr) + { + SecurityIdentifier sid; + try + { + sid = new SecurityIdentifier(sidPtr); + } + catch (Exception e) + { + throw new ArgumentException(String.Format("Failed to cast IntPtr to SecurityIdentifier: {0}", e)); + } + + SetSidInfo(sid); + } + + public Sid(SecurityIdentifier sid) + { + SetSidInfo(sid); + } + + public override string ToString() + { + return SidString; + } + + private void SetSidInfo(SecurityIdentifier sid) + { + byte[] sidBytes = new byte[sid.BinaryLength]; + sid.GetBinaryForm(sidBytes, 0); + + StringBuilder lpName = new StringBuilder(); + UInt32 cchName = 0; + StringBuilder referencedDomainName = new StringBuilder(); + UInt32 cchReferencedDomainName = 0; + SID_NAME_USE peUse; + LookupAccountSid(null, sidBytes, lpName, ref cchName, referencedDomainName, ref cchReferencedDomainName, out peUse); + + lpName.EnsureCapacity((int)cchName); + referencedDomainName.EnsureCapacity((int)cchReferencedDomainName); + + SidString = sid.ToString(); + if (!LookupAccountSid(null, sidBytes, lpName, ref cchName, referencedDomainName, ref cchReferencedDomainName, out peUse)) + { + int lastError = Marshal.GetLastWin32Error(); + + if (lastError != 1332 && lastError != 1789) // Fails to lookup Logon Sid + { + throw new Win32Exception(lastError, String.Format("LookupAccountSid() failed for SID: {0} {1}", sid.ToString(), lastError)); + } + else if (SidString.StartsWith("S-1-5-5-")) + { + AccountName = String.Format("LogonSessionId_{0}", SidString.Substring(8)); + DomainName = "NT AUTHORITY"; + SidType = SID_NAME_USE.SidTypeLogon; + } + else + { + AccountName = null; + DomainName = null; + SidType = SID_NAME_USE.SidTypeUnknown; + } + } + else + { + AccountName = lpName.ToString(); + DomainName = referencedDomainName.ToString(); + SidType = peUse; + } + } + } + + public class SessionUtil + { + [DllImport("secur32.dll", SetLastError = false)] + private static extern uint LsaFreeReturnBuffer( + IntPtr Buffer); + + [DllImport("secur32.dll", SetLastError = false)] + private static extern uint LsaEnumerateLogonSessions( + out UInt64 LogonSessionCount, + out IntPtr LogonSessionList); + + [DllImport("secur32.dll", SetLastError = false)] + private static extern uint LsaGetLogonSessionData( + IntPtr LogonId, + out IntPtr ppLogonSessionData); + + [DllImport("advapi32.dll", SetLastError = false)] + private static extern int LsaNtStatusToWinError( + uint Status); + + [DllImport("advapi32", SetLastError = true)] + private static extern uint LsaEnumerateAccountRights( + IntPtr PolicyHandle, + IntPtr AccountSid, + out IntPtr UserRights, + out UInt64 CountOfRights); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool LookupPrivilegeName( + string lpSystemName, + ref LUID lpLuid, + StringBuilder lpName, + ref UInt32 cchName); + + private const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001; + private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002; + private const UInt32 STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034; + private const UInt32 STATUS_ACCESS_DENIED = 0xC0000022; + + public static SessionInfo GetSessionInfo() + { + AccessToken accessToken = new AccessToken(TokenAccessLevels.Query); + + // Get Privileges + Hashtable privilegeInfo = new Hashtable(); + TOKEN_PRIVILEGES privileges; + IntPtr privilegesPtr = accessToken.GetTokenInformation(out privileges, AccessToken.TOKEN_INFORMATION_CLASS.TokenPrivileges); + LUID_AND_ATTRIBUTES[] luidAndAttributes = new LUID_AND_ATTRIBUTES[privileges.PrivilegeCount]; + try + { + PtrToStructureArray(luidAndAttributes, privilegesPtr.ToInt64() + Marshal.SizeOf(privileges.PrivilegeCount)); + } + finally + { + Marshal.FreeHGlobal(privilegesPtr); + } + foreach (LUID_AND_ATTRIBUTES luidAndAttribute in luidAndAttributes) + { + LUID privLuid = luidAndAttribute.Luid; + UInt32 privNameLen = 0; + StringBuilder privName = new StringBuilder(); + LookupPrivilegeName(null, ref privLuid, null, ref privNameLen); + privName.EnsureCapacity((int)(privNameLen + 1)); + if (!LookupPrivilegeName(null, ref privLuid, privName, ref privNameLen)) + throw new Win32Exception("LookupPrivilegeName() failed"); + + string state = "disabled"; + if ((luidAndAttribute.Attributes & SE_PRIVILEGE_ENABLED) == SE_PRIVILEGE_ENABLED) + state = "enabled"; + if ((luidAndAttribute.Attributes & SE_PRIVILEGE_ENABLED_BY_DEFAULT) == SE_PRIVILEGE_ENABLED_BY_DEFAULT) + state = "enabled-by-default"; + privilegeInfo.Add(privName.ToString(), state); + } + + // Get Current Process LogonSID, User Rights and Groups + Sid logonSid = null; + ArrayList userRights = new ArrayList(); + ArrayList userGroups = new ArrayList(); + TOKEN_GROUPS groups; + IntPtr groupsPtr = accessToken.GetTokenInformation(out groups, AccessToken.TOKEN_INFORMATION_CLASS.TokenGroups); + SID_AND_ATTRIBUTES[] sidAndAttributes = new SID_AND_ATTRIBUTES[groups.GroupCount]; + LsaHandle lsaHandle = null; + // We can only get rights if we are an admin + if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) + lsaHandle = new LsaHandle(LsaHandle.DesiredAccess.POLICY_LOOKUP_NAMES); + try + { + PtrToStructureArray(sidAndAttributes, groupsPtr.ToInt64() + IntPtr.Size); + foreach (SID_AND_ATTRIBUTES sidAndAttribute in sidAndAttributes) + { + TokenGroupAttributes attributes = (TokenGroupAttributes)sidAndAttribute.Attributes; + if (attributes.HasFlag(TokenGroupAttributes.SE_GROUP_ENABLED) && lsaHandle != null) + { + ArrayList rights = GetAccountRights(lsaHandle.handle, sidAndAttribute.Sid); + foreach (string right in rights) + { + // Includes both Privileges and Account Rights, only add the ones with Logon in the name + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx + if (!userRights.Contains(right) && right.Contains("Logon")) + userRights.Add(right); + } + } + // Do not include the Logon SID in the groups category + if (attributes.HasFlag(TokenGroupAttributes.SE_GROUP_LOGON_ID)) + logonSid = new Sid(sidAndAttribute.Sid); + else + { + Hashtable groupInfo = new Hashtable(); + Sid group = new Sid(sidAndAttribute.Sid); + ArrayList groupAttributes = new ArrayList(); + foreach (TokenGroupAttributes attribute in Enum.GetValues(typeof(TokenGroupAttributes))) + { + if (attributes.HasFlag(attribute)) + { + string attributeName = attribute.ToString().Substring(9); + attributeName = attributeName.Replace('_', ' '); + attributeName = attributeName.First().ToString().ToUpper() + attributeName.Substring(1).ToLower(); + groupAttributes.Add(attributeName); + } + } + // Using snake_case here as I can't generically convert all dict keys in PS (see Privileges) + groupInfo.Add("sid", group.SidString); + groupInfo.Add("domain_name", group.DomainName); + groupInfo.Add("account_name", group.AccountName); + groupInfo.Add("type", group.SidType); + groupInfo.Add("attributes", groupAttributes); + userGroups.Add(groupInfo); + } + } + } + finally + { + Marshal.FreeHGlobal(groupsPtr); + if (lsaHandle != null) + lsaHandle.Dispose(); + } + + // Get Integrity Level + Sid integritySid = null; + TOKEN_MANDATORY_LABEL mandatoryLabel; + IntPtr mandatoryLabelPtr = accessToken.GetTokenInformation(out mandatoryLabel, AccessToken.TOKEN_INFORMATION_CLASS.TokenIntegrityLevel); + Marshal.FreeHGlobal(mandatoryLabelPtr); + integritySid = new Sid(mandatoryLabel.Label.Sid); + + // Get Token Statistics + TOKEN_STATISTICS tokenStats; + IntPtr tokenStatsPtr = accessToken.GetTokenInformation(out tokenStats, AccessToken.TOKEN_INFORMATION_CLASS.TokenStatistics); + Marshal.FreeHGlobal(tokenStatsPtr); + + SessionInfo sessionInfo = GetSessionDataForLogonSession(tokenStats.AuthenticationId); + sessionInfo.LogonSid = logonSid; + sessionInfo.Groups = userGroups; + sessionInfo.Label = integritySid; + sessionInfo.ImpersonationLevel = tokenStats.ImpersonationLevel; + sessionInfo.TokenType = tokenStats.TokenType; + sessionInfo.Privileges = privilegeInfo; + sessionInfo.Rights = userRights; + return sessionInfo; + } + + private static ArrayList GetAccountRights(IntPtr lsaHandle, IntPtr sid) + { + UInt32 res; + ArrayList rights = new ArrayList(); + IntPtr userRightsPointer = IntPtr.Zero; + UInt64 countOfRights = 0; + + res = LsaEnumerateAccountRights(lsaHandle, sid, out userRightsPointer, out countOfRights); + if (res != 0 && res != STATUS_OBJECT_NAME_NOT_FOUND) + throw new Win32Exception(LsaNtStatusToWinError(res), "LsaEnumerateAccountRights() failed"); + else if (res != STATUS_OBJECT_NAME_NOT_FOUND) + { + LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[countOfRights]; + PtrToStructureArray(userRights, userRightsPointer.ToInt64()); + rights = new ArrayList(); + foreach (LSA_UNICODE_STRING right in userRights) + rights.Add(Marshal.PtrToStringUni(right.buffer)); + } + + return rights; + } + + private static SessionInfo GetSessionDataForLogonSession(LUID logonSession) + { + uint res; + UInt64 count = 0; + IntPtr luidPtr = IntPtr.Zero; + SessionInfo sessionInfo = null; + UInt64 processDataId = ConvertLuidToUint(logonSession); + + res = LsaEnumerateLogonSessions(out count, out luidPtr); + if (res != 0) + throw new Win32Exception(LsaNtStatusToWinError(res), "LsaEnumerateLogonSessions() failed"); + Int64 luidAddr = luidPtr.ToInt64(); + + try + { + for (UInt64 i = 0; i < count; i++) + { + IntPtr dataPointer = IntPtr.Zero; + res = LsaGetLogonSessionData(luidPtr, out dataPointer); + if (res == STATUS_ACCESS_DENIED) // Non admins won't be able to get info for session's that are not their own + { + luidPtr = new IntPtr(luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID))); + continue; + } + else if (res != 0) + throw new Win32Exception(LsaNtStatusToWinError(res), String.Format("LsaGetLogonSessionData() failed {0}", res)); + + SECURITY_LOGON_SESSION_DATA sessionData = (SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(dataPointer, typeof(SECURITY_LOGON_SESSION_DATA)); + UInt64 sessionDataid = ConvertLuidToUint(sessionData.LogonId); + + if (sessionDataid == processDataId) + { + ArrayList userFlags = new ArrayList(); + UserFlags flags = (UserFlags)sessionData.UserFlags; + foreach (UserFlags flag in Enum.GetValues(typeof(UserFlags))) + { + if (flags.HasFlag(flag)) + { + string flagName = flag.ToString().Substring(6); + flagName = flagName.Replace('_', ' '); + flagName = flagName.First().ToString().ToUpper() + flagName.Substring(1).ToLower(); + userFlags.Add(flagName); + } + } + + sessionInfo = new SessionInfo() + { + AuthenticationPackage = Marshal.PtrToStringUni(sessionData.AuthenticationPackage.buffer), + DnsDomainName = Marshal.PtrToStringUni(sessionData.DnsDomainName.buffer), + LoginDomain = Marshal.PtrToStringUni(sessionData.LoginDomain.buffer), + LoginTime = ConvertIntegerToDateString(sessionData.LoginTime), + LogonId = ConvertLuidToUint(sessionData.LogonId), + LogonServer = Marshal.PtrToStringUni(sessionData.LogonServer.buffer), + LogonType = sessionData.LogonType, + Upn = Marshal.PtrToStringUni(sessionData.Upn.buffer), + UserFlags = userFlags, + Account = new Sid(sessionData.Sid) + }; + break; + } + luidPtr = new IntPtr(luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID))); + } + } + finally + { + LsaFreeReturnBuffer(new IntPtr(luidAddr)); + } + + if (sessionInfo == null) + throw new Exception(String.Format("Could not find the data for logon session {0}", processDataId)); + return sessionInfo; + } + + private static string ConvertIntegerToDateString(UInt64 time) + { + if (time == 0) + return null; + if (time > (UInt64)DateTime.MaxValue.ToFileTime()) + return null; + + DateTime dateTime = DateTime.FromFileTime((long)time); + return dateTime.ToString("o"); + } + + private static UInt64 ConvertLuidToUint(LUID luid) + { + UInt32 low = luid.LowPart; + UInt64 high = (UInt64)luid.HighPart; + high = high << 32; + UInt64 uintValue = (high | (UInt64)low); + return uintValue; + } + + private static void PtrToStructureArray(T[] array, Int64 pointerAddress) + { + Int64 pointerOffset = pointerAddress; + for (int i = 0; i < array.Length; i++, pointerOffset += Marshal.SizeOf(typeof(T))) + array[i] = (T)Marshal.PtrToStructure(new IntPtr(pointerOffset), typeof(T)); + } + + public static IEnumerable GetValues() + { + return Enum.GetValues(typeof(T)).Cast(); + } + } +} +'@ + +Add-Type -TypeDefinition $session_util +$session_info = [Ansible.SessionUtil]::GetSessionInfo() + +Function Convert-Value($value) { + $new_value = $value + if ($value -is [System.Collections.ArrayList]) { + $new_value = [System.Collections.ArrayList]@() + foreach ($list_value in $value) { + $new_list_value = Convert-Value -value $list_value + [void]$new_value.Add($new_list_value) + } + } elseif ($value -is [Hashtable]) { + $new_value = @{} + foreach ($entry in $value.GetEnumerator()) { + $entry_value = Convert-Value -value $entry.Value + # manually convert Sid type entry to remove the SidType prefix + if ($entry.Name -eq "type") { + $entry_value = $entry_value.Replace("SidType", "") + } + $new_value[$entry.Name] = $entry_value + } + } elseif ($value -is [Ansible.Sid]) { + $new_value = @{ + sid = $value.SidString + account_name = $value.AccountName + domain_name = $value.DomainName + type = $value.SidType.ToString().Replace("SidType", "") + } + } elseif ($value -is [Enum]) { + $new_value = $value.ToString() + } + + return ,$new_value +} + +$result = @{ + changed = $false +} + +$properties = [type][Ansible.SessionInfo] +foreach ($property in $properties.DeclaredProperties) { + $property_name = $property.Name + $property_value = $session_info.$property_name + $snake_name = Convert-StringToSnakeCase -string $property_name + + $result.$snake_name = Convert-Value -value $property_value +} + +Exit-Json -obj $result diff --git a/lib/ansible/modules/windows/win_whoami.py b/lib/ansible/modules/windows/win_whoami.py new file mode 100644 index 00000000000..fc2cb82ec1a --- /dev/null +++ b/lib/ansible/modules/windows/win_whoami.py @@ -0,0 +1,226 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = r''' +--- +module: win_whoami +version_added: "2.5" +short_description: Returns information about the current user and process +description: +- Designed to return the same information as the C(whoami /all) command. +- Also includes information missing from C(whoami) such as logon metadata like + logon rights, id, type. +options: +notes: +- If running this module with a non admin user, the logon rights will be an + empty list as Administrator rights are required to query LSA for the + information. +author: +- Jordan Borean (@jborean93) +''' + +EXAMPLES = r''' +- name: get whoami information + win_whoami: +''' + +RETURN = r''' +authentication_package: + description: The name of the authentication package used to authenticate the + user in the session. + returned: success + type: str + sample: Negotiate +user_flags: + description: The user flags for the logon session, see UserFlags in + U(https://msdn.microsoft.com/en-us/library/windows/desktop/aa380128). + returned: success + type: str + sample: Winlogon +upn: + description: The user principal name of the current user. + returned: success + type: str + sample: Administrator@DOMAIN.COM +logon_type: + description: The logon type that identifies the logon method, see + U(https://msdn.microsoft.com/en-us/library/windows/desktop/aa380129.aspx). + returned: success + type: str + sample: Network +privileges: + description: A dictionary of privileges and their state on the logon token. + returned: success + type: dict + sample: { + "SeChangeNotifyPrivileges": "enabled-by-default", + "SeRemoteShutdownPrivilege": "disabled", + "SeDebugPrivilege": "enabled" + } +label: + description: The mandatory label set to the logon session. + returned: success + type: complex + contains: + domain_name: + description: The domain name of the label SID. + returned: success + type: str + sample: Mandatory Label + sid: + description: The SID in string form. + returned: success + type: str + sample: S-1-16-12288 + account_name: + description: The account name of the label SID. + returned: success + type: str + sample: High Mandatory Level + type: + description: The type of SID. + returned: success + type: str + sample: Label +impersonation_level: + description: The impersonation level of the token, only valid if + C(token_type) is C(TokenImpersonation), see + U(https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572.aspx). + returned: success + type: str + sample: SecurityAnonymous +login_time: + description: The logon time in ISO 8601 format + returned: success + type: str + sample: '2017-11-27T06:24:14.3321665+10:00' +groups: + description: A list of groups and attributes that the user is a member of. + returned: success + type: list + sample: [ + { + "account_name": "Domain Users", + "domain_name": "DOMAIN", + "attributes": [ + "Mandatory", + "Enabled by default", + "Enabled" + ], + "sid": "S-1-5-21-1654078763-769949647-2968445802-513", + "type": "Group" + }, + { + "account_name": "Administrators", + "domain_name": "BUILTIN", + "attributes": [ + "Mandatory", + "Enabled by default", + "Enabled", + "Owner" + ], + "sid": "S-1-5-32-544", + "type": "Alias" + } + ] +logon_sid: + description: The logon SID details. + returned: success + type: complex + contains: + domain_name: + description: The domain name of the logon SID. + returned: success + type: str + sample: NT AUTHORITY + sid: + description: The SID in string form. + returned: success + type: str + sample: S-1-5-5-0-4163312 + account_name: + description: The account name of the logon SID. + returned: success + type: str + sample: LogonSessionId_0_4163312 + type: + description: The type of SID. + returned: success + type: str + sample: Logon +account: + description: The running account SID details. + returned: success + type: complex + contains: + domain_name: + description: The domain name of the account SID. + returned: success + type: str + sample: DOMAIN + sid: + description: The SID in string form. + returned: success + type: str + sample: S-1-5-21-1654078763-769949647-2968445802-500 + account_name: + description: The account name of the account SID. + returned: success + type: str + sample: Administrator + type: + description: The type of SID. + returned: success + type: str + sample: User +login_domain: + description: The name of the domain used to authenticate the owner of the + session. + returned: success + type: str + sample: DOMAIN +rights: + description: A list of logon rights assigned to the logon. + returned: success and running user is a member of the local Administrators group + type: list + sample: [ + "SeNetworkLogonRight", + "SeInteractiveLogonRight", + "SeBatchLogonRight", + "SeRemoteInteractiveLogonRight" + ] +logon_server: + description: The name of the server used to authentcate the owner of the + logon session. + returned: success + type: str + sample: DC01 +logon_id: + description: The unique identifier of the logon session. + returned: success + type: int + sample: 20470143 +dns_domain_name: + description: The DNS name of the logon session, this is an empty string if + this is not set. + returned: success + type: str + sample: DOMAIN.COM +token_type: + description: The token type to indicate whether it is a primary or + impersonation token. + returned: success + type: str + sample: TokenPrimary +''' diff --git a/test/integration/targets/win_whoami/aliases b/test/integration/targets/win_whoami/aliases new file mode 100644 index 00000000000..c6d61981670 --- /dev/null +++ b/test/integration/targets/win_whoami/aliases @@ -0,0 +1 @@ +windows/ci/group3 diff --git a/test/integration/targets/win_whoami/tasks/main.yml b/test/integration/targets/win_whoami/tasks/main.yml new file mode 100644 index 00000000000..22603ac1803 --- /dev/null +++ b/test/integration/targets/win_whoami/tasks/main.yml @@ -0,0 +1,238 @@ +--- +- name: run win_whoami with normal execution + win_whoami: + register: win_whoami_result + +- name: assert win_whoami with normal execution + assert: + that: + - not win_whoami_result is changed + - win_whoami_result.account.account_name is defined + - win_whoami_result.account.domain_name is defined + - win_whoami_result.account.sid is defined + - win_whoami_result.account.type == 'User' + - win_whoami_result.authentication_package is defined + - win_whoami_result.dns_domain_name is defined + - win_whoami_result.groups|count >= 1 + - win_whoami_result.groups[0].account_name is defined + - win_whoami_result.groups[0].attributes is defined + - win_whoami_result.groups[0].domain_name is defined + - win_whoami_result.groups[0].sid is defined + - win_whoami_result.groups[0].type is defined + - win_whoami_result.impersonation_level == 'SecurityAnonymous' + - win_whoami_result.label.account_name == 'High Mandatory Level' + - win_whoami_result.label.domain_name == 'Mandatory Label' + - win_whoami_result.label.sid == 'S-1-16-12288' + - win_whoami_result.label.type == 'Label' + - win_whoami_result.login_domain is defined + - win_whoami_result.login_time is defined + - win_whoami_result.logon_id is defined + - win_whoami_result.logon_server is defined + - win_whoami_result.logon_sid.account_name is defined + - win_whoami_result.logon_sid.domain_name is defined + - win_whoami_result.logon_sid.sid is defined + - win_whoami_result.logon_sid.type == 'Logon' + - win_whoami_result.logon_type.startswith('Network') + - win_whoami_result.privileges is defined + - win_whoami_result.rights|count >= 1 + - win_whoami_result.token_type == 'TokenPrimary' + - win_whoami_result.upn is defined + - win_whoami_result.user_flags is defined + +- name: run win_whoami with SYSTEM execution + win_whoami: + become: yes + become_method: runas + become_user: SYSTEM + register: win_whoami_result + +- name: assert win_whoami with SYSTEM execution + assert: + that: + - not win_whoami_result is changed + - win_whoami_result.account.account_name == 'SYSTEM' + - win_whoami_result.account.domain_name == 'NT AUTHORITY' + - win_whoami_result.account.sid == 'S-1-5-18' + - win_whoami_result.account.type == 'User' + - win_whoami_result.authentication_package is defined + - win_whoami_result.dns_domain_name is defined + - win_whoami_result.groups|count >= 1 + - win_whoami_result.groups[0].account_name is defined + - win_whoami_result.groups[0].attributes is defined + - win_whoami_result.groups[0].domain_name is defined + - win_whoami_result.groups[0].sid is defined + - win_whoami_result.groups[0].type is defined + - win_whoami_result.impersonation_level == 'SecurityAnonymous' + - win_whoami_result.label.account_name == 'System Mandatory Level' + - win_whoami_result.label.domain_name == 'Mandatory Label' + - win_whoami_result.label.sid == 'S-1-16-16384' + - win_whoami_result.label.type == 'Label' + - win_whoami_result.login_domain is defined + - win_whoami_result.login_time is defined + - win_whoami_result.logon_id is defined + - win_whoami_result.logon_server is defined + - win_whoami_result.logon_sid == None + - win_whoami_result.logon_type == 'System' + - win_whoami_result.privileges is defined + - win_whoami_result.rights|count >= 1 + - win_whoami_result.token_type == 'TokenPrimary' + - win_whoami_result.upn is defined + - win_whoami_result.user_flags is defined + +- set_fact: + become_username: ansible_become + become_username_limited: ansible_limited + gen_pw: password123! + {{lookup('password', '/dev/null chars=ascii_letters,digits length=8')}} + +- name: ensure current user is not the become user + win_shell: whoami + register: whoami_out + failed_when: whoami_out.stdout_lines[0].endswith(become_username) or whoami_out.stdout_lines[0].endswith(become_username_limited) + +- name: create user + win_user: + name: '{{become_username}}' + password: '{{gen_pw}}' + update_password: always + groups: Administrators + register: become_user_info + +- name: create user limited + win_user: + name: '{{become_username_limited}}' + password: '{{gen_pw}}' + update_password: always + groups: Users + register: become_user_info_limited + +- block: + - name: get become user profile dir so we can clean it up later + vars: &become_vars + ansible_become_user: '{{become_username}}' + ansible_become_password: '{{gen_pw}}' + ansible_become_method: runas + ansible_become: yes + win_shell: $env:USERPROFILE + register: profile_dir_out + + - name: ensure profile dir contains test username (eg, if become fails silently, prevent deletion of real user profile) + assert: + that: + - become_username in profile_dir_out.stdout_lines[0] + + - name: get become user limited profile dir so we can clean it up later + vars: &become_vars_limited + ansible_become_user: '{{become_username_limited}}' + ansible_become_password: '{{gen_pw}}' + ansible_become_method: runas + ansible_become: yes + win_shell: $env:USERPROFILE + register: profile_dir_out_limited + + - name: ensure limited profile dir contains test username (eg, if become fails silently, prevent deletion of real user profile) + assert: + that: + - become_username_limited in profile_dir_out_limited.stdout_lines[0] + + - name: run win_whoami with become execution + win_whoami: + vars: *become_vars + register: win_whoami_result + + - name: assert win_whoami with become execution + assert: + that: + - not win_whoami_result is changed + - win_whoami_result.account.account_name == "ansible_become" + - win_whoami_result.account.domain_name is defined + - win_whoami_result.account.sid == become_user_info.sid + - win_whoami_result.account.type == 'User' + - win_whoami_result.authentication_package == "NTLM" + - win_whoami_result.dns_domain_name == "" + - win_whoami_result.groups|count >= 1 + - win_whoami_result.groups[0].account_name is defined + - win_whoami_result.groups[0].attributes is defined + - win_whoami_result.groups[0].domain_name is defined + - win_whoami_result.groups[0].sid is defined + - win_whoami_result.groups[0].type is defined + - win_whoami_result.impersonation_level is defined + - win_whoami_result.label.account_name == 'High Mandatory Level' + - win_whoami_result.label.domain_name == 'Mandatory Label' + - win_whoami_result.label.sid == 'S-1-16-12288' + - win_whoami_result.label.type == 'Label' + - win_whoami_result.login_domain is defined + - win_whoami_result.login_time is defined + - win_whoami_result.logon_id is defined + - win_whoami_result.logon_server is defined + - win_whoami_result.logon_sid.account_name is defined + - win_whoami_result.logon_sid.domain_name is defined + - win_whoami_result.logon_sid.sid is defined + - win_whoami_result.logon_sid.type == 'Logon' + - win_whoami_result.logon_type == "Interactive" + - win_whoami_result.privileges is defined + - '"SeInteractiveLogonRight" in win_whoami_result.rights' + - win_whoami_result.token_type == 'TokenPrimary' + - win_whoami_result.upn == '' + - win_whoami_result.user_flags is defined + + - name: run win_whoami with limited become execution + win_whoami: + vars: *become_vars_limited + register: win_whoami_result + + - name: assert win_whoami with limited become execution + assert: + that: + - not win_whoami_result is changed + - win_whoami_result.account.account_name == "ansible_limited" + - win_whoami_result.account.domain_name is defined + - win_whoami_result.account.sid == become_user_info_limited.sid + - win_whoami_result.account.type == 'User' + - win_whoami_result.authentication_package == "NTLM" + - win_whoami_result.dns_domain_name == "" + - win_whoami_result.groups|count >= 1 + - win_whoami_result.groups[0].account_name is defined + - win_whoami_result.groups[0].attributes is defined + - win_whoami_result.groups[0].domain_name is defined + - win_whoami_result.groups[0].sid is defined + - win_whoami_result.groups[0].type is defined + - win_whoami_result.impersonation_level is defined + - win_whoami_result.label.account_name == 'Medium Mandatory Level' + - win_whoami_result.label.domain_name == 'Mandatory Label' + - win_whoami_result.label.sid == 'S-1-16-8192' + - win_whoami_result.label.type == 'Label' + - win_whoami_result.login_domain is defined + - win_whoami_result.login_time is defined + - win_whoami_result.logon_id is defined + - win_whoami_result.logon_server is defined + - win_whoami_result.logon_sid.account_name is defined + - win_whoami_result.logon_sid.domain_name is defined + - win_whoami_result.logon_sid.sid is defined + - win_whoami_result.logon_sid.type == 'Logon' + - win_whoami_result.logon_type == "Interactive" + - win_whoami_result.privileges is defined + - win_whoami_result.rights == [] + - win_whoami_result.token_type == 'TokenPrimary' + - win_whoami_result.upn == '' + - win_whoami_result.user_flags is defined + + always: + - name: ensure test user is deleted + win_user: + name: '{{item}}' + state: absent + with_items: + - '{{become_username}}' + - '{{become_username_limited}}' + + - name: ensure test user profile is deleted + win_shell: rmdir /S /Q {{profile_dir_out.stdout_lines[0]}} + args: + executable: cmd.exe + when: become_username in profile_dir_out.stdout_lines[0] + + - name: ensure limited test user profile is deleted + win_shell: rmdir /S /Q {{profile_dir_out_limited.stdout_lines[0]}} + args: + executable: cmd.exe + when: become_username_limited in profile_dir_out_limited.stdout_lines[0]