I've read various posts online about developers' travails with OSX's AuthorizationExecuteWithPrivileges API, and misunderstanding about how it works. So, here is some of what I've learned about it.
Apple's documentation states that it "Runs an executable tool with root privileges." But, users soon discover that the new process does not, in fact, run as root. In reality, the new process executes at an elevated privilege level that allows it to become the root user by calling 'setuid(0)'. For example:
// Fork a child process
pid_t child_pid = fork();
if(child_pid == 0)
{
// This is done by the child process
// Change to the root user
uid_t userid = getuid();
setuid(0); // set root permissions
// Do things as root ...
// Restore normal permissions
setuid(userid);
exit(0);
}
Note that there is danger in using AuthorizationExecuteWithPrivileges. For example, your app might invoke a helper tool named 'grok' that will execute with the elevated privileges. If a hacker figures out that 'grok' is started this way, he can replace 'grok' with his own trojan binary, which can then make itself root and thus do nasty things to your system.
The first parameter to AuthorizationExecuteWithPrivileges is an AuthorizationRef object. This is obtained via a call to AuthorizationCreate:
AuthorizationRef authorizationRef;
OSStatus status = AuthorizationCreate(NULL,
kAuthorizationEmptyEnvironment,
kAuthorizationFlagDefaults,
&authorizationRef);
if (status != errAuthorizationSuccess)
{
// Notify user of the error ...
}
The actual elevated rights you request are specified via a call to AuthorizationCopyRights, at which time the user is prompted to enter his password. Along with requesting certain rights, you can specify a custom icon and text for the password popup. Note however that your icon will not appear if it resides in a directory beneath any ancestor directory that lacks Everyone access. Here's what the icon specification looks like, which is passed as the AuthorizationEnvironment parameter to AuthorizationCopyRights:AuthorizationItem kAuthEnv[1];
const char *iconPath = "/Applications/MyApp.app/Resources/myicon.icns";
kAuthEnv[0].name = kAuthorizationEnvironmentIcon;
kAuthEnv[0].valueLength = strlen(iconPath);
kAuthEnv[0].value = (void *)iconPath; // fully qualified path
kAuthEnv[0].flags = 0;
AuthorizationEnvironment authorizationEnvironment;
authorizationEnvironment.items = kAuthEnv;
authorizationEnvironment.count = 1;
Next, in order to use AuthorizationExecuteWithPrivileges, you must request the "system.privilege.admin" right. You set this up as follows:
const char *grokPath = "/Utilities/grokUtil/grok";
AuthorizationItem executeRight = {
kAuthorizationRightExecute,
strlen(grokPath)
(void *)grokPath,
0};
AuthorizationRights rightsSet = {1, &executeRight};
Note that kAuthorizationRightExecute is defined as "system.privilege.admin" in the Security framework's AuthorizationTags.h. This is then used in AuthorizationCopyRights to actually acquire the rights for the AuthorizationRef object:
AuthorizationFlags flags =
kAuthorizationFlagDefaults |
kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize |
kAuthorizationFlagExtendRights;
// Call AuthorizationCopyRights to determine
// or extend the allowable rights
OSStatus status = AuthorizationCopyRights(
authorizationRef,
&rightsSet,
&authorizationEnvironment,
flags,
NULL);
if (errAuthorizationCanceled == status)
{
// User canceled authentication ...
}
else if (status != errAuthorizationSuccess)
{
// Notify the user of the error ...
}
All that's left at this point is to execute the privileged helper tool:
FILE *fpStdout = NULL;
status = AuthorizationExecuteWithPrivileges(
authorizationRef,
(const char *)grokPath,
kAuthorizationFlagDefaults,
argv, // normal argv array of program args
&fpStdout);
bool success = (status == errAuthorizationSuccess);
pid_t newProcId;
if (success)
{
// Get the new process id
newProcId = fcntl(fileno(fpStdout), F_GETOWN, 0);
fclose(fpStdout);
}
else
{
// Notify user of the error ...
}
AuthorizationExecuteWithPrivileges returns as soon as the new process has started. It does not execute it as a child of the current process, so a waitpid on the resulting process id gives an error.