feature: local auth & backup way to install proxy helper

This commit is contained in:
yicheng 2020-04-22 11:22:24 +08:00
parent 60691df9c3
commit aeef5261dd
12 changed files with 158 additions and 68 deletions

View File

@ -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 = "<group>"; };
49B10869216A356D0064FFCE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
49B4575C244F4A2A00463C39 /* PrivilegedHelperManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivilegedHelperManager.swift; sourceTree = "<group>"; };
49B4575E244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PrivilegedHelperManager+Legacy.swift"; sourceTree = "<group>"; };
49BC061B212931F4005A0FE7 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
49C9EF63223E78F5005D8B6A /* ClashProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = "<group>"; };
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 = "<group>";
@ -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",

View File

@ -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.
//

View File

@ -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 '
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>\(PrivilegedHelperManager.machServiceName)</string>
<key>MachServices</key>
<dict>
<key>\(PrivilegedHelperManager.machServiceName)</key>
<true/>
</dict>
<key>Program</key>
<string>/Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName)</string>
<key>ProgramArguments</key>
<array>
<string>/Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName)</string>
</array>
</dict>
</plist>
' > ${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)")
}
}
}

View File

@ -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!, &currentRight)
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):

View File

@ -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)
}

View File

@ -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";

View File

@ -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";

View File

@ -9,7 +9,7 @@
<key>CFBundleName</key>
<string>com.west2online.ClashX.ProxyConfigHelper</string>
<key>CFBundleShortVersionString</key>
<string>1.4</string>
<string>1.5</string>
<key>CFBundleVersion</key>
<string>2</string>
<key>SMAuthorizedClients</key>

View File

@ -90,25 +90,14 @@ ProxyConfigRemoteProcessProtocol
- (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 @@ ProxyConfigRemoteProcessProtocol
- (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);
}

View File

@ -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;

View File

@ -12,7 +12,6 @@ NS_ASSUME_NONNULL_BEGIN
@interface ProxySettingTool : NSObject
- (NSString *)setupAuth:(NSData *)authData;
- (void)enableProxyWithport:(int)port socksPort:(int)socksPort;
- (void)disableProxy;

View File

@ -21,6 +21,13 @@
@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 @@
return authFlags;
}
/*
- (NSString *)setupAuth:(NSData *)authData {
if (authData.length == 0 || authData.length != kAuthorizationExternalFormLength) {
return @"PrivilegedTaskRunnerHelper: Authorization data is malformed";
@ -274,6 +282,23 @@
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) {