diff options
Diffstat (limited to 'lib/ansible/modules/windows/win_mapped_drive.ps1')
-rw-r--r-- | lib/ansible/modules/windows/win_mapped_drive.ps1 | 444 |
1 files changed, 0 insertions, 444 deletions
diff --git a/lib/ansible/modules/windows/win_mapped_drive.ps1 b/lib/ansible/modules/windows/win_mapped_drive.ps1 deleted file mode 100644 index aab65d455c..0000000000 --- a/lib/ansible/modules/windows/win_mapped_drive.ps1 +++ /dev/null @@ -1,444 +0,0 @@ -#!powershell - -# Copyright: (c) 2017, Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -#AnsibleRequires -CSharpUtil Ansible.AccessToken -#AnsibleRequires -CSharpUtil Ansible.Basic -#Requires -Module Ansible.ModuleUtils.AddType - -$spec = @{ - options = @{ - letter = @{ type = "str"; required = $true } - path = @{ type = "path"; } - state = @{ type = "str"; default = "present"; choices = @("absent", "present") } - username = @{ type = "str" } - password = @{ type = "str"; no_log = $true } - } - required_if = @( - ,@("state", "present", @("path")) - ) - supports_check_mode = $true -} - -$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) - -$letter = $module.Params.letter -$path = $module.Params.path -$state = $module.Params.state -$username = $module.Params.username -$password = $module.Params.password - -if ($letter -notmatch "^[a-zA-z]{1}$") { - $module.FailJson("letter must be a single letter from A-Z, was: $letter") -} -$letter_root = "$($letter):" - -$module.Diff.before = "" -$module.Diff.after = "" - -Add-CSharpType -AnsibleModule $module -References @' -using Microsoft.Win32.SafeHandles; -using System; -using System.Collections.Generic; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; - -namespace Ansible.MappedDrive -{ - internal class NativeHelpers - { - public enum ResourceScope : uint - { - Connected = 0x00000001, - GlobalNet = 0x00000002, - Remembered = 0x00000003, - Recent = 0x00000004, - Context = 0x00000005, - } - - [Flags] - public enum ResourceType : uint - { - Any = 0x0000000, - Disk = 0x00000001, - Print = 0x00000002, - Reserved = 0x00000008, - Unknown = 0xFFFFFFFF, - } - - public enum CloseFlags : uint - { - None = 0x00000000, - UpdateProfile = 0x00000001, - } - - [Flags] - public enum AddFlags : uint - { - UpdateProfile = 0x00000001, - UpdateRecent = 0x00000002, - Temporary = 0x00000004, - Interactive = 0x00000008, - Prompt = 0x00000010, - Redirect = 0x00000080, - CurrentMedia = 0x00000200, - CommandLine = 0x00000800, - CmdSaveCred = 0x00001000, - CredReset = 0x00002000, - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct NETRESOURCEW - { - public ResourceScope dwScope; - public ResourceType dwType; - public UInt32 dwDisplayType; - public UInt32 dwUsage; - [MarshalAs(UnmanagedType.LPWStr)] public string lpLocalName; - [MarshalAs(UnmanagedType.LPWStr)] public string lpRemoteName; - [MarshalAs(UnmanagedType.LPWStr)] public string lpComment; - [MarshalAs(UnmanagedType.LPWStr)] public string lpProvider; - } - } - - internal class NativeMethods - { - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool CloseHandle( - IntPtr hObject); - - [DllImport("advapi32.dll", SetLastError = true)] - public static extern bool ImpersonateLoggedOnUser( - IntPtr hToken); - - [DllImport("advapi32.dll", SetLastError = true)] - public static extern bool RevertToSelf(); - - [DllImport("Mpr.dll", CharSet = CharSet.Unicode)] - public static extern UInt32 WNetAddConnection2W( - NativeHelpers.NETRESOURCEW lpNetResource, - [MarshalAs(UnmanagedType.LPWStr)] string lpPassword, - [MarshalAs(UnmanagedType.LPWStr)] string lpUserName, - NativeHelpers.AddFlags dwFlags); - - [DllImport("Mpr.dll", CharSet = CharSet.Unicode)] - public static extern UInt32 WNetCancelConnection2W( - [MarshalAs(UnmanagedType.LPWStr)] string lpName, - NativeHelpers.CloseFlags dwFlags, - bool fForce); - - [DllImport("Mpr.dll")] - public static extern UInt32 WNetCloseEnum( - IntPtr hEnum); - - [DllImport("Mpr.dll", CharSet = CharSet.Unicode)] - public static extern UInt32 WNetEnumResourceW( - IntPtr hEnum, - ref Int32 lpcCount, - SafeMemoryBuffer lpBuffer, - ref UInt32 lpBufferSize); - - [DllImport("Mpr.dll", CharSet = CharSet.Unicode)] - public static extern UInt32 WNetOpenEnumW( - NativeHelpers.ResourceScope dwScope, - NativeHelpers.ResourceType dwType, - UInt32 dwUsage, - IntPtr lpNetResource, - out IntPtr lphEnum); - } - - internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid - { - public SafeMemoryBuffer() : base(true) { } - public SafeMemoryBuffer(int cb) : base(true) - { - base.SetHandle(Marshal.AllocHGlobal(cb)); - } - public SafeMemoryBuffer(IntPtr handle) : base(true) - { - base.SetHandle(handle); - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - protected override bool ReleaseHandle() - { - Marshal.FreeHGlobal(handle); - return true; - } - } - - internal class Impersonation : IDisposable - { - private IntPtr hToken = IntPtr.Zero; - - public Impersonation(IntPtr token) - { - hToken = token; - if (token != IntPtr.Zero) - if (!NativeMethods.ImpersonateLoggedOnUser(hToken)) - throw new Win32Exception("Failed to impersonate token with ImpersonateLoggedOnUser()"); - } - - public void Dispose() - { - if (hToken != null) - NativeMethods.RevertToSelf(); - GC.SuppressFinalize(this); - } - ~Impersonation() { Dispose(); } - } - - public class DriveInfo - { - public string Drive; - public string Path; - } - - 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); } - } - - public class Utils - { - private const UInt32 ERROR_SUCCESS = 0x00000000; - private const UInt32 ERROR_NO_MORE_ITEMS = 0x0000103; - - public static void AddMappedDrive(string drive, string path, IntPtr iToken, string username = null, string password = null) - { - NativeHelpers.NETRESOURCEW resource = new NativeHelpers.NETRESOURCEW - { - dwType = NativeHelpers.ResourceType.Disk, - lpLocalName = drive, - lpRemoteName = path, - }; - NativeHelpers.AddFlags dwFlags = NativeHelpers.AddFlags.UpdateProfile; - // While WNetAddConnection2W supports user/pass, this is only used for the first connection and the - // password is not remembered. We will delete the username mapping afterwards as it interferes with - // the implicit credential cache used in Windows - using (Impersonation imp = new Impersonation(iToken)) - { - UInt32 res = NativeMethods.WNetAddConnection2W(resource, password, username, dwFlags); - if (res != ERROR_SUCCESS) - throw new Win32Exception((int)res, String.Format("Failed to map {0} to '{1}' with WNetAddConnection2W()", drive, path)); - } - } - - public static List<DriveInfo> GetMappedDrives(IntPtr iToken) - { - using (Impersonation imp = new Impersonation(iToken)) - { - IntPtr enumPtr = IntPtr.Zero; - UInt32 res = NativeMethods.WNetOpenEnumW(NativeHelpers.ResourceScope.Remembered, NativeHelpers.ResourceType.Disk, - 0, IntPtr.Zero, out enumPtr); - if (res != ERROR_SUCCESS) - throw new Win32Exception((int)res, "WNetOpenEnumW()"); - - List<DriveInfo> resources = new List<DriveInfo>(); - try - { - // MS recommend a buffer size of 16 KiB - UInt32 bufferSize = 16384; - int lpcCount = -1; - - // keep iterating the enum until ERROR_NO_MORE_ITEMS is returned - do - { - using (SafeMemoryBuffer buffer = new SafeMemoryBuffer((int)bufferSize)) - { - res = NativeMethods.WNetEnumResourceW(enumPtr, ref lpcCount, buffer, ref bufferSize); - if (res == ERROR_NO_MORE_ITEMS) - continue; - else if (res != ERROR_SUCCESS) - throw new Win32Exception((int)res, "WNetEnumResourceW()"); - lpcCount = lpcCount < 0 ? 0 : lpcCount; - - NativeHelpers.NETRESOURCEW[] rawResources = new NativeHelpers.NETRESOURCEW[lpcCount]; - PtrToStructureArray(rawResources, buffer.DangerousGetHandle()); - foreach (NativeHelpers.NETRESOURCEW resource in rawResources) - { - DriveInfo currentDrive = new DriveInfo - { - Drive = resource.lpLocalName, - Path = resource.lpRemoteName, - }; - resources.Add(currentDrive); - } - } - } - while (res != ERROR_NO_MORE_ITEMS); - } - finally - { - NativeMethods.WNetCloseEnum(enumPtr); - } - - return resources; - } - } - - public static void RemoveMappedDrive(string drive, IntPtr iToken) - { - using (Impersonation imp = new Impersonation(iToken)) - { - UInt32 res = NativeMethods.WNetCancelConnection2W(drive, NativeHelpers.CloseFlags.UpdateProfile, true); - if (res != ERROR_SUCCESS) - throw new Win32Exception((int)res, String.Format("Failed to remove mapped drive {0} with WNetCancelConnection2W()", drive)); - } - } - - private static void PtrToStructureArray<T>(T[] array, IntPtr ptr) - { - IntPtr ptrOffset = ptr; - for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T)))) - array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T)); - } - } -} -'@ - -Function Get-LimitedToken { - $h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess() - $h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Duplicate, Query") - - try { - # If we don't have a Full token, we don't need to get the limited one to set a mapped drive - $tet = [Ansible.AccessToken.TokenUtil]::GetTokenElevationType($h_token) - if ($tet -ne [Ansible.AccessToken.TokenElevationType]::Full) { - return - } - - foreach ($system_token in [Ansible.AccessToken.TokenUtil]::EnumerateUserTokens("S-1-5-18", "Duplicate")) { - # To get the TokenLinkedToken we need the SeTcbPrivilege, not all SYSTEM tokens have this assigned so - # we need to check before impersonating that token - $token_privileges = [Ansible.AccessToken.TokenUtil]::GetTokenPrivileges($system_token) - if ($null -eq ($token_privileges | Where-Object { $_.Name -eq "SeTcbPrivilege" })) { - continue - } - - [Ansible.AccessToken.TokenUtil]::ImpersonateToken($system_token) - try { - return [Ansible.AccessToken.TokenUtil]::GetTokenLinkedToken($h_token) - } finally { - [Ansible.AccessToken.TokenUtil]::RevertToSelf() - } - } - } finally { - $h_token.Dispose() - } -} - -<# -When we run with become and UAC is enabled, the become process will most likely be the Admin/Full token. This is -an issue with the WNetConnection APIs as the Full token is unable to add/enumerate/remove connections due to -Windows storing the connection details on each token session ID. Unless EnabledLinkedConnections (reg key) is -set to 1, the Full token is unable to manage connections in a persisted way whereas the Limited token is. This -is similar to running 'net use' normally and an admin process is unable to see those and vice versa. - -To overcome this problem, we attempt to get a handle on the Limited token for the current logon and impersonate -that before making any WNetConnection calls. If the token is not split, or we are already running on the Limited -token then no impersonatoin is used/required. This allows the module to run with become (required to access the -credential store) but still be able to manage the mapped connections. - -These are the following scenarios we have to handle; - - 1. Run without become - A network logon is usually not split so GetLimitedToken() will return $null and no impersonation is needed - 2. Run with become on admin user with admin priv - We will have a Full token, GetLimitedToken() will return the limited token and impersonation is used - 3. Run with become on admin user without admin priv - We are already running with a Limited token, GetLimitedToken() return $nul and no impersonation is needed - 4. Run with become on standard user - There's no split token, GetLimitedToken() will return $null and no impersonation is needed -#> -$impersonation_token = Get-LimitedToken - -try { - $i_token_ptr = [System.IntPtr]::Zero - if ($null -ne $impersonation_token) { - $i_token_ptr = $impersonation_token.DangerousGetHandle() - } - - $existing_targets = [Ansible.MappedDrive.Utils]::GetMappedDrives($i_token_ptr) - $existing_target = $existing_targets | Where-Object { $_.Drive -eq $letter_root } - - if ($existing_target) { - $module.Diff.before = @{ - letter = $letter - path = $existing_target.Path - } - } - - if ($state -eq "absent") { - if ($null -ne $existing_target) { - if ($null -ne $path -and $existing_target.Path -ne $path) { - $module.FailJson("did not delete mapped drive $letter, the target path is pointing to a different location at $( $existing_target.Path )") - } - if (-not $module.CheckMode) { - [Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $i_token_ptr) - } - - $module.Result.changed = $true - } - } else { - $physical_drives = Get-PSDrive -PSProvider "FileSystem" - if ($letter -in $physical_drives.Name) { - $module.FailJson("failed to create mapped drive $letter, this letter is in use and is pointing to a non UNC path") - } - - # PowerShell converts a $null value to "" when crossing the .NET marshaler, we need to convert the input - # to a missing value so it uses the defaults. We also need to Invoke it with MethodInfo.Invoke so the defaults - # are still used - $input_username = $username - if ($null -eq $username) { - $input_username = [Type]::Missing - } - $input_password = $password - if ($null -eq $password) { - $input_password = [Type]::Missing - } - $add_method = [Ansible.MappedDrive.Utils].GetMethod("AddMappedDrive") - - if ($null -ne $existing_target) { - if ($existing_target.Path -ne $path) { - if (-not $module.CheckMode) { - [Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $i_token_ptr) - $add_method.Invoke($null, [Object[]]@($letter_root, $path, $i_token_ptr, $input_username, $input_password)) - } - $module.Result.changed = $true - } - } else { - if (-not $module.CheckMode) { - $add_method.Invoke($null, [Object[]]@($letter_root, $path, $i_token_ptr, $input_username, $input_password)) - } - - $module.Result.changed = $true - } - - # If username was set and we made a change, remove the UserName value so Windows will continue to use the cred - # cache. If we don't do this then the drive will fail to map in the future as WNetAddConnection does not cache - # the password and relies on the credential store. - if ($null -ne $username -and $module.Result.changed -and -not $module.CheckMode) { - Set-ItemProperty -Path HKCU:\Network\$letter -Name UserName -Value "" -WhatIf:$module.CheckMode - } - - $module.Diff.after = @{ - letter = $letter - path = $path - } - } -} finally { - if ($null -ne $impersonation_token) { - $impersonation_token.Dispose() - } -} - -$module.ExitJson() |