How to run a macOS app as root and use system calls? - xcode

I'm trying to run the setpriority command from my macOS app (objective-c). It never works and I'm assuming it is because the app is not being run as the root user.
I'm logged in to the admin account on my computer
I've tried opening the app with sudo
I've tried using chmod on the app
I've tried adding the app to the Accessibility list under Security and Privacy
Xcode version 9.2 (9C40b)
I would appreciate any help, thanks!

You want to run as root, or you want to run with sudo? There's a difference. Running as root is definitely not recommended, you will get strange behaviour from the system.
You wrote:
I've tried opening the app with sudo
That should work. How have you tried? You need to call the binary within the .app bundle. Running open against the bundle won't work.
e.g.
sudo ./Xcode.app/Contents/MacOS/Xcode

It's not recommended to run GUI apps on macOS as root. Instead, you should factor out the part of your application which needs root access into a separate helper tool, launch that tool as root using the SMJobBless() function, and then communicate with the tool using XPC.
Apple provides the EvenBetterAuthorizationSample example code to give a pretty good basic framework to work from.
EDIT: I decided to make my own authorization sample project a while ago that should be a little easier to use than the venerable EvenBetterAuthorizationSample. You can check it out at CSAuthSample.

Related

sudo is not installing Cordova to root folder on Mac

Given the Covid situation I'm having to work with students remotely and they are all on different machines. Specifically I'm attempting to help a student to remotely configure her Mac Book to run Cordova but when she uses node to install it's adding it to her user folder and not to the root user folder.
Overview:
I can confirm she has 'allow user to administer this computer' toggled on for her account and there are now other users other than guest, so I would think she should have full admin rights.
She is using sudo to enter the command and it's asking for her password to confirm
She's installing globally using the -g switch
However after installation Cordova is showing in '/User/[name]/.npm/bin/cordova' instead of '/usr/local/bin/cordova' so subsequently the cordova command is not recognized.
I use Windows so I'm not as familiar with the MacOS/Terminal but I would think if I can't fix this I can just add her personal user folder to the environment variables but I'm not sure if that opens a new kettle of fish with regards to permissions.
So with that in mind, I'd rather not go down that road if there's a quick solution that I'm not aware of.
UPDATE: after some more looking around I found that the prefix for node wasn't set correctly. I'm still not completely sure why this computer was different but regardless setting the prefix corrected the issue.

Uninstalling items installed by an .app when user deletes it, including SMJobBless helpers

