aboutsummaryrefslogtreecommitdiffstats
path: root/WireGuard/Coordinators/AppCoordinator.swift
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--WireGuard/Coordinators/AppCoordinator.swift468
1 files changed, 0 insertions, 468 deletions
diff --git a/WireGuard/Coordinators/AppCoordinator.swift b/WireGuard/Coordinators/AppCoordinator.swift
deleted file mode 100644
index aa20c16..0000000
--- a/WireGuard/Coordinators/AppCoordinator.swift
+++ /dev/null
@@ -1,468 +0,0 @@
-//
-// Copyright © 2018 WireGuard LLC. All rights reserved.
-//
-
-import Foundation
-import NetworkExtension
-import os.log
-import ZIPFoundation
-import PromiseKit
-
-import CoreData
-import BNRCoreDataStack
-
-import MobileCoreServices
-
-enum AppCoordinatorError: Error {
- case configImportError(msg: String)
-}
-
-extension UINavigationController: Identifyable {}
-
-let APPGROUP = "group.com.wireguard.ios"
-let VPNBUNDLE = "com.wireguard.ios.network-extension"
-
-class AppCoordinator: RootViewCoordinator { // swiftlint:disable:this type_body_length
-
- let persistentContainer = NSPersistentContainer(name: "WireGuard")
- let storyboard = UIStoryboard(name: "Main", bundle: nil)
- var providerManagers: [NETunnelProviderManager]?
- let documentPickerDelegateObject: AppDocumentPickerDelegate
-
- // MARK: - Properties
-
- var childCoordinators: [Coordinator] = []
-
- var rootViewController: UIViewController {
- return self.tunnelsTableViewController
- }
-
- var tunnelsTableViewController: TunnelsTableViewController!
-
- /// Window to manage
- let window: UIWindow
-
- let navigationController: UINavigationController = {
- let navController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(type: UINavigationController.self)
- return navController
- }()
-
- // MARK: - Init
- public init(window: UIWindow) {
- self.window = window
-
- self.window.rootViewController = self.navigationController
- self.window.makeKeyAndVisible()
-
- documentPickerDelegateObject = AppDocumentPickerDelegate()
- documentPickerDelegateObject.appCoordinator = self
-
- NotificationCenter.default.addObserver(self,
- selector: #selector(VPNStatusDidChange(notification:)),
- name: .NEVPNStatusDidChange,
- object: nil)
- }
-
- // MARK: - Functions
-
- /// Starts the coordinator
- public func start() {
- _ = refreshProviderManagers().then { () -> Promise<Void> in
- self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
- self.persistentContainer.loadPersistentStores { [weak self] (_, error) in
- if let error = error {
- os_log("Unable to load Persistent Store: %{public}@", log: Log.general, type: .error, error.localizedDescription)
- } else {
- DispatchQueue.main.async {
- //start
- if let tunnelsTableViewController = self?.storyboard.instantiateViewController(type: TunnelsTableViewController.self) {
- self?.tunnelsTableViewController = tunnelsTableViewController
- self?.tunnelsTableViewController.viewContext = self?.persistentContainer.viewContext
- self?.tunnelsTableViewController.delegate = self
- self?.navigationController.viewControllers = [tunnelsTableViewController]
- }
- }
- }
- }
- return Promise.value(())
- }
- }
-
- func refreshProviderManagers() -> Promise<Void> {
- return Promise { (resolver) in
- NETunnelProviderManager.loadAllFromPreferences { [weak self] (managers, error) in
- if let error = error {
- os_log("Unable to load provider managers: %{public}@", log: Log.general, type: .error, error.localizedDescription)
- }
- self?.providerManagers = managers
- resolver.fulfill(())
- }
- }
- }
-
- func importConfig(config: URL) throws {
- do {
- try importConfig(configString: try String(contentsOf: config), title: config.deletingPathExtension().lastPathComponent)
- } catch {
- throw AppCoordinatorError.configImportError(msg: "Failed")
- }
- }
-
- func importConfig(configString: String, title: String) throws {
- do {
- let addContext = persistentContainer.newBackgroundContext()
- let tunnel = try Tunnel.fromConfig(configString, context: addContext)
- tunnel.title = title
- addContext.saveContext()
- self.saveTunnel(tunnel)
- } catch {
- throw AppCoordinatorError.configImportError(msg: "Failed")
- }
- }
-
- func importConfigs(configZip: URL) throws {
- if let archive = Archive(url: configZip, accessMode: .read) {
- for entry in archive {
- var entryData = Data(capacity: 0)
- _ = try archive.extract(entry) { (data) in
- entryData.append(data)
- }
- if let config = String(data: entryData, encoding: .utf8) {
- try importConfig(configString: config, title: entry.path)
- }
- }
- }
- }
-
- func checkAndCleanConfigs() {
- _ = refreshProviderManagers().then { () -> Promise<Void> in
- guard let providerManagers = self.providerManagers else {
- return Promise.value(())
- }
- let tunnels = try Tunnel.allInContext(self.persistentContainer.viewContext)
- let tunnelIdentifiers = tunnels.compactMap {$0.tunnelIdentifier}
-
- let unknownManagers = providerManagers.filter {
- guard let prot = $0.protocolConfiguration as? NETunnelProviderProtocol else {
- return false
- }
- guard let candidateTunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
- return false
- }
-
- return !tunnelIdentifiers.contains(candidateTunnelIdentifier)
- }
-
- let deletionPromises = unknownManagers.map({ (manager) -> Promise<NETunnelProviderManager> in
- return Promise(resolver: { resolver in
- return manager.removeFromPreferences(completionHandler: { (error) in
- if let error = error {
- resolver.reject(error)
- } else {
- resolver.fulfill(manager)
- }
- })
- })
- })
-
- return when(resolved: deletionPromises).asVoid()
-
- }
- }
-
- // swiftlint:disable next function_body_length
- func exportConfigs(sourceView: UIView) {
- guard let path = FileManager.default
- .urls(for: .documentDirectory, in: .userDomainMask).first else {
- return
- }
- let saveFileURL = path.appendingPathComponent("wireguard-export.zip")
- do {
- try FileManager.default.removeItem(at: saveFileURL)
- } catch {
- os_log("Failed to delete file: %{public}@ : %{public}@", log: Log.general, type: .error, saveFileURL.absoluteString, error.localizedDescription)
- }
-
- guard let archive = Archive(url: saveFileURL, accessMode: .create) else {
- return
- }
-
- do {
- var tunnelsByTitle = [String: [Tunnel]]()
- let tunnels = try Tunnel.allInContext(persistentContainer.viewContext)
- tunnels.forEach {
- guard let title = $0.title ?? $0.tunnelIdentifier else {
- // there is always a tunnelidentifier.
- return
- }
- if let tunnels = tunnelsByTitle[title] {
- tunnelsByTitle[title] = tunnels + [$0]
- } else {
- tunnelsByTitle[title] = [$0]
- }
- }
-
- func addEntry(title: String, tunnel: Tunnel) throws {
- let data = tunnel.export().data(using: .utf8)!
- let byteCount: UInt32 = UInt32(data.count)
- try archive.addEntry(with: "\(title).conf", type: .file, uncompressedSize: byteCount, provider: { (position, size) -> Data in
- return data.subdata(in: position ..< size)
- })
- }
-
- try tunnelsByTitle.keys.forEach {
- if let tunnels = tunnelsByTitle[$0] {
- if tunnels.count == 1 {
- try addEntry(title: $0, tunnel: tunnels[0])
- } else {
- for (index, tunnel) in tunnels.enumerated() {
- try addEntry(title: $0 + "-\(index + 1)", tunnel: tunnel)
- }
- }
- }
- }
- } catch {
- os_log("Failed to create archive file: %{public}@ : %{public}@", log: Log.general, type: .error, saveFileURL.absoluteString, error.localizedDescription)
- return
- }
-
- let activityViewController = UIActivityViewController(
- activityItems: [saveFileURL],
- applicationActivities: nil)
- if let popoverPresentationController = activityViewController.popoverPresentationController {
- popoverPresentationController.sourceView = sourceView
- }
- navigationController.present(activityViewController, animated: true) {
- }
- }
-
- func exportConfig(tunnel: Tunnel, barButtonItem: UIBarButtonItem) {
- let exportString = tunnel.export()
-
- guard let path = FileManager.default
- .urls(for: .documentDirectory, in: .userDomainMask).first else {
- return
- }
- let saveFileURL = path.appendingPathComponent("/\(tunnel.title ?? "wireguard").conf")
- do {
- try exportString.write(to: saveFileURL, atomically: true, encoding: .utf8)
- } catch {
- os_log("Failed to export tunnel to: %{public}@", log: Log.general, type: .error, saveFileURL.absoluteString)
- return
- }
-
- let activityViewController = UIActivityViewController(
- activityItems: [saveFileURL],
- applicationActivities: nil)
- if let popoverPresentationController = activityViewController.popoverPresentationController {
- popoverPresentationController.barButtonItem = barButtonItem
- }
- self.navigationController.present(activityViewController, animated: true) {
- }
- }
-
- // MARK: - NEVPNManager handling
-
- @objc private func VPNStatusDidChange(notification: NSNotification) {
- guard let session = notification.object as? NETunnelProviderSession else {
- return
- }
-
- guard let prot = session.manager.protocolConfiguration as? NETunnelProviderProtocol else {
- return
- }
-
- guard let changedTunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
- return
- }
-
- providerManagers?.first(where: { (manager) -> Bool in
- guard let prot = manager.protocolConfiguration as? NETunnelProviderProtocol else {
- return false
- }
- guard let candidateTunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
- return false
- }
-
- return changedTunnelIdentifier == candidateTunnelIdentifier
-
- })?.loadFromPreferences(completionHandler: { [weak self] (_) in
- self?.tunnelsTableViewController.updateStatus(for: changedTunnelIdentifier)
- })
- }
-
- func showTunnelInfoViewController(tunnel: Tunnel, context: NSManagedObjectContext) {
- let tunnelInfoViewController = storyboard.instantiateViewController(type: TunnelInfoTableViewController.self)
-
- tunnelInfoViewController.configure(context: context, delegate: self, tunnel: tunnel)
-
- self.navigationController.pushViewController(tunnelInfoViewController, animated: true)
- }
-
- func showTunnelConfigurationViewController(tunnel: Tunnel?, context: NSManagedObjectContext) {
- let tunnelConfigurationViewController = storyboard.instantiateViewController(type: TunnelConfigurationTableViewController.self)
-
- tunnelConfigurationViewController.configure(context: context, delegate: self, tunnel: tunnel)
-
- self.navigationController.pushViewController(tunnelConfigurationViewController, animated: true)
- }
-
- func showSettings() {
- let settingsTableViewController = storyboard.instantiateViewController(type: SettingsTableViewController.self)
-
- settingsTableViewController.delegate = self
-
- self.navigationController.pushViewController(settingsTableViewController, animated: true)
- }
-
- public func showError(_ error: Error) {
- showAlert(title: NSLocalizedString("Error", comment: "Error alert title"), message: error.localizedDescription)
- }
-
- func connect(tunnel: Tunnel) {
- _ = refreshProviderManagers().then { () -> Promise<Void> in
- guard let manager = self.providerManager(for: tunnel) else {
- return Promise.value(())
- }
- let block = {
- switch manager.connection.status {
- case .invalid, .disconnected:
- os_log("connect tunnel: %{public}@", log: Log.general, type: .info, tunnel.description)
- // Should the manager be enabled?
-
- let manager = self.providerManager(for: tunnel)
- manager?.isEnabled = true
- manager?.saveToPreferences { (error) in
- if let error = error {
- os_log("error saving preferences: %{public}@", log: Log.general, type: .error, error.localizedDescription)
- return
- }
- os_log("saved preferences", log: Log.general, type: .info)
-
- let session = manager?.connection as! NETunnelProviderSession //swiftlint:disable:this force_cast
- do {
- try session.startTunnel()
- } catch let error {
- os_log("error starting tunnel: %{public}@", log: Log.general, type: .error, error.localizedDescription)
- }
- }
-
- default:
- break
- }
- }
-
- if manager.connection.status == .invalid {
- manager.loadFromPreferences { (_) in
- block()
- }
- } else {
- block()
- }
-
- return Promise.value(())
- }
- }
-
- func disconnect(tunnel: Tunnel) {
- _ = refreshProviderManagers().then { () -> Promise<Void> in
- let manager = self.providerManager(for: tunnel)!
- let block = {
- switch manager.connection.status {
- case .connected, .connecting:
- let manager = self.providerManager(for: tunnel)
- manager?.connection.stopVPNTunnel()
- default:
- break
- }
- }
-
- if manager.connection.status == .invalid {
- manager.loadFromPreferences { (_) in
- block()
- }
- } else {
- block()
- }
- return Promise.value(())
- }
- }
-
- private func showAlert(title: String, message: String) {
- let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
- alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "OK button"), style: .default))
- self.navigationController.present(alert, animated: true)
- }
-
- func providerManager(for tunnel: Tunnel) -> NETunnelProviderManager? {
- return self.providerManagers?.first {
- guard let prot = $0.protocolConfiguration as? NETunnelProviderProtocol else {
- return false
- }
- guard let tunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else {
- return false
- }
- return tunnelIdentifier == tunnel.tunnelIdentifier
- }
- }
-
- func extensionGoVersionInformation() -> Promise<String> {
- return Promise(resolver: { (resolver) in
- guard let session = self.providerManagers?.first(where: { $0.isEnabled })?.connection as? NETunnelProviderSession else {
- resolver.reject(GoVersionCoordinatorError.noEnabledSession)
- return
- }
- do {
- try session.sendProviderMessage(ExtensionMessage.requestVersion.data, responseHandler: { (data) in
- guard let data = data, let responseString = String(data: data, encoding: .utf8) else {
- resolver.reject(GoVersionCoordinatorError.noResponse)
- return
- }
- resolver.fulfill(responseString)
- })
- } catch {
- resolver.reject(error)
- }
- })
- }
-}
-
-class AppDocumentPickerDelegate: NSObject, UIDocumentPickerDelegate {
- weak var appCoordinator: AppCoordinator?
-
- func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
- if url.pathExtension == "conf" {
- do {
- try appCoordinator?.importConfig(config: url)
- } catch {
- os_log("Unable to import config: %{public}@", log: Log.general, type: .error, url.absoluteString)
- }
- } else if url.pathExtension == "zip" {
- do {
- try appCoordinator?.importConfigs(configZip: url)
- } catch {
- os_log("Unable to import config: %{public}@", log: Log.general, type: .error, url.absoluteString)
- }
- }
-
- }
-}
-
-extension NEVPNStatus {
- var statusDescription: String {
- switch self {
- case .connected:
- return "Connected"
- case .connecting:
- return "Connecting"
- case .disconnected:
- return "Disconnected"
- case .disconnecting:
- return "Disconnecting"
- case .invalid:
- return "Invalid"
- case .reasserting:
- return "Reasserting"
- }
- }
-}