582 lines
18 KiB
Swift
582 lines
18 KiB
Swift
import Foundation
|
||
|
||
final class PrivadoHiveClient: NSObject {
|
||
|
||
enum FirewallError: Error {
|
||
case enable(Error)
|
||
case activate(Error)
|
||
case deactivate(Error)
|
||
case state(Error)
|
||
}
|
||
|
||
// MARK: - XPCСommonClient
|
||
|
||
private(set) var state: XPCClientState = .undefined {
|
||
didSet {
|
||
print("self.state: \(self.state)")
|
||
guard self.state != oldValue else { return }
|
||
let state = self.state
|
||
if self.state == .requireUpdate {
|
||
self.reset()
|
||
}
|
||
|
||
self.stateEmitter.invoke(state)
|
||
}
|
||
}
|
||
|
||
let stateEmitter = Emitter<XPCClientState>()
|
||
|
||
// MARK: - Properties
|
||
|
||
private var connection: NSXPCConnection?
|
||
|
||
private var isValidConnection: Bool { self.connection.isExist }
|
||
|
||
private var installed = false
|
||
|
||
weak var consoleClient: XPCСonsoleClient?
|
||
|
||
// MARK: - Static
|
||
|
||
private static var instance: PrivadoHiveClient?
|
||
|
||
static func shared() -> PrivadoHiveClient {
|
||
if let instance = self.instance {
|
||
return instance
|
||
}
|
||
|
||
let instance = PrivadoHiveClient()
|
||
self.instance = instance
|
||
return instance
|
||
}
|
||
|
||
// MARK: - Init
|
||
|
||
private override init() {
|
||
|
||
super.init()
|
||
|
||
self.createConnection()
|
||
}
|
||
|
||
// MARK: - XPCСommonClient
|
||
|
||
func install(installer: @escaping (String) -> Bool) -> Future<XPCСommonClient> {
|
||
|
||
self.state = .installing
|
||
|
||
return Future<XPCСommonClient> { completion in
|
||
|
||
let success = installer(PrivadoConstants.Hive.bundle)
|
||
self.installed = success
|
||
|
||
let result: Result<XPCСommonClient, Error> = success ? .success(self) : .failure(FutureError.none)
|
||
completion(result)
|
||
}
|
||
}
|
||
|
||
func resume() {
|
||
guard let connection = self.connection else {
|
||
self.createConnection()
|
||
return
|
||
}
|
||
|
||
connection.resume()
|
||
}
|
||
|
||
// MARK: - Private
|
||
|
||
private func reset() {
|
||
guard self.state == .requireUpdate else { return }
|
||
self.connection?.invalidate()
|
||
self.connection = nil
|
||
}
|
||
|
||
private func createConnection() {
|
||
|
||
guard !self.connection.isExist else { return }
|
||
|
||
let connection = NSXPCConnection(machServiceName: PrivadoConstants.Hive.bundle, options: .privileged)
|
||
|
||
connection.exportedInterface = NSXPCInterface(with: IntercommConsoleClientType.self)
|
||
connection.exportedObject = self
|
||
connection.remoteObjectInterface = NSXPCInterface(with: IntercommHiveType.self)
|
||
|
||
connection.invalidationHandler = self.connectionInvalidationHandler
|
||
connection.interruptionHandler = self.connetionInterruptionHandler
|
||
|
||
self.connection = connection
|
||
|
||
connection.resume()
|
||
}
|
||
|
||
/*
|
||
func ping(completion: @escaping () -> Void)
|
||
|
||
func connect(type: Int, configuration: HiveVPNConfiguration, completion: @escaping (Error?) -> Void)
|
||
func disconnect(completion: @escaping (HiveVPNStatus) -> Void) // sudo killall openvpn
|
||
*/
|
||
|
||
private func intercomm() -> Future<IntercommHiveType> {
|
||
|
||
return Future<IntercommHiveType> { [weak self] completion in
|
||
|
||
guard let self = self else {
|
||
completion(.failure(XPCСommonClientError.unowned))
|
||
return
|
||
}
|
||
|
||
guard let connection = self.connection
|
||
, let intercomm = connection.remoteObjectProxyWithErrorHandler({ error in
|
||
completion(.failure(XPCСommonClientError.proxy(error)))
|
||
}) as? IntercommHiveType else {
|
||
completion(.failure(XPCСommonClientError.incorrectType))
|
||
return
|
||
}
|
||
completion(.success(intercomm))
|
||
}.onResult { result in
|
||
|
||
switch result {
|
||
case let .failure(hiveError):
|
||
switch hiveError {
|
||
case XPCСommonClientError.unowned:
|
||
break
|
||
case let XPCСommonClientError.proxy(error):
|
||
CometLogger.shared.record(error: error)
|
||
if self.state == .initializing {
|
||
self.state = .requireUpdate
|
||
}
|
||
case XPCСommonClientError.incorrectType:
|
||
if self.state == .initializing {
|
||
self.state = .requireUpdate
|
||
}
|
||
default:
|
||
break
|
||
}
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
private func checkCallAllowed() -> Future<IntercommHiveType> {
|
||
|
||
return Future<Void> { [weak self] completion in
|
||
|
||
guard let self = self else {
|
||
completion(.failure(XPCСommonClientError.unowned))
|
||
return
|
||
}
|
||
|
||
guard self.state == .connected else {
|
||
completion(.failure(XPCСommonClientError.notConnected))
|
||
return
|
||
/*throw NSError(domain: PrivadoConstants.Hive.error,
|
||
code: PrivadoConstants.Hive.ErrorCode.xpcNotConnected,
|
||
userInfo: nil)*/
|
||
}
|
||
|
||
completion(.success( () ))
|
||
|
||
}.flatMap { [weak self] _ -> Future<IntercommHiveType> in
|
||
|
||
guard let self = self else { return Future(error: XPCСommonClientError.unowned) }
|
||
return self.intercomm()
|
||
}.onFailure { [weak self] _ in
|
||
|
||
self?.state = .undefined
|
||
/*throw NSError(domain: PrivadoConstants.Hive.error,
|
||
code: PrivadoConstants.Hive.ErrorCode.xpcEmptyRemoteObject,
|
||
userInfo: nil)*/
|
||
}
|
||
}
|
||
|
||
private func connetionInterruptionHandler() {
|
||
guard self.state != .requireUpdate else { return }
|
||
|
||
if self.state == .initializing
|
||
, !self.installed {
|
||
self.state = .requireUpdate
|
||
return
|
||
}
|
||
self.state = .interrupted
|
||
// NSLog("[SMJBS]: \(#function)")
|
||
}
|
||
|
||
private func connectionInvalidationHandler() {
|
||
|
||
if self.installed {
|
||
// Think what I must todo here
|
||
return
|
||
}
|
||
|
||
if self.state == .undefined
|
||
, !self.installed {
|
||
self.state = .requireUpdate
|
||
return
|
||
}
|
||
|
||
guard self.state != .requireUpdate else { return }
|
||
|
||
if self.state == .initializing {
|
||
self.state = .requireUpdate
|
||
return
|
||
}
|
||
|
||
self.state = .invalid
|
||
// NSLog("[SMJBS]: \(#function)")
|
||
}
|
||
}
|
||
|
||
// MARK: - XPCSystemClient
|
||
|
||
extension PrivadoHiveClient: XPCSystemClient {
|
||
|
||
func checkVersion() -> Future<Void> {
|
||
|
||
return Future<Void> { [weak self] completion in
|
||
|
||
guard let self = self else {
|
||
completion(.failure(XPCСommonClientError.unowned))
|
||
return
|
||
}
|
||
|
||
if self.state == .initializing {
|
||
completion(.failure(XPCСommonClientError.alreadyConnected))
|
||
return
|
||
}
|
||
|
||
completion(.success(()))
|
||
|
||
}.flatMap { [weak self] _ -> Future<IntercommHiveType> in
|
||
guard let self = self else { return Future(error: XPCСommonClientError.unowned) }
|
||
return self.intercomm()
|
||
}.onFailure { [weak self] _ in
|
||
if let self = self, self.isValidConnection { self.state = .undefined }
|
||
}.onSuccess { [weak self] _ in
|
||
if let self = self, self.isValidConnection { self.state = .initializing }
|
||
}.flatMap { [weak self] intercomm in
|
||
|
||
if !(self?.isValidConnection ?? false) { return Future(value: ()) }
|
||
|
||
return Future<Void> { completion in
|
||
|
||
intercomm.version { [weak self] (shortVersion, bundleVersion) in
|
||
|
||
if let self = self {
|
||
let state: XPCClientState = Environment.shortVersion == shortVersion && Environment.bundleVersion == bundleVersion
|
||
? .connected
|
||
: .requireUpdate
|
||
|
||
CometLogger.shared.vpnEvent(type: PrivadoConstants.Event.hive,
|
||
userInfo: [
|
||
PrivadoConstants.Event.Attributes.shortVersion: Environment.shortVersion,
|
||
PrivadoConstants.Event.Attributes.bundleVersion: Environment.bundleVersion
|
||
],
|
||
secured: nil)
|
||
|
||
self.state = state
|
||
}
|
||
|
||
completion(.success(()))
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
func flushDNS() -> Future<Bool> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Bool> in
|
||
|
||
return Future<Bool> { completion in
|
||
intercomm.flushDNS { completion(.success($0)) }
|
||
}
|
||
}
|
||
}
|
||
|
||
func resolveDNS(name: String) -> Future<String> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<String> in
|
||
|
||
return Future<String> { completion in
|
||
intercomm.resolveDNS(name: name) { completion(.success($0)) }
|
||
}
|
||
}.recover {
|
||
return "127.0.0.1"
|
||
}
|
||
}
|
||
|
||
func ifconfig() -> Future<String> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<String> in
|
||
|
||
return Future<String> { completion in
|
||
intercomm.ifconfig { completion(.success($0)) }
|
||
}
|
||
}
|
||
}
|
||
|
||
func changeDNS(servers: [String], hardware: String) -> Future<Bool> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Bool> in
|
||
|
||
return Future<Bool> { completion in
|
||
intercomm.changeDNS(servers: servers, hardware: hardware) { completion(.success($0)) }
|
||
}
|
||
}
|
||
}
|
||
|
||
func currentDNS(hardware: [String]) -> Future<[String: [String]]> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<[String: [String]]> in
|
||
|
||
return Future<[String: [String]]> { completion in
|
||
intercomm.currentDNS(hardware: hardware) { completion(.success($0)) }
|
||
}
|
||
}
|
||
}
|
||
|
||
func hardwarePorts() -> Future<[HardwarePort]> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<[HardwarePort]> in
|
||
|
||
return Future<[HardwarePort]> { completion in
|
||
intercomm.hardwarePorts { completion(.success($0)) }
|
||
}
|
||
}
|
||
}
|
||
|
||
func removeUninstaller() -> Future<Void> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Void> in
|
||
|
||
return Future<Void> { completion in
|
||
intercomm.removeUninstaller {
|
||
completion(.success(()))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - XPCOpenVPNClient
|
||
|
||
extension PrivadoHiveClient: XPCOpenVPNClient {
|
||
|
||
func connect(type: Int, configuration: OpenVPNConfiguration) -> Future<Void> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Void> in
|
||
|
||
return Future<Void> { completion in
|
||
|
||
intercomm.connect(type: type,
|
||
ovpnPath: configuration.ovpnPath,
|
||
configPath: configuration.configPath,
|
||
username: configuration.username,
|
||
password: configuration.password) { error in
|
||
|
||
guard let error = error else {
|
||
completion(.success(()))
|
||
return
|
||
}
|
||
|
||
completion(.failure(XPCСommonClientError.proxy(error)))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func disconnect() -> Future<Void> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Void> in
|
||
|
||
return Future<Void> { completion in
|
||
|
||
intercomm.disconnect { error in
|
||
|
||
guard let error = error else {
|
||
completion(.success(()))
|
||
return
|
||
}
|
||
|
||
completion(.failure(XPCСommonClientError.proxy(error)))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func isConnected() -> Future<Bool> {
|
||
|
||
return Future<Void> { [weak self] completion in
|
||
|
||
guard let self = self else {
|
||
completion(.failure(XPCСommonClientError.unowned))
|
||
return
|
||
}
|
||
|
||
guard self.state == .connected else {
|
||
completion(.failure(XPCСommonClientError.notConnected))
|
||
return
|
||
}
|
||
}.flatMap { [weak self] _ -> Future<IntercommHiveType> in
|
||
|
||
guard let self = self else {
|
||
return Future(error: XPCСommonClientError.unowned)
|
||
}
|
||
|
||
return self.intercomm()
|
||
}.onFailure { [weak self] _ in
|
||
if let self = self { self.state = .undefined }
|
||
}.flatMap { intercomm -> Future<Bool> in
|
||
|
||
return Future<Bool> { completion in
|
||
intercomm.isConnected { connected in
|
||
completion(.success(connected))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - XPCFirewallClient
|
||
|
||
extension PrivadoHiveClient: XPCFirewallClient {
|
||
|
||
func firewallState() -> Future<Bool> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Bool> in
|
||
|
||
return Future<Bool> { completion in
|
||
|
||
intercomm.firewallState { success, error in
|
||
|
||
guard let error = error else {
|
||
completion(.success(success))
|
||
return
|
||
}
|
||
|
||
completion(.failure(FirewallError.state(error)))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func firewallEnable(_ enable: Bool) -> Future<Bool> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Bool> in
|
||
|
||
return Future<Bool> { completion in
|
||
|
||
intercomm.firewallEnable(enable) { success, error in
|
||
|
||
guard let error = error else {
|
||
completion(.success(success))
|
||
return
|
||
}
|
||
|
||
completion(.failure(FirewallError.enable(error)))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func firewallActivate(rulesPath: String, validate: Bool) -> Future<Bool> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Bool> in
|
||
|
||
return Future<Bool> { completion in
|
||
|
||
intercomm.firewallActivate(rulesPath: rulesPath, validate: validate) { success, error in
|
||
|
||
guard let error = error else {
|
||
completion(.success(success))
|
||
return
|
||
}
|
||
|
||
completion(.failure(FirewallError.activate(error)))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func firewallDeactivate() -> Future<Bool> {
|
||
|
||
return self
|
||
.checkCallAllowed()
|
||
.flatMap { intercomm -> Future<Bool> in
|
||
|
||
return Future<Bool> { completion in
|
||
|
||
intercomm.firewallDeactivate { success, error in
|
||
|
||
guard let error = error else {
|
||
completion(.success(success))
|
||
return
|
||
}
|
||
|
||
completion(.failure(FirewallError.deactivate(error)))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func firewallRulesSave() -> Future<Void> {
|
||
return Future(value: ())
|
||
}
|
||
|
||
func firewallRulesRestore() -> Future<Void> {
|
||
return Future(value: ())
|
||
}
|
||
}
|
||
|
||
// MARK: - IntercommConsoleClientType
|
||
|
||
extension PrivadoHiveClient: IntercommConsoleClientType {
|
||
|
||
// MARK: - IntercommConsoleClientType
|
||
|
||
func log(message: String, completion: @escaping () -> Void) {
|
||
print(message)
|
||
CometLogger.shared.vpnEvent(type: PrivadoConstants.Event.Vpn.ovpn, userInfo: nil, secured: message)
|
||
}
|
||
|
||
func error(message: String, errNo: Int, completion: @escaping () -> Void) {
|
||
print(message)
|
||
CometLogger.shared.vpnEvent(type: PrivadoConstants.Event.error,
|
||
userInfo: [PrivadoConstants.Event.Attributes.value: errNo],
|
||
secured: message)
|
||
completion()
|
||
}
|
||
|
||
func console(message: String, completion: @escaping () -> Void) {
|
||
guard let consoleClient = self.consoleClient else { return }
|
||
consoleClient.console(message: message)
|
||
completion()
|
||
}
|
||
}
|