sudo auth session while executing command using NSTask in cocoa application - objective-c

I have an application that requires executing a command with sudo. How can I ask for a password and if successful, then run the sudo command using NSTask.

If you're looking for a more lightweight solution, there is another way. 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
}
Hat tip to user950473.

Use the Authorization Services, Luke. (If you've ever seen the "Application XYZ needs an admin password to continue", this is how it's implemented. It does not use sudo under the sheets.)
http://developer.apple.com/library/mac/#documentation/Security/Conceptual/authorization_concepts/01introduction/introduction.html

Related

run applescript from cocoa app stopped working

This code had been working fine until just recently. I hadn't' changed anything nor upgraded my system and I'm completely flummoxed.
I've been using it for 6 years and now it dies on me.
Is there an easier or better way of running an applescript from within a cocoa application? At this point I'm happy to pay to fix this problem!
utils.h
#import <Foundation/Foundation.h>
#interface Utils : NSObject
// Runs an applescript with a given map of variables (name/value)
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables;
// Runs an applescript from a file pathwith a given map of variables
// (name/value)
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables;
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor;
// String is empty or only has white characters (space, tab...)
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string;
#end
Utils.M
#import "Utils.h"
#implementation Utils
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor {
// Enumerate the apple descriptors (lists) returned by the applescript and
// make them into arrays
NSMutableArray *returnArray = [NSMutableArray array];
NSInteger counter, count = [descriptor numberOfItems];
for (counter = 1; counter <= count; counter++) {
NSAppleEventDescriptor *desc = [descriptor descriptorAtIndex:counter];
if (nil != [desc descriptorAtIndex:1]) {
[returnArray addObject:[Utils arrayFromDescriptor:desc]];
} else {
NSString *stringValue = [[descriptor descriptorAtIndex:counter] stringValue];
if (nil != stringValue) {
[returnArray addObject:stringValue];
} else {
[returnArray addObject:#""];
}
}
}
return returnArray;
}
+ (NSString *)escapeCharacters:(NSString *)string {
return [string stringByReplacingOccurrencesOfString:#"\"" withString:#"\\\""];
}
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables {
NSString *input = #"";
NSArray *variableNames = [variables allKeys];
// Transform the dictionary of names/values to set sentences of applescript
for (NSString *variableName in variableNames) {
NSObject *variableValue = [variables objectForKey:variableName];
if ([variableValue isKindOfClass:[NSString class]]) {
input =
[input stringByAppendingString:[NSString stringWithFormat:#"set %# to (\"%#\" as text)\n", variableName,
[Utils escapeCharacters:variableValue], nil]];
} else if ([variableValue isKindOfClass:[NSNumber class]]) {
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to (%# as integer)\n",
variableName, variableValue, nil]];
} else if ([variableValue isKindOfClass:[NSArray class]]) {
// Initialize a list
NSString *entry;
NSArray *values = (NSArray *)variableValue;
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to {", variableName]];
BOOL first = TRUE;
for (entry in values) {
if (!first) {
input = [input stringByAppendingString:#", "];
}
input = [input
stringByAppendingString:[NSString stringWithFormat:#"\"%#\"", [Utils escapeCharacters:entry], nil]];
first = FALSE;
}
input = [input stringByAppendingString:#"}\n"];
}
}
NSString *finalScript = [input stringByAppendingString:[NSString stringWithFormat:#"\n\n%#", source]];
NSLog(#"Final script: %#", finalScript);
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:finalScript];
NSDictionary *error;
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&error];
NSLog(#"applescript error: %#", [error description]);
// Transform the return value of applescript to nested nsarrays
return [Utils arrayFromDescriptor:descriptor];
}
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables {
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:scriptName ofType:#"applescript"];
NSString *scriptSource =
[[NSString alloc] initWithContentsOfFile:scriptPath encoding:NSASCIIStringEncoding error:nil];
return [Utils runApplescript:scriptSource withVariables:variables];
}
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string {
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [string isEqualToString:#""];
}
#end
Easier, yes, although whether that’s your actual problem is another question.
http://appscript.sourceforge.net/asoc.html
I assume you’ve already got other details, including sandboxing and hardening settings and plist entries, taken care of. (Recent Xcode upgrades also had a habit of breaking it when auto-upgrading your project files, by turning on hardening for you so Apple events can’t get out.)

objective c gets EXC_BAD_ACCESS error on completionhandler

i'm new to objective-c, please bear with me if i ask stupid questions :)
The following is part of code i have to start vpn tunnel, but keeps getting EXC_BAD_ACCESS error
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL * error))completionHandler {
vpnAdapter = [[OpenAdapter alloc] init];
vpnAdapter.delegate = self;
// get config
config = [[NSDictionary alloc] init];
NETunnelProviderProtocol *protocol = (NETunnelProviderProtocol *)self.protocolConfiguration;
config = protocol.providerConfiguration;
host = config[#"server"];
// Load config data
username = config[#"username"];
password = config[#"password"];
if(option != nil){
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
// return success;
completionHandler(&success); // Thread 2: EXC_BAD_ACCESS (code=1, address=0xbcc68f020)
}];
}else{
[vpnAdapter connect:host user:username pass:password add:NO completionHandler:^(BOOL success){
completionHandler(&success);
}];
}
}
here is connect method
- (void)connect: (NSString *) host user:(NSString *)username pass:(NSString *) password add:(Boolean) isAdd completionHandler:(void (^)(BOOL success)) completionHandler{
dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
dispatch_queue_t connectQueue = dispatch_queue_create("me.ss-abramchuk.open-adapter.connection", attributes);
dispatch_async(connectQueue, ^{
// Call connect
//int ret=1;
NSArray* options = [NSArray arrayWithObjects:
#"--user", username,
host,
nil];
if(isAdd){
options = [NSArray arrayWithObjects:
#"--user", username,
#"--protocol", #"ad",
host,
nil];
}
//NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
//NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:1+[options count]];
[arguments addObject:#"connect"];
[arguments addObjectsFromArray:options];
int argc = [arguments count];
char **argv = (char **)malloc(sizeof(char*) * (argc + 1));
[arguments enumerateObjectsUsingBlock:^(NSString *option, NSUInteger i, BOOL *stop) {
const char * c_string = [option UTF8String];
int length = (int)strlen(c_string);
char *c_string_copy = (char *) malloc(sizeof(char) * (length + 1));
strcpy(c_string_copy, c_string);
argv[i] = c_string_copy;
}];
argv[argc] = NULL;
const char *cfPass=[password UTF8String];
int ret = self.vpnClient->start2connect(argc, argv, cfPass);
BOOL result;
if (ret!=0){
result=false;
}
else {result = true;}
completionHandler(result);
});
}
all of these are from networkextension and while debugging, i found int ret = self.vpnClient->start2connect(argc, argv, cfPass);
seems not returning any value.
however, i confirmed that the start2connect method does return int value
so for now, anyone can help explain what's wrong?
thanks
The BOOL * is a pointer to a BOOL. We don’t use that pattern very often. We use it where the block needs to update a BOOL property somewhere, e.g. in enumerateMatchesinString, where you can update the boolean that stop points to in order to stop the enumeration.
But this is a completion handler, so there’s no point in passing a pointer to the boolean (one that presumably was on the stack, inviting problems). Just pass the boolean itself, not a pointer to it.
I would suggest that instead of:
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL * error))completionHandler {
...
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
completionHandler(&success);
}];
...
}
That you want:
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL success))completionHandler {
...
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
completionHandler(success);
}];
...
}
Note the block parameter isn’t BOOL * error but rather BOOL success and when it calls the completionHandler, there’s not & before success.
If there’s some reason you needed to update the BOOL, then that’s a different matter, but it doesn’t make sense in the context of a completion handler.

chatDidReceiveMessage method not called QuickBlox

I am using QuickBlox-iOS SDK for chatting. Login/Signup is working perfectly. Also I am able to send message but the delegate method
- (void)chatDidReceiveMessage:(QBChatMessage *)message;
is not getting called. Here's the code I am using to setup chat. Adding the following code in appDelegate :
// connect to Chat
[[QBChat instance] addDelegate:self];
QBUUser *currentUser = [QBUUser user];
currentUser.ID = [Global sharedInstance].currentUser.ID;
currentUser.password = #"password";
[[QBChat instance] connectWithUser:currentUser completion:^(NSError * _Nullable error) {
NSLog(#"connect to chat error %#",error);
}];
And the below code I am using to send message :
QBChatMessage *message = [QBChatMessage message];
message.recipientID=[Global sharedInstance].QBUserID;
message.senderID=[Global sharedInstance].currentUser.ID;
[message setText:messageTextView.text];
message.dateSent = [NSDate date];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[#"save_to_history"] = #YES;
[message setCustomParameters:params];
[QBRequest createMessage:message successBlock:^(QBResponse *response, QBChatMessage *createdMessage) {
NSLog(#"success: %#", createdMessage);
} errorBlock:^(QBResponse *response) {
NSLog(#"ERROR: %#", response.error);
}]
I checked on QuickBlox dashboard. It shows all the sent/received messages. But the delegate is not getting called when I send message to another user. I am not using any additional services classes (QMServices) like they are using in their Example Project. Any help would be appreciated. Thanks
I don't understand why you're using the [QBRequest createMessage:successBlock:errorBlock:] method to send messages to another user.
For me what always worked was to create a chatDialog with the user you're trying to message, like so:
QBChatDialog *dialog = [[QBChatDialog alloc] initWithDialogID:nil
type: QBChatDialogTypePrivate];
dialog.occupantIDs = #[#([Global instance].QBUserID),
#([Global instance].currentUser.user.ID)];
Afterwards, you can call Quickblox method to create the dialog on the servers:
if (dialog.ID == nil) {
[QBRequest createDialog:dialog successBlock:^(QBResponse *response, QBChatDialog *createdDialog) {
[self sendMessageToDialog: dialog withText:#"Hello friend!"];
} errorBlock:^(QBResponse *response) {
NSLog(#"dialog creation err: %#", response);
}];
}
Create the message:
- (QBChatMessage *) createMessageWithText: (NSString *)text andDialog: (QBChatDialog*)dialog {
QBChatMessage *message = [QBChatMessage message];
message.text = text;
message.senderID = [Global instance].currentUser.ID;
message.markable = YES;
message.deliveredIDs = #[#([Global instance].currentUser.ID)];
message.readIDs = #[#([Global instance].currentUser.ID)];
message.dialogID = dialog.ID;
message.dateSent = [NSDate date];
message.recipientID = dialog.recipientID;
message.customParameters = [NSMutableDictionary dictionary];
message.customParameters[kQMCustomParameterDialogID] = dialog.ID;
message.customParameters[kQMCustomParameterDialogType] = [NSString stringWithFormat:#"%lu",(unsigned long)dialog.type];
message.customParameters[#"application_id"] = #"<your-application-id>";
message.customParameters[#"save_to_history"] = #"1";
if (dialog.lastMessageDate != nil){
NSNumber *lastMessageDate = #((NSUInteger)[dialog.lastMessageDate timeIntervalSince1970]);
message.customParameters[kQMCustomParameterDialogRoomLastMessageDate] = [lastMessageDate stringValue];
}
if (dialog.updatedAt != nil) {
NSNumber *updatedAt = #((NSUInteger)[dialog.updatedAt timeIntervalSince1970]);
message.customParameters[kQMCustomParameterDialogRoomUpdatedDate] = [updatedAt stringValue];
}
return message;
}
And then send the message to the dialog:
- (void) sendMessageToDialog: (QBChatDialog *)dialog withText: (NSString *)text {
QBChatMessage *message = [[ChatService shared] createMessageWithText:text andDialog:self.dialog];
[dialog sendMessage:message completionBlock:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"error creating message %#", error);
} else {
NSLog(#"message sent!");
}
}];
}
I think following this flux you'll be able to receive the callback through the delegate.
EDIT - I forgot to mention the consts I used in the code above are:
NSString const *kQMCustomParameterDialogID = #"dialog_id";
NSString const *kQMCustomParameterDialogRoomName = #"room_name";
NSString const *kQMCustomParameterDialogRoomPhoto = #"room_photo";
NSString const *kQMCustomParameterDialogRoomLastMessageDate = #"room_last_message_date";
NSString const *kQMCustomParameterDialogUpdatedDate = #"dialog_updated_date";
NSString const *kQMCustomParameterDialogType = #"type";
NSString const *kQMCustomParameterDialogRoomUpdatedDate = #"room_updated_date";
Have you added <QBChatDelegate> into your .h file.

Objective-C: How to remove multiple files with admin privileges?

I am trying to remove multiple files using apple script (given below) but it is not working and giving the following error:
Expected expression but found unknown token.
Here is my code:
{
///this string will be a contact of all the paths saperated by ' '
NSString* removingLocationInString = #"";
///for loop in order to concat all the string(paths) in one string
for (NSString* str in locations) {
removingLocationInString = [NSString stringWithFormat:#"%# \"%#\"", removingLocationInString, str];
}
///creating the command that will be run from apple script
///e.g. rm "~/user/vikas/desktop/file.txt" "~/user/vikas/desktop/file2.txt"
NSString* command = [NSString stringWithFormat:#"rm %#", removingLocationInString];
[self runScriptAsAdmin:command];
}
-(BOOL)runScriptAsAdmin:(NSString*) fullScript
{
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
NSArray *arguments = [NSArray arrayWithObjects:
#"-c" ,
[NSString stringWithFormat:#"%#", fullScript],
nil];
NSString * output = nil;
NSString * processErrorDescription = nil;
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
NSString *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
NSString *output = [eventResult stringValue];
return YES;
}
return NO;
}
Here is the script that is being generated
do shell script "rm "/Users/vikas/.Trash/.DS_Store"
"/Users/vikas/.Trash/SimpleCocoaBrowser 2.zip"
"/Users/vikas/.Trash/SimpleCocoaBrowser 3.zip"
"/Users/vikas/.Trash/SimpleCocoaBrowser 4 4.55.07 pm.zip"
"/Users/vikas/.Trash/SimpleCocoaBrowser 4.zip"" with administrator
privileges
In runScriptAsAdmin function add the following line:
/// if there are " in script then then make it \"
fullScript = [fullScript stringByReplacingOccurrencesOfString:#"\"" withString:#"\\\""];
The actual apple script should be as follows:
do shell script "rm \"/Users/vikas/.Trash/SimpleCocoaBrowser 2.zip\"
\"/Users/vikas/.Trash/SimpleCocoaBrowser 3.zip\"
\"/Users/vikas/.Trash/SimpleCocoaBrowser 4.zip\"" with administrator
privileges

How to add Root privileges to my OSX application?

I made an application as root user, it worked perfectly(in root user). When I try same application with a standard user it didn't work out. Then I get to know that I need root privileges to run the application. I Google for few day but didn't get it. I have read some questions and apple doc. which are-
https://developer.apple.com/library/mac/documentation/Security/Conceptual/authorization_concepts/01introduction/introduction.html
How to set my application to always run as root OSX
How to programmatically gain root privileges?
How to running application under root privilege?
but still I didn't get anything.
One more thing I get to know is I need to make a new project to get the root privileges, Am I right? Anything that you can tell me that will help me please do it. Every suggestion is most welcome.
for now I am trying this-
- (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;
}
}
NSString * output = nil;
NSString * processErrorDescription = nil;
BOOL success = [self runProcessAsAdministrator:#"/usr/bin/id"
withArguments:[NSArray arrayWithObjects:#"-un", nil]
output:&output
errorDescription:&processErrorDescription];
if (!success) // Process failed to run
{
// ...look at errorDescription
}
else
{
[objDisk setFileDescriptor:open(cDriveMountedPath, O_RDONLY)];
//[objDisk setDiskPath:cDriveMountedPath];
}
Thanks a lot in advance.
I use this code to get the root privilege for my application. I made a new project to use this code.
// Create authorization reference
OSStatus status;
AuthorizationRef authorizationRef;
// AuthorizationCreate and pass NULL as the initial
// AuthorizationRights set so that the AuthorizationRef gets created
// successfully, and then later call AuthorizationCopyRights to
// determine or extend the allowable rights.
// http://developer.apple.com/qa/qa2001/qa1172.html
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
kAuthorizationFlagDefaults, &authorizationRef);
if (status != errAuthorizationSuccess)
NSLog(#"Error Creating Initial Authorization: %d", status);
// kAuthorizationRightExecute == "system.privilege.admin"
AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights rights = {1, &right};
AuthorizationFlags flags = kAuthorizationFlagDefaults |
kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize |
kAuthorizationFlagExtendRights;
// Call AuthorizationCopyRights to determine or extend the allowable rights.
status = AuthorizationCopyRights(authorizationRef, &rights, NULL, flags, NULL);
if (status != errAuthorizationSuccess)
NSLog(#"Copy Rights Unsuccessful: %d", status);
NSLog(#"\n\n** %# **\n\n", #"This command should work.");
char *tool = "/sbin/dmesg";
char *args[] = {NULL};
FILE *pipe = NULL;
status = AuthorizationExecuteWithPrivileges(authorizationRef, tool,
flags, args, &pipe);
if (status != errAuthorizationSuccess)
NSLog(#"Error: %d", status);
// The only way to guarantee that a credential acquired when you
// request a right is not shared with other authorization instances is
// to destroy the credential. To do so, call the AuthorizationFree
// function with the flag kAuthorizationFlagDestroyRights.
// http://developer.apple.com/documentation/Security/Conceptual/authorization_concepts/02authconcepts/chapter_2_section_7.html
status = AuthorizationFree(authorizationRef, kAuthorizationFlagDestroyRights);