From c6cb5a646aa5f9b29d0a0fdee9dd3a0449cf8a0c Mon Sep 17 00:00:00 2001 From: ThinkChaos Date: Mon, 21 Oct 2019 21:47:45 +0200 Subject: [PATCH] Add Proxy Auto-Configuration (PAC) support --- .../OpenVPNTunnelProvider+Configuration.swift | 8 ++++++++ .../AppExtension/OpenVPNTunnelProvider.swift | 19 +++++++++++++++---- .../Protocols/OpenVPN/Configuration.swift | 12 ++++++++++-- .../OpenVPN/ConfigurationParser.swift | 17 +++++++++++++++-- .../OpenVPN/ConfigurationParserTests.swift | 2 ++ 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift index 0f2e382..e111af9 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift @@ -181,6 +181,8 @@ extension OpenVPNTunnelProvider { static let httpsProxy = "HTTPSProxy" + static let proxyAutoConfURL = "proxyAutoConfURL" + static let proxyBypassDomains = "ProxyBypassDomains" static let routingPolicies = "RoutingPolicies" @@ -595,6 +597,9 @@ private extension OpenVPN.Configuration { if let httpsProxy = httpsProxy { dict[S.httpsProxy] = httpsProxy.rawValue } + if let proxyAutoConfURL = proxyAutoConfURL { + dict[S.proxyAutoConfURL] = proxyAutoConfURL.absoluteString + } if let proxyBypassDomains = proxyBypassDomains { dict[S.proxyBypassDomains] = proxyBypassDomains } @@ -668,6 +673,9 @@ private extension OpenVPN.Configuration { if let httpsProxy = httpsProxy { log.info("\tHTTPS proxy: \(httpsProxy.maskedDescription)") } + if let proxyAutoConfURL = proxyAutoConfURL { + log.info("\tPAC: \(proxyAutoConfURL)") + } if let proxyBypassDomains = proxyBypassDomains { log.info("\tProxy bypass domains: \(proxyBypassDomains.maskedDescription)") } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift index 958632c..722adf1 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift @@ -514,6 +514,9 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { if let proxy = options.httpsProxy { log.info("\t\tHTTPS: \(proxy.maskedDescription)") } + if let pacURL = options.proxyAutoConfURL { + log.info("\t\tPAC: \(pacURL)") + } if let bypass = options.proxyBypassDomains { log.info("\t\tBypass domains: \(bypass.maskedDescription)") } @@ -665,18 +668,26 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate { var proxySettings: NEProxySettings? if let httpsProxy = cfg.sessionConfiguration.httpsProxy ?? options.httpsProxy { proxySettings = NEProxySettings() - proxySettings?.httpsServer = httpsProxy.neProxy() - proxySettings?.httpsEnabled = true + proxySettings!.httpsServer = httpsProxy.neProxy() + proxySettings!.httpsEnabled = true log.info("Routing: Setting HTTPS proxy \(httpsProxy.address.maskedDescription):\(httpsProxy.port)") } if let httpProxy = cfg.sessionConfiguration.httpProxy ?? options.httpProxy { if proxySettings == nil { proxySettings = NEProxySettings() } - proxySettings?.httpServer = httpProxy.neProxy() - proxySettings?.httpEnabled = true + proxySettings!.httpServer = httpProxy.neProxy() + proxySettings!.httpEnabled = true log.info("Routing: Setting HTTP proxy \(httpProxy.address.maskedDescription):\(httpProxy.port)") } + if let pacURL = cfg.sessionConfiguration.proxyAutoConfURL ?? options.proxyAutoConfURL { + if proxySettings == nil { + proxySettings = NEProxySettings() + } + proxySettings!.proxyAutoConfigurationURL = pacURL + proxySettings!.autoProxyConfigurationEnabled = true + log.info("Routing: Setting PAC \(pacURL)") + } // only set if there is a proxy (proxySettings set to non-nil above) if let bypass = cfg.sessionConfiguration.proxyBypassDomains ?? options.proxyBypassDomains { diff --git a/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift b/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift index 7970527..0155c2d 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift @@ -244,9 +244,12 @@ extension OpenVPN { /// The search domain. public var searchDomain: String? + /// The Proxy Auto-Configuration (PAC) url. + public var proxyAutoConfURL: URL? + /// The HTTP proxy. public var httpProxy: Proxy? - + /// The HTTPS proxy. public var httpsProxy: Proxy? @@ -291,6 +294,7 @@ extension OpenVPN { searchDomain: searchDomain, httpProxy: httpProxy, httpsProxy: httpsProxy, + proxyAutoConfURL: proxyAutoConfURL, proxyBypassDomains: proxyBypassDomains, routingPolicies: routingPolicies ) @@ -385,10 +389,13 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.httpProxy` public let httpProxy: Proxy? - + /// - Seealso: `ConfigurationBuilder.httpsProxy` public let httpsProxy: Proxy? + /// - Seealso: `ConfigurationBuilder.proxyAutoConfURL` + public let proxyAutoConfURL: URL? + /// - Seealso: `ConfigurationBuilder.proxyBypassDomains` public let proxyBypassDomains: [String]? @@ -449,6 +456,7 @@ extension OpenVPN.Configuration { builder.searchDomain = searchDomain builder.httpProxy = httpProxy builder.httpsProxy = httpsProxy + builder.proxyAutoConfURL = proxyAutoConfURL builder.proxyBypassDomains = proxyBypassDomains builder.routingPolicies = routingPolicies return builder diff --git a/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift b/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift index 03225a1..70a2b62 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift @@ -94,7 +94,7 @@ extension OpenVPN { static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+") - static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS?) +[^ ]+ +\\d+") + static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS? +[^ ]+ +\\d+|AUTO_CONFIG_URL +[^ ]+)") static let proxyBypass = NSRegularExpression("^dhcp-option +PROXY_BYPASS +.+") @@ -225,6 +225,7 @@ extension OpenVPN { var optSearchDomain: String? var optHTTPProxy: Proxy? var optHTTPSProxy: Proxy? + var optProxyAutoConfURL: URL? var optProxyBypass: [String]? var optRedirectGateway: Set? @@ -517,6 +518,17 @@ extension OpenVPN { optSearchDomain = $0[1] } Regex.proxy.enumerateArguments(in: line) { + if $0.count == 2 { + let maybeURL = URL(string: $0[1]) + if maybeURL != nil { + optProxyAutoConfURL = maybeURL! + } + else { + unsupportedError = ConfigurationError.malformed(option: "dhcp-option PROXY_AUTO_CONFIG_URL has malformed URL") + } + return + } + guard $0.count == 3, let port = UInt16($0[2]) else { return } @@ -526,7 +538,7 @@ extension OpenVPN { case "PROXY_HTTP": optHTTPProxy = Proxy($0[1], port) - + default: break } @@ -714,6 +726,7 @@ extension OpenVPN { sessionBuilder.searchDomain = optSearchDomain sessionBuilder.httpProxy = optHTTPProxy sessionBuilder.httpsProxy = optHTTPSProxy + sessionBuilder.proxyAutoConfURL = optProxyAutoConfURL sessionBuilder.proxyBypassDomains = optProxyBypass if let flags = optRedirectGateway { diff --git a/TunnelKitTests/OpenVPN/ConfigurationParserTests.swift b/TunnelKitTests/OpenVPN/ConfigurationParserTests.swift index 34fd0d0..2664a91 100644 --- a/TunnelKitTests/OpenVPN/ConfigurationParserTests.swift +++ b/TunnelKitTests/OpenVPN/ConfigurationParserTests.swift @@ -56,6 +56,7 @@ class ConfigurationParserTests: XCTestCase { "dhcp-option DOMAIN example.com", "dhcp-option PROXY_HTTP 1.2.3.4 8081", "dhcp-option PROXY_HTTPS 7.8.9.10 8082", + "dhcp-option PROXY_AUTO_CONFIG_URL https://pac/", "dhcp-option PROXY_BYPASS foo.com bar.org net.chat" ] XCTAssertNoThrow(try OpenVPN.ConfigurationParser.parsed(fromLines: lines)) @@ -67,6 +68,7 @@ class ConfigurationParserTests: XCTestCase { XCTAssertEqual(parsed.httpProxy?.port, 8081) XCTAssertEqual(parsed.httpsProxy?.address, "7.8.9.10") XCTAssertEqual(parsed.httpsProxy?.port, 8082) + XCTAssertEqual(parsed.proxyAutoConfURL?.absoluteString, "https://pac/") XCTAssertEqual(parsed.proxyBypassDomains, ["foo.com", "bar.org", "net.chat"]) }