Feature: Use apple prefer way to run privileged task

This commit is contained in:
yicheng 2019-08-17 13:47:43 +08:00 committed by yichengchen
parent a6e09a625e
commit b42ba7123f
24 changed files with 1389 additions and 655 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ ClashX.h
ClashX/Resources/dashboard
ClashX.app
*.dmg
**/xcuserdata/

View File

@ -44,20 +44,23 @@
499A485C22ED793C00F6C675 /* NSView+Nib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485B22ED793C00F6C675 /* NSView+Nib.swift */; };
499A485E22ED9B7C00F6C675 /* NSTableView+Reload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485D22ED9B7C00F6C675 /* NSTableView+Reload.swift */; };
499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A486422EEA3FC00F6C675 /* Array+Safe.swift */; };
49AF9104225B2B03004CC077 /* ProxyConfig in CopyFiles */ = {isa = PBXBuildFile; fileRef = 49AF9103225B2A96004CC077 /* ProxyConfig */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
49B1086A216A356D0064FFCE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B10869216A356D0064FFCE /* String+Extension.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 */; };
49CF3B2520CD7465001EBF94 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B2420CD7465001EBF94 /* Assets.xcassets */; };
49CF3B2820CD7465001EBF94 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B2620CD7465001EBF94 /* Main.storyboard */; };
49CF3B5C20CE8068001EBF94 /* ProxyConfigHelperManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B5B20CE8068001EBF94 /* ProxyConfigHelperManager.swift */; };
49CF3B5F20CE80D2001EBF94 /* install_proxy_helper.sh in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B5E20CE80D2001EBF94 /* install_proxy_helper.sh */; };
49CF3B6320CED9CF001EBF94 /* check_proxy_helper.sh in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B6220CED934001EBF94 /* check_proxy_helper.sh */; };
49CF3B5C20CE8068001EBF94 /* ClashResourceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B5B20CE8068001EBF94 /* ClashResourceManager.swift */; };
49CF3B6520CEE06C001EBF94 /* ConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B6420CEE06C001EBF94 /* ConfigManager.swift */; };
49E07A8C20D501A000A088A3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49E07A8920D501A000A088A3 /* Main.storyboard */; };
49EC8D1922F13F330079B8F4 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 49EC8D0F22F13DEE0079B8F4 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
49EC8D1A22F13FFC0079B8F4 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49EC8D0F22F13DEE0079B8F4 /* Sparkle.framework */; };
F935B2F02307C52E009E4D33 /* com.west2online.ClashX.ProxyConfigHelper in Copy Files */ = {isa = PBXBuildFile; fileRef = F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
F935B2F42307CD32009E4D33 /* ProxyConfigHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F32307CD32009E4D33 /* ProxyConfigHelper.m */; };
F935B2FA23083EE6009E4D33 /* ProxySettingTool.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F923083EE6009E4D33 /* ProxySettingTool.m */; };
F935B2FC23085515009E4D33 /* SystemProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F935B2FB23085515009E4D33 /* SystemProxyManager.swift */; };
F94245092306DABA0005B196 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49E07A8A20D501A000A088A3 /* Main.storyboard */; };
F9A7C06C2306E874007163C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F9A7C06B2306E874007163C7 /* main.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -68,12 +71,12 @@
remoteGlobalIDString = 495A44BB20D2660A00888A0A;
remoteInfo = ClashXLaunchHelper;
};
49AF9102225B2A96004CC077 /* PBXContainerItemProxy */ = {
F935B2EB2307B7CD009E4D33 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 49AF90FE225B2A95004CC077 /* ProxyConfig.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 49AF90F3225B17D2004CC077;
remoteInfo = ProxyConfig;
containerPortal = 49CF3B1520CD7463001EBF94 /* Project object */;
proxyType = 1;
remoteGlobalIDString = F9A7C0682306E874007163C7;
remoteInfo = ProxyConfigHelper;
};
/* End PBXContainerItemProxy section */
@ -98,16 +101,27 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
663E4677213FCDC4006F11BB /* CopyFiles */ = {
663E4677213FCDC4006F11BB /* Copy Files */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 7;
dstPath = Contents/Library/LaunchServices;
dstSubfolderSpec = 1;
files = (
49AF9104225B2B03004CC077 /* ProxyConfig in CopyFiles */,
F935B2F02307C52E009E4D33 /* com.west2online.ClashX.ProxyConfigHelper in Copy Files */,
);
name = "Copy Files";
runOnlyForDeploymentPostprocessing = 0;
};
F9A7C0672306E874007163C7 /* Copy Files */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
name = "Copy Files";
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@ -157,7 +171,6 @@
499A485B22ED793C00F6C675 /* NSView+Nib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSView+Nib.swift"; sourceTree = "<group>"; };
499A485D22ED9B7C00F6C675 /* NSTableView+Reload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reload.swift"; sourceTree = "<group>"; };
499A486422EEA3FC00F6C675 /* Array+Safe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Safe.swift"; sourceTree = "<group>"; };
49AF90FE225B2A95004CC077 /* ProxyConfig.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ProxyConfig.xcodeproj; path = ProxyConfig/ProxyConfig.xcodeproj; sourceTree = "<group>"; };
49B10869216A356D0064FFCE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.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>"; };
@ -168,14 +181,22 @@
49CF3B2920CD7465001EBF94 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
49CF3B2A20CD7465001EBF94 /* ClashX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClashX.entitlements; sourceTree = "<group>"; };
49CF3B3520CD75DF001EBF94 /* ClashX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ClashX-Bridging-Header.h"; sourceTree = "<group>"; };
49CF3B5B20CE8068001EBF94 /* ProxyConfigHelperManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyConfigHelperManager.swift; sourceTree = "<group>"; };
49CF3B5E20CE80D2001EBF94 /* install_proxy_helper.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = install_proxy_helper.sh; sourceTree = "<group>"; };
49CF3B6220CED934001EBF94 /* check_proxy_helper.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = check_proxy_helper.sh; sourceTree = "<group>"; };
49CF3B5B20CE8068001EBF94 /* ClashResourceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashResourceManager.swift; sourceTree = "<group>"; };
49CF3B6420CEE06C001EBF94 /* ConfigManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigManager.swift; sourceTree = "<group>"; };
49E07A8A20D501A000A088A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = "<group>"; };
49E07A8A20D501A000A088A3 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
49EC8D0F22F13DEE0079B8F4 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = "<group>"; };
5217C006C5A22A1CEA24BFC1 /* Pods-ClashX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClashX.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ClashX/Pods-ClashX.debug.xcconfig"; sourceTree = "<group>"; };
A1485BCE642059532D01B8BA /* Pods-ClashX.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClashX.release.xcconfig"; path = "Pods/Target Support Files/Pods-ClashX/Pods-ClashX.release.xcconfig"; sourceTree = "<group>"; };
F935B2EA2307B6BA009E4D33 /* Helper-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Helper-Info.plist"; sourceTree = "<group>"; };
F935B2F12307C802009E4D33 /* Helper-Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Helper-Launchd.plist"; sourceTree = "<group>"; };
F935B2F22307CD32009E4D33 /* ProxyConfigHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProxyConfigHelper.h; sourceTree = "<group>"; };
F935B2F32307CD32009E4D33 /* ProxyConfigHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProxyConfigHelper.m; sourceTree = "<group>"; };
F935B2F52307D00D009E4D33 /* ProxyConfigRemoteProcessProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProxyConfigRemoteProcessProtocol.h; sourceTree = "<group>"; };
F935B2F823083EE6009E4D33 /* ProxySettingTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProxySettingTool.h; sourceTree = "<group>"; };
F935B2F923083EE6009E4D33 /* ProxySettingTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProxySettingTool.m; sourceTree = "<group>"; };
F935B2FB23085515009E4D33 /* SystemProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemProxyManager.swift; sourceTree = "<group>"; };
F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = com.west2online.ClashX.ProxyConfigHelper; sourceTree = BUILT_PRODUCTS_DIR; };
F9A7C06B2306E874007163C7 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -196,6 +217,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F9A7C0662306E874007163C7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -232,10 +260,11 @@
isa = PBXGroup;
children = (
4952C3CF2117027C004A4FA8 /* ConfigFileManager.swift */,
49CF3B5B20CE8068001EBF94 /* ProxyConfigHelperManager.swift */,
49CF3B5B20CE8068001EBF94 /* ClashResourceManager.swift */,
49CF3B6420CEE06C001EBF94 /* ConfigManager.swift */,
495BFB8721919B9800C8779D /* RemoteConfigManager.swift */,
4952C3BE2115C7CA004A4FA8 /* MenuItemFactory.swift */,
F935B2FB23085515009E4D33 /* SystemProxyManager.swift */,
);
path = Managers;
sourceTree = "<group>";
@ -275,6 +304,7 @@
495A44BD20D2660A00888A0A /* ClashXLaunchHelper */ = {
isa = PBXGroup;
children = (
49E07A8A20D501A000A088A3 /* Main.storyboard */,
49E07A8920D501A000A088A3 /* Main.storyboard */,
495A44BE20D2660A00888A0A /* AppDelegate.swift */,
495A44C720D2660B00888A0A /* Info.plist */,
@ -306,7 +336,6 @@
isa = PBXGroup;
children = (
49761DA621C9497000AE13EF /* dashboard */,
49CF3B5D20CE80D2001EBF94 /* script */,
);
path = Resources;
sourceTree = "<group>";
@ -334,19 +363,12 @@
path = Basic;
sourceTree = "<group>";
};
49AF90FF225B2A95004CC077 /* Products */ = {
isa = PBXGroup;
children = (
49AF9103225B2A96004CC077 /* ProxyConfig */,
);
name = Products;
sourceTree = "<group>";
};
49CF3B1420CD7463001EBF94 = {
isa = PBXGroup;
children = (
49CF3B1F20CD7463001EBF94 /* ClashX */,
495A44BD20D2660A00888A0A /* ClashXLaunchHelper */,
F9A7C06A2306E874007163C7 /* ProxyConfigHelper */,
49CF3B1E20CD7463001EBF94 /* Products */,
76229F122B00E935D126742A /* Pods */,
CF1AC9FACC36FCE7663C5583 /* Frameworks */,
@ -359,6 +381,7 @@
children = (
49CF3B1D20CD7463001EBF94 /* ClashX.app */,
495A44BC20D2660A00888A0A /* ClashXLaunchHelper.app */,
F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */,
);
name = Products;
sourceTree = "<group>";
@ -376,7 +399,6 @@
4989F98520D0AA300001E564 /* ViewControllers */,
49761DA521C9490400AE13EF /* Resources */,
49CF3B3A20CD783A001EBF94 /* Support Files */,
49AF90FE225B2A95004CC077 /* ProxyConfig.xcodeproj */,
49CF3B2020CD7463001EBF94 /* AppDelegate.swift */,
49CF3B2420CD7465001EBF94 /* Assets.xcassets */,
497F0DF220DE2FE50077AD41 /* Icon.icns */,
@ -401,15 +423,6 @@
path = "Support Files";
sourceTree = "<group>";
};
49CF3B5D20CE80D2001EBF94 /* script */ = {
isa = PBXGroup;
children = (
49CF3B5E20CE80D2001EBF94 /* install_proxy_helper.sh */,
49CF3B6220CED934001EBF94 /* check_proxy_helper.sh */,
);
path = script;
sourceTree = "<group>";
};
76229F122B00E935D126742A /* Pods */ = {
isa = PBXGroup;
children = (
@ -427,6 +440,21 @@
name = Frameworks;
sourceTree = "<group>";
};
F9A7C06A2306E874007163C7 /* ProxyConfigHelper */ = {
isa = PBXGroup;
children = (
F9A7C06B2306E874007163C7 /* main.m */,
F935B2F12307C802009E4D33 /* Helper-Launchd.plist */,
F935B2EA2307B6BA009E4D33 /* Helper-Info.plist */,
F935B2F22307CD32009E4D33 /* ProxyConfigHelper.h */,
F935B2F32307CD32009E4D33 /* ProxyConfigHelper.m */,
F935B2F52307D00D009E4D33 /* ProxyConfigRemoteProcessProtocol.h */,
F935B2F823083EE6009E4D33 /* ProxySettingTool.h */,
F935B2F923083EE6009E4D33 /* ProxySettingTool.m */,
);
path = ProxyConfigHelper;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -457,13 +485,14 @@
49CF3B1B20CD7463001EBF94 /* Resources */,
495A44CC20D266B000888A0A /* CopyFiles */,
A741C26F5755233F0D7CEC6F /* [CP] Embed Pods Frameworks */,
663E4677213FCDC4006F11BB /* CopyFiles */,
663E4677213FCDC4006F11BB /* Copy Files */,
49EC8D1822F13F100079B8F4 /* CopyFiles */,
4929F1E621BC170D0059D8B4 /* ShellScript */,
4929F1E621BC170D0059D8B4 /* Run Script - Fabric */,
);
buildRules = (
);
dependencies = (
F935B2EC2307B7CD009E4D33 /* PBXTargetDependency */,
495A44CF20D2671F00888A0A /* PBXTargetDependency */,
);
name = ClashX;
@ -471,13 +500,30 @@
productReference = 49CF3B1D20CD7463001EBF94 /* ClashX.app */;
productType = "com.apple.product-type.application";
};
F9A7C0682306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */ = {
isa = PBXNativeTarget;
buildConfigurationList = F9A7C06D2306E874007163C7 /* Build configuration list for PBXNativeTarget "com.west2online.ClashX.ProxyConfigHelper" */;
buildPhases = (
F9A7C0652306E874007163C7 /* Sources */,
F9A7C0662306E874007163C7 /* Frameworks */,
F9A7C0672306E874007163C7 /* Copy Files */,
);
buildRules = (
);
dependencies = (
);
name = com.west2online.ClashX.ProxyConfigHelper;
productName = ProxyConfigHelper;
productReference = F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
49CF3B1520CD7463001EBF94 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0940;
LastSwiftUpdateCheck = 1030;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = west2online;
TargetAttributes = {
@ -502,6 +548,9 @@
};
};
};
F9A7C0682306E874007163C7 = {
CreatedOnToolsVersion = 10.3;
};
};
};
buildConfigurationList = 49CF3B1820CD7463001EBF94 /* Build configuration list for PBXProject "ClashX" */;
@ -512,40 +561,25 @@
en,
Base,
"zh-Hans",
global,
);
mainGroup = 49CF3B1420CD7463001EBF94;
productRefGroup = 49CF3B1E20CD7463001EBF94 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 49AF90FF225B2A95004CC077 /* Products */;
ProjectRef = 49AF90FE225B2A95004CC077 /* ProxyConfig.xcodeproj */;
},
);
projectRoot = "";
targets = (
49CF3B1C20CD7463001EBF94 /* ClashX */,
495A44BB20D2660A00888A0A /* ClashXLaunchHelper */,
F9A7C0682306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */,
);
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
49AF9103225B2A96004CC077 /* ProxyConfig */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = ProxyConfig;
remoteRef = 49AF9102225B2A96004CC077 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
495A44BA20D2660A00888A0A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F94245092306DABA0005B196 /* Main.storyboard in Resources */,
49E07A8C20D501A000A088A3 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -559,12 +593,10 @@
4981C88B216BAE4A008CC14A /* Localizable.strings in Resources */,
495340B020DE5F7200B0D3FF /* StatusItemView.xib in Resources */,
49CF3B2820CD7465001EBF94 /* Main.storyboard in Resources */,
49CF3B5F20CE80D2001EBF94 /* install_proxy_helper.sh in Resources */,
499A485A22ED781100F6C675 /* RemoteConfigAddView.xib in Resources */,
4989F98420D02D200001E564 /* Country.mmdb in Resources */,
497F0DF320DE2FE50077AD41 /* Icon.icns in Resources */,
4989F98E20D0AE990001E564 /* sampleConfig.yaml in Resources */,
49CF3B6320CED9CF001EBF94 /* check_proxy_helper.sh in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -589,7 +621,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
4929F1E621BC170D0059D8B4 /* ShellScript */ = {
4929F1E621BC170D0059D8B4 /* Run Script - Fabric */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -597,6 +629,7 @@
inputPaths = (
"$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)",
);
name = "Run Script - Fabric";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
@ -636,12 +669,13 @@
buildActionMask = 2147483647;
files = (
499A485522ED707300F6C675 /* RemoteConfigViewController.swift in Sources */,
49CF3B5C20CE8068001EBF94 /* ProxyConfigHelperManager.swift in Sources */,
49CF3B5C20CE8068001EBF94 /* ClashResourceManager.swift in Sources */,
4952C3D02117027C004A4FA8 /* ConfigFileManager.swift in Sources */,
4997732520D251A60009B136 /* SWBApplication.m in Sources */,
49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */,
4949D154213242F600EF85E6 /* Paths.swift in Sources */,
495340B320DE68C300B0D3FF /* StatusItemView.swift in Sources */,
F935B2FC23085515009E4D33 /* SystemProxyManager.swift in Sources */,
495A44D320D267D000888A0A /* LaunchAtLogin.swift in Sources */,
493AEAE3221AE3420016FE98 /* AppVersionUtil.swift in Sources */,
49CF3B2120CD7463001EBF94 /* AppDelegate.swift in Sources */,
@ -670,6 +704,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F9A7C0652306E874007163C7 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F935B2F42307CD32009E4D33 /* ProxyConfigHelper.m in Sources */,
F9A7C06C2306E874007163C7 /* main.m in Sources */,
F935B2FA23083EE6009E4D33 /* ProxySettingTool.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -678,6 +722,11 @@
target = 495A44BB20D2660A00888A0A /* ClashXLaunchHelper */;
targetProxy = 495A44CE20D2671F00888A0A /* PBXContainerItemProxy */;
};
F935B2EC2307B7CD009E4D33 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F9A7C0682306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */;
targetProxy = F935B2EB2307B7CD009E4D33 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@ -702,7 +751,6 @@
49E07A8920D501A000A088A3 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
49E07A8A20D501A000A088A3 /* Base */,
4981C888216BAB8A008CC14A /* zh-Hans */,
);
name = Main.storyboard;
@ -716,6 +764,7 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ClashXLaunchHelper/ClashXLaunchHelper.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = MEWHFZ92DY;
@ -879,7 +928,7 @@
baseConfigurationReference = 5217C006C5A22A1CEA24BFC1 /* Pods-ClashX.debug.xcconfig */;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = MEWHFZ92DY;
@ -950,6 +999,57 @@
};
name = Release;
};
F9A7C06E2306E874007163C7 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = MEWHFZ92DY;
INFOPLIST_FILE = "$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
"-sectcreate",
__TEXT,
__info_plist,
"\"$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist\"",
"-sectcreate",
__TEXT,
__launchd_plist,
"\"$(SRCROOT)/ProxyConfigHelper/Helper-Launchd.plist\"",
);
PRODUCT_BUNDLE_IDENTIFIER = com.west2online.ClashX.ProxyConfigHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
};
name = Debug;
};
F9A7C06F2306E874007163C7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = MEWHFZ92DY;
INFOPLIST_FILE = "$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
"-sectcreate",
__TEXT,
__info_plist,
"\"$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist\"",
"-sectcreate",
__TEXT,
__launchd_plist,
"\"$(SRCROOT)/ProxyConfigHelper/Helper-Launchd.plist\"",
);
PRODUCT_BUNDLE_IDENTIFIER = com.west2online.ClashX.ProxyConfigHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -980,6 +1080,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F9A7C06D2306E874007163C7 /* Build configuration list for PBXNativeTarget "com.west2online.ClashX.ProxyConfigHelper" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F9A7C06E2306E874007163C7 /* Debug */,
F9A7C06F2306E874007163C7 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 49CF3B1520CD7463001EBF94 /* Project object */;

