diff options
Diffstat (limited to 'test/support/windows-integration/plugins/modules/win_user_right.ps1')
-rw-r--r-- | test/support/windows-integration/plugins/modules/win_user_right.ps1 | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/test/support/windows-integration/plugins/modules/win_user_right.ps1 b/test/support/windows-integration/plugins/modules/win_user_right.ps1 new file mode 100644 index 0000000..3fac52a --- /dev/null +++ b/test/support/windows-integration/plugins/modules/win_user_right.ps1 @@ -0,0 +1,349 @@ +#!powershell + +# 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.SID + +$ErrorActionPreference = 'Stop' + +$params = Parse-Args $args -supports_check_mode $true +$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false +$diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false +$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP + +$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true +$users = Get-AnsibleParam -obj $params -name "users" -type "list" -failifempty $true +$action = Get-AnsibleParam -obj $params -name "action" -type "str" -default "set" -validateset "add","remove","set" + +$result = @{ + changed = $false + added = @() + removed = @() +} + +if ($diff_mode) { + $result.diff = @{} +} + +$sec_helper_util = @" +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Security.Principal; + +namespace Ansible +{ + public class LsaRightHelper : IDisposable + { + // Code modified from https://gallery.technet.microsoft.com/scriptcenter/Grant-Revoke-Query-user-26e259b0 + + enum Access : int + { + POLICY_READ = 0x20006, + POLICY_ALL_ACCESS = 0x00F0FFF, + POLICY_EXECUTE = 0X20801, + POLICY_WRITE = 0X207F8 + } + + IntPtr lsaHandle; + + const string LSA_DLL = "advapi32.dll"; + const CharSet DEFAULT_CHAR_SET = CharSet.Unicode; + + const uint STATUS_NO_MORE_ENTRIES = 0x8000001a; + const uint STATUS_NO_SUCH_PRIVILEGE = 0xc0000060; + + internal sealed class Sid : IDisposable + { + public IntPtr pSid = IntPtr.Zero; + public SecurityIdentifier sid = null; + + public Sid(string sidString) + { + try + { + sid = new SecurityIdentifier(sidString); + } catch + { + throw new ArgumentException(String.Format("SID string {0} could not be converted to SecurityIdentifier", sidString)); + } + + Byte[] buffer = new Byte[sid.BinaryLength]; + sid.GetBinaryForm(buffer, 0); + + pSid = Marshal.AllocHGlobal(sid.BinaryLength); + Marshal.Copy(buffer, 0, pSid, sid.BinaryLength); + } + + public void Dispose() + { + if (pSid != IntPtr.Zero) + { + Marshal.FreeHGlobal(pSid); + pSid = IntPtr.Zero; + } + GC.SuppressFinalize(this); + } + ~Sid() { Dispose(); } + } + + [StructLayout(LayoutKind.Sequential)] + private struct LSA_OBJECT_ATTRIBUTES + { + public int Length; + public IntPtr RootDirectory; + public IntPtr ObjectName; + public int Attributes; + public IntPtr SecurityDescriptor; + public IntPtr SecurityQualityOfService; + } + + [StructLayout(LayoutKind.Sequential, CharSet = DEFAULT_CHAR_SET)] + private struct LSA_UNICODE_STRING + { + public ushort Length; + public ushort MaximumLength; + [MarshalAs(UnmanagedType.LPWStr)] + public string Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + private struct LSA_ENUMERATION_INFORMATION + { + public IntPtr Sid; + } + + [DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)] + private static extern uint LsaOpenPolicy( + LSA_UNICODE_STRING[] SystemName, + ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, + int AccessMask, + out IntPtr PolicyHandle + ); + + [DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)] + private static extern uint LsaAddAccountRights( + IntPtr PolicyHandle, + IntPtr pSID, + LSA_UNICODE_STRING[] UserRights, + int CountOfRights + ); + + [DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)] + private static extern uint LsaRemoveAccountRights( + IntPtr PolicyHandle, + IntPtr pSID, + bool AllRights, + LSA_UNICODE_STRING[] UserRights, + int CountOfRights + ); + + [DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)] + private static extern uint LsaEnumerateAccountsWithUserRight( + IntPtr PolicyHandle, + LSA_UNICODE_STRING[] UserRights, + out IntPtr EnumerationBuffer, + out ulong CountReturned + ); + + [DllImport(LSA_DLL)] + private static extern int LsaNtStatusToWinError(int NTSTATUS); + + [DllImport(LSA_DLL)] + private static extern int LsaClose(IntPtr PolicyHandle); + + [DllImport(LSA_DLL)] + private static extern int LsaFreeMemory(IntPtr Buffer); + + public LsaRightHelper() + { + 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)); + + lsaHandle = IntPtr.Zero; + + LSA_UNICODE_STRING[] system = new LSA_UNICODE_STRING[1]; + system[0] = InitLsaString(""); + + uint ret = LsaOpenPolicy(system, ref lsaAttr, (int)Access.POLICY_ALL_ACCESS, out lsaHandle); + if (ret != 0) + throw new Win32Exception(LsaNtStatusToWinError((int)ret)); + } + + public void AddPrivilege(string sidString, string privilege) + { + uint ret = 0; + using (Sid sid = new Sid(sidString)) + { + LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1]; + privileges[0] = InitLsaString(privilege); + ret = LsaAddAccountRights(lsaHandle, sid.pSid, privileges, 1); + } + if (ret != 0) + throw new Win32Exception(LsaNtStatusToWinError((int)ret)); + } + + public void RemovePrivilege(string sidString, string privilege) + { + uint ret = 0; + using (Sid sid = new Sid(sidString)) + { + LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1]; + privileges[0] = InitLsaString(privilege); + ret = LsaRemoveAccountRights(lsaHandle, sid.pSid, false, privileges, 1); + } + if (ret != 0) + throw new Win32Exception(LsaNtStatusToWinError((int)ret)); + } + + public string[] EnumerateAccountsWithUserRight(string privilege) + { + uint ret = 0; + ulong count = 0; + LSA_UNICODE_STRING[] rights = new LSA_UNICODE_STRING[1]; + rights[0] = InitLsaString(privilege); + IntPtr buffer = IntPtr.Zero; + + ret = LsaEnumerateAccountsWithUserRight(lsaHandle, rights, out buffer, out count); + switch (ret) + { + case 0: + string[] accounts = new string[count]; + for (int i = 0; i < (int)count; i++) + { + LSA_ENUMERATION_INFORMATION LsaInfo = (LSA_ENUMERATION_INFORMATION)Marshal.PtrToStructure( + IntPtr.Add(buffer, i * Marshal.SizeOf(typeof(LSA_ENUMERATION_INFORMATION))), + typeof(LSA_ENUMERATION_INFORMATION)); + + accounts[i] = new SecurityIdentifier(LsaInfo.Sid).ToString(); + } + LsaFreeMemory(buffer); + return accounts; + + case STATUS_NO_MORE_ENTRIES: + return new string[0]; + + case STATUS_NO_SUCH_PRIVILEGE: + throw new ArgumentException(String.Format("Invalid privilege {0} not found in LSA database", privilege)); + + default: + throw new Win32Exception(LsaNtStatusToWinError((int)ret)); + } + } + + static LSA_UNICODE_STRING InitLsaString(string s) + { + // Unicode strings max. 32KB + if (s.Length > 0x7ffe) + throw new ArgumentException("String too long"); + + LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING(); + lus.Buffer = s; + lus.Length = (ushort)(s.Length * sizeof(char)); + lus.MaximumLength = (ushort)(lus.Length + sizeof(char)); + + return lus; + } + + public void Dispose() + { + if (lsaHandle != IntPtr.Zero) + { + LsaClose(lsaHandle); + lsaHandle = IntPtr.Zero; + } + GC.SuppressFinalize(this); + } + ~LsaRightHelper() { Dispose(); } + } +} +"@ + +$original_tmp = $env:TMP +$env:TMP = $_remote_tmp +Add-Type -TypeDefinition $sec_helper_util +$env:TMP = $original_tmp + +Function Compare-UserList($existing_users, $new_users) { + $added_users = [String[]]@() + $removed_users = [String[]]@() + if ($action -eq "add") { + $added_users = [Linq.Enumerable]::Except($new_users, $existing_users) + } elseif ($action -eq "remove") { + $removed_users = [Linq.Enumerable]::Intersect($new_users, $existing_users) + } else { + $added_users = [Linq.Enumerable]::Except($new_users, $existing_users) + $removed_users = [Linq.Enumerable]::Except($existing_users, $new_users) + } + + $change_result = @{ + added = $added_users + removed = $removed_users + } + + return $change_result +} + +# C# class we can use to enumerate/add/remove rights +$lsa_helper = New-Object -TypeName Ansible.LsaRightHelper + +$new_users = [System.Collections.ArrayList]@() +foreach ($user in $users) { + $user_sid = Convert-ToSID -account_name $user + $new_users.Add($user_sid) > $null +} +$new_users = [String[]]$new_users.ToArray() +try { + $existing_users = $lsa_helper.EnumerateAccountsWithUserRight($name) +} catch [ArgumentException] { + Fail-Json -obj $result -message "the specified right $name is not a valid right" +} catch { + Fail-Json -obj $result -message "failed to enumerate existing accounts with right: $($_.Exception.Message)" +} + +$change_result = Compare-UserList -existing_users $existing_users -new_user $new_users +if (($change_result.added.Length -gt 0) -or ($change_result.removed.Length -gt 0)) { + $result.changed = $true + $diff_text = "[$name]`n" + + # used in diff mode calculation + $new_user_list = [System.Collections.ArrayList]$existing_users + foreach ($user in $change_result.removed) { + if (-not $check_mode) { + $lsa_helper.RemovePrivilege($user, $name) + } + $user_name = Convert-FromSID -sid $user + $result.removed += $user_name + $diff_text += "-$user_name`n" + $new_user_list.Remove($user) > $null + } + foreach ($user in $change_result.added) { + if (-not $check_mode) { + $lsa_helper.AddPrivilege($user, $name) + } + $user_name = Convert-FromSID -sid $user + $result.added += $user_name + $diff_text += "+$user_name`n" + $new_user_list.Add($user) > $null + } + + if ($diff_mode) { + if ($new_user_list.Count -eq 0) { + $diff_text = "-$diff_text" + } else { + if ($existing_users.Count -eq 0) { + $diff_text = "+$diff_text" + } + } + $result.diff.prepared = $diff_text + } +} + +Exit-Json $result |