I'm using authopen inside one of my programs to modify files owned by root. As can be seen in the screenshot below authopen asks for a admin password. What I'd like to achieve is that the dialog shows my app's name and then passes the authorization to authopen.
Code
Launching authopen which returns an authorized file descriptor.
int pipe[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, pipe);
if (fork() == 0) { // child
// close parent's pipe
close(pipe[0]);
dup2(pipe[1], STDOUT_FILENO);
const char *authopenPath = "/usr/libexec/authopen";
execl(authopenPath,
authopenPath,
"-stdoutpipe",
[self.device.devicePath fileSystemRepresentation],
NULL);
NSLog(#"Fatal error, we should never reach %s:%d", __FILE__, __LINE__);
exit(-1);
} else { // parent
close(pipe[1]);
}
// get file descriptor through sockets
I'd really like not to use AuthorizationExecuteWithPrivileges because then I'd have to get more rights than I want to.
Apple has added an option to authopen in OS X 10.9 Mavericks that allows exactly this. Prior this seemed to be impossible.
-extauth
specifies that authopen should read one AuthorizationExternalForm structure from stdin,
convert it to an AuthorizationRef, and attempt to use it to authorize the open(2)
operation.
The authorization should refer to the sys.openfile right corresponding to the requested operation.
The authorization data will be read before any additional data supplied on stdin, and will
not be included in data written with -w.
I have not used this yet, so I do not have any sample code. If someone has, please add it to this answer.
I think if you give your app path in the 1st arg:
execl(authopenPath,
"app path", // <--
"-stdoutpipe",
[self.device.devicePath fileSystemRepresentation],
NULL);
the dialog will show:
"app namerequires that you type your password"
You need to be looking directly at the security framework, introduced in 10.4, I think, and been the main authorization source since 10.5. OSX still works within PAM (like Linux), but /etc/authorization now supersedes this. Apple does have one or two samples of code on how you could pragmatically create a class/entry for someone to authorize themselves against (or preauthorize/be preauthorized like folks who are allowed to print).
This question is old but it seems to be possible, as explained in this technical note :
Technical Note TN2095 : Authorization for Everyone
http://developer.apple.com/library/mac/#technotes/tn2095/_index.html
Related
I know the response to this question is "set it in the info.plist" but if I understand the way this works then if the user rejects the permissions you are requesting they don't get prompted again which effectively bricks your app unless you ...write code to check for permissions. Is that right? So I don't know why everyone acts like it's just automatic on iOS. You still have to check for permissions, right?
Assuming I am right, where can I find documentation on how to do it correctly. I checked this (solution was plugin I don't want to use), this (code is too complicated), this (long-winded non-answer), and this plus a few others from google which point to this plugin which I don't want to use. I just want a link to the documentation on how to check and request permissions on iOS. Is there such a link?
What I have looks like this:
private bool HasLocationPermission()
{
return CoreLocation.CLLocationManager.Status == CoreLocation.CLAuthorizationStatus.Authorized ||
CoreLocation.CLLocationManager.Status == CoreLocation.CLAuthorizationStatus.AuthorizedAlways ||
CoreLocation.CLLocationManager.Status == CoreLocation.CLAuthorizationStatus.AuthorizedWhenInUse;
}
but, of course, that is just for the "Location" permission. I don't see any information about what to check for in the LocationManager documentation. There's something about request rational or something? Where can I find how to do this? No plugins please.
If you study the code in the Permissions Plugin you linked to you can pretty easily deduct what you have to do.
In iOS there are 2 different types of location permissions, for either of them to work, you need to set up some descriptions in your Info.plist, which will be shown when prompted the permission dialog.
Set up either NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription depending on your needs. The distinction between this mode is. When in use, is for when occasionally needing location services, i.e. for briefly showing a map. While always is typically for apps needing it to track the users location all the time.
The section in the Info.plist will look something like:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Do you want My App to access your location?</string>
As you've already figured out you can use CoreLocation and CLLocationManager to get the current permission status:
var locationManager = new CLLocationManager();
var status = locationManager.Status;
Status will either be:
NotDetermined - the App doesn't have permission yet or maybe never asked for it.
AuthorizedAlways - if you requested Always location and added the key NSLocationAlwaysUsageDescription.
AuthorizedWhenInUse - if you requested When In Use location and added the key NSLocationWhenInUseUsageDescription
Denied - user said "no thank you"
If you want to request the permission, simply call:
locationManager.RequestAlwaysAuthorization(); // for always
locationManager.RequestWhenInUseAuthorization(); // for when in use
You can listen to Authorization changes with the AuthorizationChanged event:
locationManager.AuthorizationChanged += OnAuthorizationChanged;
The CLAuthorizationChangedEventArgs will provide you the new status. You may want to hook up this even before requesting the permission.
private void OnAuthorizationChanged(object sender, CLAuthorizationChangedEventArgs args)
{
if (args.Status == CLAuthorizationStatus.AuthorizedAlways ||
args.Status == CLAuthorizationStatus.AuthorizedWhenInUse)
{
// all green, you are good to start listening to location changes!
}
}
Now you can start listening to location changes:
locationManager.LocationsUpdated += OnLocationsUpdated;
locationManager.StartUpdatingLocation();
I've got an application that also configures and runs a daemon. I am trying to give both the daemon and the application access permissions to the keychain item. The basic code:
SecKeychainItemRef item;
// create a generic password item
SecTrustedApplicationRef appRef[2];
SecAccessRef ref;
SecTrustedApplicationCreateFromPath( NULL, &appRef[0] );
SecTrustedApplicationCreateFromPath( DAEMON_PATH, &appRef[1] );
CFArrayRef trustList = CFArrayCreate( NULL, ( void *)appRef, sizeof(appRef)/sizeof(*appRef), NULL );
SecAccessCreate( descriptor, trustList, &ref );
SecKeychainItemSetAccess( item, ref );
The keychain entry is created, however the only application listed in the Keychain Access tool as always having access is the main application. Let's call it FOO.app. DAEMON_PATH points to the absolute path of the daemon which is in the application bundle -- call it FOO.daemon.
If I manually go within Keychain Access and select the daemon, it does get added to the list.
Any idea on how to get SecTrustedApplicationCreateFromPath to honor the full/absolute path?
If you need an answer today...
I tried to replace access object for existing keychain item with no success, too. So, I decided to modify existing access object rather than replace it, and this approach works well.
The following pseudocode demonstrates the idea. Declarations, CFRelease()s and error checking are stripped for clarity sake.
SecKeychainItemCopyAccess(item, &accessObj);
SecAccessCopySelectedACLList(accessObj, CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList);
assert(CFArrayGetCount(aclList) == 1);
acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
SecACLCopySimpleContents(acl, &appList, &desc, &prompt_selector);
SecTrustedApplicationCreateFromPath(MY_APP_PATH, &app);
newAppList = CFArrayCreate(NULL, (const void**)&app, 1, NULL);
SecACLSetSimpleContents(acl, newAppList, desc, &psel);
SecKeychainItemSetAccess(item, accessObj);
I used SecAccessCopySelectedACLList to search for an ACL object with an appropriate authorization tag. You may require some other way for ACL filtering.
The straightforward access object creation must be more tricky, you are to create the same ACL structure as Keychain Access app does, rather than use default SecAccessCreate()'s ACLs. I couldn't cope with that way.
I am making a small app that deletes log files. I am using an NSTask instance which runs rm and srm (secure rm) to delete files.
I want to be able to delete files in:
/Library/Logs
~/Library/Logs
The issue is that the user account does not have permissions to access some files in the system library folder, such as the Adobe logs subfolder and others. For example, only the "system" user (group?) has r/w permissions for the Adobe logs folder and its contents, and the current user doesn't even have an entry in the permissions shown in the Get Info window for the folder.
What I want to be able to do exactly:
Obtain admin privileges.
Store the password in the Keychain so the app doesn't have to nag the user each time (Is the storage of the password a bad idea? Is it possible?)
Delete a file whatever the file permissions may be.
I am using NSTask because it offers notifications for task completion, getting text output from the task itself, etc. Would I need to use something else? If so, how could I replicate NSTask's completion notifications and output file handle while running rm and srm with admin privileges?
I am looking for the most secure way to handle the situation. i.e. I don't want my application to become a doorway for privilege escalation attacks.
I looked at the Authorization Services Programming Guide but I am not sure which case fits. At first I thought that AuthorizationExecuteWithPrivileges would be a good idea but after reading more on the subject it looks like this method is not recommended for security reasons.
A detailed answer would be very welcome. I'm sure some of you already had to do something similar and have some code and knowledge to share.
Thanks in advance!
Update:
I am now able to make the authentication dialog pop up and obtain privileges, like so:
OSStatus status;
AuthorizationRef authRef;
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
AuthorizationRights authRights;
AuthorizationItem authItems[1];
authItems[0].name = kAuthorizationRightExecute;
authRights.count = sizeof(authItems) / sizeof(authItems[0]);
authRights.items = authItems;
AuthorizationFlags authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed;
status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, authFlags, NULL);
From the looks of it, it seems that the "Factored Application" method looks the most appropriate. The thing is that, to me, rm already seems like an external helper tool. I'm not sure I get the setuid alternative suggested in the documentation. Could I set the setuid bit on rm and run it using the NSTask method I already implemented? This would mean that I wouldn't need to create my own helper tool. Could somebody elaborate on this subject?
I also looked at the BetterAuthorizationSample which is suggested as a more secure and recent alternative to the setuid bit method, but found it awfully complex for such as simple behavior. Any hints?
Thanks in advance for any help!
Perhaps a tad late, but this might be useful for future reference for other people. Most of the code is from this person.
Basically, it has a lot to do with Authorization on the Mac. You can read more about that here and here.
The code, which uses the rm tool:
+ (BOOL)removeFileWithElevatedPrivilegesFromLocation:(NSString *)location
{
// 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);
return NO;
}
// 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);
return NO;
}
// use rm tool with -rf
char *tool = "/bin/rm";
char *args[] = {"-rf", (char *)[location UTF8String], NULL};
FILE *pipe = NULL;
status = AuthorizationExecuteWithPrivileges(authorizationRef, tool, kAuthorizationFlagDefaults, args, &pipe);
if (status != errAuthorizationSuccess)
{
NSLog(#"Error: %d", status);
return NO;
}
// 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);
return YES;
}
I had this headache a few months ago. I was trying to get a shell script running with admin privileges that shutdown my computer at a certain time. I feel your pain.
I used the BetterAuthorizationSample which was a total nightmare to wade through. But I took the most pragmatic route - I didn't bother trying to understand everything that was going on, I just grabbed the guts of the code.
It didn't take me that long to get it doing what I wanted. I can't remember exactly what I altered, but you're welcome to check out my code:
http://github.com/johngallagher/TurnItOff
I hope this helps on your quest for a secure application!
I am having a problem with the AddressBook framework.
It all seems to be stemming from ABCopyRecordForUniqueId returning a record with old data.
Example:
I run up the program below in one terminal window - it shows the current data.
I make a change through the address book UI - my program continues to show old data.
I run up another instance of the same program in a new terminal window - it shows the updated data.
I have tried posting on the omnigroup site with no luck :( so any guidance is really appreciated
PS: If you would like to try the code, to get an address book ID you can export a contact as a vCard and open it with a text editor
int main (int argc, const char * argv[])
{
ABAddressBookRef addressBook = ABGetSharedAddressBook();
while(1)
{
ABRecordRef addressBookRecord = NULL;
addressBookRecord = ABCopyRecordForUniqueId(addressBook, CFSTR("4064D587-0378-4DCF-A6B9-D3702F01C94C:ABPerson"));
CFShow(addressBookRecord);
CFRelease(addressBookRecord);
sleep(1);
}
return 0;
}
I tried your example myself and am seeing the same problem. Out of curiosity, I tried asking for the shared address book inside the loop (in case there was some weirdness going on with the address book singleton) but this made no difference. I checked out the documentation (ABAddressBook C Reference) as well as the higher-level address book framework reference and guide. As far as I can tell, you're doing the right thing.
I'd file this as a bug against the framework.
thanks for the suggestion. I did file a report but it turns out this is expected
Annoying that it wasn't in the docs..
"Engineering has determined that this issue behaves as intended based on the following information:
The address book requires the run loop to be run in order to receive updates from other applications. Instead of sleep(1), use CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false)."
Thanks,
M
I'm using Microsoft's DSOFramer control to allow me to embed an Excel file in my dialog so the user can choose his sheet, then select his range of cells; it's used with an import button on my dialog.
The problem is that when I call the DSOFramer's OPEN function, if I have Excel open in another window, it closes the Excel document (but leaves Excel running). If the document it tries to close has unsaved data, I get a dialog boxclosing Excel doc in another window. If unsaved data in file, dsoframer fails to open with a messagebox: Attempt to access invalid address.
I built the source, and stepped through, and its making a call in its CDsoDocObject::CreateFromFile function, calling BindToObject on an object of class IMoniker. The HR is 0x8001010a The message filter indicated that the application is busy. On that failure, it tries to InstantiateDocObjectServer by classid of CLSID Microsoft Excel Worksheet... this fails with an HRESULT of 0x80040154 Class not registered. The InstantiateDocObjectServer just calls CoCreateInstance on the classid, first with CLSCTX_LOCAL_SERVER, then (if that fails) with CLSCTX_INPROC_SERVER.
I know DSOFramer is a popular sample project for embedding Office apps in various dialog and forms. I'm hoping someone else has had this problem and might have some insight on how I can solve this. I really don't want it to close any other open Excel documents, and I really don't want it to error-out if it can't close the document due to unsaved data.
Update 1: I've tried changing the classid that's passed in to Excel.Application (I know that class will resolve), but that didn't work. In CDsoDocObject, it tries to open key HKEY_CLASSES_ROOT\CLSID\{00024500-0000-0000-C000-000000000046}\DocObject, but fails. I've visually confirmed that the key is not present in my registry; The key is present for the guide, but there's no DocObject subkey. It then produces an error message box: The associated COM server does not support ActiveX document embedding. I get similar (different key, of course) results when I try to use the Excel.Workbook programid.
Update 2: I tried starting a 2nd instance of Excel, hoping that my automation would bind to it (being the most recently invoked) instead of the problem Excel instance, but it didn't seem to do that. Results were the same. My problem seems to have boiled down to this: I'm calling the BindToObject on an object of class IMoniker, and receiving 0x8001010A (RPC_E_SERVERCALL_RETRYLATER) The message filter indicated that the application is busy. I've tried playing with the flags passed to the BindToObject (via the SetBindOptions), but nothing seems to make any difference.
Update 3: It first tries to bind using an IMoniker class. If that fails, it calls CoCreateInstance for the clsid as a fallback method. This may work for other MS Office objects, but when it's Excel, the class is for the Worksheet. I modified the sample to CoCreateInstance _Application, then got the workbooks, then called the Workbooks::Open for the target file, which returns a Worksheet object. I then returned that pointer and merged back with the original sample code path. All working now.
#Jinjin
You can use the #import directive to import your Excel's OLB file. this should generate (and automatically include an Excel .tlh file which contains the structures for _Application (and the rest you need)). Ideally, you should find an OLB file that matches the earliest Excel version that you wish to support. The one on your local system is probably in c:\Program Files\Microsoft Office\Office12 (presuming you have Office 2007 installed). It may be named Excel.olb, or XL5EN32.OLB (different, obviously if you haven't installed the US English verion of Excel.
So, copy the .olb file to your project source directory, then at the top of the source file, add a line for #import "XL5EN32.olb".
Yes, opens older versions. Best way to guarantee that this will be the case is to find an OLB file (mentioned in item 1 above) that is from an installation of Excel that is the earliest version you wish to support. I use an Excel9.olb from Office 2000. Works fine with my testing of Excel versions all the way to the latest from Office 2007.
Yes, you should use dsoframer normally after making these changes.
I'm afraid I probably can't do that due to restrictions of my employer. However, if you take the "stock" dsoframer project, make the changes described in part 1 of this post, and the changes I described in my earlier post, you have pretty much recreated exactly what I have.
#Jinjin: did you put the import statement (#import "XL5EN32.olb") in the cpp file where you are using the Excel::_Application? If not, do that... can't just add it to the project. If you have already done that, try also adding this statement to the cpp file where you are using those mappings #import "Debug\XL5EN32.tlh". The tlh file is a header that is generated by running the #import; you should find it in your Debug directory (presuming you're performing a Debug build).
Renaming _Application to Application (and the others) is not the right way to go. The _Application structure is the one that has the mappings. That is why you are not finding the app->get_Workbooks.
What file are you looking in that you are finding Application but not _Application?
Assuming you are using the DSOFRAMER project, you need to add this code to dsofdocobj.cpp in the CreateFromFile function, at around line 348:
CLSID clsidExcelWS;
hr = CLSIDFromProgID(OLESTR("Excel.Sheet"),clsidExcelWS);
if (FAILED(hr)) return hr;
if (clsid == clsidExcelWS)
{
hr = InstantiateAndLoadExcel(pwszFile, &pole);
if (FAILED(hr)) return hr;
}
else
{
<the IMoniker::BindToObject call and it's failure handling from the "stock" sample goes here>
}
Then, define the following new member function in CDsoDocObject:
////////////////////////////////////////////////////////////////////////
// CDsoDocObject::InstantiateAndLoadExcel (protected)
//
// Create an instance of Excel and load the target file into its worksheet
//
STDMETHODIMP CDsoDocObject::InstantiateAndLoadExcel(LPWSTR pwszFile, IOleObject **ppole)
{
IUnknown *punkApp=NULL;
Excel::_Application *app=NULL;
Excel::Workbooks *wbList=NULL;
Excel::_Workbook *wb;
CLSID clsidExcel;
HRESULT hr = CLSIDFromProgID(OLESTR("Excel.Application"), &clsidExcel);
if (FAILED(hr))
return hr;
hr = CoCreateInstance(clsidExcel, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&punkApp);
if (SUCCEEDED(hr))
{
hr = punkApp->QueryInterface(__uuidof(Excel::_Application),(LPVOID *)&app);
if (SUCCEEDED(hr))
{
hr = app->get_Workbooks(&wbList);
VARIANT vNoParam;
VariantInit(&vNoParam);
V_VT(&vNoParam) = VT_ERROR;
V_ERROR(&vNoParam) = DISP_E_PARAMNOTFOUND;
VARIANT vReadOnly;
VariantInit(&vReadOnly);
V_VT(&vReadOnly) = VT_BOOL;
V_BOOL(&vReadOnly) = VARIANT_TRUE;
BSTR bstrFilename = SysAllocString(pwszFile);
hr = wbList->Open(bstrFilename, vNoParam,vNoParam,vNoParam,vNoParam,vReadOnly,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,0,&wb);
if (SUCCEEDED(hr))
hr = wb->QueryInterface(IID_IOleObject, (void**)ppole);
VariantClear(&vReadOnly);
VariantClear(&vNoParam);
SysFreeString(bstrFilename);
}
}
if (wb != NULL) wb->Release();
if (wbList != NULL) wbList->Release();
if (app != NULL) app->Release();
if (punkApp != NULL) punkApp->Release();
return hr;
}