View File

@ -21,7 +21,7 @@ private let statusItemLengthWithSpeed:CGFloat = 70
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem!
@IBOutlet weak var statusMenu: NSMenu!
@IBOutlet weak var proxySettingMenuItem: NSMenuItem!
@IBOutlet weak var autoStartMenuItem: NSMenuItem!
@ -53,6 +53,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
signal(SIGPIPE, SIG_IGN)
// setup menu item first
statusItem = NSStatusBar.system.statusItem(withLength:statusItemLengthWithSpeed)
statusItem.menu = statusMenu
@ -60,13 +62,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
statusItemView = StatusItemView.create(statusItem: statusItem)
statusItemView.frame = CGRect(x: 0, y: 0, width: statusItemLengthWithSpeed, height: 22)
statusMenu.delegate = self
// crash recorder
failLaunchProtect()
registCrashLogger()
// install proxy helper
_ = ProxyConfigHelperManager.install()
_ = ClashResourceManager.check()
SystemProxyManager.shared.checkInstall()
ConfigFileManager.copySampleConfigIfNeed()
ConfigManager.shared.refreshApiInfo()
@ -92,7 +94,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillTerminate(_ aNotification: Notification) {
if ConfigManager.shared.proxyPortAutoSet {
_ = ProxyConfigHelperManager.setUpSystemProxy(port: nil,socksPort: nil)
SystemProxyManager.shared.disableProxy()
}
}
@ -181,7 +183,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
self.updateProxyList()
if (old?.port != config.port && ConfigManager.shared.proxyPortAutoSet) {
_ = ProxyConfigHelperManager.setUpSystemProxy(port: config.port,socksPort: config.socketPort)
SystemProxyManager.shared.enableProxy(port: config.port, socksPort: config.socketPort)
}
self.httpPortMenuItem.title = "Http Port:\(config.port)"
@ -361,9 +363,9 @@ extension AppDelegate {
if ConfigManager.shared.proxyPortAutoSet {
let port = ConfigManager.shared.currentConfig?.port ?? 0
let socketPort = ConfigManager.shared.currentConfig?.socketPort ?? 0
_ = ProxyConfigHelperManager.setUpSystemProxy(port: port,socksPort:socketPort)
SystemProxyManager.shared.enableProxy(port: port, socksPort: socketPort)
} else {
_ = ProxyConfigHelperManager.setUpSystemProxy(port: nil,socksPort: nil)
SystemProxyManager.shared.disableProxy()
}
}

