// // TunnelKitProvider+FileConfiguration.swift // Passepartout // // Created by Davide De Rosa on 9/5/18. // Copyright (c) 2018 Davide De Rosa. All rights reserved. // // https://github.com/keeshux // // This file is part of Passepartout. // // Passepartout is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Passepartout is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Passepartout. If not, see . // import Foundation import TunnelKit import SwiftyBeaver private let log = SwiftyBeaver.self extension TunnelKitProvider.Configuration { private struct Regex { static let proto = Utils.regex("^proto +(udp6?|tcp6?)") static let port = Utils.regex("^port +\\d+") static let remote = Utils.regex("^remote +[^ ]+( +\\d+)?( +(udp6?|tcp6?))?") static let cipher = Utils.regex("^cipher +[\\w\\-]+") static let auth = Utils.regex("^auth +[\\w\\-]+") static let compLZO = Utils.regex("^comp-lzo") static let compress = Utils.regex("^compress") static let ping = Utils.regex("^ping +\\d+") static let renegSec = Utils.regex("^reneg-sec +\\d+") static let fragment = Utils.regex("^fragment +\\d+") static let proxy = Utils.regex("^\\w+-proxy") static let keyDirection = Utils.regex("^key-direction +\\d") static let externalFiles = Utils.regex("^(ca|cert|key|tls-auth|tls-crypt) ") static let blockBegin = Utils.regex("^<[\\w\\-]+>") static let blockEnd = Utils.regex("^<\\/[\\w\\-]+>") } static func parsed(from url: URL) throws -> (String, TunnelKitProvider.Configuration) { let lines = try String(contentsOf: url).trimmedLines() var defaultProto: TunnelKitProvider.SocketType? var defaultPort: UInt16? var remotes: [(String, UInt16?, TunnelKitProvider.SocketType?)] = [] var cipher: SessionProxy.Cipher? var digest: SessionProxy.Digest? var compressionFraming: SessionProxy.CompressionFraming = .disabled var optCA: CryptoContainer? var clientCertificate: CryptoContainer? var clientKey: CryptoContainer? var keepAliveSeconds: TimeInterval? var renegotiateAfterSeconds: TimeInterval? var keyDirection: StaticKey.Direction? var tlsStrategy: SessionProxy.TLSWrap.Strategy? var tlsKeyLines: [Substring]? var tlsWrap: SessionProxy.TLSWrap? var currentBlockName: String? var currentBlock: [String] = [] var unsupportedError: ApplicationError? = nil log.verbose("Configuration file:") for line in lines { log.verbose(line) Regex.blockBegin.enumerateComponents(in: line) { let tag = $0.first! let from = tag.index(after: tag.startIndex) let to = tag.index(before: tag.endIndex) currentBlockName = String(tag[from.. 1 { port = UInt16($0[1]) } if $0.count > 2 { proto = TunnelKitProvider.SocketType(protoString: $0[2]) } remotes.append((hostname, port, proto)) } Regex.cipher.enumerateArguments(in: line) { guard let rawValue = $0.first else { return } cipher = SessionProxy.Cipher(rawValue: rawValue.uppercased()) if cipher == nil { unsupportedError = ApplicationError.unsupportedConfiguration(option: "cipher \(rawValue)") } } Regex.auth.enumerateArguments(in: line) { guard let rawValue = $0.first else { return } digest = SessionProxy.Digest(rawValue: rawValue.uppercased()) if digest == nil { unsupportedError = ApplicationError.unsupportedConfiguration(option: "auth \(rawValue)") } } Regex.compLZO.enumerateComponents(in: line) { _ in compressionFraming = .compLZO } Regex.compress.enumerateComponents(in: line) { _ in compressionFraming = .compress } Regex.keyDirection.enumerateArguments(in: line) { guard let arg = $0.first, let value = Int(arg) else { return } keyDirection = StaticKey.Direction(rawValue: value) } Regex.ping.enumerateArguments(in: line) { guard let arg = $0.first else { return } keepAliveSeconds = TimeInterval(arg) } Regex.renegSec.enumerateArguments(in: line) { guard let arg = $0.first else { return } renegotiateAfterSeconds = TimeInterval(arg) } Regex.fragment.enumerateArguments(in: line) { (_) in unsupportedError = ApplicationError.unsupportedConfiguration(option: "fragment") } Regex.proxy.enumerateArguments(in: line) { (_) in unsupportedError = ApplicationError.unsupportedConfiguration(option: "proxy: \"\(line)\"") } Regex.externalFiles.enumerateArguments(in: line) { (_) in unsupportedError = ApplicationError.unsupportedConfiguration(option: "external file: \"\(line)\"") } if let error = unsupportedError { throw error } } guard let ca = optCA else { throw ApplicationError.missingCA } // XXX: only reads first remote // hostnames = remotes.map { $0.0 } guard !remotes.isEmpty else { throw ApplicationError.emptyRemotes } let hostname = remotes[0].0 defaultProto = defaultProto ?? .udp defaultPort = defaultPort ?? 1194 // XXX: reads endpoints from remotes with matching hostname var endpointProtocols: [TunnelKitProvider.EndpointProtocol] = [] remotes.forEach { guard $0.0 == hostname else { return } guard let port = $0.1 ?? defaultPort else { return } guard let socketType = $0.2 ?? defaultProto else { return } endpointProtocols.append(TunnelKitProvider.EndpointProtocol(socketType, port)) } assert(!endpointProtocols.isEmpty, "Must define an endpoint protocol") if let keyLines = tlsKeyLines, let strategy = tlsStrategy { let optKey: StaticKey? switch strategy { case .auth: optKey = StaticKey(lines: keyLines, direction: keyDirection) case .crypt: optKey = StaticKey(lines: keyLines, direction: .client) } if let key = optKey { tlsWrap = SessionProxy.TLSWrap(strategy: strategy, key: key) } } var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: ca) sessionBuilder.cipher = cipher ?? .aes128cbc sessionBuilder.digest = digest ?? .sha1 sessionBuilder.compressionFraming = compressionFraming sessionBuilder.tlsWrap = tlsWrap sessionBuilder.clientCertificate = clientCertificate sessionBuilder.clientKey = clientKey sessionBuilder.keepAliveInterval = keepAliveSeconds sessionBuilder.renegotiatesAfter = renegotiateAfterSeconds var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) builder.endpointProtocols = endpointProtocols return (hostname, builder.build()) } } private extension TunnelKitProvider.SocketType { init?(protoString: String) { var str = protoString if str.hasSuffix("6") { str.removeLast() } self.init(rawValue: str.uppercased()) } } private extension NSRegularExpression { func enumerateComponents(in string: String, using block: ([String]) -> Void) { enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { (result, flags, stop) in guard let range = result?.range else { return } let match = (string as NSString).substring(with: range) let tokens = match.components(separatedBy: " ") block(tokens) } } func enumerateArguments(in string: String, using block: ([String]) -> Void) { enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { (result, flags, stop) in guard let range = result?.range else { return } let match = (string as NSString).substring(with: range) var tokens = match.components(separatedBy: " ") tokens.removeFirst() block(tokens) } } }