Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 791 lines (571 sloc) 36.7 KB
/*
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
operations on behalf of your application.
@discussion BetterAuthorizationSampleLib allows you to perform privileged operations
in a helper tool. In this model, your application runs with standard
privileges and, when it needs to do a privileged operation, it makes a
request to the helper tool. The helper tool uses Authorization Services
to ensure that the user is authorized to perform that operation.
BetterAuthorizationSampleLib takes care of all of the mechanics of
installing the helper tool and communicating with it. Specifically, it
has routines that your application can call to:
1. send requests to a helper tool (BASExecuteRequestInHelperTool)
2. install the helper tool if it's not installed, or fix an installation if
it's broken (BASDiagnoseFailure and BASFixFailure)
BetterAuthorizationSampleLib also helps you implement the helper tool.
Specifically, you call the routine BASHelperToolMain in the main entry
point for your helper tool, passing it an array of command callbacks (of
type BASCommandProc). BASHelperToolMain will take care of all the details
of communication with the application and only call your callback to
execute the actual command.
A command consists of request and response CFDictionaries (or, equivalently,
NSDictionaries). BetterAuthorizationSampleLib defines three special keys for
these dictionaries:
1. kBASCommandKey -- In the request dictionary, this is the name of the
command. Its value is a string that uniquely identifies the command within
your program.
2. kBASErrorKey -- In the response dictionary, this is the error result for
the request. Its value is an OSStatus-style error code.
3. kBASDescriptorArrayKey -- In the response dictionary, if present, this is
an array of file descriptors being returned from the helper tool.
You can use any other key to represent addition parameters (or return values)
for the command. The only constraints that BetterAuthorizationSampleLib applies
to these extra parameters is that they must be serialisable as a CFPropertyList.
BetterAuthorizationSampleLib requires that you tell it about the list of commands
that you support. Each command is represented by a command specification
(BASCommandSpec). The command specification includes the following information:
1. The name of the command. This is the same as the kBASCommandKey value in
the request dictionary.
2. The authorization right associated with the command. BetterAuthorizationSampleLib
uses this to ensure that the user is authorized to use the command before
it calls your command callback in the privileged helper tool.
3. Information to create the command's authorization right specification in the
policy database. The is used by the BASSetDefaultRules function.
Finally, BetterAuthorizationSampleLib includes a number of utilities routines to help
wrangle error codes (BASErrnoToOSStatus, BASOSStatusToErrno, and BASGetErrorFromResponse)
and file descriptors (BASCloseDescriptorArray).
*/
/////////////////////////////////////////////////////////////////
#pragma mark ***** Command Description
/*!
@struct BASCommandSpec
@abstract Describes a privileged operation to BetterAuthorizationSampleLib.
@discussion Both the application and the tool must tell BetterAuthorizationSampleLib about
the operations (that is, commands) that they support. They do this by passing
in an array of BASCommandSpec structures. Each element describes one command.
The array is terminated by a command whose commandName field is NULL.
In general the application and tool should use the same array definition.
However, there are cases where these might be out of sync. For example, if you
have an older version of the application talking to a newer version of the tool,
the tool might know about more commands than the application (and thus provide a
longer array), and that's OK.
@field commandName
A identifier for this command. This can be any string that is unique within
the context of your programs. A NULL value in this field terminates the array.
The length of the command name must not be greater than 1024 UTF-16 values.
@field rightName
This is the name of the authorization right associated with the
command. This can be NULL if you don't want any right associated with the
command. If it's not NULL, BetterAuthorizationSampleLib will acquire that right
before allowing the command to execute.
@field rightDefaultRule
This is the name of an authorization rule that should be used in
the default right specification for the right. To see a full list of these rules,
look at the "rules" dictionary within the policy database (currently
"/etc/authorization"). Common values include "default" (which requires that the user
hold credentials that authenticate them as an admin user) and "allow" (which will let
anyone acquire the right).
This must be NULL if (and only if) rightName is NULL.
@field rightDescriptionKey
This is a key used to form a custom prompt for the right. The value of this
string should be a key into a .strings file whose name you supply to
BASSetDefaultRules. When BetterAuthorizationSampleLib creates the right specification,
it uses this key to get all of the localised prompt strings for the right.
This must be NULL if rightName is NULL. Otherwise, this may be NULL if you
don't want a custom prompt for your right.
@field userData
This field is is for the benefit of the client; BetterAuthorizationSampleLib
does not use it in any way.
*/
struct BASCommandSpec {
const char * commandName;
const char * rightName;
const char * rightDefaultRule;
const char * rightDescriptionKey;
const void * userData;
};
typedef struct BASCommandSpec BASCommandSpec;
/////////////////////////////////////////////////////////////////
#pragma mark ***** Request/Response Keys
// Standard keys for the request dictionary
/*!
@define kBASCommandKey
@abstract Key for the command string within the request dictionary.
@discussion Within a request, this key must reference a string that is the name of the
command to execute. This must match one of the commands in the
BASCommandSpec array.
The length of a command name must not be greater than 1024 UTF-16 values.
*/
#define kBASCommandKey "com.apple.dts.BetterAuthorizationSample.command" // CFString
// Standard keys for the response dictionary
/*!
@define kBASErrorKey
@abstract Key for the error result within the response dictionary.
@discussion Within a response, this key must reference a number that is the error result
for the response, interpreted as an OSStatus.
*/
#define kBASErrorKey "com.apple.dts.BetterAuthorizationSample.error" // CFNumber
/*!
@define kBASDescriptorArrayKey
@abstract Key for a file descriptor array within the response dictionary.
@discussion Within a response, this key, if present, must reference an array
of numbers, which are the file descriptors being returned with
the response. The numbers are interpreted as ints.
*/
#define kBASDescriptorArrayKey "com.apple.dts.BetterAuthorizationSample.descriptors" // CFArray of CFNumber
/////////////////////////////////////////////////////////////////
#pragma mark ***** Helper Tool Routines
/*!
@functiongroup Helper Tool Routines
*/
/*!
@typedef BASCommandProc
@abstract Command processing callback.
@discussion When your helper tool calls BASHelperToolMain, it passes in a pointer to an
array of callback functions of this type. When BASHelperToolMain receives a
valid command, it calls one of these function so that your program-specific
code can process the request. BAS guarantees that the effective, save and
real user IDs (EUID, SUID, RUID) will all be zero at this point (that is,
you're "running as root").
By the time this callback is called, BASHelperToolMain has already verified that
this is a known command. It also acquires the authorization right associated
with the command, if any. However, it does nothing to validate the other
parameters in the request. These parameters come from a non-privileged source
and you should verify them carefully.
Your implementation should get any input parameters from the request and place
any output parameters in the response. It can also put an array of file
descriptors into the response using the kBASDescriptorArrayKey key.
If an error occurs, you should just return an appropriate error code.
BASHelperToolMain will ensure that this gets placed in the response.
You should attempt to fail before adding any file descriptors to the response,
or remove them once you know that you're going to fail. If you put file
descriptors into the response and then return an error, those descriptors will
still be passed back to the client. It's likely the client isn't expecting this.
Calls to this function will be serialised; that is, once your callback is
running, BASHelperToolMain won't call you again until you return. Your callback
should avoid blocking for long periods of time. If you block for too long, the
BAS watchdog will kill the entire helper tool process.
This callback runs in a daemon context; you must avoid doing things that require the
user's context. For example, launching a GUI application would be bad. See
Technote 2083 "Daemons and Agents" for more information about execution contexts.
@param auth This is a reference to the authorization instance associated with the original
application that made the request.
This will never be NULL.
@param userData This is the value from the userData field of the corresponding entry in the
BASCommandSpec array that you passed to BASHelperToolMain.
@param request This dictionary contains the request. It will have, at a bare minimum, a
kBASCommandKey item whose value matches one of the commands in the
BASCommandSpec array you passed to BASHelperToolMain. It may also have
other, command-specific parameters.
This will never be NULL.
@param response This is a dictionary into which you can place the response. It will start out
empty, and you can add any results you please to it.
If you need to return file descriptors, place them in an array and place that
array in the response using the kBASDescriptorArrayKey key.
There's no need to set the error result in the response. BASHelperToolMain will
do that for you. However, if you do set a value for the kBASErrorKey key,
that value will take precedence; in this case, the function result is ignored.
This will never be NULL.
@param asl A reference to the ASL client handle for logging.
This may be NULL. However, ASL handles a NULL input, so you don't need to
conditionalise your code.
@param aslMsg A reference to a ASL message template for logging.
This may be NULL. However, ASL handles a NULL input, so you don't need to
conditionalise your code.
*/
typedef OSStatus (*BASCommandProc)(
AuthorizationRef auth,
const void * userData,
CFDictionaryRef request,
CFMutableDictionaryRef response,
aslclient asl,
aslmsg aslMsg
);
/*!
@function BASHelperToolMain
@abstract Entry point for a privileged helper tool.
@discussion You should call this function from the main function of your helper tool. It takes
care of all of the details of receiving and processing commands. It will call you
back (via one of the commandProcs callbacks) when a valid request arrives.
This function assumes acts like a replacement for main. Thus, it assumes that
it owns various process-wide resources (like SIGALRM and the disposition of
SIGPIPE). You should not use those resources, either in your main function or
in your callback function. Also, you should not call this function on a thread,
or start any other threads in the process. Finally, this function has a habit of
exiting the entire process if something goes wrong. You should not expect the
function to always return.
This function does not clean up after itself. When this function returns, you
are expected to exit. If the function result is noErr, the command processing
loop quit in an expected manner (typically because of an idle timeout). Otherwise
it quit because of an error.
@param commands An array that describes the commands that you implement, and their associated
rights. The array is terminated by a command with a NULL name. There must be
at least one valid command.
@param commandProcs
An array of callback routines that are called when a valid request arrives. The
array is expected to perform the operation associated with the corresponding
command and set up the response values, if any. The array is terminated by a
NULL pointer.
IMPORTANT: The array must have exactly the same number of entries as the
commands array.
@result An integer representing EXIT_SUCCESS or EXIT_FAILURE.
*/
extern int BASHelperToolMain(
const BASCommandSpec commands[],
const BASCommandProc commandProcs[]
);
/////////////////////////////////////////////////////////////////
#pragma mark ***** Application Routines
/*!
@functiongroup Application Routines
*/
/*!
@function BASSetDefaultRules
@abstract Creates default right specifications in the policy database.
@discussion This routine ensures that the policy database (currently
"/etc/authorization") contains right specifications for all of the rights
that you use (as specified by the commands array). This has two important
consequences:
1. It makes the rights that you use visible to the system administrator.
All they have to do is run your program once and they can see your default
right specifications in the policy database.
2. It means that, when the privileged helper tool tries to acquire the right,
it will use your specification of the right (as modified by the system
administrator) rather than the default right specification.
You must call this function before calling BASExecuteRequestInHelperTool.
Typically you would call it at application startup time, or lazily, immediately
before calling BASExecuteRequestInHelperTool.
@param auth A reference to your program's authorization instance; you typically get this
by calling AuthorizationCreate.
This must not be NULL.
@param commands An array that describes the commands that you implement, and their associated
rights. There must be at least one valid command.
@param bundleID The bundle identifier for your program.
This must not be NULL.
@param descriptionStringTableName
The name of the .strings file from which to fetch the localised custom
prompts for the rights in the commands array (if any). A NULL value is
equivalent to passing "Localizable" (that is, it gets the prompts from
"Localizable.strings").
For example, imagine you have a command for which you require a custom prompt.
You should put the custom prompt in a .strings file, let's call it
"AuthPrompts.strings". You should then pass "AuthPrompts" to this parameter
and put the key that gets the prompt into the rightDescriptionKey of the command.
*/
extern void BASSetDefaultRules(
AuthorizationRef auth,
const BASCommandSpec commands[],
CFStringRef bundleID,
CFStringRef descriptionStringTableName
);
/*!
@function BASExecuteRequestInHelperTool
@abstract Executes a request in the privileged helper tool, returning the response.
@discussion This routine synchronously executes a request in the privileged helper tool and
returns the response.
If the function returns an error, the IPC between your application and the helper tool
failed. Unfortunately it's not possible to tell whether this failure occurred while
sending the request or receiving the response, thus it's not possible to know whether
the privileged operation was done or not.
If the functions returns no error, the IPC between your application and the helper tool
was successful. However, the command may still have failed. You must get the error
value from the response (typically using BASGetErrorFromResponse) to see if the
command succeeded or not.
On success the response dictionary may contain a value for the kBASDescriptorArrayKey key.
If so, that will be a non-empty CFArray of CFNumbers, each of which can be accessed as an int.
Each value is a descriptor that is being returned to you from the helper tool. You are
responsible for closing these descriptors when you're done with them.
@param auth A reference to your program's authorization instance; you typically get this
by calling AuthorizationCreate.
This must not be NULL.
@param commands An array that describes the commands that you implement, and their associated
rights. There must be at least one valid command.
@param bundleID The bundle identifier for your program.
This must not be NULL.
@param request A dictionary describing the requested operation. This must, at least, contain
a string value for the kBASCommandKey. Furthermore, this string must match
one of the commands in the array.
The dictionary may also contain other values. These are passed to the helper
tool unintepreted. All values must be serialisable using the CFPropertyList
API.
This must not be NULL.
@param response This must not be NULL. On entry, *response must be NULL. On success, *response
will not be NULL. On error, *response will be NULL.
On success, you are responsible for disposing of *response. You are also
responsible for closing any descriptors returned in the response.
@result An OSStatus code (see BASErrnoToOSStatus and BASOSStatusToErrno).
*/
extern OSStatus BASExecuteRequestInHelperTool(
AuthorizationRef auth,
const BASCommandSpec commands[],
CFStringRef bundleID,
CFDictionaryRef request,
CFDictionaryRef * response
);
/*!
@enum BASFailCode
@abstract Indicates why a request failed.
@discussion If BASExecuteRequestInHelperTool fails with an error (indicating
an IPC failure), you can call BASDiagnoseFailure to determine what
went wrong. BASDiagnoseFailure will return the value of this
type that best describes the failure.
@constant kBASFailUnknown
Indicates that BASDiagnoseFailure could not accurately determine the cause of the
failure.
@constant kBASFailDisabled
The request failed because the helper tool is installed but disabled.
@constant kBASFailPartiallyInstalled
The request failed because the helper tool is only partially installed.
@constant kBASFailNotInstalled
The request failed because the helper tool is not installed at all.
@constant kBASFailNeedsUpdate
The request failed because the helper tool is installed but out of date.
BASDiagnoseFailure will never return this value. However, if you detect that
the helper tool is out of date (typically by sending it a "get version" request)
you can pass this value to BASFixFailure to force it to update the tool.
*/
enum {
kBASFailUnknown,
kBASFailDisabled,
kBASFailPartiallyInstalled,
kBASFailNotInstalled,
kBASFailNeedsUpdate
};
typedef uint32_t BASFailCode;
/*!
@function BASDiagnoseFailure
@abstract Determines the cause of a failed request.
@discussion If BASExecuteRequestInHelperTool fails with an error (indicating an
IPC failure), you can call this routine to determine what went wrong.
It returns a BASFailCode value indicating the cause of the failure.
You should use this value to tell the user what's going on and what
you intend to do about it. Once you get the user's consent, you can
call BASFixFailure to fix the problem.
For example, if this function result is kBASFailDisabled, you could put up the
dialog saying:
My privileged helper tool is disabled. Would you like to enable it?
This operation may require you to authorize as an admin user.
[Cancel] [[Enable]]
On the other hand, if this function result is kBASFailNotInstalled, the dialog might be:
My privileged helper tool is not installed. Would you like to install it?
This operation may require you to authorize as an admin user.
[Cancel] [[Install]]
BASDiagnoseFailure will never return kBASFailNeedsUpdate. It's your responsibility
to detect version conflicts (a good way to do this is by sending a "get version" request
to the helper tool). However, once you've detected a version conflict, you can pass
kBASFailNeedsUpdate to BASFixFailure to get it to install the latest version of your
helper tool.
If you call this routine when everything is working properly, you're likely to get
a result of kBASFailUnknown.
@param auth A reference to your program's authorization instance; you typically get this
by calling AuthorizationCreate.
This must not be NULL.
@param bundleID The bundle identifier for your program.
This must not be NULL.
@result A BASFailCode value indicating the cause of the failure. This will never be
kBASFailNeedsUpdate.
*/
extern BASFailCode BASDiagnoseFailure(
AuthorizationRef auth,
CFStringRef bundleID
);
/*
@function BASFixFailure
@abstract Installs, or reinstalls, the privileged helper tool.
@discussion This routine installs or reinstalls the privileged helper tool. Typically
you call this in response to an IPC failure talking to the tool. You first
diagnose the failure using BASDiagnoseFailure and then call this routine to
fix the failure by installing (or reinstalling) the tool.
Because the helper tool is privileged, installing it is a privileged
operation. This routine will do its work by calling
AuthorizationExecuteWithPrivileges, which is likely to prompt the user
for an admin name and password.
@param auth A reference to your program's authorization instance; you typically get this
by calling AuthorizationCreate.
This must not be NULL.
@param bundleID The bundle identifier for your program.
This must not be NULL.
@param installToolName
The name of the install tool within your bundle. You should place the tool
in the executable directory within the bundle. Specifically, the tool must be
available by passing this name to CFBundleCopyAuxiliaryExecutableURL.
This must not be NULL.
@param helperToolName
The name of the helper tool within your bundle. You should place the tool
in the executable directory within the bundle. Specifically, the tool must be
available by passing this name to CFBundleCopyAuxiliaryExecutableURL.
This must not be NULL.
@param failCode A value indicating the type of failure that's occurred. In most cases you get this
value by calling BASDiagnoseFailure.
@result An OSStatus code (see BASErrnoToOSStatus and BASOSStatusToErrno).
*/
extern OSStatus BASFixFailure(
AuthorizationRef auth,
CFStringRef bundleID,
CFURLRef installToolURL,
CFURLRef helperToolURL,
BASFailCode failCode
);
/////////////////////////////////////////////////////////////////
#pragma mark ***** Utility Routines
/*!
@functiongroup Utilities
*/
/*!
@function BASErrnoToOSStatus
@abstract Convert an errno value to an OSStatus value.
@discussion All errno values have accepted alternatives in the errSecErrnoBase
OSStatus range, and this routine does the conversion. For example,
ENOENT becomes errSecErrnoBase + ENOENT. Any value that's not
recognised just gets passed through unmodified.
A value of 0 becomes noErr.
For more information about errSecErrnoBase, see DTS Q&A 1499
<http://developer.apple.com/qa/qa2006/qa1499.html>.
@param errNum The errno value to convert.
@result An OSStatus code representing the errno equivalent.
*/
extern OSStatus BASErrnoToOSStatus(int errNum);
/*!
@function BASOSStatusToErrno
@abstract Convert an OSStatus value to an errno value.
@discussion This function converts some specific OSStatus values (Open Transport and
errSecErrnoBase ranges) to their corresponding errno values. It more-or-less
undoes the conversion done by BASErrnoToOSStatus, including a pass
through for unrecognised values.
It's worth noting that there are many more defined OSStatus error codes
than errno error codes, so you're more likely to encounter a passed
through value when going in this direction.
A value of noErr becomes 0.
For more information about errSecErrnoBase, see DTS Q&A 1499
<http://developer.apple.com/qa/qa2006/qa1499.html>.
@param errNum The OSStatus value to convert.
@result An integer code representing the OSStatus equivalent.
*/
extern int BASOSStatusToErrno(OSStatus errNum);
/*!
@function BASGetErrorFromResponse
@abstract Extracts the error status from a helper tool response.
@discussion This function extracts the error status from a helper tool response.
Specifically, its uses the kBASErrorKey key to get a CFNumber and
it gets the resulting value from that number.
@param response A helper tool response, typically acquired by calling BASExecuteRequestInHelperTool.
This must not be NULL
@result An OSStatus code (see BASErrnoToOSStatus and BASOSStatusToErrno).
*/
extern OSStatus BASGetErrorFromResponse(CFDictionaryRef response);
/*!
@function BASCloseDescriptorArray
@abstract Closes all of the file descriptors referenced by a CFArray.
@discussion Given a CFArray of CFNumbers, treat each number as a file descriptor
and close it.
The most common reason to use this routine is that you've executed,
using BASExecuteRequestInHelperTool, a request that returns a response
with embedded file descriptors, and you want to close those descriptors.
In that case, you typically call this as:
BASCloseDescriptorArray( CFDictionaryGetValue(response, CFSTR(kBASDescriptorArrayKey)) );
@param descArray
The array containing the descriptors to close.
This may be NULL, in which case the routine does nothing.
*/
extern void BASCloseDescriptorArray(
CFArrayRef descArray
);
extern void BASTerminateCommand(
AuthorizationRef auth,
const char * bundleID,
const char * installToolPath
);
/////////////////////////////////////////////////////////////////
#pragma mark ***** Utility Routines
// The following definitions are exported purely for the convenience of the
// install tool ("BetterAuthorizationSampleLibInstallTool.c"). You must not
// use them in your own code.
#if !defined(BAS_PRIVATE)
#define BAS_PRIVATE 0
#endif
#if BAS_PRIVATE
// Hard-wired file system paths for the launchd property list file and
// the privileged helper tool. In all cases, %s is a placeholder
// for the bundle ID (in file system representation).
#define kBASPlistPathFormat "/Library/LaunchDaemons/%s.plist"
#define kBASToolDirPath "/Library/PrivilegedHelperTools" // KEEP IN SYNC!
#define kBASToolPathFormat "/Library/PrivilegedHelperTools/%s" // KEEP IN SYNC!
// Commands strings for the install tool.
#define kBASInstallToolInstallCommand "install"
#define kBASInstallToolEnableCommand "enable"
#define kBASInstallToolTerminateCommand "terminate"
// Magic values used to bracket the process ID returned by the install tool.
#define kBASAntiZombiePIDToken1 "cricket<"
#define kBASAntiZombiePIDToken2 ">bat"
// Magic value used to indicate success or failure from the install tool.
#define kBASInstallToolSuccess "oK"
#define kBASInstallToolFailure "FailUrE %d"
#endif
#ifdef __cplusplus
}
#endif
#endif