View File

@ -2,3 +2,4 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ClashX.h"
#import "ProxyConfigRemoteProcessProtocol.h"

View File

@ -0,0 +1,95 @@
import Foundation
import AppKit
class ClashResourceManager {
static let kProxyConfigFolder = (NSHomeDirectory() as NSString).appendingPathComponent("/.config/clash")
static func check() -> Bool {
checkConfigDir()
checkMMDB()
upgardeYmlExtensionName()
checkAndRemoveOldErrorConfig()
return true
}
static func checkConfigDir() {
var isDir : ObjCBool = true
if !FileManager.default.fileExists(atPath: kProxyConfigFolder, isDirectory:&isDir) {
do {
try FileManager.default.createDirectory(atPath: kProxyConfigFolder, withIntermediateDirectories: true, attributes: nil)
} catch {
showCreateConfigDirFailAlert()
}
}
}
static func checkMMDB() {
let fileManage = FileManager.default
let destMMDBPath = "\(kProxyConfigFolder)/Country.mmdb"
// Remove old mmdb file after version update.
if fileManage.fileExists(atPath: destMMDBPath) {
if AppVersionUtil.hasVersionChanged || AppVersionUtil.isFirstLaunch {
try? fileManage.removeItem(atPath: destMMDBPath)
}
}
if !fileManage.fileExists(atPath: destMMDBPath) {
if let mmdbPath = Bundle.main.path(forResource: "Country", ofType: "mmdb") {
try? fileManage.copyItem(at: URL(fileURLWithPath: mmdbPath), to: URL(fileURLWithPath: destMMDBPath))
}
}
}
static func checkAndRemoveOldErrorConfig() {
if FileManager.default.fileExists(atPath: kDefaultConfigFilePath) {
do {
let defaultConfigData = try Data(contentsOf: URL(fileURLWithPath: kDefaultConfigFilePath))
var checkSum: UInt8 = 0
for byte in defaultConfigData {
checkSum &+= byte
}
if checkSum == 101 {
// old error config
Logger.log(msg: "removing old config.yaml")
try FileManager.default.removeItem(atPath: kDefaultConfigFilePath)
}
} catch let err {
Logger.log(msg: "removing old config.yaml fail: \(err.localizedDescription)")
}
}
}
static func upgardeYmlExtensionName() {
do {
let fileURLs = try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: kConfigFolderPath, isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
for upgradeUrl in fileURLs.filter({$0.pathExtension == "yml" }) {
let dest = upgradeUrl.deletingPathExtension().appendingPathExtension("yaml")
try FileManager.default.moveItem(at: upgradeUrl, to: dest)
}
} catch let err {
Logger.log(msg: err.localizedDescription)
}
}
static func showCreateConfigDirFailAlert() {
let alert = NSAlert()
alert.messageText = NSLocalizedString("ClashX fail to create ~/.config/clash folder. Please check privileges or manually create folder and restart ClashX.", comment: "")
alert.alertStyle = .warning
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
alert.runModal()
NSApplication.shared.terminate(nil)
}
}

View File

