From dcc90285a6a457ad3c42dfd143d6ea5e491a8103 Mon Sep 17 00:00:00 2001 From: bli-r7 Date: Tue, 16 Nov 2021 15:40:49 -0500 Subject: [PATCH] =?UTF-8?q?Add=20a=20commandline=20argument=20to=20support?= =?UTF-8?q?=20returning=20a=20list=20of=20proxy=20when=20=E2=80=A6=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a commandline argument to support returning a list of proxy when proxy auto-config returns a list * Fix arguments in provider_windows and help doc corrections * Handle nil proxy when calling GetProxies * Fix incorrect provider type in provider_linux * Minor clean up --- main.go | 37 +++++++++++--- proxy/provider.go | 1 + proxy/provider_darwin.go | 8 +++ proxy/provider_linux.go | 8 +++ proxy/provider_windows.go | 104 +++++++++++++++++++++----------------- 5 files changed, 106 insertions(+), 52 deletions(-) diff --git a/main.go b/main.go index 7a93f19..8cd9ac6 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,8 @@ func main() { targetP := flag.String("t", "", "Optional. Target URL which the proxy will be used for. Default: *") jsonP := flag.Bool("j", false, "Optional. If a proxy is found, write it as JSON instead of a URL.") verboseP := flag.Bool("v", false, "Optional. If set, log content will be sent to stderr.") + useListP := flag.Bool("l", false, "Optional. If set, a list of proxy will be returned.") + flag.Parse() var ( protocol string @@ -35,6 +37,7 @@ func main() { target string jsonOut bool verbose bool + useList bool ) if protocolP != nil { protocol = *protocolP @@ -57,17 +60,37 @@ func main() { } else { log.SetOutput(ioutil.Discard) } - p := proxy.NewProvider(config).GetProxy(protocol, target) + if useListP != nil { + useList = *useListP + } var exit int - if p != nil { - if jsonOut { - b, _ := json.MarshalIndent(p, "", " ") - fmt.Println(string(b)) + + if useList { + ps := proxy.NewProvider(config).GetProxies(protocol, target) + if ps != nil { + if jsonOut { + b, _ := json.MarshalIndent(ps, "", " ") + fmt.Println(string(b)) + } else { + for _, p := range ps { + println(p.URL().String()) + } + } } else { - println(p.URL().String()) + exit = 1 } } else { - exit = 1 + p := proxy.NewProvider(config).GetProxy(protocol, target) + if p != nil { + if jsonOut { + b, _ := json.MarshalIndent(p, "", " ") + fmt.Println(string(b)) + } else { + println(p.URL().String()) + } + } else { + exit = 1 + } } os.Exit(exit) } diff --git a/proxy/provider.go b/proxy/provider.go index 72f1ff7..e0e0be1 100644 --- a/proxy/provider.go +++ b/proxy/provider.go @@ -97,6 +97,7 @@ type Provider interface { receive: Time in milliseconds to receive a response to a request. Provider default is 20000. */ SetTimeouts(resolve int, connect int, send int, receive int) + GetProxies(protocol string, targetUrl string) []Proxy } const ( diff --git a/proxy/provider_darwin.go b/proxy/provider_darwin.go index 949fd0b..83bc13f 100644 --- a/proxy/provider_darwin.go +++ b/proxy/provider_darwin.go @@ -112,6 +112,14 @@ func (p *providerDarwin) GetSOCKSProxy(targetUrl string) Proxy { return p.GetProxy(protocolSOCKS, targetUrl) } +func (p *providerDarwin) GetProxies(protocol string, targetUrl string) []Proxy { + proxy := p.GetProxy(protocol, targetUrl) + if proxy != nil { + return []Proxy{proxy} + } + return []Proxy{} +} + const ( scUtilBinary = "scutil" scUtilBinaryArgument = "--proxy" diff --git a/proxy/provider_linux.go b/proxy/provider_linux.go index 53463ed..89fa167 100644 --- a/proxy/provider_linux.go +++ b/proxy/provider_linux.go @@ -95,3 +95,11 @@ Returns: func (p *providerLinux) GetSOCKSProxy(targetUrl string) Proxy { return p.GetProxy(protocolSOCKS, targetUrl) } + +func (p *providerLinux) GetProxies(protocol string, targetUrl string) []Proxy { + proxy := p.GetProxy(protocol, targetUrl) + if proxy != nil { + return []Proxy{proxy} + } + return []Proxy{} +} diff --git a/proxy/provider_windows.go b/proxy/provider_windows.go index cb99e96..645bc81 100644 --- a/proxy/provider_windows.go +++ b/proxy/provider_windows.go @@ -54,7 +54,8 @@ func (p *providerWindows) GetProxy(protocol string, targetUrlStr string) Proxy { if proxy != nil { return proxy } - return p.readWinHttpProxy(protocol, targetUrl) + proxies := p.readWinHttpProxy(protocol, targetUrl) + return proxies[len(proxies) - 1] } /* @@ -109,6 +110,16 @@ func (p *providerWindows) GetSOCKSProxy(targetUrl string) Proxy { return p.GetProxy(protocolSOCKS, targetUrl) } +func (p *providerWindows) GetProxies(protocol string, targetUrlStr string) []Proxy { + targetUrl := ParseTargetURL(targetUrlStr, protocol) + proxy := p.provider.get(protocol, targetUrl) + if proxy != nil { + return []Proxy{proxy} + } + proxies := p.readWinHttpProxy(protocol, targetUrl) + return proxies +} + const ( userAgent = "ir_agent" srcAutoDetect = "WinHTTP:AutoDetect" @@ -122,7 +133,7 @@ type providerWindows struct { } //noinspection SpellCheckingInspection -func (p *providerWindows) readWinHttpProxy(protocol string, targetUrl *url.URL) Proxy { +func (p *providerWindows) readWinHttpProxy(protocol string, targetUrl *url.URL) []Proxy { // Internet Options ieProxyConfig, err := p.getIeProxyConfigCurrentUser() if err != nil { @@ -138,24 +149,25 @@ func (p *providerWindows) readWinHttpProxy(protocol string, targetUrl *url.URL) } } if autoConfigUrl := winhttp.LpwstrToString(ieProxyConfig.LpszAutoConfigUrl); autoConfigUrl != "" { - proxy, err := p.getProxyAutoConfigUrl(protocol, targetUrl, autoConfigUrl) + proxies, err := p.getProxyAutoConfigUrl(protocol, targetUrl, autoConfigUrl) if err == nil { - return proxy + return proxies } else if !isNotFound(err) { log.Printf("[proxy.Provider.readWinHttpProxy] No proxy discovered via AutoConfigUrl, %s: %s\n", autoConfigUrl, err) } } - proxy, err := p.parseProxyInfo(srcNamedProxy, protocol, targetUrl, ieProxyConfig.LpszProxy, ieProxyConfig.LpszProxyBypass) + // LpszProxy may contain multiple proxies which needs to be parsed into a list of Proxy + proxies, err := p.parseProxyInfo(srcNamedProxy, protocol, targetUrl, ieProxyConfig.LpszProxy, ieProxyConfig.LpszProxyBypass) if err == nil { - return proxy + return proxies } else if !isNotFound(err) { log.Printf("[proxy.Provider.readWinHttpProxy] Failed to parse named proxy: %s\n", err) } } // netsh winhttp - proxy, err := p.getProxyWinHttpDefault(protocol, targetUrl) + proxies, err := p.getProxyWinHttpDefault(protocol, targetUrl) if err == nil { - return proxy + return proxies } else if !isNotFound(err) { log.Printf("[proxy.Provider.readWinHttpProxy] Failed to parse WinHttp default proxy info: %s\n", err) } @@ -187,7 +199,7 @@ Returns: nil, notFoundError: No proxy was found nil, error: An error occurred */ -func (p *providerWindows) getProxyAutoDetect(protocol string, targetUrl *url.URL) (Proxy, error) { +func (p *providerWindows) getProxyAutoDetect(protocol string, targetUrl *url.URL) ([]Proxy, error) { return p.getProxyForUrl(srcAutoDetect, protocol, targetUrl, &winhttp.AutoProxyOptions{ DwFlags: winhttp.WINHTTP_AUTOPROXY_AUTO_DETECT, @@ -207,7 +219,7 @@ Returns: nil, notFoundError: No proxy was found nil, error: An error occurred */ -func (p *providerWindows) getProxyAutoConfigUrl(protocol string, targetUrl *url.URL, autoConfigUrl string) (Proxy, error) { +func (p *providerWindows) getProxyAutoConfigUrl(protocol string, targetUrl *url.URL, autoConfigUrl string) ([]Proxy, error) { return p.getProxyForUrl(srcAutoConfigUrl, protocol, targetUrl, &winhttp.AutoProxyOptions{ DwFlags: winhttp.WINHTTP_AUTOPROXY_CONFIG_URL, @@ -227,7 +239,7 @@ Returns: nil, notFoundError: No proxy was found nil, error: An error occurred */ -func (p *providerWindows) getProxyWinHttpDefault(protocol string, targetUrl *url.URL) (Proxy, error) { +func (p *providerWindows) getProxyWinHttpDefault(protocol string, targetUrl *url.URL) ([]Proxy, error) { pInfo, err := winhttp.GetDefaultProxyConfiguration() if err != nil { return nil, err @@ -237,18 +249,18 @@ func (p *providerWindows) getProxyWinHttpDefault(protocol string, targetUrl *url } /* -Returns the Proxy found through either automatic detection or a automatic configuration URL. +Returns the Proxies found through either automatic detection or a automatic configuration URL. Params: src: If a proxy is constructed, the human readable source to associated it with. protocol: The protocol of traffic the proxy is to be used for. (i.e. http, https, ftp, socks) targetUrl: The URL the proxy is to be used for. (i.e. https://test.endpoint.rapid7.com) autoProxyOptions: Use this to inform WinHTTP what route to take when doing the lookup (automatic detection, or automatic configuration URL) Returns: - Proxy, nil: A proxy was found + Proxy, nil: A list of proxy was found nil, notFoundError: No proxy was found nil, error: An error occurred */ -func (p *providerWindows) getProxyForUrl(src string, protocol string, targetUrl *url.URL, autoProxyOptions *winhttp.AutoProxyOptions) (Proxy, error) { +func (p *providerWindows) getProxyForUrl(src string, protocol string, targetUrl *url.URL, autoProxyOptions *winhttp.AutoProxyOptions) ([]Proxy, error) { pInfo, err := p.getProxyInfoForUrl(targetUrl, autoProxyOptions) if err != nil { return nil, err @@ -297,59 +309,61 @@ Params: lpszProxy: The Lpwstr which represents the proxy value (if any). This value can be optionally separated by protocol. lpszProxyBypass: The Lpwstr which represents the proxy bypass value (if any). Returns: - Proxy, nil: A proxy was found + Proxy, nil: A list of proxies matching the lookup criteria nil, notFoundError: No proxy was found or was bypassed nil, error: An error occurred */ //noinspection SpellCheckingInspection -func (p *providerWindows) parseProxyInfo(src string, protocol string, targetUrl *url.URL, lpszProxy winhttp.Lpwstr, lpszProxyBypass winhttp.Lpwstr) (Proxy, error) { - proxyUrlStr := p.parseLpszProxy(protocol, winhttp.LpwstrToString(lpszProxy)) - if proxyUrlStr == "" { - return nil, new(notFoundError) - } - proxyUrl, err := ParseURL(proxyUrlStr, "") - if err != nil { - return nil, err - } +func (p *providerWindows) parseProxyInfo(src string, protocol string, targetUrl *url.URL, lpszProxy winhttp.Lpwstr, lpszProxyBypass winhttp.Lpwstr) ([]Proxy, error) { + proxies := []Proxy{} proxyBypass := winhttp.LpwstrToString(lpszProxyBypass) if proxyBypass != "" { - bypass := p.isLpszProxyBypass(targetUrl, proxyBypass) - log.Printf("[proxy.Provider.parseProxyInfo]: lpszProxyBypass=\"%s\", targetUrl=%s, bypass=%t", proxyBypass, targetUrl, bypass) - if bypass { - return nil, new(notFoundError) + if p.isLpszProxyBypass(targetUrl, proxyBypass) { + return proxies, nil + } + } + + proxyUrlStrList := p.parseLpszProxy(protocol, winhttp.LpwstrToString(lpszProxy)) + if len(proxyUrlStrList) == 0 { + return nil, new(notFoundError) + } + for _, proxyUrlStr := range proxyUrlStrList { + proxyUrl, err := ParseURL(proxyUrlStr, "") + if err != nil { + log.Printf("Failed to parse proxy URL\"$\"", proxyUrlStr) + continue } + pr, _ := NewProxy(proxyUrl, src) + proxies = append(proxies, pr) } - return NewProxy(proxyUrl, src) + return proxies, nil } /* -Parse the lpszProxy into a single proxy URL, represented as a string. +Parse the lpszProxy into a list of proxy URL, represented as a list of string. For example: - ("https", "1.2.3.4") -> "1.2.3.4" - ("https", "https=1.2.3.4;http=4.5.6.7") -> "1.2.3.4" - ("https", "") -> "" - ("https", "http=4.5.6.7") -> "" + ("https", "1.2.3.4") -> ["1.2.3.4"] + ("https", "https=1.2.3.4;http=4.5.6.7") -> ["1.2.3.4"] + ("https", "https=1.2.3.4;https=4.5.6.7") -> ["1.2.3.4","4.5.6.7"] + ("https", "") -> [] + ("https", "http=4.5.6.7") -> [] Params: protocol: The protocol of traffic the proxy is to be used for. (i.e. http, https, ftp, socks) lpszProxy: The Lpwstr which represents the proxy value (if any). This value can be optionally separated by protocol. Returns: - string: The proxy URL (if any) from the lpszProxy value. + string: The list of proxy URL (if any) from the lpszProxy value. */ //noinspection SpellCheckingInspection -func (p *providerWindows) parseLpszProxy(protocol string, lpszProxy string) string { - m := "" +func (p *providerWindows) parseLpszProxy(protocol string, lpszProxy string) []string { + proxies := []string{} for _, s := range strings.Split(lpszProxy, ";") { parts := strings.SplitN(s, "=", 2) - // No protocol? - if len(parts) < 2 { - // Assign a match, but keep looking in case we have a protocol specific match - m = s - } else if strings.TrimSpace(parts[0]) == protocol { - m = parts[1] - break + // Include the proxy if protocol matches or if no protocol is specified + if len(parts) < 2 || strings.TrimSpace(parts[0]) == protocol { + proxies = append(proxies, s) } } - return m + return proxies } /*