The short version: is it possible to delete helper tools which were set up by the app (SMJobBless() etc.) when the app is deleted? If so, how?
The long version:
The Mac app we are developing unfortunately requires admin privileges to perform an occasional operation, and it also requires a background task to be live for other apps' plugins to connect to even when the app itself isn't running (this one can be unprivileged). The app will be signed with a Developer ID certificate, and distributed only outside the App Store.
We'd like the app to be a "good citizen" as far as possible, also on uninstall.
For the background task, we're using a login item, created using SMLoginItemSetEnabled(). This isn't amazing, because XPC messaging doesn't seem to work (we're using CFMessagePort instead - alternative suggestions welcome), but if the user deletes the app, the login item at least doesn't get loaded anymore on next login. I suspect there's still a trace of it somewhere in the system, but the executable inside the .app bundle is used, and when that disappears, the login item no longer runs.
For the occasional operation requiring admin rights, we've got a privileged helper tool which our app installs using SMJobBless(), and which implements a named XPC service, so the task spins up on demand when it receives a message from the main app. This is what Apple recommends and describes in its Even Better Authorization Sample.
The helper executable is copied to /Library/PrivilegedHelperTools/ by SMJobBless(), and the embedded launchd.plist ends up in /Library/LaunchDaemons/. Even though the OS has the information on which app "owns" the helper, it doesn't seem to uninstall it when the user deletes the app. Apple's sample is silent on uninstalling, other than the uninstall.sh script which is apparently intended to be used during development only. We don't need this helper while the app isn't running, so installing it as a full-blown launch daemon is slightly overkill, but we'd also like to avoid repeatedly annoying the user with the password prompt too. Besides, Apple advises against other forms of running code with admin privileges than SMJobBless() these days - for example SMJobSubmit() is marked deprecated.
So how do we clean up after ourselves?
I've found SMJobRemove(), but (a) when would we call that in our case - you can't run code on .app bundle deletion, or can you? and (b) it doesn't actually seem to clean up.
The only 2 things I can think of are not terribly satisfying:
Some kind of uninstaller app or script. But that seems pretty ugly too.
Don't worry about it and just leave a mess behind when the user deletes our app.
Update:
There have been some changes in this area with macOS 13.0 Ventura; there's an introduction to the new mechanism in the WWDC22 session 'What’s new in privacy'. The new SMAppService APIs support automatic cleanup for daemons, agents and login items. Unfortunately you'll of course still have to find a workaround for any older macOS versions you support.
Original answer:
There has been a similar question on the Apple Developer Forums at https://forums.developer.apple.com/thread/66821 - the recommendation by Apple is a manual uninstall mechanism, and consuming as few resources as possible if the user does not do this.
Apple DTS staff further recommended implementing a self-uninstall mechanism in the privileged launch daemon, to be triggered from the app via XPC. This is what we're going with.
I think the only solution you have right now is to use the uninstall shell code that you mentioned in order to physically remove the privileged helper from disk or to build an uninstaller for it. Either way you will have to ask the user to enter his/her password. This what all installers / uninstallers that require privileged access to the system do, and for a very good reason. That's why I avoid like the plague to use privileged helpers, but I understand that sometimes you really have to. I don't think it is good that you leave such a helper in the user's system, because it will reload next time the user starts up the computer.
I just checked ServiceManagement.h header and they state that SMJobRemove will be replaced by an API that will be made available through libxpc in the future. (Sometimes you really need to go to the headers to get extra info that the documentation does not give you.) Hopefully this promised replacement will uninstall it for us. However, I'd file a bug report and ask for that enhancement.
One solution you could consider is to include an uninstaller script or program in your .app bundle.
You can then pass the path of this small tool to your helper tool (via IPC) and have the execute the the uninstaller, thereby deleting itself. You will have to be careful that components are removed in the right order but it can be made to work.
You're correct that Apple does not provide an API to uninstall a helper tool installed with SMJobBless nor do they do so automatically. As for why macOS doesn't automatically do an uninstall, my educated guess is because macOS fundamentally doesn't have a unified concept of "install". While it's convention for apps to be located in /Applications (and a few other locations), it's perfectly valid for apps to be located and run from anywhere on the system including external drives and network drives. For example should macOS uninstall helper tools when apps disappear because the drive they're on is disconnected?
In terms of how to uninstall, doing so requires root permission and so realistically have the helper tool itself do the uninstall is the easiest option. You can have your app via XPC tell the helper to uninstall itself. Here's an example in Swift of how to do this; it's part of SwiftAuthorizationSample. The basic idea is:
Use the launchctl command line tool to unload the helper tool
Delete the helper tool executable
Delete the helper tool launchd plist
But there's a bit of additional complexity involved because launchctl won't let you unload a running process.

How to create application to launch wine and Windows application

