mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-05-10 11:11:29 -07:00
* Added win_http_proxy and win_inet_proxy * Fix up docs sanity issues * removed duplicate doc entry * Fix docs issues and fix for empty proxy * Removed <-loopback> for win_http_proxy * doc changes from review
495 lines
19 KiB
PowerShell
495 lines
19 KiB
PowerShell
#!powershell
|
|
|
|
# Copyright: (c) 2019, Ansible Project
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
#AnsibleRequires -CSharpUtil Ansible.Basic
|
|
#Requires -Module Ansible.ModuleUtils.AddType
|
|
|
|
$spec = @{
|
|
options = @{
|
|
auto_detect = @{ type = "bool"; default = $true }
|
|
auto_config_url = @{ type = "str" }
|
|
proxy = @{ type = "raw" }
|
|
bypass = @{ type = "list" }
|
|
connection = @{ type = "str" }
|
|
}
|
|
required_by = @{
|
|
bypass = @("proxy")
|
|
}
|
|
supports_check_mode = $true
|
|
}
|
|
|
|
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
|
|
|
$auto_detect = $module.Params.auto_detect
|
|
$auto_config_url = $module.Params.auto_config_url
|
|
$proxy = $module.Params.proxy
|
|
$bypass = $module.Params.bypass
|
|
$connection = $module.Params.connection
|
|
|
|
# Parse the raw value, it should be a Dictionary or String
|
|
if ($proxy -is [System.Collections.IDictionary]) {
|
|
$valid_keys = [System.Collections.Generic.List`1[String]]@("http", "https", "ftp", "socks")
|
|
# Check to make sure we don't have any invalid keys in the dict
|
|
$invalid_keys = [System.Collections.Generic.List`1[String]]@()
|
|
foreach ($k in $proxy.Keys) {
|
|
if ($k -notin $valid_keys) {
|
|
$invalid_keys.Add($k)
|
|
}
|
|
}
|
|
|
|
if ($invalid_keys.Count -gt 0) {
|
|
$invalid_keys = $invalid_keys | Sort-Object # So our test assertion doesn't fail due to random ordering
|
|
$module.FailJson("Invalid keys found in proxy: $($invalid_keys -join ', '). Valid keys are $($valid_keys -join ', ').")
|
|
}
|
|
|
|
# Build the proxy string in the form 'protocol=host;', the order of valid_keys is also important
|
|
$proxy_list = [System.Collections.Generic.List`1[String]]@()
|
|
foreach ($k in $valid_keys) {
|
|
if ($proxy.ContainsKey($k)) {
|
|
$proxy_list.Add("$k=$($proxy.$k)")
|
|
}
|
|
}
|
|
$proxy = $proxy_list -join ";"
|
|
} elseif ($null -ne $proxy) {
|
|
$proxy = $proxy.ToString()
|
|
}
|
|
|
|
if ($bypass) {
|
|
if ([System.String]::IsNullOrEmpty($proxy)) {
|
|
$module.FailJson("missing parameter(s) required by ''bypass'': proxy")
|
|
}
|
|
$bypass = $bypass -join ';'
|
|
}
|
|
|
|
$win_inet_invoke = @'
|
|
using Microsoft.Win32.SafeHandles;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.ConstrainedExecution;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Ansible.WinINetProxy
|
|
{
|
|
internal class NativeHelpers
|
|
{
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
public class INTERNET_PER_CONN_OPTION_LISTW : IDisposable
|
|
{
|
|
public UInt32 dwSize;
|
|
public IntPtr pszConnection;
|
|
public UInt32 dwOptionCount;
|
|
public UInt32 dwOptionError;
|
|
public IntPtr pOptions;
|
|
|
|
public INTERNET_PER_CONN_OPTION_LISTW()
|
|
{
|
|
dwSize = (UInt32)Marshal.SizeOf(this);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (pszConnection != IntPtr.Zero)
|
|
Marshal.FreeHGlobal(pszConnection);
|
|
if (pOptions != IntPtr.Zero)
|
|
Marshal.FreeHGlobal(pOptions);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
~INTERNET_PER_CONN_OPTION_LISTW() { this.Dispose(); }
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
public class INTERNET_PER_CONN_OPTIONW : IDisposable
|
|
{
|
|
public INTERNET_PER_CONN_OPTION dwOption;
|
|
public ValueUnion Value;
|
|
|
|
[StructLayout(LayoutKind.Explicit)]
|
|
public class ValueUnion
|
|
{
|
|
[FieldOffset(0)]
|
|
public UInt32 dwValue;
|
|
|
|
[FieldOffset(0)]
|
|
public IntPtr pszValue;
|
|
|
|
[FieldOffset(0)]
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME ftValue;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// We can't just check if Value.pszValue is not IntPtr.Zero as the union means it could be set even
|
|
// when the value is a UInt32 or FILETIME. We check against a known string option type and only free
|
|
// the value in those cases.
|
|
List<INTERNET_PER_CONN_OPTION> stringOptions = new List<INTERNET_PER_CONN_OPTION>
|
|
{
|
|
{ INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL },
|
|
{ INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS },
|
|
{ INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER }
|
|
};
|
|
if (Value != null && Value.pszValue != IntPtr.Zero && stringOptions.Contains(dwOption))
|
|
Marshal.FreeHGlobal(Value.pszValue);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
~INTERNET_PER_CONN_OPTIONW() { this.Dispose(); }
|
|
}
|
|
|
|
public enum INTERNET_OPTION : uint
|
|
{
|
|
INTERNET_OPTION_PER_CONNECTION_OPTION = 75,
|
|
INTERNET_OPTION_PROXY_SETTINGS_CHANGED = 95,
|
|
}
|
|
|
|
public enum INTERNET_PER_CONN_OPTION : uint
|
|
{
|
|
INTERNET_PER_CONN_FLAGS = 1,
|
|
INTERNET_PER_CONN_PROXY_SERVER = 2,
|
|
INTERNET_PER_CONN_PROXY_BYPASS = 3,
|
|
INTERNET_PER_CONN_AUTOCONFIG_URL = 4,
|
|
INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5,
|
|
INTERNET_PER_CONN_FLAGS_UI = 10, // IE8+ - Included with Windows 7 and Server 2008 R2
|
|
}
|
|
|
|
[Flags]
|
|
public enum PER_CONN_FLAGS : uint
|
|
{
|
|
PROXY_TYPE_DIRECT = 0x00000001,
|
|
PROXY_TYPE_PROXY = 0x00000002,
|
|
PROXY_TYPE_AUTO_PROXY_URL = 0x00000004,
|
|
PROXY_TYPE_AUTO_DETECT = 0x00000008,
|
|
}
|
|
}
|
|
|
|
internal class NativeMethods
|
|
{
|
|
[DllImport("Wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern bool InternetQueryOptionW(
|
|
IntPtr hInternet,
|
|
NativeHelpers.INTERNET_OPTION dwOption,
|
|
SafeMemoryBuffer lpBuffer,
|
|
ref UInt32 lpdwBufferLength);
|
|
|
|
[DllImport("Wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern bool InternetSetOptionW(
|
|
IntPtr hInternet,
|
|
NativeHelpers.INTERNET_OPTION dwOption,
|
|
SafeMemoryBuffer lpBuffer,
|
|
UInt32 dwBufferLength);
|
|
|
|
[DllImport("Rasapi32.dll", CharSet = CharSet.Unicode)]
|
|
public static extern UInt32 RasValidateEntryNameW(
|
|
string lpszPhonebook,
|
|
string lpszEntry);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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 WinINetProxy
|
|
{
|
|
private string Connection;
|
|
|
|
public string AutoConfigUrl;
|
|
public bool AutoDetect;
|
|
public string Proxy;
|
|
public string ProxyBypass;
|
|
|
|
public WinINetProxy(string connection)
|
|
{
|
|
Connection = connection;
|
|
Refresh();
|
|
}
|
|
|
|
public static bool IsValidConnection(string name)
|
|
{
|
|
// RasValidateEntryName is used to verify is a name can be a valid phonebook entry. It returns 0 if no
|
|
// entry exists and 183 if it already exists. We just need to check if it returns 183 to verify the
|
|
// connection name.
|
|
return NativeMethods.RasValidateEntryNameW(null, name) == 183;
|
|
}
|
|
|
|
public void Refresh()
|
|
{
|
|
using (var connFlags = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS_UI))
|
|
using (var autoConfigUrl = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL))
|
|
using (var server = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER))
|
|
using (var bypass = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS))
|
|
{
|
|
NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options = new NativeHelpers.INTERNET_PER_CONN_OPTIONW[]
|
|
{
|
|
connFlags, autoConfigUrl, server, bypass
|
|
};
|
|
|
|
try
|
|
{
|
|
QueryOption(options, Connection);
|
|
}
|
|
catch (Win32Exception e)
|
|
{
|
|
if (e.NativeErrorCode == 87) // ERROR_INVALID_PARAMETER
|
|
{
|
|
// INTERNET_PER_CONN_FLAGS_UI only works for IE8+, try the fallback in case we are still working
|
|
// with an ancient version.
|
|
connFlags.dwOption = NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS;
|
|
QueryOption(options, Connection);
|
|
}
|
|
else
|
|
throw;
|
|
}
|
|
|
|
NativeHelpers.PER_CONN_FLAGS flags = (NativeHelpers.PER_CONN_FLAGS)connFlags.Value.dwValue;
|
|
|
|
AutoConfigUrl = flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_PROXY_URL)
|
|
? Marshal.PtrToStringUni(autoConfigUrl.Value.pszValue) : null;
|
|
AutoDetect = flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_DETECT);
|
|
if (flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_PROXY))
|
|
{
|
|
Proxy = Marshal.PtrToStringUni(server.Value.pszValue);
|
|
ProxyBypass = Marshal.PtrToStringUni(bypass.Value.pszValue);
|
|
}
|
|
else
|
|
{
|
|
Proxy = null;
|
|
ProxyBypass = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Set()
|
|
{
|
|
using (var connFlags = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS_UI))
|
|
using (var autoConfigUrl = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL))
|
|
using (var server = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER))
|
|
using (var bypass = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS))
|
|
{
|
|
List<NativeHelpers.INTERNET_PER_CONN_OPTIONW> options = new List<NativeHelpers.INTERNET_PER_CONN_OPTIONW>();
|
|
|
|
// PROXY_TYPE_DIRECT seems to always be set, need to verify
|
|
NativeHelpers.PER_CONN_FLAGS flags = NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_DIRECT;
|
|
if (AutoDetect)
|
|
flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_DETECT;
|
|
|
|
if (!String.IsNullOrEmpty(AutoConfigUrl))
|
|
{
|
|
flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_PROXY_URL;
|
|
autoConfigUrl.Value.pszValue = Marshal.StringToHGlobalUni(AutoConfigUrl);
|
|
}
|
|
options.Add(autoConfigUrl);
|
|
|
|
if (!String.IsNullOrEmpty(Proxy))
|
|
{
|
|
flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_PROXY;
|
|
server.Value.pszValue = Marshal.StringToHGlobalUni(Proxy);
|
|
}
|
|
options.Add(server);
|
|
|
|
if (!String.IsNullOrEmpty(ProxyBypass))
|
|
bypass.Value.pszValue = Marshal.StringToHGlobalUni(ProxyBypass);
|
|
options.Add(bypass);
|
|
|
|
connFlags.Value.dwValue = (UInt32)flags;
|
|
options.Add(connFlags);
|
|
|
|
SetOption(options.ToArray(), Connection);
|
|
|
|
// Tell IE that the proxy settings have been changed.
|
|
if (!NativeMethods.InternetSetOptionW(
|
|
IntPtr.Zero,
|
|
NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PROXY_SETTINGS_CHANGED,
|
|
new SafeMemoryBuffer(IntPtr.Zero),
|
|
0))
|
|
{
|
|
throw new Win32Exception("InternetSetOptionW(INTERNET_OPTION_PROXY_SETTINGS_CHANGED) failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static NativeHelpers.INTERNET_PER_CONN_OPTIONW CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION option)
|
|
{
|
|
return new NativeHelpers.INTERNET_PER_CONN_OPTIONW
|
|
{
|
|
dwOption = option,
|
|
Value = new NativeHelpers.INTERNET_PER_CONN_OPTIONW.ValueUnion(),
|
|
};
|
|
}
|
|
|
|
internal static void QueryOption(NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection = null)
|
|
{
|
|
using (NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList = new NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW())
|
|
using (SafeMemoryBuffer optionListPtr = MarshalOptionList(optionList, options, connection))
|
|
{
|
|
UInt32 bufferSize = optionList.dwSize;
|
|
if (!NativeMethods.InternetQueryOptionW(
|
|
IntPtr.Zero,
|
|
NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
|
|
optionListPtr,
|
|
ref bufferSize))
|
|
{
|
|
throw new Win32Exception("InternetQueryOptionW(INTERNET_OPTION_PER_CONNECTION_OPTION) failed");
|
|
}
|
|
|
|
for (int i = 0; i < options.Length; i++)
|
|
{
|
|
IntPtr opt = IntPtr.Add(optionList.pOptions, i * Marshal.SizeOf(typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW)));
|
|
NativeHelpers.INTERNET_PER_CONN_OPTIONW option = (NativeHelpers.INTERNET_PER_CONN_OPTIONW)Marshal.PtrToStructure(opt,
|
|
typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW));
|
|
options[i].Value = option.Value;
|
|
option.Value = null; // Stops the GC from freeing the same memory twice
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void SetOption(NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection = null)
|
|
{
|
|
using (NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList = new NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW())
|
|
using (SafeMemoryBuffer optionListPtr = MarshalOptionList(optionList, options, connection))
|
|
{
|
|
if (!NativeMethods.InternetSetOptionW(
|
|
IntPtr.Zero,
|
|
NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
|
|
optionListPtr,
|
|
optionList.dwSize))
|
|
{
|
|
throw new Win32Exception("InternetSetOptionW(INTERNET_OPTION_PER_CONNECTION_OPTION) failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static SafeMemoryBuffer MarshalOptionList(NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList,
|
|
NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection)
|
|
{
|
|
optionList.pszConnection = Marshal.StringToHGlobalUni(connection);
|
|
optionList.dwOptionCount = (UInt32)options.Length;
|
|
|
|
int optionSize = Marshal.SizeOf(typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW));
|
|
optionList.pOptions = Marshal.AllocHGlobal(optionSize * options.Length);
|
|
for (int i = 0; i < options.Length; i++)
|
|
{
|
|
IntPtr option = IntPtr.Add(optionList.pOptions, i * optionSize);
|
|
Marshal.StructureToPtr(options[i], option, false);
|
|
}
|
|
|
|
SafeMemoryBuffer optionListPtr = new SafeMemoryBuffer((int)optionList.dwSize);
|
|
Marshal.StructureToPtr(optionList, optionListPtr.DangerousGetHandle(), false);
|
|
return optionListPtr;
|
|
}
|
|
}
|
|
}
|
|
'@
|
|
Add-CSharpType -References $win_inet_invoke -AnsibleModule $module
|
|
|
|
# We need to validate the connection because WinINet will just silently continue even if the connection does not
|
|
# already exist.
|
|
if ($null -ne $connection -and -not [Ansible.WinINetProxy.WinINetProxy]::IsValidConnection($connection)) {
|
|
$module.FailJson("The connection '$connection' does not exist.")
|
|
}
|
|
|
|
$actual_proxy = New-Object -TypeName Ansible.WinINetProxy.WinINetProxy -ArgumentList @(,$connection)
|
|
$module.Diff.before = @{
|
|
auto_config_url = $actual_proxy.AutoConfigUrl
|
|
auto_detect = $actual_proxy.AutoDetect
|
|
bypass = $actual_proxy.ProxyBypass
|
|
server = $actual_proxy.Proxy
|
|
}
|
|
|
|
# Make sure an empty string is converted to $null for easier comparisons
|
|
if ([String]::IsNullOrEmpty($auto_config_url)) {
|
|
$auto_config_url = $null
|
|
}
|
|
if ([String]::IsNullOrEmpty($proxy)) {
|
|
$proxy = $null
|
|
}
|
|
if ([String]::IsNullOrEmpty($bypass)) {
|
|
$bypass = $null
|
|
}
|
|
|
|
# Record the original values in case we need to revert on a failure
|
|
$previous_auto_config_url = $actual_proxy.AutoConfigUrl
|
|
$previous_auto_detect = $actual_proxy.AutoDetect
|
|
$previous_proxy = $actual_proxy.Proxy
|
|
$previous_bypass = $actual_proxy.ProxyBypass
|
|
|
|
$changed = $false
|
|
if ($auto_config_url -ne $previous_auto_config_url) {
|
|
$actual_proxy.AutoConfigUrl = $auto_config_url
|
|
$changed = $true
|
|
}
|
|
|
|
if ($auto_detect -ne $previous_auto_detect) {
|
|
$actual_proxy.AutoDetect = $auto_detect
|
|
$changed = $true
|
|
}
|
|
|
|
if ($proxy -ne $previous_proxy) {
|
|
$actual_proxy.Proxy = $proxy
|
|
$changed = $true
|
|
}
|
|
|
|
if ($bypass -ne $previous_bypass) {
|
|
$actual_proxy.ProxyBypass = $bypass
|
|
$changed = $true
|
|
}
|
|
|
|
if ($changed -and -not $module.CheckMode) {
|
|
$actual_proxy.Set()
|
|
|
|
# Validate that the change was made correctly and revert if it wasn't. THe Set() method won't fail on invalid
|
|
# values so we need to check again to make sure all was good
|
|
$actual_proxy.Refresh()
|
|
if ($actual_proxy.AutoConfigUrl -ne $auto_config_url -or
|
|
$actual_proxy.AutoDetect -ne $auto_detect -or
|
|
$actual_proxy.Proxy -ne $proxy -or
|
|
$actual_proxy.ProxyBypass -ne $bypass) {
|
|
|
|
$actual_proxy.AutoConfigUrl = $previous_auto_config_url
|
|
$actual_proxy.AutoDetect = $previous_auto_detect
|
|
$actual_proxy.Proxy = $previous_proxy
|
|
$actual_proxy.ProxyBypass = $previous_bypass
|
|
$actual_proxy.Set()
|
|
|
|
$module.FailJson("Unknown error when trying to set auto_config_url '$auto_config_url', proxy '$proxy', or bypass '$bypass'")
|
|
}
|
|
}
|
|
$module.Result.changed = $changed
|
|
|
|
$module.Diff.after = @{
|
|
auto_config_url = $auto_config_url
|
|
auto_detect = $auto_detect
|
|
bypass = $bypass
|
|
proxy = $proxy
|
|
}
|
|
|
|
$module.ExitJson()
|