Files

582 lines
18 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}
}