Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Use launchd mechanism for installation

  • Loading branch information...
commit 357abb93d358ce37f5025914d2638d3f1a68143a 1 parent 3e3b94a
@aleffert authored
View
195 Appstagram.xcodeproj/project.pbxproj
@@ -12,6 +12,15 @@
77200C8F1537DFF800831670 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 77200C8E1537DFF800831670 /* main.m */; };
77200C931537DFF800831670 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 77200C911537DFF800831670 /* Credits.rtf */; };
77200C991537DFF800831670 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77200C971537DFF800831670 /* MainMenu.xib */; };
+ 775EDD6115695FF300722E8C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77114DCF1533D07700EF693C /* Foundation.framework */; };
+ 775EDD6415695FF300722E8C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 775EDD6315695FF300722E8C /* main.m */; };
+ 775EDD761569677000722E8C /* com.ognid.install-appstagram in CopyFiles */ = {isa = PBXBuildFile; fileRef = 775EDD5F15695FF300722E8C /* com.ognid.install-appstagram */; };
+ 775EDD7815696C3300722E8C /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 775EDD7715696C3300722E8C /* ServiceManagement.framework */; };
+ 775EDD7C15696E4500722E8C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 775EDD7B15696E4500722E8C /* Security.framework */; };
+ 775EDD8515697AE200722E8C /* BetterAuthorizationSampleLib.c in Sources */ = {isa = PBXBuildFile; fileRef = 775EDD8315697AE200722E8C /* BetterAuthorizationSampleLib.c */; };
+ 775EDD86156980AE00722E8C /* AppstagramCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 778B29FB1537E1FB00F31379 /* AppstagramCommon.m */; };
+ 775EDD87156983D000722E8C /* BetterAuthorizationSampleLib.c in Sources */ = {isa = PBXBuildFile; fileRef = 775EDD8315697AE200722E8C /* BetterAuthorizationSampleLib.c */; };
+ 775EDD88156983DD00722E8C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 775EDD7B15696E4500722E8C /* Security.framework */; };
778B29F71537E0FA00F31379 /* AppstagramDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 778B29F61537E0FA00F31379 /* AppstagramDelegate.m */; };
778B29FC1537E1FB00F31379 /* AppstagramCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 778B29FB1537E1FB00F31379 /* AppstagramCommon.m */; };
77956DA2153B7713007C8424 /* menu-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 77956DA1153B7713007C8424 /* menu-icon.png */; };
@@ -38,6 +47,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ 775EDD73156963FC00722E8C /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 77114DBE1533D07700EF693C /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 775EDD5E15695FF300722E8C;
+ remoteInfo = "install-appstagram";
+ };
7780DDF21564B2F7007FA24D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 77114DBE1533D07700EF693C /* Project object */;
@@ -47,6 +63,28 @@
};
/* End PBXContainerItemProxy section */
+/* Begin PBXCopyFilesBuildPhase section */
+ 775EDD5D15695FF300722E8C /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = /usr/share/man/man1/;
+ dstSubfolderSpec = 0;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 1;
+ };
+ 775EDD751569676900722E8C /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = Contents/Library/LaunchServices;
+ dstSubfolderSpec = 1;
+ files = (
+ 775EDD761569677000722E8C /* com.ognid.install-appstagram in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
77114DCA1533D07700EF693C /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
77114DCD1533D07700EF693C /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
@@ -67,6 +105,15 @@
773B938F156014CD00C75B2A /* sun-overlay.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "sun-overlay.png"; sourceTree = SOURCE_ROOT; };
77548167153D1FC80073A599 /* AppstagramFilterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppstagramFilterView.h; path = Appstagram/Source/AppstagramFilterView.h; sourceTree = "<group>"; };
77548168153D1FC80073A599 /* AppstagramFilterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppstagramFilterView.m; path = Appstagram/Source/AppstagramFilterView.m; sourceTree = "<group>"; };
+ 775EDD5F15695FF300722E8C /* com.ognid.install-appstagram */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "com.ognid.install-appstagram"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 775EDD6315695FF300722E8C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ 775EDD6615695FF300722E8C /* install-appstagram-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "install-appstagram-Prefix.pch"; sourceTree = "<group>"; };
+ 775EDD7715696C3300722E8C /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; };
+ 775EDD7915696DA500722E8C /* install-appstagram-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "install-appstagram-Info.plist"; sourceTree = "<group>"; };
+ 775EDD7A15696DA500722E8C /* install-appstagram-Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "install-appstagram-Launchd.plist"; sourceTree = "<group>"; };
+ 775EDD7B15696E4500722E8C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
+ 775EDD8315697AE200722E8C /* BetterAuthorizationSampleLib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = BetterAuthorizationSampleLib.c; path = Source/BetterAuthorizationSampleLib.c; sourceTree = "<group>"; };
+ 775EDD8415697AE200722E8C /* BetterAuthorizationSampleLib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BetterAuthorizationSampleLib.h; path = Source/BetterAuthorizationSampleLib.h; sourceTree = "<group>"; };
777B8C36154F7FAA00F8C04F /* paper-overlay.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "paper-overlay.png"; sourceTree = SOURCE_ROOT; };
7781450A153D12A10049EEAE /* AppstagramOverlayWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppstagramOverlayWindow.h; path = Source/AppstagramOverlayWindow.h; sourceTree = "<group>"; };
7781450B153D12A10049EEAE /* AppstagramOverlayWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppstagramOverlayWindow.m; path = Source/AppstagramOverlayWindow.m; sourceTree = "<group>"; };
@@ -97,12 +144,23 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 775EDD7C15696E4500722E8C /* Security.framework in Frameworks */,
+ 775EDD7815696C3300722E8C /* ServiceManagement.framework in Frameworks */,
77A3B13E15535EA000B8B446 /* ScriptingBridge.framework in Frameworks */,
77AF4DDD153A1864002E01E2 /* QuartzCore.framework in Frameworks */,
77200C871537DFF800831670 /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 775EDD5C15695FF300722E8C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 775EDD6115695FF300722E8C /* Foundation.framework in Frameworks */,
+ 775EDD88156983DD00722E8C /* Security.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
77F1A3921564ACBD00AD3518 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -121,6 +179,7 @@
778B29FE1537E20600F31379 /* Common */,
77114DD01533D07700EF693C /* AppstagramOSAX */,
77200C881537DFF800831670 /* Appstagram */,
+ 775EDD6215695FF300722E8C /* install-appstagram */,
77114DC81533D07700EF693C /* Products */,
77114DC91533D07700EF693C /* Frameworks */,
77F1A3A11564ACBE00AD3518 /* AppstagramOSAX copy-Info.plist */,
@@ -132,6 +191,7 @@
children = (
77200C851537DFF800831670 /* Appstagram.app */,
77F1A3A01564ACBD00AD3518 /* AppstagramOSAX.osax */,
+ 775EDD5F15695FF300722E8C /* com.ognid.install-appstagram */,
);
name = Products;
sourceTree = "<group>";
@@ -139,6 +199,8 @@
77114DC91533D07700EF693C /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 775EDD7B15696E4500722E8C /* Security.framework */,
+ 775EDD7715696C3300722E8C /* ServiceManagement.framework */,
77114DCD1533D07700EF693C /* AppKit.framework */,
77114DCE1533D07700EF693C /* CoreData.framework */,
77114DCF1533D07700EF693C /* Foundation.framework */,
@@ -184,6 +246,8 @@
77200C881537DFF800831670 /* Appstagram */ = {
isa = PBXGroup;
children = (
+ 775EDD8315697AE200722E8C /* BetterAuthorizationSampleLib.c */,
+ 775EDD8415697AE200722E8C /* BetterAuthorizationSampleLib.h */,
778B29F51537E0FA00F31379 /* AppstagramDelegate.h */,
778B29F61537E0FA00F31379 /* AppstagramDelegate.m */,
77200C891537DFF800831670 /* Supporting Files */,
@@ -206,6 +270,25 @@
name = "Supporting Files";
sourceTree = "<group>";
};
+ 775EDD6215695FF300722E8C /* install-appstagram */ = {
+ isa = PBXGroup;
+ children = (
+ 775EDD6315695FF300722E8C /* main.m */,
+ 775EDD7915696DA500722E8C /* install-appstagram-Info.plist */,
+ 775EDD7A15696DA500722E8C /* install-appstagram-Launchd.plist */,
+ 775EDD6515695FF300722E8C /* Supporting Files */,
+ );
+ path = "install-appstagram";
+ sourceTree = "<group>";
+ };
+ 775EDD6515695FF300722E8C /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 775EDD6615695FF300722E8C /* install-appstagram-Prefix.pch */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
778B29FE1537E20600F31379 /* Common */ = {
isa = PBXGroup;
children = (
@@ -230,10 +313,12 @@
77200C811537DFF800831670 /* Sources */,
77200C821537DFF800831670 /* Frameworks */,
77200C831537DFF800831670 /* Resources */,
+ 775EDD751569676900722E8C /* CopyFiles */,
);
buildRules = (
);
dependencies = (
+ 775EDD74156963FC00722E8C /* PBXTargetDependency */,
7780DDF31564B2F7007FA24D /* PBXTargetDependency */,
);
name = Appstagram;
@@ -241,6 +326,23 @@
productReference = 77200C851537DFF800831670 /* Appstagram.app */;
productType = "com.apple.product-type.application";
};
+ 775EDD5E15695FF300722E8C /* install-appstagram */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 775EDD6B15695FF300722E8C /* Build configuration list for PBXNativeTarget "install-appstagram" */;
+ buildPhases = (
+ 775EDD5B15695FF300722E8C /* Sources */,
+ 775EDD5C15695FF300722E8C /* Frameworks */,
+ 775EDD5D15695FF300722E8C /* CopyFiles */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "install-appstagram";
+ productName = "install-appstagram";
+ productReference = 775EDD5F15695FF300722E8C /* com.ognid.install-appstagram */;
+ productType = "com.apple.product-type.tool";
+ };
77F1A38A1564ACBD00AD3518 /* AppstagramOSAX */ = {
isa = PBXNativeTarget;
buildConfigurationList = 77F1A39D1564ACBD00AD3518 /* Build configuration list for PBXNativeTarget "AppstagramOSAX" */;
@@ -280,6 +382,7 @@
targets = (
77200C841537DFF800831670 /* Appstagram */,
77F1A38A1564ACBD00AD3518 /* AppstagramOSAX */,
+ 775EDD5E15695FF300722E8C /* install-appstagram */,
);
};
/* End PBXProject section */
@@ -323,6 +426,17 @@
77200C8F1537DFF800831670 /* main.m in Sources */,
778B29F71537E0FA00F31379 /* AppstagramDelegate.m in Sources */,
778B29FC1537E1FB00F31379 /* AppstagramCommon.m in Sources */,
+ 775EDD8515697AE200722E8C /* BetterAuthorizationSampleLib.c in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 775EDD5B15695FF300722E8C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 775EDD6415695FF300722E8C /* main.m in Sources */,
+ 775EDD86156980AE00722E8C /* AppstagramCommon.m in Sources */,
+ 775EDD87156983D000722E8C /* BetterAuthorizationSampleLib.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -342,6 +456,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 775EDD74156963FC00722E8C /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 775EDD5E15695FF300722E8C /* install-appstagram */;
+ targetProxy = 775EDD73156963FC00722E8C /* PBXContainerItemProxy */;
+ };
7780DDF31564B2F7007FA24D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 77F1A38A1564ACBD00AD3518 /* AppstagramOSAX */;
@@ -433,10 +552,12 @@
77200C9B1537DFF800831670 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CODE_SIGN_IDENTITY = Ognid;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Appstagram/Appstagram-Prefix.pch";
INFOPLIST_FILE = "Appstagram/Appstagram-Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
WRAPPER_EXTENSION = app;
};
name = Debug;
@@ -444,14 +565,80 @@
77200C9C1537DFF800831670 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CODE_SIGN_IDENTITY = Ognid;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Appstagram/Appstagram-Prefix.pch";
INFOPLIST_FILE = "Appstagram/Appstagram-Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
WRAPPER_EXTENSION = app;
};
name = Release;
};
+ 775EDD6915695FF300722E8C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = Ognid;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "install-appstagram/install-appstagram-Prefix.pch";
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ OTHER_LDFLAGS = (
+ "-sectcreate",
+ __TEXT,
+ __info_plist,
+ "install-appstagram/install-appstagram-Info.plist",
+ "-sectcreate",
+ __TEXT,
+ __launchd_plist,
+ "install-appstagram/install-appstagram-Launchd.plist",
+ );
+ PRODUCT_NAME = "com.ognid.install-appstagram";
+ PROVISIONING_PROFILE = "";
+ WARNING_LDFLAGS = (
+ "-sectcreate",
+ __TEXT,
+ __info_plist,
+ "install-appstagram/install-appstagram-Info.plist",
+ "-sectcreate",
+ __TEXT,
+ __launchd_plist,
+ "install-appstagram/install-appstagram-Launchd.plist",
+ );
+ };
+ name = Debug;
+ };
+ 775EDD6A15695FF300722E8C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = Ognid;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "install-appstagram/install-appstagram-Prefix.pch";
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ OTHER_LDFLAGS = (
+ "-sectcreate",
+ __TEXT,
+ __info_plist,
+ "install-appstagram/install-appstagram-Info.plist",
+ "-sectcreate",
+ __TEXT,
+ __launchd_plist,
+ "install-appstagram/install-appstagram-Launchd.plist",
+ );
+ PRODUCT_NAME = "com.ognid.install-appstagram";
+ PROVISIONING_PROFILE = "";
+ WARNING_LDFLAGS = (
+ "-sectcreate",
+ __TEXT,
+ __info_plist,
+ "install-appstagram/install-appstagram-Info.plist",
+ "-sectcreate",
+ __TEXT,
+ __launchd_plist,
+ "install-appstagram/install-appstagram-Launchd.plist",
+ );
+ };
+ name = Release;
+ };
77F1A39E1564ACBD00AD3518 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -501,6 +688,14 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 775EDD6B15695FF300722E8C /* Build configuration list for PBXNativeTarget "install-appstagram" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 775EDD6915695FF300722E8C /* Debug */,
+ 775EDD6A15695FF300722E8C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ };
77F1A39D1564ACBD00AD3518 /* Build configuration list for PBXNativeTarget "AppstagramOSAX" */ = {
isa = XCConfigurationList;
buildConfigurations = (
View
5 Appstagram/Appstagram-Info.plist
@@ -34,5 +34,10 @@
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
+ <key>SMPrivilegedExecutables</key>
+ <dict>
+ <key>com.ognid.install-appstagram</key>
+ <string>identifier &quot;com.ognid.install-appstagram&quot; and certificate leaf[subject.CN] = &quot;Ognid&quot;</string>
+ </dict>
</dict>
</plist>
View
2  Appstagram/Appstagram-Prefix.pch
@@ -4,6 +4,6 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
-
+#import <ServiceManagement/ServiceManagement.h>
#import <QuartzCore/CoreImage.h>
#endif
View
9 Appstagram/Source/AppstagramCommon.h
@@ -8,6 +8,15 @@
#import <Foundation/Foundation.h>
+
+#import "BetterAuthorizationSampleLib.h"
+
extern NSString* AppstagramFilterAnnouncementNotification;
extern NSString* AppstagramChangedNotification;
extern NSString* AppstagramFilterNameKey;
+
+extern NSString* AppstagramInstallationSourcePathKey;
+extern NSString* AppstagramInstallationCommand;
+extern NSString* AppstagramInstallationCommandResponseKey;
+
+extern const BASCommandSpec AppstagramPrivilegedHelperCommandSet[];
View
22 Appstagram/Source/AppstagramCommon.m
@@ -11,4 +11,24 @@
NSString* AppstagramFilterAnnouncementNotification = @"AppstagramFilterAnnouncementNotification";
NSString* AppstagramChangedNotification = @"AppstagramChangedNotification";
-NSString* AppstagramFilterNameKey = @"AppstagramFilterNameKey";
+NSString* AppstagramFilterNameKey = @"AppstagramFilterNameKey";
+
+NSString* AppstagramInstallationSourcePathKey = @"AppstagramInstallationSourcePathKey";
+NSString* AppstagramInstallationCommand = @"AppstagramInstallationCommand";
+NSString* AppstagramInstallationCommandResponseKey = @"AppstagramInstallationCommandResponseKey";
+
+const BASCommandSpec AppstagramPrivilegedHelperCommandSet[] = {
+ { "AppstagramInstallationCommand", // commandName
+ "com.ognid.install-appstagram", // rightName
+ "default", // rightDefaultRule -- by default, you have to have admin credentials (see the "default" rule in the authorization policy database, currently "/etc/authorization")
+ "AuthInstallCommandLineToolPrompt", // rightDescriptionKey -- key for custom prompt in "Localizable.strings
+ NULL // userData
+ },
+
+ { NULL, // the array is null terminated
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ }
+};
View
95 Appstagram/Source/AppstagramDelegate.m
@@ -9,6 +9,7 @@
#import "AppstagramDelegate.h"
#import "AppstagramCommon.h"
+#import "BetterAuthorizationSampleLib.h"
#import "CGSPrivate.h"
@@ -84,7 +85,7 @@ - (void)appOpened:(NSNotification*)notification {
}
- (NSString*)pluginSourcePath {
- return [[NSBundle mainBundle] pathForResource:@"AppstagramOSAX" ofType:@"bundle"];
+ return [[NSBundle mainBundle] pathForResource:@"AppstagramOSAX" ofType:@"osax"];
}
- (NSString*)pluginDestinationPath {
@@ -99,8 +100,98 @@ - (BOOL)isPluginInstalled {
return [self isPluginInstalledAtPath:[self pluginDestinationPath]];
}
+- (BOOL)blessHelperWithLabel:(NSString *)label error:(NSError **)error;
+{
+ BOOL result = NO;
+
+ AuthorizationItem authItem = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 };
+ AuthorizationRights authRights = { 1, &authItem };
+ AuthorizationFlags flags = kAuthorizationFlagDefaults |
+ kAuthorizationFlagInteractionAllowed |
+ kAuthorizationFlagPreAuthorize |
+ kAuthorizationFlagExtendRights;
+
+ AuthorizationRef authRef = NULL;
+
+ /* Obtain the right to install privileged helper tools (kSMRightBlessPrivilegedHelper). */
+ OSStatus status = AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &authRef);
+ if (status != errAuthorizationSuccess) {
+ NSLog(@"Failed to create AuthorizationRef, return code %i", status);
+ } else {
+ /* This does all the work of verifying the helper tool against the application
+ * and vice-versa. Once verification has passed, the embedded launchd.plist
+ * is extracted and placed in /Library/LaunchDaemons and then loaded. The
+ * executable is placed in /Library/PrivilegedHelperTools.
+ */
+ result = SMJobBless(kSMDomainSystemLaunchd, (CFStringRef)label, authRef, (CFErrorRef *)error);
+ }
+
+ AuthorizationFree(authRef, kAuthorizationFlagDefaults);
+
+ return result;
+}
+
+// Via http://www.stevestreeting.com/2012/03/05/follow-up-os-x-privilege-escalation-without-using-deprecated-methods/
+- (BOOL)helperInstallPlugin:(NSString*)pluginPath authorization:(AuthorizationRef)auth {
+ OSStatus err = noErr;
+ NSString* bundleID = @"com.appstagram.install-appstagram";
+ CFDictionaryRef response = NULL;
+
+ NSDictionary* request = [NSDictionary dictionaryWithObjectsAndKeys:
+ AppstagramInstallationCommand, @kBASCommandKey,
+ pluginPath, AppstagramInstallationSourcePathKey, nil];
+
+ // Execute it.
+
+ err = BASExecuteRequestInHelperTool(auth, AppstagramPrivilegedHelperCommandSet, (CFStringRef) bundleID, (CFDictionaryRef) request, &response);
+
+ // If the above went OK, it means that the IPC to the helper tool worked. We
+ // now have to check the response dictionary to see if the command's execution
+ // within the helper tool was successful.
+
+ if (err == noErr) {
+ err = BASGetErrorFromResponse(response);
+
+ NSString* respStr = [(NSDictionary *)response objectForKey:AppstagramInstallationCommandResponseKey];
+
+
+ if(respStr == nil) {
+ [NSAlert alertWithMessageText:@"Installation succeeded! You will need to log out and log back in for the changes to take effect." defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@""];
+ }
+ else {
+ [NSAlert alertWithMessageText:respStr defaultButton:@"Quit" alternateButton:nil otherButton:nil informativeTextWithFormat:@""];
+ }
+ [[NSApplication sharedApplication] terminate:self];
+ }
+
+ if (response)
+ CFRelease(response);
+
+ return (err == noErr);
+}
+
+// Via http://www.stevestreeting.com/2012/03/05/follow-up-os-x-privilege-escalation-without-using-deprecated-methods/
- (void)installPlugin {
- // TODO spawn installer
+ NSError* error = nil;
+ [self blessHelperWithLabel:@"com.ognid.install-appstagram" error:&error];
+ if(error != nil) {
+ [[NSAlert alertWithError:error] runModal];
+ [[NSApplication sharedApplication] terminate:self];
+ }
+ else {
+ // The helper is installed. Execute it
+ AuthorizationRef auth;
+ if (AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &auth)) {
+ [NSAlert alertWithMessageText:@"Unable to create authorization." defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@""];
+ [[NSApplication sharedApplication] terminate:self];
+ }
+
+ BASSetDefaultRules(auth,
+ AppstagramPrivilegedHelperCommandSet,
+ CFBundleGetIdentifier(CFBundleGetMainBundle()),
+ NULL); // No separate strings file, use Localizable.strings
+ [self helperInstallPlugin:[self pluginSourcePath] authorization:auth];
+ }
}
- (void)uninstallPlugin {
View
1,911 Appstagram/Source/BetterAuthorizationSampleLib.c
@@ -0,0 +1,1911 @@
+/*
+ File: BetterAuthorizationSampleLib.c
+
+ Contains: Implementation of reusable code for privileged helper tools.
+
+ Written by: DTS
+
+ Copyright: Copyright (c) 2007 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.
+
+*/
+
+// Define BAS_PRIVATE so that we pick up our private definitions from
+// "BetterAuthorizationSampleLib.h".
+
+#define BAS_PRIVATE 1
+
+#include "BetterAuthorizationSampleLib.h"
+
+#include <launch.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/event.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+
+// At runtime BAS only requires CoreFoundation. However, at build time we need
+// CoreServices for the various OSStatus error codes in "MacErrors.h". Thus, by default,
+// we include CoreServices at build time. However, you can flip this switch to check
+// that you're not accidentally using any other CoreServices things.
+
+#if 1
+ #include <CoreServices/CoreServices.h>
+#else
+ #warning Do not ship this way!
+ #include <CoreFoundation/CoreFoundation.h>
+ #include "/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/MacErrors.h"
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////
+#pragma mark ***** Constants
+
+enum {
+ kIdleTimeoutInSeconds = 120, // if we get no requests in 2 minutes, we quit
+ kWatchdogTimeoutInSeconds = 65 // any given request must be completely in 65 seconds
+};
+
+// IMPORTANT:
+// These values must be greater than 60 seconds. If a job runs for less than 60
+// seconds, launchd will consider it to have failed.
+
+// kBASMaxNumberOfKBytes has two uses:
+//
+// 1. When receiving a dictionary, it is used to limit the size of the incoming
+// data. This ensures that a non-privileged client can't exhaust the
+// address space of a privileged helper tool.
+//
+// 2. Because it's less than 4 GB, this limit ensures that the dictionary size
+// can be sent as an architecture-neutral uint32_t.
+
+#define kBASMaxNumberOfKBytes (1024 * 1024)
+
+// A hard-wired file system path for the UNIX domain socket; %s is the placeholder
+// for the bundle ID (in file system representation).
+
+#define kBASSocketPathFormat "/var/run/%s.socket"
+
+// The key used to get our describe our socket in the launchd property list file.
+
+#define kLaunchDSocketDictKey "MasterSocket"
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Common Code
+
+extern int BASOSStatusToErrno(OSStatus errNum)
+ // See comment in header.
+{
+ int retval;
+
+ #define CASE(ident) \
+ case k ## ident ## Err: \
+ retval = ident; \
+ break
+ switch (errNum) {
+ case noErr:
+ retval = 0;
+ break;
+ case kENORSRCErr:
+ retval = ESRCH; // no ENORSRC on Mac OS X, so use ESRCH
+ break;
+ case memFullErr:
+ retval = ENOMEM;
+ break;
+ CASE(EDEADLK);
+ CASE(EAGAIN);
+ case kEOPNOTSUPPErr:
+ retval = ENOTSUP;
+ break;
+ CASE(EPROTO);
+ CASE(ETIME);
+ CASE(ENOSR);
+ CASE(EBADMSG);
+ case kECANCELErr:
+ retval = ECANCELED; // note spelling difference
+ break;
+ CASE(ENOSTR);
+ CASE(ENODATA);
+ CASE(EINPROGRESS);
+ CASE(ESRCH);
+ CASE(ENOMSG);
+ default:
+ if ( (errNum <= kEPERMErr) && (errNum >= kENOMSGErr) ) {
+ retval = (-3200 - errNum) + 1; // OT based error
+ } else if ( (errNum >= errSecErrnoBase) && (errNum <= (errSecErrnoBase + ELAST)) ) {
+ retval = (int) errNum - errSecErrnoBase; // POSIX based error
+ } else {
+ retval = (int) errNum; // just return the value unmodified
+ }
+ }
+ #undef CASE
+ return retval;
+}
+
+extern OSStatus BASErrnoToOSStatus(int errNum)
+ // See comment in header.
+{
+ OSStatus retval;
+
+ if ( errNum == 0 ) {
+ retval = noErr;
+ } else if ( (errNum >= EPERM) && (errNum <= ELAST) ) {
+ retval = (OSStatus) errNum + errSecErrnoBase;
+ } else {
+ retval = (int) errNum; // just return the value unmodified
+ }
+
+ return retval;
+}
+
+static Boolean BASIsBinaryPropertyListData(const void * plistBuffer, size_t plistSize)
+ // Make sure that whatever is passed into the buffer that will
+ // eventually become a plist (and then sequentially a dictionary)
+ // is NOT in binary format.
+{
+ static const char kBASBinaryPlistWatermark[6] = "bplist";
+
+ assert(plistBuffer != NULL);
+
+ return (plistSize >= sizeof(kBASBinaryPlistWatermark))
+ && (memcmp(plistBuffer, kBASBinaryPlistWatermark, sizeof(kBASBinaryPlistWatermark)) == 0);
+}
+
+static void NormaliseOSStatusErrorCode(OSStatus *errPtr)
+ // Normalise the cancelled error code to reduce the number of checks that our clients
+ // have to do. I made this a function in case I ever want to expand this to handle
+ // more than just this one case.
+{
+ assert(errPtr != NULL);
+
+ if ( (*errPtr == errAuthorizationCanceled) || (*errPtr == (errSecErrnoBase + ECANCELED)) ) {
+ *errPtr = userCanceledErr;
+ }
+}
+
+static int BASRead(int fd, void *buf, size_t bufSize, size_t *bytesRead)
+ // A wrapper around <x-man-page://2/read> that keeps reading until either
+ // bufSize bytes are read or until EOF is encountered, in which case you get
+ // EPIPE.
+ //
+ // If bytesRead is not NULL, *bytesRead will be set to the number
+ // of bytes successfully read. On success, this will always be equal to
+ // bufSize. On error, it indicates how much was read before the error
+ // occurred (which could be zero).
+{
+ int err;
+ char * cursor;
+ size_t bytesLeft;
+ ssize_t bytesThisTime;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert(buf != NULL);
+ // bufSize may be 0
+ assert(bufSize <= kBASMaxNumberOfKBytes);
+ // bytesRead may be NULL
+
+ err = 0;
+ bytesLeft = bufSize;
+ cursor = (char *) buf;
+ while ( (err == 0) && (bytesLeft != 0) ) {
+ bytesThisTime = read(fd, cursor, bytesLeft);
+ if (bytesThisTime > 0) {
+ cursor += bytesThisTime;
+ bytesLeft -= bytesThisTime;
+ } else if (bytesThisTime == 0) {
+ err = EPIPE;
+ } else {
+ assert(bytesThisTime == -1);
+
+ err = errno;
+ assert(err != 0);
+ if (err == EINTR) {
+ err = 0; // let's loop again
+ }
+ }
+ }
+ if (bytesRead != NULL) {
+ *bytesRead = bufSize - bytesLeft;
+ }
+
+ return err;
+}
+
+static int BASWrite(int fd, const void *buf, size_t bufSize, size_t *bytesWritten)
+ // A wrapper around <x-man-page://2/write> that keeps writing until either
+ // all the data is written or an error occurs, in which case
+ // you get EPIPE.
+ //
+ // If bytesWritten is not NULL, *bytesWritten will be set to the number
+ // of bytes successfully written. On success, this will always be equal to
+ // bufSize. On error, it indicates how much was written before the error
+ // occurred (which could be zero).
+{
+ int err;
+ char * cursor;
+ size_t bytesLeft;
+ ssize_t bytesThisTime;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert(buf != NULL);
+ // bufSize may be 0
+ assert(bufSize <= kBASMaxNumberOfKBytes);
+ // bytesWritten may be NULL
+
+ // SIGPIPE occurs when you write to pipe or socket
+ // whose other end has been closed. The default action
+ // for SIGPIPE is to terminate the process. That's
+ // probably not what you wanted. So, in the debug build,
+ // we check that you've set the signal action to SIG_IGN
+ // (ignore). Of course, you could be building a program
+ // that needs SIGPIPE to work in some special way, in
+ // which case you should define BAS_WRITE_CHECK_SIGPIPE
+ // to 0 to bypass this check.
+
+ #if !defined(BAS_WRITE_CHECK_SIGPIPE)
+ #define BAS_WRITE_CHECK_SIGPIPE 1
+ #endif
+ #if !defined(NDEBUG) && BAS_WRITE_CHECK_SIGPIPE
+ {
+ int junk;
+ struct stat sb;
+ struct sigaction currentSignalState;
+ int val;
+ socklen_t valLen;
+
+ junk = fstat(fd, &sb);
+ assert(junk == 0);
+
+ if ( S_ISFIFO(sb.st_mode) || S_ISSOCK(sb.st_mode) ) {
+ junk = sigaction(SIGPIPE, NULL, &currentSignalState);
+ assert(junk == 0);
+
+ valLen = sizeof(val);
+ junk = getsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &val, &valLen);
+ assert(junk == 0);
+ assert(valLen == sizeof(val));
+
+ // If you hit this assertion, you need to either disable SIGPIPE in
+ // your process or on the specific socket you're writing to. The
+ // standard code for the former is:
+ //
+ // (void) signal(SIGPIPE, SIG_IGN);
+ //
+ // You typically add this code to your main function.
+ //
+ // The standard code for the latter is:
+ //
+ // static const int kOne = 1;
+ // err = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &kOne, sizeof(kOne));
+ //
+ // You typically do this just after creating the socket.
+
+ assert( (currentSignalState.sa_handler == SIG_IGN) || (val == 1) );
+ }
+ }
+ #endif
+
+ err = 0;
+ bytesLeft = bufSize;
+ cursor = (char *) buf;
+ while ( (err == 0) && (bytesLeft != 0) ) {
+ bytesThisTime = write(fd, cursor, bytesLeft);
+ if (bytesThisTime > 0) {
+ cursor += bytesThisTime;
+ bytesLeft -= bytesThisTime;
+ } else if (bytesThisTime == 0) {
+ assert(false);
+ err = EPIPE;
+ } else {
+ assert(bytesThisTime == -1);
+
+ err = errno;
+ assert(err != 0);
+ if (err == EINTR) {
+ err = 0; // let's loop again
+ }
+ }
+ }
+ if (bytesWritten != NULL) {
+ *bytesWritten = bufSize - bytesLeft;
+ }
+
+ return err;
+}
+
+static int BASReadDictionary(int fdIn, CFDictionaryRef *dictPtr)
+ // Create a CFDictionary by reading the XML data from fdIn.
+ // It first reads the size of the XML data, then allocates a
+ // buffer for that data, then reads the data in, and finally
+ // unflattens the data into a CFDictionary.
+ //
+ // On success, the caller is responsible for releasing *dictPtr.
+ //
+ // See also the companion routine, BASWriteDictionary, below.
+{
+ int err = 0;
+ uint32_t dictSize;
+ void * dictBuffer;
+ CFDataRef dictData;
+ CFPropertyListRef dict;
+
+ // Pre-conditions
+
+ assert(fdIn >= 0);
+ assert( dictPtr != NULL);
+ assert(*dictPtr == NULL);
+
+ dictBuffer = NULL;
+ dictData = NULL;
+ dict = NULL;
+
+ // Read the data size and allocate a buffer. Always read the length as a big-endian
+ // uint32_t, so that the app and the helper tool can be different architectures.
+
+ err = BASRead(fdIn, &dictSize, sizeof(dictSize), NULL);
+ if (err == 0) {
+ dictSize = OSSwapBigToHostInt32(dictSize);
+ if (dictSize == 0) {
+ // According to the C language spec malloc(0) may return NULL (although the Mac OS X
+ // malloc doesn't ever do this), so we specifically check for and error out in
+ // that case.
+ err = EINVAL;
+ } else if (dictSize > kBASMaxNumberOfKBytes) {
+ // Abitrary limit to prevent potentially hostile client overwhelming us with data.
+ err = EINVAL;
+ }
+ }
+ if (err == 0) {
+ dictBuffer = malloc( (size_t) dictSize);
+ if (dictBuffer == NULL) {
+ err = ENOMEM;
+ }
+ }
+
+ // Read the data and unflatten.
+
+ if (err == 0) {
+ err = BASRead(fdIn, dictBuffer, dictSize, NULL);
+ }
+ if ( (err == 0) && BASIsBinaryPropertyListData(dictBuffer, dictSize) ) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ if (err == 0) {
+ dictData = CFDataCreateWithBytesNoCopy(NULL, dictBuffer, dictSize, kCFAllocatorNull);
+ if (dictData == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+ if (err == 0) {
+ dict = CFPropertyListCreateFromXMLData(NULL, dictData, kCFPropertyListImmutable, NULL);
+ if (dict == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+ if ( (err == 0) && (CFGetTypeID(dict) != CFDictionaryGetTypeID()) ) {
+ err = EINVAL; // only CFDictionaries need apply
+ }
+ // CFShow(dict);
+
+ // Clean up.
+
+ if (err != 0) {
+ if (dict != NULL) {
+ CFRelease(dict);
+ }
+ dict = NULL;
+ }
+ *dictPtr = (CFDictionaryRef) dict;
+ free(dictBuffer);
+ if (dictData != NULL) {
+ CFRelease(dictData);
+ }
+
+ assert( (err == 0) == (*dictPtr != NULL) );
+
+ return err;
+}
+
+static int BASWriteDictionary(CFDictionaryRef dict, int fdOut)
+ // Write a dictionary to a file descriptor by flattening
+ // it into XML. Send the size of the XML before sending
+ // the data so that BASReadDictionary knows how much to
+ // read.
+ //
+ // See also the companion routine, BASReadDictionary, above.
+{
+ int err = 0;
+ CFDataRef dictData;
+ uint32_t dictSize;
+
+ // Pre-conditions
+
+ assert(dict != NULL);
+ assert(fdOut >= 0);
+
+ dictData = NULL;
+
+ // Get the dictionary as XML data.
+
+ dictData = CFPropertyListCreateXMLData(NULL, dict);
+ if (dictData == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+
+ // Send the length, then send the data. Always send the length as a big-endian
+ // uint32_t, so that the app and the helper tool can be different architectures.
+ //
+ // The MoreAuthSample version of this code erroneously assumed that CFDataGetBytePtr
+ // can fail and thus allocated an extra buffer to copy the data into. In reality,
+ // CFDataGetBytePtr can't fail, so this version of the code doesn't do the unnecessary
+ // allocation.
+
+ if ( (err == 0) && (CFDataGetLength(dictData) > kBASMaxNumberOfKBytes) ) {
+ err = EINVAL;
+ }
+ if (err == 0) {
+ dictSize = OSSwapHostToBigInt32( CFDataGetLength(dictData) );
+ err = BASWrite(fdOut, &dictSize, sizeof(dictSize), NULL);
+ }
+ if (err == 0) {
+ err = BASWrite(fdOut, CFDataGetBytePtr(dictData), CFDataGetLength(dictData), NULL);
+ }
+
+ if (dictData != NULL) {
+ CFRelease(dictData);
+ }
+
+ return err;
+}
+
+// When we pass a descriptor, we have to pass at least one byte
+// of data along with it, otherwise the recvmsg call will not
+// block if the descriptor hasn't been written to the other end
+// of the socket yet.
+
+static const char kDummyData = 'D';
+
+// Due to a kernel bug in Mac OS X 10.4.x and earlier <rdar://problem/4650646>,
+// you will run into problems if you write data to a socket while a process is
+// trying to receive a descriptor from that socket. A common symptom of this
+// problem is that, if you write two descriptors back-to-back, the second one
+// just disappears.
+//
+// To avoid this problem, we explicitly ACK all descriptor transfers.
+// After writing a descriptor, the sender reads an ACK byte from the socket.
+// After reading a descriptor, the receiver sends an ACK byte (kACKData)
+// to unblock the sender.
+
+static const char kACKData = 'A';
+
+static int BASReadDescriptor(int fd, int *fdRead)
+ // Read a descriptor from fd and place it in *fdRead.
+ //
+ // On success, the caller is responsible for closing *fdRead.
+ //
+ // See the associated BASWriteDescriptor, below.
+{
+ int err;
+ int junk;
+ struct msghdr msg;
+ struct iovec iov;
+ struct {
+ struct cmsghdr hdr;
+ int fd;
+ } control;
+ char dummyData;
+ ssize_t bytesReceived;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert( fdRead != NULL);
+ assert(*fdRead == -1);
+
+ iov.iov_base = (char *) &dummyData;
+ iov.iov_len = sizeof(dummyData);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = (caddr_t) &control;
+ msg.msg_controllen = sizeof(control);
+ msg.msg_flags = MSG_WAITALL;
+
+ do {
+ bytesReceived = recvmsg(fd, &msg, 0);
+ if (bytesReceived == sizeof(dummyData)) {
+ if ( (dummyData != kDummyData)
+ || (msg.msg_flags != 0)
+ || (msg.msg_control == NULL)
+ || (msg.msg_controllen != sizeof(control))
+ || (control.hdr.cmsg_len != sizeof(control))
+ || (control.hdr.cmsg_level != SOL_SOCKET)
+ || (control.hdr.cmsg_type != SCM_RIGHTS)
+ || (control.fd < 0) ) {
+ err = EINVAL;
+ } else {
+ *fdRead = control.fd;
+ err = 0;
+ }
+ } else if (bytesReceived == 0) {
+ err = EPIPE;
+ } else {
+ assert(bytesReceived == -1);
+
+ err = errno;
+ assert(err != 0);
+ }
+ } while (err == EINTR);
+
+ // Send the ACK. If that fails, we have to act like we never got the
+ // descriptor in our to maintain our post condition.
+
+ if (err == 0) {
+ err = BASWrite(fd, &kACKData, sizeof(kACKData), NULL);
+ if (err != 0) {
+ junk = close(*fdRead);
+ assert(junk == 0);
+ *fdRead = -1;
+ }
+ }
+
+ assert( (err == 0) == (*fdRead >= 0) );
+
+ return err;
+}
+
+static int BASWriteDescriptor(int fd, int fdToWrite)
+ // Write the descriptor fdToWrite to fd.
+ //
+ // See the associated BASReadDescriptor, above.
+{
+ int err;
+ struct msghdr msg;
+ struct iovec iov;
+ struct {
+ struct cmsghdr hdr;
+ int fd;
+ } control;
+ ssize_t bytesSent;
+ char ack;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert(fdToWrite >= 0);
+
+ control.hdr.cmsg_len = sizeof(control);
+ control.hdr.cmsg_level = SOL_SOCKET;
+ control.hdr.cmsg_type = SCM_RIGHTS;
+ control.fd = fdToWrite;
+
+ iov.iov_base = (char *) &kDummyData;
+ iov.iov_len = sizeof(kDummyData);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = (caddr_t) &control;
+ msg.msg_controllen = control.hdr.cmsg_len;
+ msg.msg_flags = 0;
+ do {
+ bytesSent = sendmsg(fd, &msg, 0);
+ if (bytesSent == sizeof(kDummyData)) {
+ err = 0;
+ } else {
+ assert(bytesSent == -1);
+
+ err = errno;
+ assert(err != 0);
+ }
+ } while (err == EINTR);
+
+ // After writing the descriptor, try to read an ACK back from the
+ // recipient. If that fails, or we get the wrong ACK, we've failed.
+
+ if (err == 0) {
+ err = BASRead(fd, &ack, sizeof(ack), NULL);
+ if ( (err == 0) && (ack != kACKData) ) {
+ err = EINVAL;
+ }
+ }
+
+ return err;
+}
+
+extern void BASCloseDescriptorArray(
+ CFArrayRef descArray
+)
+ // See comment in header.
+{
+ int junk;
+ CFIndex descCount;
+ CFIndex descIndex;
+
+ // I decided to allow descArray to be NULL because it makes it
+ // easier to call this routine using the code.
+ //
+ // BASCloseDescriptorArray((CFArrayRef) CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)));
+
+ if (descArray != NULL) {
+ if (CFGetTypeID(descArray) == CFArrayGetTypeID()) {
+ descCount = CFArrayGetCount(descArray);
+
+ for (descIndex = 0; descIndex < descCount; descIndex++) {
+ CFNumberRef thisDescNum;
+ int thisDesc;
+
+ thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(descArray, descIndex);
+ if ( (thisDescNum == NULL)
+ || (CFGetTypeID(thisDescNum) != CFNumberGetTypeID())
+ || ! CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc) ) {
+ assert(false);
+ } else {
+ assert(thisDesc >= 0);
+ junk = close(thisDesc);
+ assert(junk == 0);
+ }
+ }
+ } else {
+ assert(false);
+ }
+ }
+}
+
+static int BASReadDictioanaryTranslatingDescriptors(int fd, CFDictionaryRef *dictPtr)
+ // Reads a dictionary and its associated descriptors (if any) from fd,
+ // putting the dictionary (modified to include the translated descriptor
+ // numbers) in *dictPtr.
+ //
+ // On success, the caller is responsible for releasing *dictPtr and for
+ // closing any descriptors it references (BASCloseDescriptorArray makes
+ // the second part easy).
+{
+ int err;
+ int junk;
+ CFDictionaryRef dict;
+ CFArrayRef incomingDescs;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+ assert( dictPtr != NULL);
+ assert(*dictPtr == NULL);
+
+ dict = NULL;
+
+ // Read the dictionary.
+
+ err = BASReadDictionary(fd, &dict);
+
+ // Now read the descriptors, if any.
+
+ if (err == 0) {
+ incomingDescs = (CFArrayRef) CFDictionaryGetValue(dict, CFSTR(kBASDescriptorArrayKey));
+ if (incomingDescs == NULL) {
+ // No descriptors. Not much to do. Just use dict as the response,
+ // NULLing it out so that we don't release it at the end.
+
+ *dictPtr = dict;
+ dict = NULL;
+ } else {
+ CFMutableArrayRef translatedDescs;
+ CFMutableDictionaryRef mutableDict;
+ CFIndex descCount;
+ CFIndex descIndex;
+
+ // We have descriptors, so there's lots of stuff to do. Have to
+ // receive each of the descriptors assemble them into the
+ // translatedDesc array, then create a mutable dictionary based
+ // on response (mutableDict) and replace the
+ // kBASDescriptorArrayKey with translatedDesc.
+
+ translatedDescs = NULL;
+ mutableDict = NULL;
+
+ // Start by checking incomingDescs.
+
+ if ( CFGetTypeID(incomingDescs) != CFArrayGetTypeID() ) {
+ err = EINVAL;
+ }
+
+ // Create our output data.
+
+ if (err == 0) {
+ translatedDescs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ if (translatedDescs == NULL) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+ if (err == 0) {
+ mutableDict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
+ if (mutableDict == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+
+ // Now read each incoming descriptor, appending the results
+ // to translatedDescs as we go. By keeping our working results
+ // in translatedDescs, we make sure that we can clean up if
+ // we fail.
+
+ if (err == 0) {
+ descCount = CFArrayGetCount(incomingDescs);
+
+ // We don't actually depend on the descriptor values in the
+ // response (that is, the elements of incomingDescs), because
+ // they only make sense it the context of the sending process.
+ // All we really care about is the number of elements, which
+ // tells us how many times to go through this loop. However,
+ // just to be paranoid, in the debug build I check that the
+ // incoming array is well formed.
+
+ #if !defined(NDEBUG)
+ for (descIndex = 0; descIndex < descCount; descIndex++) {
+ int thisDesc;
+ CFNumberRef thisDescNum;
+
+ thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(incomingDescs, descIndex);
+ assert(thisDescNum != NULL);
+ assert(CFGetTypeID(thisDescNum) == CFNumberGetTypeID());
+ assert(CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc));
+ assert(thisDesc >= 0);
+ }
+ #endif
+
+ // Here's the real work. For descCount times, read a descriptor
+ // from fd, wrap it in a CFNumber, and append it to translatedDescs.
+ // Note that we have to be very careful not to leak a descriptor
+ // if we get an error here.
+
+ for (descIndex = 0; descIndex < descCount; descIndex++) {
+ int thisDesc;
+ CFNumberRef thisDescNum;
+
+ thisDesc = -1;
+ thisDescNum = NULL;
+
+ err = BASReadDescriptor(fd, &thisDesc);
+ if (err == 0) {
+ thisDescNum = CFNumberCreate(NULL, kCFNumberIntType, &thisDesc);
+ if (thisDescNum == NULL) {
+ err = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+ if (err == 0) {
+ CFArrayAppendValue(translatedDescs, thisDescNum);
+ // The descriptor is now stashed in translatedDescs,
+ // so this iteration of the loop is no longer responsible
+ // for closing it.
+ thisDesc = -1;
+ }
+
+ if (thisDescNum != NULL) {
+ CFRelease(thisDescNum);
+ }
+ if (thisDesc != -1) {
+ junk = close(thisDesc);
+ assert(junk == 0);
+ }
+
+ if (err != 0) {
+ break;
+ }
+ }
+ }
+
+ // Clean up and establish output parameters.
+
+ if (err == 0) {
+ CFDictionarySetValue(mutableDict, CFSTR(kBASDescriptorArrayKey), translatedDescs);
+ *dictPtr = mutableDict;
+ } else {
+ BASCloseDescriptorArray(translatedDescs);
+ if (mutableDict != NULL) {
+ CFRelease(mutableDict);
+ }
+ }
+ if (translatedDescs != NULL) {
+ CFRelease(translatedDescs);
+ }
+ }
+ }
+
+ if (dict != NULL) {
+ CFRelease(dict);
+ }
+
+ assert( (err == 0) == (*dictPtr != NULL) );
+
+ return err;
+}
+
+static int BASWriteDictionaryAndDescriptors(CFDictionaryRef dict, int fd)
+ // Writes a dictionary and its associated descriptors to fd.
+{
+ int err;
+ CFArrayRef descArray;
+ CFIndex descCount;
+ CFIndex descIndex;
+
+ // Pre-conditions
+
+ assert(dict != NULL);
+ assert(fd >= 0);
+
+ // Write the dictionary.
+
+ err = BASWriteDictionary(dict, fd);
+
+ // Process any descriptors. The descriptors are indicated by
+ // a special key in the dictionary. If that key is present,
+ // it's a CFArray of CFNumbers that present the descriptors to be
+ // passed.
+
+ if (err == 0) {
+ descArray = (CFArrayRef) CFDictionaryGetValue(dict, CFSTR(kBASDescriptorArrayKey));
+
+ // We only do the following if the special key is present.
+
+ if (descArray != NULL) {
+
+ // If it's not an array, that's bad.
+
+ if ( CFGetTypeID(descArray) != CFArrayGetTypeID() ) {
+ err = EINVAL;
+ }
+
+ // Loop over the array, getting each descriptor and writing it.
+
+ if (err == 0) {
+ descCount = CFArrayGetCount(descArray);
+
+ for (descIndex = 0; descIndex < descCount; descIndex++) {
+ CFNumberRef thisDescNum;
+ int thisDesc;
+
+ thisDescNum = (CFNumberRef) CFArrayGetValueAtIndex(descArray, descIndex);
+ if ( (thisDescNum == NULL)
+ || (CFGetTypeID(thisDescNum) != CFNumberGetTypeID())
+ || ! CFNumberGetValue(thisDescNum, kCFNumberIntType, &thisDesc) ) {
+ err = EINVAL;
+ }
+ if (err == 0) {
+ err = BASWriteDescriptor(fd, thisDesc);
+ }
+
+ if (err != 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return err;
+}
+
+static OSStatus FindCommand(
+ CFDictionaryRef request,
+ const BASCommandSpec commands[],
+ size_t * commandIndexPtr
+)
+ // FindCommand is a simple utility routine for checking that the
+ // command name within a request is valid (that is, matches one of the command
+ // names in the BASCommandSpec array).
+ //
+ // On success, *commandIndexPtr will be the index of the requested command
+ // in the commands array. On error, the value in *commandIndexPtr is undefined.
+{
+ OSStatus retval = noErr;
+ CFStringRef commandStr;
+ char * command;
+ long commandSize = 0;
+ size_t index = 0;
+
+ // Pre-conditions
+
+ assert(request != NULL);
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(commandIndexPtr != NULL);
+
+ command = NULL;
+
+ // Get the command as a C string. To prevent untrusted command string from
+ // trying to run us out of memory, we limit its length to 1024 UTF-16 values.
+
+ commandStr = CFDictionaryGetValue(request, CFSTR(kBASCommandKey));
+ if ( (commandStr == NULL) || (CFGetTypeID(commandStr) != CFStringGetTypeID()) ) {
+ retval = paramErr;
+ }
+ commandSize = CFStringGetLength(commandStr);
+ if ( (retval == noErr) && (commandSize > 1024) ) {
+ retval = paramErr;
+ }
+ if (retval == noErr) {
+ size_t bufSize;
+
+ bufSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(commandStr), kCFStringEncodingUTF8) + 1;
+ command = malloc(bufSize);
+
+ if (command == NULL) {
+ retval = memFullErr;
+ } else if ( ! CFStringGetCString(commandStr, command, bufSize, kCFStringEncodingUTF8) ) {
+ retval = coreFoundationUnknownErr;
+ }
+ }
+
+ // Search the commands array for that command.
+
+ if (retval == noErr) {
+ do {
+ if ( strcmp(commands[index].commandName, command) == 0 ) {
+ *commandIndexPtr = index;
+ break;
+ }
+ index += 1;
+ if (commands[index].commandName == NULL) {
+ retval = BASErrnoToOSStatus(ENOENT);
+ break;
+ }
+ } while (true);
+ }
+
+ free(command);
+
+ return retval;
+}
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** Tool Code
+
+/*
+ Watchdog Timer
+ --------------
+ BetterAuthorizationSampleLib's privileged helper tool server is single threaded. Thus,
+ it's possible for a broken or malicious client to stop progress within the helper
+ tool simply by sending the tool half a request. The single thread of execution
+ within the tool will wait forever for the rest of the request and, while it's
+ waiting, it won't be able to service other requests. Clearly this is not good.
+
+ I contemplated a number of solutions to this problem, but eventually settled
+ on a very simple solution. When it starts processing a request, the tool
+ starts a watchdog timer. If the timer expires, the tool dies. The single
+ request that the tool is blocked on will fail (because our end of the per-connection
+ socket for that request closed when we died) and subsequent requests will
+ relaunch the tool on demand, courtesy of launchd.
+
+ I use SIGALRM to implement this functionality. As stated in our header, the
+ BetterAuthorizationSampleLib code claims this signal and our clients are required not
+ to use it. Also, the default disposition for SIGALRM is to quit the process,
+ which is exactly what I want.
+*/
+
+static void EnableWatchdog(void)
+ // Start the watchdog timer. If you don't call DisableWatchdog before the
+ // timer expires, the process will die with a SIGALRM.
+{
+ (void) alarm(kWatchdogTimeoutInSeconds);
+}
+
+static void DisableWatchdog(void)
+ // Disable the watchdog timer.
+{
+ (void) alarm(0);
+}
+
+#if ! defined(NDEBUG)
+
+ static bool CommandArraySizeMatchesCommandProcArraySize(
+ const BASCommandSpec commands[],
+ const BASCommandProc commandProcs[]
+ )
+ {
+ size_t commandCount;
+ size_t procCount;
+
+ commandCount = 0;
+ while ( commands[commandCount].commandName != NULL ) {
+ commandCount += 1;
+ }
+
+ procCount = 0;
+ while ( commandProcs[procCount] != NULL ) {
+ procCount += 1;
+ }
+
+ return (commandCount == procCount);
+ }
+
+#endif
+
+/*
+ On-The-'Wire' Protocol
+ ----------------------
+ The on-the-'wire' protocol for a BetterAuthorizationSampleLib connection (from the
+ perspective of the client) is:
+
+ connect
+
+ send AuthorizationExternalForm (32 byte blob)
+ send request dictionary length (4 bytes, uint32_t, big endian)
+ send request dictionary (N bytes, flattened CFPropertyList)
+
+ read response dictionary length (4 bytes, uint32_t, big endian)
+ read response dictionary (N bytes, flattened CFPropertyList)
+ for each descriptor in dictionary
+ read 1 byte ('D') with attached descriptor
+ write 1 byte ('A')
+
+ close
+*/
+
+static int HandleConnection(
+ aslclient asl,
+ aslmsg aslMsg,
+ const BASCommandSpec commands[],
+ const BASCommandProc commandProcs[],
+ int fd
+)
+ // This routine handles a single connection from a client. This connection, in
+ // turn, represents a single command (request/response pair). commands is the
+ // list of valid commands. commandProc is a callback to call to actually
+ // execute a command. Finally, fd is the file descriptor from which the request
+ // should be read, and to which the response should be sent.
+{
+ int retval;
+ OSStatus junk;
+ int junkInt;
+ AuthorizationExternalForm extAuth;
+ AuthorizationRef auth = NULL;
+ CFDictionaryRef request = NULL;
+ size_t commandIndex;
+ CFMutableDictionaryRef response = NULL;
+ OSStatus commandProcStatus;
+
+ // Pre-conditions
+
+ // asl may be NULL
+ // aslMsg may be NULL
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(commandProcs != NULL);
+ assert( CommandArraySizeMatchesCommandProcArraySize(commands, commandProcs) );
+ assert(fd >= 0);
+
+ // Read in the external authorization reference.
+ retval = BASRead(fd, &extAuth, sizeof(extAuth), NULL);
+
+ // Internalize external authorization reference.
+ if (retval == 0) {
+ retval = BASOSStatusToErrno( AuthorizationCreateFromExternalForm(&extAuth, &auth) );
+ }
+
+ // Read in CFDictionaryRef request (the command and its arguments).
+ if (retval == 0) {
+ retval = BASReadDictionary(fd, &request);
+ }
+
+ // Create a mutable response dictionary before calling the client.
+ if (retval == 0) {
+ response = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ if (response == NULL) {
+ retval = BASOSStatusToErrno( coreFoundationUnknownErr );
+ }
+ }
+
+ // Errors that occur within this block are considered command errors, that is, they're
+ // reported to the client in the kBASErrorKey value of the response dictionary
+ // (that is, BASExecuteRequestInHelperTool returns noErr and valid response dictionary with
+ // an error value in the kBASErrorKey entry of the dictionary). In contrast, other errors
+ // are considered IPC errors and generally result in a the client getting an error status
+ // back from BASExecuteRequestInHelperTool.
+ //
+ // Notably a request with an unrecognised command string will return an error code
+ // in the response, as opposed to an IPC error. This means that a client can check
+ // whether a tool supports a particular command without triggering an IPC teardown.
+
+ if (retval == 0) {
+ // Get the command name from the request dictionary and check to see whether or
+ // not the command is valid by comparing with the BASCommandSpec array. Also,
+ // if the command is valid, return the associated right (if any).
+
+ commandProcStatus = FindCommand(request, commands, &commandIndex);
+
+ // Acquire the associated right for the command. If rightName is NULL, the
+ // commandProc is required to do its own authorization.
+
+ if ( (commandProcStatus == noErr) && (commands[commandIndex].rightName != NULL) ) {
+ AuthorizationItem item = { commands[commandIndex].rightName, 0, NULL, 0 };
+ AuthorizationRights rights = { 1, &item };
+
+ commandProcStatus = AuthorizationCopyRights(
+ auth,
+ &rights,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
+ NULL
+ );
+ }
+
+ // Call callback to execute command based on the request.
+
+ if (commandProcStatus == noErr) {
+ commandProcStatus = commandProcs[commandIndex](auth, commands[commandIndex].userData, request, response, asl, aslMsg);
+
+ if (commandProcStatus == noErr) {
+ junkInt = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Command callback succeeded");
+ assert(junkInt == 0);
+ } else {
+ junkInt = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Command callback failed: %ld", (long) commandProcStatus);
+ assert(junkInt == 0);
+ }
+ }
+
+ // If the command didn't insert its own error value, we use its function
+ // result as the error value.
+
+ if ( ! CFDictionaryContainsKey(response, CFSTR(kBASErrorKey)) ) {
+ CFNumberRef numRef;
+
+ numRef = CFNumberCreate(NULL, kCFNumberSInt32Type, &commandProcStatus);
+ if (numRef == NULL) {
+ retval = BASOSStatusToErrno( coreFoundationUnknownErr );
+ } else {
+ CFDictionaryAddValue(response, CFSTR(kBASErrorKey), numRef);
+ CFRelease(numRef);
+ }
+ }
+ }
+
+ // Write response back to the client.
+ if (retval == 0) {
+ retval = BASWriteDictionaryAndDescriptors(response, fd);
+ }
+
+ // Clean up.
+
+ if (response != NULL) {
+ // If there are any descriptors in response, we've now passed them off to the client,
+ // so we can (and must) close our references to them.
+ BASCloseDescriptorArray( CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)) );
+ CFRelease(response);
+ }
+ if (request != NULL) {
+ CFRelease(request);
+ }
+ if (auth != NULL) {
+ junk = AuthorizationFree(auth, kAuthorizationFlagDefaults);
+ assert(junk == noErr);
+ }
+
+ return retval;
+}
+
+#if !defined(NDEBUG)
+
+ static void WaitForDebugger(aslclient asl, aslmsg aslMsg)
+ // You can force a debug version of the tool to stop and wait on
+ // launch using the following Terminal command:
+ //
+ // $ sudo launchctl stop com.example.BetterAuthorizationSample
+ // $ sudo launchctl setenv BASWaitForDebugger 1
+ {
+ int err;
+ const char *value;
+
+ // asl may be NULL
+ // aslMsg may be NULL
+
+ value = getenv("BASWaitForDebugger");
+ if ( ((value != NULL) && (atoi(value) != 0)) ) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Waiting for debugger");
+ assert(err == 0);
+ (void) pause();
+ }
+ }
+
+#endif
+
+static int CheckInWithLaunchd(aslclient asl, aslmsg aslMsg, const char **errStrPtr)
+ // Checks in with launchd and gets back our listening socket.
+ // Returns the socket as the function result (or -1 on error).
+ // Also, on error, set *errStrPtr to a error string suitable
+ // for logging with ASL. If the message contains a %m, which
+ // causes ASL to log errno, errno will be set appropriately.
+{
+ int err;
+ launch_data_t checkinRequest = NULL;
+ launch_data_t checkinResponse = NULL;
+ launch_data_t socketsDict;
+ launch_data_t fdArray;
+ launch_data_t fdData;
+ int fd = -1;
+
+ // Pre-conditions
+
+ // asl may be NULL
+ // aslMsg may be NULL
+ assert( errStrPtr != NULL);
+ assert(*errStrPtr == NULL);
+
+ // Check in with launchd. Create a checkin request, then run it, then
+ // check if we got an error.
+
+ checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN);
+ if (checkinRequest == NULL) {
+ *errStrPtr = "Could not create checkin request: %m";
+ goto done;
+ }
+ checkinResponse = launch_msg(checkinRequest);
+ if (checkinResponse == NULL) {
+ *errStrPtr = "Error checking in: %m";
+ goto done;
+ }
+ if (launch_data_get_type(checkinResponse) == LAUNCH_DATA_ERRNO) {
+ errno = launch_data_get_errno(checkinResponse); // set errno so %m picks it up
+ *errStrPtr = "Checkin failed: %m";
+ goto done;
+ }
+
+ // Retrieve the dictionary of sockets entries from the job. This corresponds to the
+ // value of the "Sockets" key in our plist file.
+
+ socketsDict = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_SOCKETS);
+ if (socketsDict == NULL) {
+ *errStrPtr = "Could not get socket dictionary from checkin response: %m";
+ goto done;
+ }
+ if (launch_data_get_type(socketsDict) != LAUNCH_DATA_DICTIONARY) {
+ *errStrPtr = "Could not get socket dictionary from checkin response: Type mismatch";
+ goto done;
+ }
+ if (launch_data_dict_get_count(socketsDict) > 1) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_WARNING, "Some sockets in dictionary will be ignored");
+ assert(err == 0);
+ }
+
+ // Get the dictionary value from the key "MasterSocket", as defined in the launchd
+ // property list file.
+
+ fdArray = launch_data_dict_lookup(socketsDict, kLaunchDSocketDictKey);
+ if (fdArray == NULL) {
+ *errStrPtr = "Could not get file descriptor array: %m";
+ goto done;
+ }
+ if (launch_data_get_type(fdArray) != LAUNCH_DATA_ARRAY) {
+ *errStrPtr = "Could not get file descriptor array: Type mismatch";
+ goto done;
+ }
+ if (launch_data_array_get_count(fdArray) > 1) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_WARNING, "Some sockets in array will be ignored");
+ assert(err == 0);
+ }
+
+ // Get the socket file descriptor from the array.
+
+ fdData = launch_data_array_get_index(fdArray, 0);
+ if (fdData == NULL) {
+ *errStrPtr = "Could not get file descriptor array entry: %m";
+ goto done;
+ }
+ if (launch_data_get_type(fdData) != LAUNCH_DATA_FD) {
+ *errStrPtr = "Could not get file descriptor array entry: Type mismatch";
+ goto done;
+ }
+ fd = launch_data_get_fd(fdData);
+ assert(fd >= 0);
+
+ // The following was used to debug a problem with launchd <rdar://problem/5410487>.
+ // I'm going to leave it in, disabled, until that problem is resolved.
+
+ if (false) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Listening descriptor is %d", fd);
+ assert(err == 0);
+ }
+
+done:
+ if (checkinResponse != NULL) {
+ launch_data_free(checkinResponse);
+ }
+ if (checkinRequest != NULL) {
+ launch_data_free(checkinRequest);
+ }
+
+ return fd;
+}
+
+static int SetNonBlocking(int fd, Boolean nonBlocking)
+ // Sets the non-blocking state of fd.
+{
+ int err;
+ int flags;
+
+ // Pre-conditions
+
+ assert(fd >= 0);
+
+ // Get the flags.
+
+ err = 0;
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ err = errno;
+ }
+
+ // If the current state of O_NONBLOCK doesn't match the required
+ // state, toggle that flag and set it back.
+
+ if ( (err == 0) && (((flags & O_NONBLOCK) != 0) != nonBlocking) ) {
+ flags ^= O_NONBLOCK;
+ err = fcntl(fd, F_SETFL, flags);
+ if (err < 0) {
+ err = errno;
+ }
+ }
+
+ return err;
+}
+
+extern int BASHelperToolMain(
+ const BASCommandSpec commands[],
+ const BASCommandProc commandProcs[]
+)
+ // See comment in header.
+{
+ const char * errStr = NULL;
+ int err;
+ aslclient asl = NULL;
+ aslmsg aslMsg = NULL;
+ sig_t pipeSet;
+ int listener;
+ int kq;
+ struct kevent initEvent;
+
+ // Pre-conditions
+
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(commandProcs != NULL);
+ assert( CommandArraySizeMatchesCommandProcArraySize(commands, commandProcs) );
+
+ // Create a new ASL client object, and a template message for any messages that
+ // we log. We don't care if these fail because ASL will do the right thing
+ // if you pass it NULL (that is, nothing).
+
+ asl = asl_open(NULL, "HelperTools", ASL_OPT_STDERR);
+ assert(asl != NULL);
+
+ aslMsg = asl_new(ASL_TYPE_MSG);
+ assert(aslMsg != NULL);
+
+ err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Starting up");
+ assert(err == 0);
+
+ #if !defined(NDEBUG)
+ WaitForDebugger(asl, aslMsg);
+ #endif
+
+ // Set up the signal handlers we are interested in.
+ //
+ // o SIGTERM -- launchd sends us this when it wants us to quit. We don't
+ // actually need to set up a handler because the default behaviour (process
+ // termination) is fine.
+ //
+ // o SIGALRM -- No need to set it up because the default behaviour (process
+ // termination) is fine. See the "Watchdog Timer" comment (above) for details.
+ //
+ // o SIGPIPE -- We don't want to quit when write to a dead socket, so we
+ // ignore this signal.
+
+ pipeSet = signal(SIGPIPE, SIG_IGN);
+ if (pipeSet == SIG_ERR) {
+ errStr = "Could not ignore SIGPIPE: %m";
+ goto done;
+ }
+
+ // Check in with launchd and get our listening socket.
+
+ listener = CheckInWithLaunchd(asl, aslMsg, &errStr);
+ if (listener < 0) {
+ assert(errStr != NULL);
+ goto done;
+ }
+
+ // Create a kqueue and wrap the listening socket in it.
+
+ kq = kqueue();
+ if (kq < 0) {
+ errStr = "Could not create kqueue: %m";
+ goto done;
+ }
+
+ EV_SET(&initEvent, listener, EVFILT_READ, EV_ADD, 0, 0, NULL);
+ err = kevent(kq, &initEvent, 1, NULL, 0, NULL);
+ if (err < 0) {
+ errStr = "Could not add listening socket to kqueue: %m";
+ goto done;
+ }
+
+ // Force the listening socket to non-blocking mode. Without this, our timeout
+ // handling won't work properly. Specifically, we could get stuck in an accept
+ // if a connection request appears and then disappears. Eventually the watchdog
+ // would clean up, but that's not a great solution.
+
+ err = SetNonBlocking(listener, true);
+ if (err != 0) {
+ errno = err; // for %m
+ errStr = "Could not check/set socket flags: %m";
+ goto done;
+ }
+
+ // Loop servicing connection requests one at a time.
+
+ while (true) {
+ int eventCount;
+ struct kevent thisEvent;
+ uintptr_t thisConnection;
+ int thisConnectionError;
+ struct sockaddr_storage clientAddr; // we don't need this info, but accept won't let us ignore it
+ socklen_t clientAddrLen = sizeof(clientAddr);
+ static const struct timespec kIdleTimeout = { kIdleTimeoutInSeconds , 0 };
+
+ // Wait on the kqueue for a connection request.
+
+ eventCount = kevent(kq, NULL, 0, &thisEvent, 1, &kIdleTimeout);
+ if (eventCount == 0) {
+ // We've hit our idle timer. Just break out of the connection loop.
+ break;
+ } else if (eventCount == -1) {
+ // We got some sort of error from kevent; quit with an error.
+ errStr = "Unexpected error while listening for connections: %m";
+ goto done;
+ }
+
+ // From this point on, we're running on the watchdog timer. If we get
+ // stuck anywhere, the watchdog will fire eventually and we'll quit.
+
+ EnableWatchdog();
+
+ // The accept should never get stuck because this is a non-blocking
+ // socket.
+
+ thisConnection = accept(thisEvent.ident, (struct sockaddr *) &clientAddr, &clientAddrLen);
+ if (thisConnection == -1) {
+ if (errno == EWOULDBLOCK) {
+ // If the incoming connection just disappeared (perhaps the client
+ // died before we accepted the connection), don't log that as an error
+ // and don't quit.
+ err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Connection disappeared before we could accept it: %m");
+ assert(err == 0);
+ } else {
+ // Other errors mean that we're in a very weird state; we respond by
+ // failing out with an error.
+ errStr = "Unexpected error while accepting a connection: %m";
+ goto done;
+ }
+ }
+
+ // Because the accept can fail in a non-fatal fashion, thisConnection can be
+ // -1 here. In that case, we just skip the next step.
+
+ if (thisConnection != -1) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Request started");
+ assert(err == 0);
+
+ // thisConnection inherits its non-blocking setting from listener, but
+ // we want it to be blocking from here on in, so we switch the status.
+ // We're now relying on the watchdog to kill us if we get stuck.
+
+ thisConnectionError = BASErrnoToOSStatus( SetNonBlocking(thisConnection, false) );
+
+ // Entering heavy liftiing. We have a separate routine to actually
+ // read the request from the connection, call the client, and send
+ // the reply.
+
+ if (thisConnectionError == noErr) {
+ thisConnectionError = HandleConnection(asl, aslMsg, commands, commandProcs, thisConnection);
+ }
+
+ err = close(thisConnection);
+ assert(err == 0);
+
+ if (thisConnectionError == 0) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_DEBUG, "Request finished");
+ } else {
+ errno = thisConnectionError; // so it can be picked up by %m
+ err = asl_log(asl, aslMsg, ASL_LEVEL_ERR, "Request failed: %m");
+ }
+ assert(err == 0);
+ }
+
+ DisableWatchdog();
+ }
+
+done:
+ // At this point, errStr is either NULL, in which case we're quitting because
+ // of our idle timer, or non-NULL, in which case we're dying with an error.
+
+ // We expect the caller to immediately quit once we return. Thus, we
+ // don't bother cleaning up any resources we have allocated here, including
+ // asl, aslMsg, and kq.
+
+ if (errStr != NULL) {
+ err = asl_log(asl, aslMsg, ASL_LEVEL_ERR, "%s", errStr);
+ assert(err == 0);
+ }
+ err = asl_log(asl, aslMsg, ASL_LEVEL_INFO, "Shutting down");
+ assert(err == 0);
+ return (errStr == NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+/////////////////////////////////////////////////////////////////
+#pragma mark ***** App Code
+
+extern void BASSetDefaultRules(
+ AuthorizationRef auth,
+ const BASCommandSpec commands[],
+ CFStringRef bundleID,
+ CFStringRef descriptionStringTableName
+)
+ // See comment in header.
+{
+ OSStatus err;
+ CFBundleRef bundle;
+ size_t commandIndex;
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(bundleID != NULL);
+ // descriptionStringTableName may be NULL
+
+ bundle = CFBundleGetBundleWithIdentifier(bundleID);
+ assert(bundle != NULL);
+
+ // For each command, set up the default authorization right specification, as
+ // indicated by the command specification.
+
+ commandIndex = 0;
+ while (commands[commandIndex].commandName != NULL) {
+ // Some no-obvious assertions:
+
+ // If you have a right name, you must supply a default rule.
+ // If you have no right name, you can't supply a default rule.
+
+ assert( (commands[commandIndex].rightName == NULL) == (commands[commandIndex].rightDefaultRule == NULL) );
+
+ // If you have no right name, you can't supply a right description.
+ // OTOH, if you have a right name, you may supply a NULL right description
+ // (in which case you get no custom prompt).
+
+ assert( (commands[commandIndex].rightName != NULL) || (commands[commandIndex].rightDescriptionKey == NULL) );
+
+ // If there's a right name but no current right specification, set up the
+ // right specification.
+
+ if (commands[commandIndex].rightName != NULL) {
+ err = AuthorizationRightGet(commands[commandIndex].rightName, (CFDictionaryRef*) NULL);
+ if (err == errAuthorizationDenied) {
+ CFStringRef thisDescription;
+ CFStringRef thisRule;
+
+ // The right is not already defined. Set up a definition based on
+ // the fields in the command specification.
+
+ thisRule = CFStringCreateWithCString(
+ kCFAllocatorDefault,
+ commands[commandIndex].rightDefaultRule,
+ kCFStringEncodingUTF8
+ );
+ assert(thisRule != NULL);
+
+ thisDescription = NULL;
+ if (commands[commandIndex].rightDescriptionKey != NULL) {
+ thisDescription = CFStringCreateWithCString (
+ kCFAllocatorDefault,
+ commands[commandIndex].rightDescriptionKey,
+ kCFStringEncodingUTF8
+ );
+ assert(thisDescription != NULL);
+ }
+
+ err = AuthorizationRightSet(
+ auth, // authRef
+ commands[commandIndex].rightName, // rightName
+ thisRule, // rightDefinition
+ thisDescription, // descriptionKey
+ bundle, // bundle
+ descriptionStringTableName // localeTableName
+ ); // NULL indicates "Localizable.strings"
+ assert(err == noErr);
+
+ if (thisDescription != NULL) {
+ CFRelease(thisDescription);
+ }
+ if (thisRule != NULL) {
+ CFRelease(thisRule);
+ }
+ } else {
+ // A right already exists (err == noErr) or any other error occurs, we
+ // assume that it has been set up in advance by the system administrator or
+ // this is the second time we've run. Either way, there's nothing more for
+ // us to do.
+ }
+ }
+ commandIndex += 1;
+ }
+}
+
+extern OSStatus BASExecuteRequestInHelperTool(
+ AuthorizationRef auth,
+ const BASCommandSpec commands[],
+ CFStringRef bundleID,
+ CFDictionaryRef request,
+ CFDictionaryRef * response
+)
+ // See comment in header.
+{
+ OSStatus retval = noErr;
+ int junk;
+ size_t commandIndex;
+ char bundleIDC[PATH_MAX];
+ int fd = -1;
+ struct sockaddr_un addr;
+ AuthorizationExternalForm extAuth;
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(commands != NULL);
+ assert(commands[0].commandName != NULL); // there must be at least one command
+ assert(bundleID != NULL);
+ assert(request != NULL);
+ assert( response != NULL);
+ assert(*response == NULL);
+
+ // For debugging.
+
+ assert(CFDictionaryContainsKey(request, CFSTR(kBASCommandKey)));
+ assert(CFGetTypeID(CFDictionaryGetValue(request, CFSTR(kBASCommandKey))) == CFStringGetTypeID());
+
+ // Look up the command and preauthorize. This has the nice side effect that
+ // the authentication dialog comes up, in the typical case, here, rather than
+ // in the helper tool. This is good because the helper tool is global /and/
+ // single threaded, so if it's waiting for an authentication dialog for user A
+ // it can't handle requests from user B.
+
+ retval = FindCommand(request, commands, &commandIndex);
+
+ #if !defined(BAS_PREAUTHORIZE)
+ #define BAS_PREAUTHORIZE 1
+ #endif
+ #if BAS_PREAUTHORIZE
+ if ( (retval == noErr) && (commands[commandIndex].rightName != NULL) ) {
+ AuthorizationItem item = { commands[commandIndex].rightName, 0, NULL, 0 };
+ AuthorizationRights rights = { 1, &item };
+
+ retval = AuthorizationCopyRights(auth, &rights, kAuthorizationEmptyEnvironment, kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize, NULL);
+ }
+ #endif
+
+ // Create the socket and tell it to not generate SIGPIPE.
+
+ if (retval == noErr) {
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ retval = BASErrnoToOSStatus(errno);
+ }
+ }
+ if (retval == noErr) {
+ static const int kOne = 1;
+
+ if ( setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &kOne, sizeof(kOne)) < 0 ) {
+ retval = BASErrnoToOSStatus(errno);
+ }
+ }
+
+ // Form the socket address, including a path based on the bundle ID.
+
+ if (retval == noErr) {
+ if ( ! CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC)) ) {
+ retval = coreFoundationUnknownErr;
+ }
+ }
+ if (retval == noErr) {
+ int pathLen;
+
+ memset(&addr, 0, sizeof(addr));
+
+ addr.sun_family = AF_UNIX;
+ pathLen = snprintf(addr.sun_path, sizeof(addr.sun_path), kBASSocketPathFormat, bundleIDC);
+ if (pathLen >= sizeof(addr.sun_path)) {
+ retval = paramErr; // length of bundle pushed us over the UNIX domain socket path length limit
+ } else {
+ addr.sun_len = SUN_LEN(&addr);
+ }
+ }
+
+ // Attempt to connect.
+
+ if (retval == noErr) {
+ if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
+ retval = BASErrnoToOSStatus(errno);
+ }
+ }
+
+ // Send the flattened AuthorizationRef to the tool.
+
+ if (retval == noErr) {
+ retval = AuthorizationMakeExternalForm(auth, &extAuth);
+ }
+ if (retval == noErr) {
+ retval = BASErrnoToOSStatus( BASWrite(fd, &extAuth, sizeof(extAuth), NULL) );
+ }
+
+ // Write the request.
+
+ if (retval == noErr) {
+ retval = BASErrnoToOSStatus( BASWriteDictionary(request, fd) );
+ }
+
+ // Read response, including any descriptors.
+
+ if (retval == noErr) {
+ retval = BASErrnoToOSStatus( BASReadDictioanaryTranslatingDescriptors(fd, response) );
+ }
+
+ // Clean up.
+
+ if (fd != -1) {
+ junk = close(fd);
+ assert(junk == 0);
+ }
+ NormaliseOSStatusErrorCode(&retval);
+
+ assert( (retval == noErr) == (*response != NULL) );
+
+ return retval;
+}
+
+extern OSStatus BASGetErrorFromResponse(CFDictionaryRef response)
+ // See comment in header.
+{
+ OSStatus err;
+ CFNumberRef num;
+
+ assert(response != NULL);
+
+ num = (CFNumberRef) CFDictionaryGetValue(response, CFSTR(kBASErrorKey));
+ err = noErr;
+ if ( (num == NULL) || (CFGetTypeID(num) != CFNumberGetTypeID()) ) {
+ err = coreFoundationUnknownErr;
+ }
+ if (err == noErr) {
+ if ( ! CFNumberGetValue(num, kCFNumberSInt32Type, &err) ) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+
+ NormaliseOSStatusErrorCode(&err);
+ return err;
+}
+
+extern BASFailCode BASDiagnoseFailure(
+ AuthorizationRef auth,
+ CFStringRef bundleID
+)
+ // See comment in header.
+{
+ BASFailCode retval = kBASFailUnknown;
+ int err;
+ int pathLen;
+ char bundleIDC [ PATH_MAX ];
+ char toolPath [ PATH_MAX ];
+ char plistPath [ PATH_MAX ];
+
+ struct stat fileStatus;
+ int toolErr;
+ int plistErr;
+ int fd;
+ struct sockaddr_un addr;
+
+ // Pre-conditions
+
+ assert(auth != NULL);
+ assert(bundleID != NULL);
+
+ // Construct paths to the tool and plist.
+
+ if ( CFStringGetFileSystemRepresentation(bundleID, bundleIDC, sizeof(bundleIDC)) ) {
+
+ pathLen = snprintf(toolPath, sizeof(toolPath), kBASToolPathFormat, bundleIDC);
+ assert(pathLen < PATH_MAX); // snprintf truncated the string; won't crash us, but we want to know
+
+ pathLen = snprintf(plistPath, sizeof(plistPath), kBASPlistPathFormat, bundleIDC);
+ assert(pathLen < PATH_MAX); // snprintf truncated the string; won't crash us, but we want to know
+
+ // Check if files exist at those paths.
+
+ toolErr = stat(toolPath, &fileStatus);
+ plistErr = stat(plistPath, &fileStatus);
+
+ if ( (toolErr == 0) && (plistErr == 0) ) {
+ // If both items are present, try to connect and see what we get.
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd != -1) {
+ memset(&addr, 0, sizeof(addr));
+
+ addr.sun_family = AF_UNIX;
+ (void) snprintf(addr.sun_path, sizeof(addr.sun_path), kBASSocketPathFormat, bundleIDC);
+ addr.sun_len = SUN_LEN(&addr);
+
+ // Attempt to connect to the socket. If we get ECONNREFUSED, it means no one is
+ // listening.
+
+ if ( (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) && (errno == ECONNREFUSED) ) {
+ retval = kBASFailDisabled;
+ }
+ err = close(fd);
+ assert(err == 0);
+ }
+ } else {
+ if ( (toolErr == 0) || (plistErr == 0) ) {
+ retval = kBASFailPartiallyInstalled;
+ } else {
+ retval = kBASFailNotInstalled;
+ }
+ }
+ }
+
+ return retval;
+}
+
+
+
+
View
783 Appstagram/Source/BetterAuthorizationSampleLib.h
@@ -0,0 +1,783 @@
+/*
+ File: BetterAuthorizationSampleLib.h
+
+ Contains: Interface to reusable code for privileged helper tools.
+
+ Written by: DTS
+
+ Copyright: Copyright (c) 2007 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.
+
+*/
+
+#ifndef _BetterAuthorizationSampleLIB_H
+#define _BetterAuthorizationSampleLIB_H
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+#include <asl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/////////////////////////////////////////////////////////////////
+
+/*
+ This header has extensive HeaderDoc comments. To see these comments in a more
+ felicitous form, you can generate HTML from the HeaderDoc comments using the
+ following command:
+
+ $ headerdoc2html BetterAuthorizationSampleLib.h
+ $ open BetterAuthorizationSampleLib/index.html
+*/
+
+/*!
+ @header BetterAuthorizationSampleLib
+
+ @abstract Reusable library for creating helper tools that perform privileged