Export: Exporting config files
Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class WgQuickConfigFileWriter {
|
||||
static func writeConfigFile(from tc: TunnelConfiguration) -> Data? {
|
||||
let interface = tc.interface
|
||||
var output = "[Interface]\n"
|
||||
output.append("PrivateKey=\(interface.privateKey.base64EncodedString())\n")
|
||||
if let listenPort = interface.listenPort {
|
||||
output.append("ListenPort=\(listenPort)\n")
|
||||
}
|
||||
if (!interface.addresses.isEmpty) {
|
||||
let addressString = interface.addresses.map { $0.stringRepresentation() }.joined(separator: ", ")
|
||||
output.append("Address=\(addressString)\n")
|
||||
}
|
||||
if (!interface.dns.isEmpty) {
|
||||
let dnsString = interface.dns.map { $0.stringRepresentation() }.joined(separator: ", ")
|
||||
output.append("DNS=\(dnsString)\n")
|
||||
}
|
||||
if let mtu = interface.mtu {
|
||||
output.append("MTU=\(mtu)\n")
|
||||
}
|
||||
output.append("\n")
|
||||
|
||||
for peer in tc.peers {
|
||||
output.append("[Peers]\n")
|
||||
output.append("PublicKey=\(peer.publicKey.base64EncodedString())\n")
|
||||
if let preSharedKey = peer.preSharedKey {
|
||||
output.append("PresharedKey=\(preSharedKey.base64EncodedString())\n")
|
||||
}
|
||||
if (!peer.allowedIPs.isEmpty) {
|
||||
let allowedIPsString = peer.allowedIPs.map { $0.stringRepresentation() }.joined(separator: ", ")
|
||||
output.append("AllowedIPs=\(allowedIPsString)\n")
|
||||
}
|
||||
if let endpoint = peer.endpoint {
|
||||
output.append("Endpoint=\(endpoint.stringRepresentation())\n")
|
||||
}
|
||||
if let persistentKeepAlive = peer.persistentKeepAlive {
|
||||
output.append("PersistentKeepalive=\(persistentKeepAlive)\n")
|
||||
}
|
||||
output.append("\n")
|
||||
}
|
||||
|
||||
return output.data(using: .utf8)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Copyright © 2018 WireGuard LLC. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
class SettingsTableViewController: UITableViewController {
|
||||
|
||||
@@ -16,7 +17,10 @@ class SettingsTableViewController: UITableViewController {
|
||||
[.exportZipArchive]
|
||||
]
|
||||
|
||||
init() {
|
||||
let tunnelsManager: TunnelsManager?
|
||||
|
||||
init(tunnelsManager: TunnelsManager?) {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
super.init(style: .grouped)
|
||||
}
|
||||
|
||||
@@ -39,6 +43,65 @@ class SettingsTableViewController: UITableViewController {
|
||||
@objc func doneTapped() {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func exportConfigurationsAsZipFile(sourceView: UIView) {
|
||||
guard let tunnelsManager = tunnelsManager, tunnelsManager.numberOfTunnels() > 0 else {
|
||||
showErrorAlert(title: "Nothing to export", message: "There are no tunnel configurations to export")
|
||||
return
|
||||
}
|
||||
var inputsToArchiver: [(fileName: String, contents: Data)] = []
|
||||
var usedNames: Set<String> = []
|
||||
for i in 0 ..< tunnelsManager.numberOfTunnels() {
|
||||
guard let tunnelConfiguration = tunnelsManager.tunnel(at: i).tunnelConfiguration() else { continue }
|
||||
if let contents = WgQuickConfigFileWriter.writeConfigFile(from: tunnelConfiguration) {
|
||||
let name = tunnelConfiguration.interface.name
|
||||
var nameToCheck = name
|
||||
var i = 0
|
||||
while (usedNames.contains(nameToCheck)) {
|
||||
i = i + 1
|
||||
nameToCheck = "\(name)\(i)"
|
||||
}
|
||||
usedNames.insert(nameToCheck)
|
||||
inputsToArchiver.append((fileName: "\(nameToCheck).conf", contents: contents))
|
||||
}
|
||||
}
|
||||
|
||||
// Based on file export code by Jeroen Leenarts <jeroen.leenarts@gmail.com> in commit ca35168
|
||||
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
return
|
||||
}
|
||||
let destinationURL = destinationDir.appendingPathComponent("wireguard-export.zip")
|
||||
do {
|
||||
try FileManager.default.removeItem(at: destinationURL)
|
||||
} catch {
|
||||
os_log("Failed to delete file: %{public}@ : %{public}@", log: OSLog.default, type: .error, destinationURL.absoluteString, error.localizedDescription)
|
||||
}
|
||||
|
||||
var ok = false
|
||||
do {
|
||||
try ZipArchive.archive(inputs: inputsToArchiver, to: destinationURL)
|
||||
ok = true
|
||||
} catch {
|
||||
os_log("Failed to create archive: %{public}@ : %{public}@", log: OSLog.default, type: .error, destinationURL.absoluteString)
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
|
||||
// popoverPresentationController shall be non-nil on the iPad
|
||||
activityVC.popoverPresentationController?.sourceView = sourceView
|
||||
present(activityVC, animated: true)
|
||||
} else {
|
||||
showErrorAlert(title: "Could not export", message: "There was an error creating the tunnel configuration archive")
|
||||
}
|
||||
}
|
||||
|
||||
func showErrorAlert(title: String, message: String) {
|
||||
let okAction = UIAlertAction(title: "Ok", style: .default)
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alert.addAction(okAction)
|
||||
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
@@ -79,6 +142,9 @@ extension SettingsTableViewController {
|
||||
assert(field == .exportZipArchive)
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelSettingsTableViewButtonCell.id, for: indexPath) as! TunnelSettingsTableViewButtonCell
|
||||
cell.buttonText = field.rawValue
|
||||
cell.onTapped = { [weak self] in
|
||||
self?.exportConfigurationsAsZipFile(sourceView: cell.button)
|
||||
}
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class TunnelsListTableViewController: UITableViewController {
|
||||
}
|
||||
|
||||
@objc func settingsButtonTapped(sender: UIBarButtonItem!) {
|
||||
let settingsVC = SettingsTableViewController()
|
||||
let settingsVC = SettingsTableViewController(tunnelsManager: tunnelsManager)
|
||||
let settingsNC = UINavigationController(rootViewController: settingsVC)
|
||||
settingsNC.modalPresentationStyle = .formSheet
|
||||
self.present(settingsNC, animated: true)
|
||||
|
||||
@@ -5,11 +5,29 @@ import Foundation
|
||||
|
||||
enum ZipArchiveError: Error {
|
||||
case cantOpenInputZipFile
|
||||
case cantOpenOutputZipFileForWriting
|
||||
case badArchive
|
||||
}
|
||||
|
||||
class ZipArchive {
|
||||
|
||||
static func archive(inputs: [(fileName: String, contents: Data)], to destinationURL: URL) throws {
|
||||
let destinationPath = destinationURL.path
|
||||
guard let zipFile = zipOpen(destinationPath, APPEND_STATUS_CREATE) else {
|
||||
throw ZipArchiveError.cantOpenOutputZipFileForWriting
|
||||
}
|
||||
for input in inputs {
|
||||
let fileName = input.fileName
|
||||
let contents = input.contents
|
||||
zipOpenNewFileInZip(zipFile, fileName.cString(using: .utf8), nil, nil, 0, nil, 0, nil, Z_DEFLATED, Z_DEFAULT_COMPRESSION)
|
||||
contents.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) -> Void in
|
||||
zipWriteInFileInZip(zipFile, UnsafeRawPointer(ptr), UInt32(contents.count))
|
||||
}
|
||||
zipCloseFileInZip(zipFile)
|
||||
}
|
||||
zipClose(zipFile, nil)
|
||||
}
|
||||
|
||||
static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileName: String, contents: Data)] {
|
||||
|
||||
var results: [(fileName: String, contents: Data)] = []
|
||||
|
||||
Reference in New Issue
Block a user