From aeef5261dd01dd0926084d7889679513c77c2da7 Mon Sep 17 00:00:00 2001 From: yicheng <11733500+yichengchen@users.noreply.github.com> Date: Wed, 22 Apr 2020 11:22:24 +0800 Subject: [PATCH] feature: local auth & backup way to install proxy helper --- ClashX.xcodeproj/project.pbxproj | 8 +- ClashX/Extensions/DateFormatter+.swift | 2 +- .../PrivilegedHelperManager+Legacy.swift | 76 +++++++++++++++++++ .../Managers/PrivilegedHelperManager.swift | 73 +++++++++--------- .../General/Managers/SystemProxyManager.swift | 10 +-- .../en.lproj/Localizable.strings | 3 + .../zh-Hans.lproj/Localizable.strings | 3 + ProxyConfigHelper/Helper-Info.plist | 2 +- ProxyConfigHelper/ProxyConfigHelper.m | 19 +---- .../ProxyConfigRemoteProcessProtocol.h | 4 +- ProxyConfigHelper/ProxySettingTool.h | 1 - ProxyConfigHelper/ProxySettingTool.m | 25 ++++++ 12 files changed, 158 insertions(+), 68 deletions(-) create mode 100644 ClashX/General/Managers/PrivilegedHelperManager+Legacy.swift diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index d61dc39c9..70d8d6479 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 49ABB749236B0F9E00535CD7 /* UnsafePointer+bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ABB748236B0F9E00535CD7 /* UnsafePointer+bridge.swift */; }; 49B1086A216A356D0064FFCE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B10869216A356D0064FFCE /* String+Extension.swift */; }; 49B4575D244F4A2A00463C39 /* PrivilegedHelperManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B4575C244F4A2A00463C39 /* PrivilegedHelperManager.swift */; }; + 49B4575F244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B4575E244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift */; }; 49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49BC061B212931F4005A0FE7 /* AboutViewController.swift */; }; 49C9EF64223E78F5005D8B6A /* ClashProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C9EF63223E78F5005D8B6A /* ClashProxy.swift */; }; 49CF3B2120CD7463001EBF94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B2020CD7463001EBF94 /* AppDelegate.swift */; }; @@ -153,6 +154,7 @@ 49ABB748236B0F9E00535CD7 /* UnsafePointer+bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnsafePointer+bridge.swift"; sourceTree = ""; }; 49B10869216A356D0064FFCE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 49B4575C244F4A2A00463C39 /* PrivilegedHelperManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivilegedHelperManager.swift; sourceTree = ""; }; + 49B4575E244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PrivilegedHelperManager+Legacy.swift"; sourceTree = ""; }; 49BC061B212931F4005A0FE7 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 49C9EF63223E78F5005D8B6A /* ClashProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = ""; }; 49CF3B1D20CD7463001EBF94 /* ClashX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ClashX.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -267,6 +269,7 @@ F977FAAD23669D6400C17F1F /* ConnectionManager.swift */, F9E754CF239CC21F00CEE7CC /* WebPortalManager.swift */, 49B4575C244F4A2A00463C39 /* PrivilegedHelperManager.swift */, + 49B4575E244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift */, ); path = Managers; sourceTree = ""; @@ -680,6 +683,7 @@ 499A485822ED715200F6C675 /* RemoteConfigModel.swift in Sources */, 49D176AB23575BB20093DD7B /* ProxyGroupMenuItemView.swift in Sources */, 49B4575D244F4A2A00463C39 /* PrivilegedHelperManager.swift in Sources */, + 49B4575F244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift in Sources */, 4966E9E32118153A00A391FB /* NSUserNotificationCenter+Extension.swift in Sources */, F9E754D2239CC28D00CEE7CC /* NSAlert+Extension.swift in Sources */, 499976C821359F0400E7BF83 /* ClashWebViewContoller.swift in Sources */, @@ -948,7 +952,7 @@ ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.10; - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -978,7 +982,7 @@ ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.10; - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.5; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-sectcreate", diff --git a/ClashX/Extensions/DateFormatter+.swift b/ClashX/Extensions/DateFormatter+.swift index be0de0e1a..4a970c2fe 100644 --- a/ClashX/Extensions/DateFormatter+.swift +++ b/ClashX/Extensions/DateFormatter+.swift @@ -2,7 +2,7 @@ // DateFormatter+.swift // ClashX // -// Created by Etan Chen on 2019/12/14. +// Created by yicheng on 2019/12/14. // Copyright © 2019 west2online. All rights reserved. // diff --git a/ClashX/General/Managers/PrivilegedHelperManager+Legacy.swift b/ClashX/General/Managers/PrivilegedHelperManager+Legacy.swift new file mode 100644 index 000000000..31b908617 --- /dev/null +++ b/ClashX/General/Managers/PrivilegedHelperManager+Legacy.swift @@ -0,0 +1,76 @@ +// +// PrivilegedHelperManager+Legacy.swift +// ClashX +// +// Created by yicheng 2020/4/22. +// Copyright © 2020 west2online. All rights reserved. +// + +import Cocoa + +extension PrivilegedHelperManager { + func getInstallScript() -> String { + let appPath = Bundle.main.bundlePath + let bash = """ + #!/bin/bash + set -e + + plistPath=/Library/LaunchDaemons/\(PrivilegedHelperManager.machServiceName).plist + rm -rf /Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName) + if [ -e ${plistPath} ]; then + launchctl unload -w ${plistPath} + rm ${plistPath} + fi + launchctl remove \(PrivilegedHelperManager.machServiceName) || true + + rm -f /Library/PrivilegedHelperTools/com.west2online.ClashX.ProxyConfigHelper + cp \(appPath)/Contents/Library/LaunchServices/\(PrivilegedHelperManager.machServiceName) /Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName) + + echo ' + + + + + Label + \(PrivilegedHelperManager.machServiceName) + MachServices + + \(PrivilegedHelperManager.machServiceName) + + + Program + /Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName) + ProgramArguments + + /Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName) + + + + ' > ${plistPath} + + launchctl load -w ${plistPath} + """ + return bash + } + + func legacyInstallHelper() { + defer { + resetConnection() + } + let script = getInstallScript() + let tmpPath = FileManager.default.temporaryDirectory.appendingPathComponent(NSUUID().uuidString).appendingPathExtension("sh") + do { + try script.write(to: tmpPath, atomically: true, encoding: .utf8) + let appleScriptStr = "do shell script \"bash \(tmpPath.path) \" with administrator privileges" + let appleScript = NSAppleScript(source: appleScriptStr) + var dict: NSDictionary? + if let _ = appleScript?.executeAndReturnError(&dict) { + return + } else { + Logger.log("apple script fail: \(String(describing: dict))") + } + } catch let err { + Logger.log("legacyInstallHelper create script fail: \(err)") + } + } +} diff --git a/ClashX/General/Managers/PrivilegedHelperManager.swift b/ClashX/General/Managers/PrivilegedHelperManager.swift index 9ea6b224b..b07312d89 100644 --- a/ClashX/General/Managers/PrivilegedHelperManager.swift +++ b/ClashX/General/Managers/PrivilegedHelperManager.swift @@ -11,6 +11,8 @@ import ServiceManagement class PrivilegedHelperManager { private var cancelInstallCheck = false + private var useLecgyInstall = false + private var authRef: AuthorizationRef? private var connection: NSXPCConnection? private var _helper: ProxyConfigRemoteProcessProtocol? @@ -38,6 +40,12 @@ class PrivilegedHelperManager { } } + func resetConnection() { + connection?.invalidate() + connection = nil + _helper = nil + } + private func initAuthorizationRef() { // Create an empty AuthorizationRef let status = AuthorizationCreate(nil, nil, AuthorizationFlags(), &authRef) @@ -52,9 +60,7 @@ class PrivilegedHelperManager { Logger.log("installHelperDaemon", level: .info) defer { - connection?.invalidate() - connection = nil - _helper = nil + resetConnection() } // Create authorization reference for the user @@ -69,7 +75,7 @@ class PrivilegedHelperManager { // Ask user for the admin privileges to install the var authItem = AuthorizationItem(name: (kSMRightBlessPrivilegedHelper as NSString).utf8String!, valueLength: 0, value: nil, flags: 0) - var authRights = withUnsafeMutablePointer(to: &authItem) { pointer in + var authRights = withUnsafeMutablePointer(to: &authItem) { pointer in AuthorizationRights(count: 1, items: pointer) } let flags: AuthorizationFlags = [[], .interactionAllowed, .extendRights, .preAuthorize] @@ -98,37 +104,6 @@ class PrivilegedHelperManager { return .success } - func authData() -> Data? { - guard let authRef = authRef else { return nil } - var authRefExtForm = AuthorizationExternalForm() - - // Make an external form of the AuthorizationRef - var status = AuthorizationMakeExternalForm(authRef, &authRefExtForm) - if status != OSStatus(errAuthorizationSuccess) { - Logger.log("AppviewController: AuthorizationMakeExternalForm failed", level: .error) - return nil - } - - // Add all or update required authorization right definition to the authorization database - var currentRight: CFDictionary? - - // Try to get the authorization right definition from the database - status = AuthorizationRightGet(AppAuthorizationRights.rightName.utf8String!, ¤tRight) - - if status == errAuthorizationDenied { - let defaultRules = AppAuthorizationRights.rightDefaultRule - status = AuthorizationRightSet(authRef, - AppAuthorizationRights.rightName.utf8String!, - defaultRules as CFDictionary, - AppAuthorizationRights.rightDescription, - nil, "Common" as CFString) - } - - // We need to put the AuthorizationRef to a form that can be passed through inter process call - let authData = NSData(bytes: &authRefExtForm, length: kAuthorizationExternalFormLength) - return authData as Data - } - private func helperConnection() -> NSXPCConnection? { // Check that the connection is valid before trying to do an inter process call to helper if connection == nil { @@ -193,11 +168,18 @@ extension PrivilegedHelperManager { return } + if useLecgyInstall { + useLecgyInstall = false + legacyInstallHelper() + return + } + let result = installHelperDaemon() if case .success = result { return } result.alertAction() + useLecgyInstall = result.shouldRetryLegacyWay() NSAlert.alert(with: result.alertContent) } @@ -205,7 +187,11 @@ extension PrivilegedHelperManager { let alert = NSAlert() alert.messageText = NSLocalizedString("ClashX needs to install/update a helper tool with administrator privileges to set system proxy quickly.If not helper tool installed, ClashX won't be able to set your system proxy", comment: "") alert.alertStyle = .warning - alert.addButton(withTitle: NSLocalizedString("Install", comment: "")) + if useLecgyInstall { + alert.addButton(withTitle: NSLocalizedString("Lecgy Install", comment: "")) + } else { + alert.addButton(withTitle: NSLocalizedString("Install", comment: "")) + } alert.addButton(withTitle: NSLocalizedString("Quit", comment: "")) alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) switch alert.runModal() { @@ -260,6 +246,21 @@ fileprivate enum DaemonInstallResult { } } + func shouldRetryLegacyWay() -> Bool { + switch self { + case .success: return false + case let .blessError(code): + switch code { + case kSMErrorJobMustBeEnabled: + return false + default: + return true + } + default: + return true + } + } + func alertAction() { switch self { case let .blessError(code): diff --git a/ClashX/General/Managers/SystemProxyManager.swift b/ClashX/General/Managers/SystemProxyManager.swift index e58def8c1..3a17d9c55 100644 --- a/ClashX/General/Managers/SystemProxyManager.swift +++ b/ClashX/General/Managers/SystemProxyManager.swift @@ -34,8 +34,6 @@ class SystemProxyManager: NSObject { PrivilegedHelperManager.shared.helper() } - private let authData = PrivilegedHelperManager.shared.authData - func saveProxy() { guard !disableRestoreProxy else { return } Logger.log("saveProxy", level: .debug) @@ -59,7 +57,7 @@ class SystemProxyManager: NSObject { return } Logger.log("enableProxy", level: .debug) - helper?.enableProxy(withPort: Int32(port), socksPort: Int32(socksPort), authData: authData(), error: { error in + helper?.enableProxy(withPort: Int32(port), socksPort: Int32(socksPort), error: { error in if let error = error { Logger.log("enableProxy \(error)", level: .error) } @@ -76,15 +74,15 @@ class SystemProxyManager: NSObject { Logger.log("disableProxy", level: .debug) if disableRestoreProxy || forceDisable { - helper?.disableProxy(withAuthData: authData(), error: { error in + helper?.disableProxy { error in if let error = error { Logger.log("disableProxy \(error)", level: .error) } - }) + } return } - helper?.restoreProxy(withCurrentPort: Int32(port), socksPort: Int32(socksPort), info: savedProxyInfo, authData: authData(), error: { error in + helper?.restoreProxy(withCurrentPort: Int32(port), socksPort: Int32(socksPort), info: savedProxyInfo, error: { error in if let error = error { Logger.log("restoreProxy \(error)", level: .error) } diff --git a/ClashX/Support Files/en.lproj/Localizable.strings b/ClashX/Support Files/en.lproj/Localizable.strings index 5c6f488ff..19212b811 100644 --- a/ClashX/Support Files/en.lproj/Localizable.strings +++ b/ClashX/Support Files/en.lproj/Localizable.strings @@ -65,6 +65,9 @@ /* No comment provided by engineer. */ "Invalid input" = "Invalid input"; +/* No comment provided by engineer. */ +"Lecgy Install" = "Lecgy Install"; + /* No comment provided by engineer. */ "Need to Restart the ClashX to Take effect, Please start clashX manually" = "Need to Restart the ClashX to Take effect, Please start clashX manually"; diff --git a/ClashX/Support Files/zh-Hans.lproj/Localizable.strings b/ClashX/Support Files/zh-Hans.lproj/Localizable.strings index b6be6f758..6d7d46f76 100644 --- a/ClashX/Support Files/zh-Hans.lproj/Localizable.strings +++ b/ClashX/Support Files/zh-Hans.lproj/Localizable.strings @@ -65,6 +65,9 @@ /* No comment provided by engineer. */ "Invalid input" = "输入不合法"; +/* No comment provided by engineer. */ +"Lecgy Install" = "传统安装"; + /* No comment provided by engineer. */ "Need to Restart the ClashX to Take effect, Please start clashX manually" = "需要重启ClashX生效,请手动启动ClashX"; diff --git a/ProxyConfigHelper/Helper-Info.plist b/ProxyConfigHelper/Helper-Info.plist index 5021e0265..4c5dda643 100755 --- a/ProxyConfigHelper/Helper-Info.plist +++ b/ProxyConfigHelper/Helper-Info.plist @@ -9,7 +9,7 @@ CFBundleName com.west2online.ClashX.ProxyConfigHelper CFBundleShortVersionString - 1.4 + 1.5 CFBundleVersion 2 SMAuthorizedClients diff --git a/ProxyConfigHelper/ProxyConfigHelper.m b/ProxyConfigHelper/ProxyConfigHelper.m index c8afb5747..6cef676c2 100644 --- a/ProxyConfigHelper/ProxyConfigHelper.m +++ b/ProxyConfigHelper/ProxyConfigHelper.m @@ -90,25 +90,14 @@ - (void)getVersion:(stringReplyBlock)reply { - (void)enableProxyWithPort:(int)port socksPort:(int)socksPort - authData:(NSData *)authData error:(stringReplyBlock)reply { ProxySettingTool *tool = [ProxySettingTool new]; - NSString *err = [tool setupAuth:authData]; - if (err != nil) { - reply(err); - return; - } [tool enableProxyWithport:port socksPort:socksPort]; reply(nil); } -- (void)disableProxyWithAuthData:(NSData *)authData error:(stringReplyBlock)reply { +- (void)disableProxy:(stringReplyBlock)reply { ProxySettingTool *tool = [ProxySettingTool new]; - NSString *err = [tool setupAuth:authData]; - if (err != nil) { - reply(err); - return; - } [tool disableProxy]; reply(nil); } @@ -117,14 +106,8 @@ - (void)disableProxyWithAuthData:(NSData *)authData error:(stringReplyBlock)repl - (void)restoreProxyWithCurrentPort:(int)port socksPort:(int)socksPort info:(NSDictionary *)dict - authData:(NSData *)authData error:(stringReplyBlock)reply { ProxySettingTool *tool = [ProxySettingTool new]; - NSString *err = [tool setupAuth:authData]; - if (err != nil) { - reply(err); - return; - } [tool restoreProxySettint:dict currentPort:port currentSocksPort:socksPort]; reply(nil); } diff --git a/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.h b/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.h index 1b05b116d..1c35dce37 100644 --- a/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.h +++ b/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.h @@ -19,15 +19,13 @@ typedef void(^dictReplyBlock)(NSDictionary *); - (void)enableProxyWithPort:(int)port socksPort:(int)socksPort - authData:(NSData *)authData error:(stringReplyBlock)reply; -- (void)disableProxyWithAuthData:(NSData *)authData error:(stringReplyBlock)reply; +- (void)disableProxy:(stringReplyBlock)reply; - (void)restoreProxyWithCurrentPort:(int)port socksPort:(int)socksPort info:(NSDictionary *)dict - authData:(NSData *)authData error:(stringReplyBlock)reply; - (void)getCurrentProxySetting:(dictReplyBlock)reply; diff --git a/ProxyConfigHelper/ProxySettingTool.h b/ProxyConfigHelper/ProxySettingTool.h index 9c00b697d..54615bfbb 100644 --- a/ProxyConfigHelper/ProxySettingTool.h +++ b/ProxyConfigHelper/ProxySettingTool.h @@ -12,7 +12,6 @@ NS_ASSUME_NONNULL_BEGIN @interface ProxySettingTool : NSObject -- (NSString *)setupAuth:(NSData *)authData; - (void)enableProxyWithport:(int)port socksPort:(int)socksPort; - (void)disableProxy; diff --git a/ProxyConfigHelper/ProxySettingTool.m b/ProxyConfigHelper/ProxySettingTool.m index f51e23377..1066afd1a 100644 --- a/ProxyConfigHelper/ProxySettingTool.m +++ b/ProxyConfigHelper/ProxySettingTool.m @@ -21,6 +21,13 @@ @interface ProxySettingTool() @implementation ProxySettingTool +- (instancetype)init { + if (self = [super init]) { + [self localAuth]; + } + return self; +} + // MARK: - Public - (void)enableProxyWithport:(int)port socksPort:(int)socksPort { @@ -242,6 +249,7 @@ - (AuthorizationFlags)authFlags { return authFlags; } +/* - (NSString *)setupAuth:(NSData *)authData { if (authData.length == 0 || authData.length != kAuthorizationExternalFormLength) { return @"PrivilegedTaskRunnerHelper: Authorization data is malformed"; @@ -274,6 +282,23 @@ - (NSString *)setupAuth:(NSData *)authData { self.authRef = authRef; return nil; } + */ + +- (void)localAuth { + OSStatus myStatus; + AuthorizationFlags myFlags = [self authFlags]; + myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, myFlags, &_authRef); + + if (myStatus != errAuthorizationSuccess) + { + return; + } + + AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0}; + AuthorizationRights myRights = {1, &myItems}; + myStatus = AuthorizationCopyRights (self.authRef, &myRights, NULL, myFlags, NULL ); +} + - (void)freeAuth { if (self.authRef) {