@ -1,175 +0,0 @@
import Foundation
import AppKit
class ProxyConfigHelperManager {
static let kProxyConfigFolder = (NSHomeDirectory() as NSString).appendingPathComponent("/.config/clash")
static let kVersion = "0.1.3"
static func vaildHelper() -> Bool {
let scriptPath = "\(Bundle.main.resourcePath!)/check_proxy_helper.sh"
let appleScriptStr = "do shell script \"bash \\\"\(scriptPath)\\\" \(kProxyConfigFolder) \(kVersion) \" "
let appleScript = NSAppleScript(source: appleScriptStr)
var dict: NSDictionary?
if let res = appleScript?.executeAndReturnError(&dict) {
if (res.stringValue?.contains("success")) ?? false {
return true
}
} else {
Logger.log(msg: "\(String(describing: dict))",level: .error)
}
return false
}
static func install() -> Bool {
checkConfigDir()
checkMMDB()
upgardeYmlExtensionName()
checkAndRemoveOldErrorConfig()
let proxyHelperPath = Bundle.main.path(forResource: "ProxyConfig", ofType: nil)
let targetPath = "\(kProxyConfigFolder)/ProxyConfig"
if !vaildHelper() {
if (!showInstallHelperAlert()) {
NSApplication.shared.terminate(nil)
}
if (FileManager.default.fileExists(atPath: targetPath)) {
try? FileManager.default.removeItem(atPath: targetPath)
}
try? FileManager.default.copyItem(at: URL(fileURLWithPath: proxyHelperPath!), to: URL(fileURLWithPath: targetPath))
let scriptPath = "\(Bundle.main.resourcePath!)/install_proxy_helper.sh"
let appleScriptStr = "do shell script \"bash \(scriptPath) \(kProxyConfigFolder) \" with administrator privileges"
let appleScript = NSAppleScript(source: appleScriptStr)
var dict: NSDictionary?
if let _ = appleScript?.executeAndReturnError(&dict) {
return true
} else {
return false
}
}
return true
}
static func checkConfigDir() {
var isDir : ObjCBool = true
if !FileManager.default.fileExists(atPath: kProxyConfigFolder, isDirectory:&isDir) {
do {
try FileManager.default.createDirectory(atPath: kProxyConfigFolder, withIntermediateDirectories: true, attributes: nil)
} catch {
showCreateConfigDirFailAlert()
}
}
}
static func checkMMDB() {
let fileManage = FileManager.default
let destMMDBPath = "\(kProxyConfigFolder)/Country.mmdb"
// Remove old mmdb file after version update.
if fileManage.fileExists(atPath: destMMDBPath) {
if AppVersionUtil.hasVersionChanged || AppVersionUtil.isFirstLaunch {
try? fileManage.removeItem(atPath: destMMDBPath)
}
}
if !fileManage.fileExists(atPath: destMMDBPath) {
if let mmdbPath = Bundle.main.path(forResource: "Country", ofType: "mmdb") {
try? fileManage.copyItem(at: URL(fileURLWithPath: mmdbPath), to: URL(fileURLWithPath: destMMDBPath))
}
}
}
static func checkAndRemoveOldErrorConfig() {
if FileManager.default.fileExists(atPath: kDefaultConfigFilePath) {
do {
let defaultConfigData = try Data(contentsOf: URL(fileURLWithPath: kDefaultConfigFilePath))
var checkSum: UInt8 = 0
for byte in defaultConfigData {
checkSum &+= byte
}
if checkSum == 101 {
// old error config
Logger.log(msg: "removing old config.yaml")
try FileManager.default.removeItem(atPath: kDefaultConfigFilePath)
}
} catch let err {
Logger.log(msg: "removing old config.yaml fail: \(err.localizedDescription)")
}
}
}
static func upgardeYmlExtensionName() {
do {
let fileURLs = try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: kConfigFolderPath, isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
for upgradeUrl in fileURLs.filter({$0.pathExtension == "yml" }) {
let dest = upgradeUrl.deletingPathExtension().appendingPathExtension("yaml")
try FileManager.default.moveItem(at: upgradeUrl, to: dest)
}
} catch let err {
Logger.log(msg: err.localizedDescription)
}
}
static func setUpSystemProxy(port: Int?,socksPort: Int?) -> Bool {
let task = Process()
task.launchPath = "\(kProxyConfigFolder)/ProxyConfig"
let hookTask:String?
if let port = port,let socksPort = socksPort {
hookTask = UserDefaults.standard.string(forKey: "kProxyEnableHook")
task.arguments = [String(port),String(socksPort), "enable"]
} else {
hookTask = UserDefaults.standard.string(forKey: "kProxyDisableHook")
task.arguments = ["0", "0", "disable"]
}
task.launch()
task.waitUntilExit()
if task.terminationStatus != 0 {
return false
}
DispatchQueue.global().async {
if let command = hookTask {
let appleScriptStr = "do shell script \"\(command)\""
let appleScript = NSAppleScript(source: appleScriptStr)
_ = appleScript?.executeAndReturnError(nil)
}
}
return true
}
static func showInstallHelperAlert() -> Bool{
let alert = NSAlert()
alert.messageText = NSLocalizedString("ClashX needs to install a small tool to ~/.config/clash with administrator privileges to set system proxy quickly.\n\nOtherwise you need to type in the administrator password every time you change system proxy through ClashX.", comment: "")
alert.alertStyle = .warning
alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
return alert.runModal() == .alertFirstButtonReturn
}
static func showCreateConfigDirFailAlert() {
let alert = NSAlert()
alert.messageText = NSLocalizedString("ClashX fail to create ~/.config/clash folder. Please check privileges or manually create folder and restart ClashX.", comment: "")
alert.alertStyle = .warning
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
alert.runModal()
NSApplication.shared.terminate(nil)
}
}

View File

@ -0,0 +1,228 @@
//
// SystemProxyManager.swift
// ClashX
//
// Created by yichengchen on 2019/8/17.
// Copyright © 2019 west2online. All rights reserved.
//
import ServiceManagement
import AppKit
class SystemProxyManager: NSObject {
static let shared = SystemProxyManager()
private static let machServiceName = "com.west2online.ClashX.ProxyConfigHelper"
private var authRef: AuthorizationRef?
private var connection: NSXPCConnection?
private var _helper: ProxyConfigRemoteProcessProtocol?
// MARK: - LifeCycle
override init() {
super.init()
initAuthorizationRef()
}
// MARK: - Public
func checkInstall() {
helperStatus { [weak self] installed in
if installed {return}
if Thread.isMainThread {
self?.notifyInstall()
} else {
DispatchQueue.main.async {
self?.notifyInstall()
}
}
}
}
func enableProxy(port: Int,socksPort: Int) {
Logger.log(msg: "enableProxy", level: .debug)
helper()?.enableProxy(withPort: Int32(port), socksPort: Int32(socksPort), authData: authData(), error: { error in
if let error = error{
Logger.log(msg: "enableProxy \(error)", level: .error)
}
})
}
func disableProxy() {
Logger.log(msg: "disableProxy", level: .debug)
helper()?.disableProxy(withAuthData: authData(), error: { error in
if let error = error{
Logger.log(msg: "disableProxy \(error)", level: .error)
}
})
}
// MARK: - Private
private func initAuthorizationRef() {
// Create an empty AuthorizationRef
let status = AuthorizationCreate(nil, nil, AuthorizationFlags(), &authRef)
if (status != OSStatus(errAuthorizationSuccess)) {
Logger.log(msg:"initAuthorizationRef AuthorizationCreate failed",level: .error)
return
}
}
/// Install new helper daemon
private func installHelperDaemon() {
// Create authorization reference for the user
var authRef: AuthorizationRef?
var authStatus = AuthorizationCreate(nil, nil, [], &authRef)
// Check if the reference is valid
guard authStatus == errAuthorizationSuccess else {
Logger.log(msg: "Authorization failed: \(authStatus)", level: .error)
return
}
// Ask user for the admin privileges to install the
var authItem = AuthorizationItem(name: kSMRightBlessPrivilegedHelper, valueLength: 0, value: nil, flags: 0)
var authRights = AuthorizationRights(count: 1, items: &authItem)
let flags: AuthorizationFlags = [[], .interactionAllowed, .extendRights, .preAuthorize]
authStatus = AuthorizationCreate(&authRights, nil, flags, &authRef)
defer {
if let ref = authRef {
AuthorizationFree(ref, [])
}
}
// Check if the authorization went succesfully
guard authStatus == errAuthorizationSuccess else {
Logger.log(msg: "Couldn't obtain admin privileges: \(authStatus)", level: .error)
return
}
// Launch the privileged helper using SMJobBless tool
var error: Unmanaged<CFError>? = nil
if(SMJobBless(kSMDomainSystemLaunchd, SystemProxyManager.machServiceName as CFString, authRef, &error) == false) {
let blessError = error!.takeRetainedValue() as Error
Logger.log(msg: "Bless Error: \(blessError)", level: .error)
} else {
Logger.log(msg: "\(SystemProxyManager.machServiceName) installed successfully", level: .info)
}
connection?.invalidate()
connection = nil
_helper = nil
}
private 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(msg: "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) {
connection = NSXPCConnection(machServiceName: SystemProxyManager.machServiceName, options: NSXPCConnection.Options.privileged)
connection?.remoteObjectInterface = NSXPCInterface(with: ProxyConfigRemoteProcessProtocol.self)
connection?.invalidationHandler = {
self.connection?.invalidationHandler = nil
OperationQueue.main.addOperation() {
self.connection = nil
Logger.log(msg: "XPC Connection Invalidated")
}
}
connection?.resume()
}
return connection
}
private func helper(failture: (() -> Void)? = nil) -> ProxyConfigRemoteProcessProtocol? {
if _helper == nil {
guard let newHelper = self.helperConnection()?.remoteObjectProxyWithErrorHandler({ error in
Logger.log(msg: "Helper connection was closed with error: \(error)")
failture?()
}) as? ProxyConfigRemoteProcessProtocol else { return nil }
_helper = newHelper
}
return _helper
}
private func helperStatus(completion: @escaping (_ installed: Bool) -> Void) {
let helperURL = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LaunchServices/" + SystemProxyManager.machServiceName)
var callback:((Bool)->Void)? = completion
guard
let helperBundleInfo = CFBundleCopyInfoDictionaryForURL(helperURL as CFURL) as? [String: Any],
let helperVersion = helperBundleInfo["CFBundleShortVersionString"] as? String,
let helper = self.helper() else {
callback?(false)
callback = nil
return
}
var installed = false
let semaphore = DispatchSemaphore(value: 0)
helper.getVersion { installedHelperVersion in
installed = installedHelperVersion == helperVersion
}
_ = semaphore.wait(timeout: DispatchTime.now()+1)
callback?(installed)
callback = nil
}
}
extension SystemProxyManager {
private func notifyInstall() {
guard showInstallHelperAlert() else {exit(0)}
self.installHelperDaemon()
}
private func showInstallHelperAlert() -> Bool{
let alert = NSAlert()
alert.messageText = NSLocalizedString("ClashX needs to install a small tool to ~/.config/clash with administrator privileges to set system proxy quickly.\n\nOtherwise you need to type in the administrator password every time you change system proxy through ClashX.", comment: "")
alert.alertStyle = .warning
alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
return alert.runModal() == .alertFirstButtonReturn
}
}
fileprivate struct AppAuthorizationRights {
static let rightName: NSString = "com.west2online.ClashX.ProxyConfigHelper.config"
static let rightDefaultRule: Dictionary = adminRightsRule
static let rightDescription: CFString = "ProxyConfigHelper wants to configure your proxy setting'" as CFString
static var adminRightsRule: [String:Any] = ["class" : "user",
"group" : "admin",
"timeout" : 0,
"version" : 1]
}