I'm trying to use "wine" on MAC osX Sierra version 10.12, wine is version 1.9.19
In the terminal I can launch Windows applications, however its a pain to have to keep typing in:
/Applications/Wine\ Staging.app/Contents/MacOS/wine ~/.wine/drive_c/Program\ Files/HeidiSQL/heidisql.exe
I've searched around for a post on how to create shortcuts/applications to add to the launchpad, but so far none of the information has led me to a working end result. Either the locations of wine is different or it just doesn't work.
I've tried creating an application script:
do shell script "/Applications/Wine Staging.app/Contents/MacOS/wine ~/.wine/drive_c/Program Files/HeidiSQL/heidisql.exe"
But this won't run either.
For anyone having the same problem, in the end I created scripts which reside in my home folder:
Launch vi, create a file called HeidiSQL, insert:
wine ~/.wine/drive_c/Program\ Files/HeidiSQL/heidisql.exe
Save and exit file, grant file execute permissions:
chmod +x HeidiSQL
Launch wine terminal and type in ./HeidiSQL to launch, I then did the same for PSPad.exe:
wine ~/.wine/drive_c/Program\ Files\ \(x86\)/PSPad\ editor/PSPad.exe
I know this thread is a little old but I was just looking for something like this to launch HeidiSQL and I came up with these three solutions which I have tried and all of them work. I am putting my findings down here for it may help someone:
Wineskin (http://wineskin.urgesoftware.com), Playonmac (https://www.playonmac.com/en) and Winebottler
(http://winebottler.kronenberg.org)
Wineskin is a mac app that download and install (and manages, updates, etc) "wine" for you. It then creates a HeidiSQL.app (any name you want with any icon you want - but you need to configure it) around the windows.exe that includes the wine version selected and is completely self contained (does not need wine installed separately). Personally this is the neatest solution and my preferred even though there its a little more technical than Playonmac and similar to Winebottler. You need to read the instructions (which are very good) and you have choices to make as to the wine version to use and to configure the app. Noteworthy is that you need to change the windows version to XP rather than 7 or some buttons won't work.
Playonmac on the other hand is very user friendly. It has HeidiSQL listed on its website as compatible and its almost a single click install. You just select HeidiSQL from the list of programs and it will download everything you need for you. The only reason I prefer Wineskin is that it does not create a true self contained HeidiSQL.app. You can create a shortcut for it in your Applications folder but this will launch Playonmac and the app needs to be installed inside Playonmac. On the plus side, Playonmac will chose all the right settings for you to run the app correctly, the correct windows and wine version etc which is something you need to fiddle with with with Wineskin.
Winebottler again makes an app like Wineskin. The only real difference I could see is that with Wineskin the configuration app is actually part of the package whereas in Winebottler you have to recreate the package each time you make a change. I stuck with Wineskin. YMMV.

Mac OS X: Launching an app using NSWorkspace from a daemon doesn't work if the daemon is run as root

I created a Command Line Tool app using Xcode.
In that app, I used NSWorkspace to launch another application bundle (.app) as suggested here.
MacOsX: How to launch an application (.app) from a "Command Line Tool" type of app
All seem to work fine until I tried to start that Command Line Tool app as a daemon using launchctl.
If the daemon is run as the currently logged in user, then the Command Line Tool app launches the external app just fine.
If the daemon is run as root, then the Command Line Tool app cannot launch the external app.
Using NSWorkspace to open an app doesn't seem to work if the daemon is run as root.
Does anyone know the correct way to open another app from a daemon that's running as root?
This is likely a security restriction within MacOS you're running into.
What I would suggest doing is to create a code-signed "helper tool" that resets itself (via setuid -- which I wouldn't do outside of a code-signed app) to the userid of currently logged in user, and then do the NSWorkspace trick to launch the app in that user context.
Creating helper tools is not trivial though. The grand concepts are described in Apple's Authorization Services Programming Guide.
Take a look at Apple's "SMJobBless" sample code, which shows how to install the helper tool that you could set the user rights on and then modify it's helper tool code to launch your app.
This is too complicated a subject to really address in a StackOverflow answer. The short answer is that daemons can't launch applications reliably. For the long answer, please read Apple's Technical Note TN2083: Daemons and Agents.
I finally got it working by using the code mentioned in this link to get the "console user's" uid and gid:
https://superuser.com/questions/180819/how-can-you-find-out-the-currently-logged-in-user-in-the-os-x-gui.
The link shows an example of how to use SCDynamicStoreCreate() and SCDynamicStoreCopyConsoleUser() to get "console user's" uid and gid.
After getting the uid and gid, just set the uid and gid to those of the console user before using NSWorkspace to open an app and that did the trick for me.

Using OSX system keychain from a daemon

I have a daemon that needs to run as root and is started by launchd. This daemon needs to store some user supplied credentials so I have it writing them to the System keychain using SecKeychainOpen and similar functions.
I'm pretty sure that since this runs as root I have to use the System keychain (since using a user's login keychain is not correct as this doesn't run as a normal user).
My installer loads this into launchd using launchctl at the end of the install. The problem is that it doesn't actually start until after a reboot. I had OnLOad set as true in the plist, but it appears that when using the system keychain I need to the reboot for it to work.
I was wondering if anyone knows of some way to deal with this since it would be a much better user experience if a reboot was not necessary. So to be clear, can I programatically access the System keychain from a daemon using launchd without a reboot?
Thanks for any advice or ideas.
Since I eventually figured out that my problem wasn't what I thought it was at all, I figure I should put the resolution to it up here.
It turns out (despite what I read on a few sites) that it is perfectly OK to programatically access the System keychain with a daemon using launchd without a reboot. Just load the plist in the normal way (with root permission of course) and it all works.
My issue was the my postinstall script was never being run and was actually never even included in my .pkg installer when building on certain macs. Apparently, if you do not have PackageMaker.app installed, macports will still make a dmg with an installer for you, but the installer is a directory rather than the proper single file and it may lack certain parts (such as my postinstall script).
PackageMaker.app can be found on the Apple Developer site in the Xcode AuxTools package (it then needs to be put in either /Applications or somewhere else macports can find it).

Resources