In an application I'm making I need to run the following command as root (user will be prompted trice if they really want to, and they will be asked to unmount their drives) using NSTask:
/bin/rm -rf /
#Yes, really
The problem is that simply using Substitute User Do (sudo
) doesn't work as the user needs to enter the password to the non-available stdin. I'd rather like to show开发者_如何转开发 the user the same window as you'd see when you click the lock in Preferences.app, like this (hopefully with a shorter password):
Can anyone help me with this? Thanks.
Check out STPrivilegedTask, an Objective-C wrapper class around AuthorizationExecuteWithPrivileges() with an NSTask-like interface.
That's one of the hardest tasks to do properly on Mac OS X.
The guide documenting how to do this is the Authorization Services Programming Guide. There are multiple possibilities, as usual the most secure is the hardest to implement.
I've started writing a tool that uses a launchd daemon (the most secure way), the code is available on google code. So if you want, you can copy that code.
I think I can now answer this, thanks to some Googling and a nice find in this SO question. It's very slightly hacky, but IMHO is a satisfactory solution.
I wrote this generic implementation which should achieve what you want:
- (BOOL) runProcessAsAdministrator:(NSString*)scriptPath
withArguments:(NSArray *)arguments
output:(NSString **)output
errorDescription:(NSString **)errorDescription {
NSString * allArgs = [arguments componentsJoinedByString:@" "];
NSString * fullScript = [NSString stringWithFormat:@"%@ %@", scriptPath, allArgs];
NSDictionary *errorInfo = [NSDictionary new];
NSString *script = [NSString stringWithFormat:@"do shell script \"%@\" with administrator privileges", fullScript];
NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
NSAppleEventDescriptor * eventResult = [appleScript executeAndReturnError:&errorInfo];
// Check errorInfo
if (! eventResult)
{
// Describe common errors
*errorDescription = nil;
if ([errorInfo valueForKey:NSAppleScriptErrorNumber])
{
NSNumber * errorNumber = (NSNumber *)[errorInfo valueForKey:NSAppleScriptErrorNumber];
if ([errorNumber intValue] == -128)
*errorDescription = @"The administrator password is required to do this.";
}
// Set error message from provided message
if (*errorDescription == nil)
{
if ([errorInfo valueForKey:NSAppleScriptErrorMessage])
*errorDescription = (NSString *)[errorInfo valueForKey:NSAppleScriptErrorMessage];
}
return NO;
}
else
{
// Set output to the AppleScript's output
*output = [eventResult stringValue];
return YES;
}
}
Usage example:
NSString * output = nil;
NSString * processErrorDescription = nil;
BOOL success = [self runProcessAsAdministrator:@"/usr/bin/id"
withArguments:[NSArray arrayWithObjects:@"-un", nil]
output:&output
errorDescription:&processErrorDescription
asAdministrator:YES];
if (!success) // Process failed to run
{
// ...look at errorDescription
}
else
{
// ...process output
}
Okay, so I ran into this while searching how to do this properly... I know it's probably the least secure method of accomplishing the task, but probably the easiest and I haven't seen this answer anywhere. I came up with this for apps that I create to run for my own purposes and as a temporary authorization routine for the type of task that user142019 is describing. I don't think Apple would approve. This is just a snippet and does not include a UI input form or any way to capture stdout, but there are plenty of other resources that can provide those pieces.
Create a blank file called "script.sh" and add it to your project's supporting files.
Add this to header file:
// set this from IBOutlets or encrypted file
@property (strong, nonatomic) NSString * password;
@property (strong, nonatomic) NSString * command;
implementation:
@synthesize password;
@synthesize command;
(IBAction)buttonExecute:(id)sender {
NSString *scriptPath = [[NSBundle mainBundle]pathForResource:@"script" ofType:@"sh"];
NSString *scriptText = [[NSString alloc]initWithFormat:@"#! usr/sh/echo\n%@ | sudo -S %@",password,command];
[scriptText writeToFile:scriptPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSTask * task = [[NSTask alloc]init];
[task setLaunchPath:@"/bin/sh"];
NSArray * args = [NSArray arrayWithObjects:scriptPath, nil];
[task setArguments:args];
[task launch];
NSString * blank = @" ";
[blank writeToFile:scriptPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
The last step is just so you don't have a cleartext admin password sitting in your bundle. I recommend making sure that anything beyond the method be obfuscated in some way.
In one of my cases this is not correct:
> The problem is that simply using Substitute User Do (sudo) doesn't work as the user needs to enter the password >
I simply edited /etc/sudoers to allow the desired user to start any .sh script without prompting for password. So you would execute a shell script which contains a command line like sudo sed [...] /etc/printers.conf to modify the printers.conf file, and the /etc/sudoers file would contain this line
myLocalUser ALL=(ALL) NOPASSWD: ALL
But of course I am looking for a better solution which correctly prompts the user to type in an admin password to allow the script or NSTask to execute. Thanks for the code which uses an AppleScript call to prompt and execute the task/shell script.
精彩评论