View File

@ -48,13 +48,12 @@ class JsBridgeUtil {
if let enable = anydata as? Bool {
ConfigManager.shared.proxyPortAutoSet = enable
if let config = ConfigManager.shared.currentConfig {
let success:Bool
if enable{
success = ProxyConfigHelperManager.setUpSystemProxy(port: config.port,socksPort: config.socketPort)
SystemProxyManager.shared.enableProxy(port: config.port, socksPort: config.socketPort)
} else {
success = ProxyConfigHelperManager.setUpSystemProxy(port: nil,socksPort: nil)
SystemProxyManager.shared.disableProxy()
}
responseCallback?(success)
responseCallback?(true)
} else {
responseCallback?(false)
}

View File

@ -55,7 +55,8 @@
<array>
<dict>
<key>KitInfo</key>
<dict/>
<dict>
</dict>
<key>KitName</key>
<string>Crashlytics</string>
</dict>
@ -78,6 +79,15 @@
<string>Main</string>
<key>NSPrincipalClass</key>
<string>SWBApplication</string>
<key>SMAuthorizedClients</key>
<array>
<string>identifier "com.west2online.ClashX" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: chen yicheng (96U846XGYH)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */</string>
</array>
<key>SMPrivilegedExecutables</key>
<dict>
<key>com.west2online.ClashX.ProxyConfigHelper</key>
<string>anchor apple generic and identifier "com.west2online.ClashX.ProxyConfigHelper" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = MEWHFZ92DY)</string>
</dict>
<key>SUEnableAutomaticChecks</key>
<true/>
<key>SUFeedURL</key>

View File

@ -1,287 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
49AF90F7225B17D2004CC077 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 49AF90F6225B17D2004CC077 /* main.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
49AF90F1225B17D2004CC077 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
49AF90F3225B17D2004CC077 /* ProxyConfig */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ProxyConfig; sourceTree = BUILT_PRODUCTS_DIR; };
49AF90F6225B17D2004CC077 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
49AF90F0225B17D2004CC077 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
49AF90EA225B17D2004CC077 = {
isa = PBXGroup;
children = (
49AF90F5225B17D2004CC077 /* ProxyConfig */,
49AF90F4225B17D2004CC077 /* Products */,
);
sourceTree = "<group>";
};
49AF90F4225B17D2004CC077 /* Products */ = {
isa = PBXGroup;
children = (
49AF90F3225B17D2004CC077 /* ProxyConfig */,
);
name = Products;
sourceTree = "<group>";
};
49AF90F5225B17D2004CC077 /* ProxyConfig */ = {
isa = PBXGroup;
children = (
49AF90F6225B17D2004CC077 /* main.m */,
);
path = ProxyConfig;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
49AF90F2225B17D2004CC077 /* ProxyConfig */ = {
isa = PBXNativeTarget;
buildConfigurationList = 49AF90FA225B17D2004CC077 /* Build configuration list for PBXNativeTarget "ProxyConfig" */;
buildPhases = (
49AF90EF225B17D2004CC077 /* Sources */,
49AF90F0225B17D2004CC077 /* Frameworks */,
49AF90F1225B17D2004CC077 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = ProxyConfig;
productName = ProxyConfig;
productReference = 49AF90F3225B17D2004CC077 /* ProxyConfig */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
49AF90EB225B17D2004CC077 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = west2online;
TargetAttributes = {
49AF90F2225B17D2004CC077 = {
CreatedOnToolsVersion = 10.2;
};
};
};
buildConfigurationList = 49AF90EE225B17D2004CC077 /* Build configuration list for PBXProject "ProxyConfig" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 49AF90EA225B17D2004CC077;
productRefGroup = 49AF90F4225B17D2004CC077 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
49AF90F2225B17D2004CC077 /* ProxyConfig */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
49AF90EF225B17D2004CC077 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
49AF90F7225B17D2004CC077 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
49AF90F8225B17D2004CC077 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
49AF90F9225B17D2004CC077 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
};
name = Release;
};
49AF90FB225B17D2004CC077 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = "";
MACOSX_DEPLOYMENT_TARGET = 10.10;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
};
name = Debug;
};
49AF90FC225B17D2004CC077 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = "";
MACOSX_DEPLOYMENT_TARGET = 10.10;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
49AF90EE225B17D2004CC077 /* Build configuration list for PBXProject "ProxyConfig" */ = {
isa = XCConfigurationList;
buildConfigurations = (
49AF90F8225B17D2004CC077 /* Debug */,
49AF90F9225B17D2004CC077 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
49AF90FA225B17D2004CC077 /* Build configuration list for PBXNativeTarget "ProxyConfig" */ = {
isa = XCConfigurationList;
buildConfigurations = (
49AF90FB225B17D2004CC077 /* Debug */,
49AF90FC225B17D2004CC077 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 49AF90EB225B17D2004CC077 /* Project object */;
}

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:ProxyConfig.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "49AF90F2225B17D2004CC077"
BuildableName = "ProxyConfig"
BlueprintName = "ProxyConfig"
ReferencedContainer = "container:ProxyConfig.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "49AF90F2225B17D2004CC077"
BuildableName = "ProxyConfig"
BlueprintName = "ProxyConfig"
ReferencedContainer = "container:ProxyConfig.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "49AF90F2225B17D2004CC077"
BuildableName = "ProxyConfig"
BlueprintName = "ProxyConfig"
ReferencedContainer = "container:ProxyConfig.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "49AF90F2225B17D2004CC077"
BuildableName = "ProxyConfig"
BlueprintName = "ProxyConfig"
ReferencedContainer = "container:ProxyConfig.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,13 +0,0 @@
#!/bin/sh
cd "$1";
CURRENT=$(./ProxyConfig version);
TARGET=$2;
if [[ `ls -l ProxyConfig` = *"root"* ]]; then
if [ "$CURRENT" == "$TARGET" ];then
echo "success"
exit
fi
fi
echo "false"

View File

@ -1,4 +0,0 @@
cd "$1";
sudo chown root:admin "ProxyConfig"
sudo chmod +s "ProxyConfig"
echo done

View File

@ -30,5 +30,10 @@
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SMPrivilegedExecutables</key>
<dict>
<key>com.github.erikberglund.SwiftPrivilegedHelper</key>
<string>identifier &quot;com.github.erikberglund.SwiftPrivilegedHelper&quot; and anchor apple generic and certificate leaf[subject.CN] = &quot;Mac Developer: chen yicheng (96U846XGYH)&quot; and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,20 @@
<?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>CFBundleIdentifier</key>
<string>com.west2online.ClashX.ProxyConfigHelper</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>com.west2online.ClashX.ProxyConfigHelper</string>
<key>CFBundleShortVersionString</key>
<string>1.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>SMAuthorizedClients</key>
<array>
<string>anchor apple generic and identifier "com.west2online.ClashX" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = MEWHFZ92DY)</string>
</array>
</dict>
</plist>

View File

@ -2,7 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
<key>Label</key>
<string>com.west2online.ClashX.ProxyConfigHelper</string>
<key>MachServices</key>
<dict>
<key>com.west2online.ClashX.ProxyConfigHelper</key>
<true/>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,17 @@
//
// ProxyConfigHelper.h
// com.west2online.ClashX.ProxyConfigHelper
//
// Created by yichengchen on 2019/8/17.
// Copyright © 2019 west2online. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ProxyConfigHelper : NSObject
- (void)run;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,116 @@
//
// ProxyConfigHelper.m
// com.west2online.ClashX.ProxyConfigHelper
//
// Created by yichengchen on 2019/8/17.
// Copyright © 2019 west2online. All rights reserved.
//
#import "ProxyConfigHelper.h"
#import <AppKit/AppKit.h>
#import "ProxyConfigRemoteProcessProtocol.h"
#import "ProxySettingTool.h"
@interface ProxyConfigHelper()
<
NSXPCListenerDelegate,
ProxyConfigRemoteProcessProtocol
>
@property (nonatomic, strong) NSXPCListener *listener;
@property (nonatomic, strong) NSMutableSet<NSXPCConnection *> *connections;
@property (nonatomic, strong) NSTimer *checkTimer;
@property (nonatomic, assign) BOOL shouldQuit;
@end
@implementation ProxyConfigHelper
- (instancetype)init {
if (self = [super init]) {
self.connections = [NSMutableSet new];
self.shouldQuit = NO;
self.listener = [[NSXPCListener alloc] initWithMachServiceName:@"com.west2online.ClashX.ProxyConfigHelper"];
self.listener.delegate = self;
}
return self;
}
- (void)run {
[self.listener resume];
self.checkTimer =
[NSTimer timerWithTimeInterval:5.f target:self selector:@selector(connectionCheckOnLaunch) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:self.checkTimer forMode:NSDefaultRunLoopMode];
while (!self.shouldQuit) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
}
}
- (void)connectionCheckOnLaunch {
if (self.connections.count == 0) {
self.shouldQuit = YES;
}
}
- (BOOL)connectionIsVaild: (NSXPCConnection *)connection {
NSRunningApplication *remoteApp =
[NSRunningApplication runningApplicationWithProcessIdentifier:connection.processIdentifier];
return remoteApp != nil;
}
// MARK: - NSXPCListenerDelegate
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
if (![self connectionIsVaild:newConnection]) {
return NO;
}
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(ProxyConfigRemoteProcessProtocol)];
newConnection.exportedObject = self;
__weak NSXPCConnection *weakConnection = newConnection;
__weak ProxyConfigHelper *weakSelf = self;
newConnection.invalidationHandler = ^{
[weakSelf.connections removeObject:weakConnection];
if (weakSelf.connections.count == 0) {
weakSelf.shouldQuit = YES;
}
};
[self.connections addObject:newConnection];
[newConnection resume];
return YES;
}
// MARK: - ProxyConfigRemoteProcessProtocol
- (void)getVersion:(stringReplyBlock)reply {
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
if (version == nil) {
version = @"unknown";
}
reply(version);
}
- (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 {
ProxySettingTool *tool = [ProxySettingTool new];
NSString *err = [tool setupAuth:authData];
if (err != nil) {
reply(err);
return;
}
[tool disableProxy];
reply(nil);
}
@end

View File

@ -0,0 +1,26 @@
//
// ProxyConfigRemoteProcessProtocol.h
// com.west2online.ClashX.ProxyConfigHelper
//
// Created by yichengchen on 2019/8/17.
// Copyright © 2019 west2online. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef void(^stringReplyBlock)(NSString *);
typedef void(^boolReplyBlock)(BOOL);
@protocol ProxyConfigRemoteProcessProtocol <NSObject>
@required
- (void)getVersion:(stringReplyBlock)reply;
- (void)enableProxyWithPort:(int)port
socksPort:(int)socksPort
authData:(NSData *)authData
error:(stringReplyBlock)reply;
- (void)disableProxyWithAuthData:(NSData *)authData error:(stringReplyBlock)reply;
@end

View File

@ -0,0 +1,20 @@
//
// ProxySettingTool.h
// com.west2online.ClashX.ProxyConfigHelper
//
// Created by yichengchen on 2019/8/17.
// Copyright © 2019 west2online. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ProxySettingTool : NSObject
- (NSString *)setupAuth:(NSData *)authData;
- (void)enableProxyWithport:(int)port socksPort:(int)socksPort;
- (void)disableProxy;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,199 @@
//
// ProxySettingTool.m
// com.west2online.ClashX.ProxyConfigHelper
//
// Created by yichengchen on 2019/8/17.
// Copyright © 2019 west2online. All rights reserved.
//
#import "ProxySettingTool.h"
#import <SystemConfiguration/SystemConfiguration.h>
#import <AppKit/AppKit.h>
@interface ProxySettingTool()
@property (nonatomic, assign) AuthorizationRef authRef;
@end
@implementation ProxySettingTool
// MARK: - Public
- (void)enableProxyWithport:(int)port socksPort:(int)socksPort {
[self applySCNetworkSettingWithRef:^(SCPreferencesRef ref) {
[self getDiviceListWithPrefRef:ref devices:^(NSString *key) {
[self enableProxySettings:ref interface:key port:port socksPort:socksPort];
}];
}];
}
- (void)disableProxy {
[self applySCNetworkSettingWithRef:^(SCPreferencesRef ref) {
[self getDiviceListWithPrefRef:ref devices:^(NSString *key) {
[self disableProxySetting:ref interface:key];
}];
}];
}
// MARK: - Private
- (void)dealloc {
[self freeAuth];
}
- (NSArray<NSString *> *)getIgnoreList {
NSString *configPath = [NSHomeDirectory() stringByAppendingString:@"/.config/clash/proxyIgnoreList.plist"];
if ([NSFileManager.defaultManager fileExistsAtPath:configPath]) {
NSArray *arr = [[NSArray alloc] initWithContentsOfFile:configPath];
if (arr != nil && arr.count > 0 && [arr containsObject:@"127.0.0.1"]) {
return arr;
}
}
NSArray *ignoreList = @[
@"192.168.0.0/16",
@"10.0.0.0/8",
@"172.16.0.0/12",
@"127.0.0.1",
@"localhost",
@"*.local",
@"*.crashlytics.com"
];
return ignoreList;
}
- (NSDictionary *)getProxySetting:(BOOL)enable port:(int) port socksPort: (int)socksPort {
NSMutableDictionary *proxySettings = [NSMutableDictionary dictionary];
NSString *ip = enable ? @"127.0.0.1" : @"";
NSInteger enableInt = enable ? 1 : 0;
proxySettings[(NSString *)kCFNetworkProxiesHTTPProxy] = ip;
proxySettings[(NSString *)kCFNetworkProxiesHTTPEnable] = @(enableInt);
proxySettings[(NSString *)kCFNetworkProxiesHTTPSProxy] = ip;
proxySettings[(NSString *)kCFNetworkProxiesHTTPSEnable] = @(enableInt);
proxySettings[(NSString *)kCFNetworkProxiesSOCKSProxy] = ip;
proxySettings[(NSString *)kCFNetworkProxiesSOCKSEnable] = @(enableInt);
if (enable) {
proxySettings[(NSString *)kCFNetworkProxiesHTTPPort] = @(port);
proxySettings[(NSString *)kCFNetworkProxiesHTTPSPort] = @(port);
proxySettings[(NSString *)kCFNetworkProxiesSOCKSPort] = @(socksPort);
} else {
proxySettings[(NSString *)kCFNetworkProxiesHTTPPort] = nil;
proxySettings[(NSString *)kCFNetworkProxiesHTTPSPort] = nil;
proxySettings[(NSString *)kCFNetworkProxiesSOCKSPort] = nil;
}
proxySettings[(NSString *)kCFNetworkProxiesExceptionsList] = [self getIgnoreList];
return proxySettings;
}
- (NSString *)proxySettingPathWithInterface:(NSString *)interfaceKey {
return [NSString stringWithFormat:@"/%@/%@/%@",
(NSString *)kSCPrefNetworkServices,
interfaceKey,
(NSString *)kSCEntNetProxies];
}
- (void)enableProxySettings:(SCPreferencesRef)prefs
interface:(NSString *)interfaceKey
port:(int) port
socksPort:(int) socksPort {
NSDictionary *proxySettings = [self getProxySetting:YES port:port socksPort:socksPort];
NSString *path = [self proxySettingPathWithInterface:interfaceKey];
SCPreferencesPathSetValue(prefs,
(__bridge CFStringRef)path,
(__bridge CFDictionaryRef)proxySettings);
}
- (void)disableProxySetting:(SCPreferencesRef)prefs
interface:(NSString *)interfaceKey {
NSDictionary *proxySettings = [self getProxySetting:NO port:0 socksPort:0];
NSString *path = [self proxySettingPathWithInterface:interfaceKey];
SCPreferencesPathSetValue(prefs,
(__bridge CFStringRef)path,
(__bridge CFDictionaryRef)proxySettings);
}
- (void)getDiviceListWithPrefRef:(SCPreferencesRef)ref devices:(void(^)(NSString *))callback {
NSDictionary *sets = (__bridge NSDictionary *)SCPreferencesGetValue(ref, kSCPrefNetworkServices);
for (NSString *key in [sets allKeys]) {
NSMutableDictionary *dict = [sets objectForKey:key];
NSString *hardware = [dict valueForKeyPath:@"Interface.Hardware"];
if ([hardware isEqualToString:@"AirPort"]
|| [hardware isEqualToString:@"Wi-Fi"]
|| [hardware isEqualToString:@"Ethernet"]) {
callback(key);
}
}
}
- (void)applySCNetworkSettingWithRef:(void(^)(SCPreferencesRef))callback {
SCPreferencesRef ref = SCPreferencesCreateWithAuthorization(nil, CFSTR("com.west2online.ClashX.ProxyConfigHelper.config"), nil, self.authRef);
if (!ref) {
return;
}
callback(ref);
SCPreferencesCommitChanges(ref);
SCPreferencesApplyChanges(ref);
SCPreferencesSynchronize(ref);
}
- (AuthorizationFlags)authFlags {
AuthorizationFlags authFlags = kAuthorizationFlagDefaults
| kAuthorizationFlagExtendRights
| kAuthorizationFlagInteractionAllowed
| kAuthorizationFlagPreAuthorize;
return authFlags;
}
- (NSString *)setupAuth:(NSData *)authData {
if (authData.length == 0 || authData.length != kAuthorizationExternalFormLength) {
return @"PrivilegedTaskRunnerHelper: Authorization data is malformed";
}
AuthorizationRef authRef;
OSStatus status = AuthorizationCreateFromExternalForm([authData bytes],&authRef);
if (status != errAuthorizationSuccess) {
return @"AuthorizationCreateFromExternalForm fail";
}
NSString *authName = @"com.west2online.ClashX.ProxyConfigHelper.config";
AuthorizationItem authItem = {authName.UTF8String, 0, NULL, 0};
AuthorizationRights authRight = {1, &authItem};
AuthorizationFlags authFlags = [self authFlags];
status = AuthorizationCopyRights(authRef, &authRight, nil, authFlags, nil);
if (status != errAuthorizationSuccess) {
AuthorizationFree(authRef, authFlags);
return @"AuthorizationCopyRights fail";
}
OSStatus authErr = AuthorizationCreate(nil, kAuthorizationEmptyEnvironment, authFlags, &authRef);
if (authErr != noErr) {
AuthorizationFree(authRef, authFlags);
return @"AuthorizationCreate fail";
}
self.authRef = authRef;
return nil;
}
- (void)freeAuth {
if (self.authRef) {
AuthorizationFree(self.authRef, [self authFlags]);
}
}
@end

17
ProxyConfigHelper/main.m Normal file
View File

@ -0,0 +1,17 @@
//
// main.m
// ProxyConfigHelper
//
// Created by yichengchen on 2019/8/16.
// Copyright © 2019 west2online. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ProxyConfigHelper.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[ProxyConfigHelper new] run];
NSLog(@"ProxyConfigHelper exit");
}
return 0;
}

