// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing // permissions and limitations under the License. // Package proxyconfig to handle set/get proxy settings package proxyconfig import ( "errors" "net/url" "os" "strings" "syscall" "unicode/utf16" "unsafe" "golang.org/x/sys/windows" "github.com/aws/amazon-ssm-agent/agent/log" ) // WinHttpIEProxyConfig represents the Internet Explorer proxy configuration information // // fAutoDetect: If TRUE, indicates that the Internet Explorer proxy configuration for the current user specifies "automatically detect settings". // lpszAutoConfigUrl: Pointer to a null-terminated Unicode string that contains the auto-configuration URL if the Internet Explorer proxy configuration for the current user specifies "Use automatic proxy configuration". // lpszProxy: Pointer to a null-terminated Unicode string that contains the proxy URL if the Internet Explorer proxy configuration for the current user specifies "use a proxy server". // lpszProxyBypass: Pointer to a null-terminated Unicode string that contains the optional proxy by-pass server list. type WinHttpIEProxyConfig struct { fAutoDetect bool lpszAutoConfigUrl *uint16 lpszProxy *uint16 lpszProxyBypass *uint16 } // WinHttpProxyInfo represents the WinHTTP machine proxy configuration. // // lpszProxy: Pointer to a string value that contains the proxy server list. // lpszProxyBypass: Pointer to a string value that contains the proxy bypass list. type WinHttpProxyInfo struct { dwAccessType uint32 lpszProxy *uint16 lpszProxyBypass *uint16 } // HttpIEProxyConfig represents the Internet Explorer proxy configuration. // // auto: indicates if the 'Automatically detect settings' option in IE is enabled // enabled: indicates if the 'Use proxy settings for your LAN' option in IE is enabled // proxy: specifies the proxy addresses to use. // bypass: specifies addresses that should be excluded from proxy type HttpIEProxyConfig struct { proxy string bypass string config string auto bool enabled bool } // HttpDefaultProxyConfig represents the WinHTTP machine proxy configuration. // // proxy: specifies the proxy addresses to use. // bypass: specifies addresses that should be excluded from proxy type HttpDefaultProxyConfig struct { proxy string bypass string } // ProxySettings represents the proxy settings for https_proxy and http_proxy type ProxySettings struct { HttpsProxy *url.URL HttpProxy *url.URL } // StringFromUTF16Ptr converts a *uint16 C string to a Go String // https://github.com/mattn/go-ieproxy/blob/master/utils.go func StringFromUTF16Ptr(s *uint16) string { if s == nil { return "" } p := (*[1<<30 - 1]uint16)(unsafe.Pointer(s)) // find the string length sz := 0 for p[sz] != 0 { sz++ } return string(utf16.Decode(p[:sz:sz])) } // For HTTP requests the agent gets the proxy address from the // environment variables http_proxy, https_proxy and no_proxy // https_proxy takes precedence over http_proxy for https requests. // SetProxySettings() overwrites the environment variables using the // Windows proxy configuration if no settings are provided in the // registry HKLM:\SYSTEM\CurrentControlSet\Services\AmazonSSMAgent\Environment func SetProxyConfig(log log.T) (proxySettings map[string]string) { var err error var ie HttpIEProxyConfig var df HttpDefaultProxyConfig var proxy string var bypass string var v = []string{} defer func() { if err := recover(); err != nil { log.Errorf("Failed while setting proxy settings: %v", err) } }() for _, value := range ProxyEnvVariables { v = append(v, value+":"+os.Getenv(value)) } log.Debugf("Current proxy environment variables: %v", strings.Join(v, ";")) v = nil // IE current user proxy settings have precedence over WinHTTP machine proxy settings if ie, err = GetIEProxySettings(log); ie.enabled && err == nil { proxy = ie.proxy bypass = ie.bypass if ie.auto { log.Debugf("IE option 'Automatically detect settings' is not supported") } if len(ie.config) > 0 { log.Debugf("IE option 'Use automatic configuration script' is not supported") } } else { if df, err = GetDefaultProxySettings(log); len(df.proxy) > 0 && err == nil { proxy = df.proxy bypass = df.bypass } } // Current registry environment variables http_proxy, https_proxy and no_proxy // have precedence over IE and WinHTTP machine proxy settings for _, value := range ProxyEnvVariables { if v := os.Getenv(value); len(v) > 0 { switch value { case "https_proxy", "http_proxy": proxy = "" case "no_proxy": bypass = "" } } } settings := ParseProxySettings(log, proxy) if settings.HttpsProxy != nil { os.Setenv("https_proxy", settings.HttpsProxy.String()) } if settings.HttpProxy != nil { os.Setenv("http_proxy", settings.HttpProxy.String()) } bypassList := ParseProxyBypass(log, bypass) if len(bypassList) > 0 { os.Setenv("no_proxy", strings.Join(bypassList, ",")) } return GetProxyConfig() } func ParseProxyBypass(log log.T, bypass string) []string { var bypassList []string for _, f := range strings.Fields(bypass) { for _, s := range strings.Split(f, ";") { if len(s) == 0 { continue } parsedUrl, err := ValidateHost(s) if err == nil { bypassList = append(bypassList, parsedUrl.Host) } else { log.Warnf("SetProxySettings invalid URL or host for no_proxy: %v\n", err.Error()) } } } return bypassList } // GetDefaultProxySettings returns the machine WinHTTP proxy configuration func GetDefaultProxySettings(log log.T) (p HttpDefaultProxyConfig, err error) { winhttp := syscall.Handle(windows.NewLazySystemDLL("Winhttp.dll").Handle()) defer syscall.FreeLibrary(winhttp) getDefaultProxy, err := syscall.GetProcAddress(winhttp, "WinHttpGetDefaultProxyConfiguration") if err != nil { log.Errorf("Failed to get default machine WinHTTP proxy configuration: %v", err.Error()) return p, err } settings := new(WinHttpProxyInfo) ret, _, err := syscall.Syscall(uintptr(getDefaultProxy), 1, uintptr(unsafe.Pointer(settings)), 0, 0) if ret != 1 { log.Errorf("Failed to get default machine WinHTTP proxy configuration: %v", err.Error()) return p, err } else { log.Infof("Getting WinHTTP proxy default configuration: %v", err.Error()) } err = nil result := HttpDefaultProxyConfig{ proxy: StringFromUTF16Ptr(settings.lpszProxy), bypass: StringFromUTF16Ptr(settings.lpszProxyBypass), } log.Debugf("WinHTTP proxy default configuration: proxy:%v,bypass:%v", result.proxy, result.bypass, ) return result, nil } // GetIEProxySettings returns the Internet Explorer proxy configuration for the current user func GetIEProxySettings(log log.T) (p HttpIEProxyConfig, err error) { p.auto = false p.enabled = false winhttp := syscall.Handle(windows.NewLazySystemDLL("Winhttp.dll").Handle()) defer syscall.FreeLibrary(winhttp) getIEProxy, err := syscall.GetProcAddress(winhttp, "WinHttpGetIEProxyConfigForCurrentUser") if err != nil { log.Error("Failed to get IE proxy configuration for current user: ", err.Error()) return p, err } settings := new(WinHttpIEProxyConfig) ret, _, err := syscall.Syscall(uintptr(getIEProxy), 1, uintptr(unsafe.Pointer(settings)), 0, 0) if ret != 1 { log.Error("Failed to get IE proxy configuration for current user: ", err.Error()) return p, err } else { log.Info("Getting IE proxy configuration for current user: ", err.Error()) } err = nil result := HttpIEProxyConfig{ proxy: StringFromUTF16Ptr(settings.lpszProxy), bypass: StringFromUTF16Ptr(settings.lpszProxyBypass), auto: settings.fAutoDetect, enabled: settings.lpszProxy != nil, config: StringFromUTF16Ptr(settings.lpszAutoConfigUrl), } log.Debugf("IE proxy configuration for current user: proxy:%v,bypass:%v,enabled:%v,automatically detect proxy settings:%v,automatic configuration script:%v", result.proxy, result.bypass, result.enabled, result.auto, result.config, ) return result, nil } // ParseProxySettings parses the proxy-list string // The Windows proxy server list contains one or more of the following strings // ([=]["://"][":"]) // Internet Explorer and WinHTTP support 4 proxy types for [=]: // http=, https=, ftp=, or socks= func ParseProxySettings(log log.T, proxy string) ProxySettings { // Parse http and https proxy settings allowing only valid URL or host[:port] values var http, https, other *url.URL var err error = nil for _, f := range strings.Fields(proxy) { for _, s := range strings.Split(f, ";") { if len(s) == 0 { continue } split := strings.SplitN(s, "=", 2) if len(split) > 1 { switch split[0] { case "https": https, err = ValidateHost(split[1]) case "http": http, err = ValidateHost(split[1]) default: continue } } else { other, err = ValidateHost(split[0]) } if err != nil { log.Warnf("ParseProxySettings, invalid URL or host for proxy: %v", err.Error()) } } } result := ProxySettings{ HttpProxy: http, HttpsProxy: https, } // If no [=] is provided http is the default option if https == nil && http == nil { result.HttpProxy = other } else if https != nil && http == nil { result.HttpProxy = other } else if https == nil && http != nil { result.HttpsProxy = other } log.Debugf("ParseProxySettings result: http_proxy:%v,https_proxy:%v", result.HttpProxy, result.HttpsProxy, ) return result } // ValidateHost tries to parse the http_proxy and https_proxy addresses func ValidateHost(s string) (*url.URL, error) { if s == "<-loopback>" || s == "" { return nil, errors.New(s + " host not supported, skipped") } // Helps url.Parse to validate an IP for example 127.0.0.1 if strings.Index(s, "//") == 0 { s = "http:" + s } // Forces http when the schema is missing if strings.Index(s, "://") == -1 { s = "http://" + s } u, err := url.Parse(s) if err != nil { return nil, errors.New(err.Error() + s + ", skipped") } return u, nil }