441
SMJobBlessUtil.py Normal file
View File

@ -0,0 +1,441 @@
#! /usr/bin/python
#
# File: SMJobBlessUtil.py
#
# Contains: Tool for checking and correcting apps that use SMJobBless.
#
# Written by: DTS
#
# Copyright: Copyright (c) 2012 Apple Inc. All Rights Reserved.
#
# Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
# ("Apple") in consideration of your agreement to the following
# terms, and your use, installation, modification or
# redistribution of this Apple software constitutes acceptance of
# these terms. If you do not agree with these terms, please do
# not use, install, modify or redistribute this Apple software.
#
# In consideration of your agreement to abide by the following
# terms, and subject to these terms, Apple grants you a personal,
# non-exclusive license, under Apple's copyrights in this
# original Apple software (the "Apple Software"), to use,
# reproduce, modify and redistribute the Apple Software, with or
# without modifications, in source and/or binary forms; provided
# that if you redistribute the Apple Software in its entirety and
# without modifications, you must retain this notice and the
# following text and disclaimers in all such redistributions of
# the Apple Software. Neither the name, trademarks, service marks
# or logos of Apple Inc. may be used to endorse or promote
# products derived from the Apple Software without specific prior
# written permission from Apple. Except as expressly stated in
# this notice, no other rights or licenses, express or implied,
# are granted by Apple herein, including but not limited to any
# patent rights that may be infringed by your derivative works or
# by other works in which the Apple Software may be incorporated.
#
# The Apple Software is provided by Apple on an "AS IS" basis.
# APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
# WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
# THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
# COMBINATION WITH YOUR PRODUCTS.
#
# IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
# INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
# OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
# OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
# OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
# OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
import sys
import os
import getopt
import subprocess
import plistlib
import operator
class UsageException (Exception):
"""
Raised when the progam detects a usage issue; the top-level code catches this
and prints a usage message.
"""
pass
class CheckException (Exception):
"""
Raised when the "check" subcommand detects a problem; the top-level code catches
this and prints a nice error message.
"""
def __init__(self, message, path=None):
self.message = message
self.path = path
def checkCodeSignature(programPath, programType):
"""Checks the code signature of the referenced program."""
# Use the codesign tool to check the signature. The second "-v" is required to enable
# verbose mode, which causes codesign to do more checking. By default it does the minimum
# amount of checking ("Is the program properly signed?"). If you enabled verbose mode it
# does other sanity checks, which we definitely want. The specific thing I'd like to
# detect is "Does the code satisfy its own designated requirement?" and I need to enable
# verbose mode to get that.
args = [
# "false",
"codesign",
"-v",
"-v",
programPath
]
try:
subprocess.check_call(args, stderr=open("/dev/null"))
except subprocess.CalledProcessError, e:
raise CheckException("%s code signature invalid" % programType, programPath)
def readDesignatedRequirement(programPath, programType):
"""Returns the designated requirement of the program as a string."""
args = [
# "false",
"codesign",
"-d",
"-r",
"-",
programPath
]
try:
req = subprocess.check_output(args, stderr=open("/dev/null"))
except subprocess.CalledProcessError, e:
raise CheckException("%s designated requirement unreadable" % programType, programPath)
reqLines = req.splitlines()
if len(reqLines) != 1 or not req.startswith("designated => "):
raise CheckException("%s designated requirement malformed" % programType, programPath)
return reqLines[0][len("designated => "):]
def readInfoPlistFromPath(infoPath):
"""Reads an "Info.plist" file from the specified path."""
try:
info = plistlib.readPlist(infoPath)
except:
raise CheckException("'Info.plist' not readable", infoPath)
if not isinstance(info, dict):
raise CheckException("'Info.plist' root must be a dictionary", infoPath)
return info
def readPlistFromToolSection(toolPath, segmentName, sectionName):
"""Reads a dictionary property list from the specified section within the specified executable."""
# Run otool -s to get a hex dump of the section.
args = [
# "false",
"otool",
"-s",
segmentName,
sectionName,
toolPath
]
try:
plistDump = subprocess.check_output(args)
except subprocess.CalledProcessError, e:
raise CheckException("tool %s / %s section unreadable" % (segmentName, sectionName), toolPath)
# Convert that hex dump to an property list.
plistLines = plistDump.splitlines()
if len(plistLines) < 3 or plistLines[1] != ("Contents of (%s,%s) section" % (segmentName, sectionName)):
raise CheckException("tool %s / %s section dump malformed (1)" % (segmentName, sectionName), toolPath)
del plistLines[0:2]
try:
bytes = []
for line in plistLines:
# line looks like this:
#
# '0000000100000b80\t3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 '
columns = line.split("\t")
assert len(columns) == 2
for hexStr in columns[1].split():
bytes.append(int(hexStr, 16))
plist = plistlib.readPlistFromString(bytearray(bytes))
except:
raise CheckException("tool %s / %s section dump malformed (2)" % (segmentName, sectionName), toolPath)
# Check the root of the property list.
if not isinstance(plist, dict):
raise CheckException("tool %s / %s property list root must be a dictionary" % (segmentName, sectionName), toolPath)
return plist
def checkStep1(appPath):
"""Checks that the app and the tool are both correctly code signed."""
if not os.path.isdir(appPath):
raise CheckException("app not found", appPath)
# Check the app's code signature.
checkCodeSignature(appPath, "app")
# Check the tool directory.
toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
if not os.path.isdir(toolDirPath):
raise CheckException("tool directory not found", toolDirPath)
# Check each tool's code signature.
toolPathList = []
for toolName in os.listdir(toolDirPath):
if toolName != ".DS_Store":
toolPath = os.path.join(toolDirPath, toolName)
if not os.path.isfile(toolPath):
raise CheckException("tool directory contains a directory", toolPath)
checkCodeSignature(toolPath, "tool")
toolPathList.append(toolPath)
# Check that we have at least one tool.
if len(toolPathList) == 0:
raise CheckException("no tools found", toolDirPath)
return toolPathList
def checkStep2(appPath, toolPathList):
"""Checks the SMPrivilegedExecutables entry in the app's "Info.plist"."""
# Create a map from the tool name (not path) to its designated requirement.
toolNameToReqMap = dict()
for toolPath in toolPathList:
req = readDesignatedRequirement(toolPath, "tool")
toolNameToReqMap[os.path.basename(toolPath)] = req
# Read the Info.plist for the app and extract the SMPrivilegedExecutables value.
infoPath = os.path.join(appPath, "Contents", "Info.plist")
info = readInfoPlistFromPath(infoPath)
if not info.has_key("SMPrivilegedExecutables"):
raise CheckException("'SMPrivilegedExecutables' not found", infoPath)
infoToolDict = info["SMPrivilegedExecutables"]
if not isinstance(infoToolDict, dict):
raise CheckException("'SMPrivilegedExecutables' must be a dictionary", infoPath)
# Check that the list of tools matches the list of SMPrivilegedExecutables entries.
if sorted(infoToolDict.keys()) != sorted(toolNameToReqMap.keys()):
raise CheckException("'SMPrivilegedExecutables' and tools in 'Contents/Library/LaunchServices' don't match")
# Check that all the requirements match.
# This is an interesting policy choice. Technically the tool just needs to match
# the requirement listed in SMPrivilegedExecutables, and we can check that by
# putting the requirement into tmp.req and then running
#
# $ codesign -v -R tmp.req /path/to/tool
#
# However, for a Developer ID signed tool we really want to have the SMPrivilegedExecutables
# entry contain the tool's designated requirement because Xcode has built a
# more complex DR that does lots of useful and important checks. So, as a matter
# of policy we require that the value in SMPrivilegedExecutables match the tool's DR.
for toolName in infoToolDict:
if infoToolDict[toolName] != toolNameToReqMap[toolName]:
raise CheckException("tool designated requirement (%s) doesn't match entry in 'SMPrivilegedExecutables' (%s)" % (toolNameToReqMap[toolName], infoToolDict[toolName]))
def checkStep3(appPath, toolPathList):
"""Checks the "Info.plist" embedded in each helper tool."""
# First get the app's designated requirement.
appReq = readDesignatedRequirement(appPath, "app")
# Then check that the tool's SMAuthorizedClients value matches it.
for toolPath in toolPathList:
info = readPlistFromToolSection(toolPath, "__TEXT", "__info_plist")
if not info.has_key("CFBundleInfoDictionaryVersion") or info["CFBundleInfoDictionaryVersion"] != "6.0":
raise CheckException("'CFBundleInfoDictionaryVersion' in tool __TEXT / __info_plist section must be '6.0'", toolPath)
if not info.has_key("CFBundleIdentifier") or info["CFBundleIdentifier"] != os.path.basename(toolPath):
raise CheckException("'CFBundleIdentifier' in tool __TEXT / __info_plist section must match tool name", toolPath)
if not info.has_key("SMAuthorizedClients"):
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section not found", toolPath)
infoClientList = info["SMAuthorizedClients"]
if not isinstance(infoClientList, list):
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must be an array", toolPath)
if len(infoClientList) != 1:
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must have one entry", toolPath)
# Again, as a matter of policy we require that the SMAuthorizedClients entry must
# match exactly the designated requirement of the app.
if infoClientList[0] != appReq:
raise CheckException("app designated requirement (%s) doesn't match entry in 'SMAuthorizedClients' (%s)" % (appReq, infoClientList[0]), toolPath)
def checkStep4(appPath, toolPathList):
"""Checks the "launchd.plist" embedded in each helper tool."""
for toolPath in toolPathList:
launchd = readPlistFromToolSection(toolPath, "__TEXT", "__launchd_plist")
if not launchd.has_key("Label") or launchd["Label"] != os.path.basename(toolPath):
raise CheckException("'Label' in tool __TEXT / __launchd_plist section must match tool name", toolPath)
# We don't need to check that the label matches the bundle identifier because
# we know it matches the tool name and step 4 checks that the tool name matches
# the bundle identifier.
def checkStep5(appPath):
"""There's nothing to do here; we effectively checked for this is steps 1 and 2."""
pass
def check(appPath):
"""Checks the SMJobBless setup of the specified app."""
# Each of the following steps matches a bullet point in the SMJobBless header doc.
toolPathList = checkStep1(appPath)
checkStep2(appPath, toolPathList)
checkStep3(appPath, toolPathList)
checkStep4(appPath, toolPathList)
checkStep5(appPath)
def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
"""
Reads information from the built app and uses it to set the SMJobBless setup
in the specified app and tool Info.plist source files.
"""
if not os.path.isdir(appPath):
raise CheckException("app not found", appPath)
if not os.path.isfile(appInfoPlistPath):
raise CheckException("app 'Info.plist' not found", appInfoPlistPath)
for toolInfoPlistPath in toolInfoPlistPaths:
if not os.path.isfile(toolInfoPlistPath):
raise CheckException("app 'Info.plist' not found", toolInfoPlistPath)
# Get the designated requirement for the app and each of the tools.
appReq = readDesignatedRequirement(appPath, "app")
toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
if not os.path.isdir(toolDirPath):
raise CheckException("tool directory not found", toolDirPath)
toolNameToReqMap = {}
print os.listdir(toolDirPath)
for toolName in os.listdir(toolDirPath):
req = readDesignatedRequirement(os.path.join(toolDirPath, toolName), "tool")
print '-----'
print toolName
print '-----'
toolNameToReqMap[toolName] = req
if len(toolNameToReqMap) > len(toolInfoPlistPaths):
raise CheckException("tool directory has more tools (%d) than you've supplied tool 'Info.plist' paths (%d)" % (len(toolNameToReqMap), len(toolInfoPlistPaths)), toolDirPath)
if len(toolNameToReqMap) < len(toolInfoPlistPaths):
raise CheckException("tool directory has fewer tools (%d) than you've supplied tool 'Info.plist' paths (%d)" % (len(toolNameToReqMap), len(toolInfoPlistPaths)), toolDirPath)
# Build the new value for SMPrivilegedExecutables.
appToolDict = {}
toolInfoPlistPathToToolInfoMap = {}
for toolInfoPlistPath in toolInfoPlistPaths:
toolInfo = readInfoPlistFromPath(toolInfoPlistPath)
toolInfoPlistPathToToolInfoMap[toolInfoPlistPath] = toolInfo
if not toolInfo.has_key("CFBundleIdentifier"):
raise CheckException("'CFBundleIdentifier' not found", toolInfoPlistPath)
bundleID = toolInfo["CFBundleIdentifier"]
if not isinstance(bundleID, basestring):
raise CheckException("'CFBundleIdentifier' must be a string", toolInfoPlistPath)
appToolDict[bundleID] = toolNameToReqMap[bundleID]
# Set the SMPrivilegedExecutables value in the app "Info.plist".
appInfo = readInfoPlistFromPath(appInfoPlistPath)
needsUpdate = not appInfo.has_key("SMPrivilegedExecutables")
if not needsUpdate:
oldAppToolDict = appInfo["SMPrivilegedExecutables"]
if not isinstance(oldAppToolDict, dict):
raise CheckException("'SMPrivilegedExecutables' must be a dictionary", appInfoPlistPath)
appToolDictSorted = sorted(appToolDict.iteritems(), key=operator.itemgetter(0))
oldAppToolDictSorted = sorted(oldAppToolDict.iteritems(), key=operator.itemgetter(0))
needsUpdate = (appToolDictSorted != oldAppToolDictSorted)
if needsUpdate:
appInfo["SMPrivilegedExecutables"] = appToolDict
plistlib.writePlist(appInfo, appInfoPlistPath)
print >> sys.stdout, "%s: updated" % appInfoPlistPath
# Set the SMAuthorizedClients value in each tool's "Info.plist".
toolAppListSorted = [ appReq ] # only one element, so obviously sorted (-:
for toolInfoPlistPath in toolInfoPlistPaths:
toolInfo = toolInfoPlistPathToToolInfoMap[toolInfoPlistPath]
needsUpdate = not toolInfo.has_key("SMAuthorizedClients")
if not needsUpdate:
oldToolAppList = toolInfo["SMAuthorizedClients"]
if not isinstance(oldToolAppList, list):
raise CheckException("'SMAuthorizedClients' must be an array", toolInfoPlistPath)
oldToolAppListSorted = sorted(oldToolAppList)
needsUpdate = (toolAppListSorted != oldToolAppListSorted)
if needsUpdate:
toolInfo["SMAuthorizedClients"] = toolAppListSorted
plistlib.writePlist(toolInfo, toolInfoPlistPath)
print >> sys.stdout, "%s: updated" % toolInfoPlistPath
def main():
options, appArgs = getopt.getopt(sys.argv[1:], "d")
debug = False
for opt, val in options:
if opt == "-d":
debug = True
else:
raise UsageException()
if len(appArgs) == 0:
raise UsageException()
command = appArgs[0]
if command == "check":
if len(appArgs) != 2:
raise UsageException()
check(appArgs[1])
elif command == "setreq":
if len(appArgs) < 4:
raise UsageException()
setreq(appArgs[1], appArgs[2], appArgs[3:])
else:
raise UsageException()
if __name__ == "__main__":
try:
main()
except CheckException, e:
if e.path is None:
print >> sys.stderr, "%s: %s" % (os.path.basename(sys.argv[0]), e.message)
else:
path = e.path
if path.endswith("/"):
path = path[:-1]
print >> sys.stderr, "%s: %s" % (path, e.message)
sys.exit(1)
except UsageException, e:
print >> sys.stderr, "usage: %s check /path/to/app" % os.path.basename(sys.argv[0])
print >> sys.stderr, " %s setreq /path/to/app /path/to/app/Info.plist /path/to/tool/Info.plist..." % os.path.basename(sys.argv[0])
sys.exit(1)