I'm trying to run a process with elevated privileges - specifically OpenVPN, which requires root privileges to add routes to the system.
Looking around for existing examples around leads me to AuthorizationExecuteWithPrivileges, which seems to be now deprecated.
I tried the new SMJobBless method but I have a few questions regarding its viability for this purpose. As I understand it, I can create a separate privileged tool and communicate with it via sockets to ask the tool to perform privileged commands. However, I can't seem to figure out how I can start the OpenVPN process and capture its standard output in real time doing it this way as the main application would not be starting the process itself.
Another option is to use setuid on the OpenVPN executable. Could I possibly use the helper installed by SMJobBless to set the file permissions and setuid on the executable, then run it normally via NSTask?
Edit:
Lastly is there some way to just run one single command with privileges without having to install anything permanently? Although this new method is more secure, it seems very heavy handed.
I managed to go the SMJobBless method by using a helper and communicating it with XPC (the method shown on Nathan de Vries's Blog). Using this helper I set the permissions on the external process to 04555 (setuid, rx). Then the SMJob is removed as it is no longer required. Essentially emulating an "one-off" privileged job.
Following that I was able to use NSTask to start the process and capture its output in my main application.
Additionally I have a check at the start to see if the permissions are set right on the executable, if not the SMJob helper is re-blessed and permissions set.
If anyone has a cleaner solution, feel free to share. Thanks!
I had the same problem as you, needed it for a OpenVPN Manager App for MacOs X. Your solution is far from optimum because you open the openvpn binary for everyone setting setuid root.
This is a security hole and should be avoided, as it is totally unnecessary when you are using smjobbless helper. This helper runs as root and could do everything you want for you and with administrative privileges, so you can launch openvpn via this helper without setting setuid root on openvpn binary.
Apple designed this process as only your App, the Main App, can communicate with this helper as your Main App and your helper are signed with your developer certificates.
Any malicious App can't use this helper.
When you look at Nathans code you see, that he managed it to send messages to this helper and to get answers from this helper.
In his example there is sth like "Hey there Helper App" and the answer is "Hey there Host App".
So to get sth useful out of this you only have to send commands to the helper app, extract these commands on helper side and launch them with elevated privileges as the helper App runs with elevated privileges.
Look at Nathans code, there is sty like (in smjobblessappcontroller.m):
xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
const char* request = "openvpn --config OpenvpnConnection.ovpn";
xpc_dictionary_set_string(message, "request", request);
[self appendLog:[NSString stringWithFormat:#"Sending request: %s", request]];
xpc_connection_send_message_with_reply(connection, message, dispatch_get_main_queue(), ^(xpc_object_t event) {
const char* response = xpc_dictionary_get_string(event, "reply");
[self appendLog:[NSString stringWithFormat:#"Received response: %s.", response]];
});
With this you send the openvpn command to your helper App. You only have to extract this command on helper side to launch the process with elevated privileges.
Look at smjobblesshelper.c and do sth like (in __XPC_Peer_Event_Handler else branch):
const char *response = xpc_dictionary_get_string(event, "request");
In string response you have your openvpn command, now simple launch it:
system(response);
Thats all, this goes with elevated privileges. Now you can use this in your App perhaps in an IBAction push button in your main app, to start openvpn connections as you want every time a user clicks this button.
Related
I need to execute a realm-cli command (disable or delete a user) from a mobile application that uses RealmDB, i didn't find any part of the docs that was related to do it.
I thought that i can use mongoClient but i didn't find any methods that allows me to execute raw cli commands.
I need to execute commands like:
realm-cli users disable --app=<Your App ID> --user=<User ID>
Font:
https://docs.mongodb.com/realm/users/delete-or-revoke/
Is there any other way ?
You may need to host the realm-cli and write a HTTP interface middleware to make these call.
I don't believe you can run the application on a mobile application as you would need access to spawning libraries. realm-cli is available open source so it would be possible to port the application to something like C++ (from golang) to make it executable for something like Android or iOS - but it may be cheaper to just buy a VPS somewhere (or even host it locally for a spell) and just pass the arguments to a web route.
I have a windowed application and windowless helper, sitting inside the app bundle, and working as login item. App can start and stop the helper, everything woks there. The problem is that I need to create some bidirectional communication channel between them. And it should work in both sandboxed and not sandboxed versions, desirably in OS X 10.7+.
I've investigated the topic and find that XPC can provide peer-to-per connection. I've read related Apple docs, as well as few topics below:
Is possible to use Mac OS X XPC like IPC to exchange messages between processes? How?
Communicate with another app using XPC
http://afewguyscoding.com/2012/07/ipc-easy-introducing-xpc-nsxpcconnection/
https://www.objc.io/issues/14-mac/xpc/
But I can't find any description of how should I organize my XCode project. I have two targets: "Main App" and "Helper App". Now I need to add the third one, taking XPC Service, as a template. OK, but what to do next? Where this XPC bundle should be located to be available for both applications? Note, that helper sits in the main app bundle, as it's a login item. So, I need some clear instruction or just a XCode project sample.
Thanks,
Alex
Alright for anyone that has been struggling with this, I was finally able to 100% get communication working between two application processes, using NSXPCConnection
The key to note is that you can only create an NSXPCConnection to three things.
An XPCService. You can connect to an XPCService strictly through
a name
A Mach Service. You can also connect to a Mach Service
strictly through a name
An NSXPCEndpoint. This is what we're
looking for, to communicate between two application processes.
The problem being that we can't directly transfer an NSXPCEndpoint from one application to another.
It involved creating a machservice Launch Agent (See this example for how to do that) that held an NSXPCEndpoint property. One application can connect to the machservice, and set that property to it's own [NSXPCListener anonymousListener].endpoint
Then the other application can connect to the machservice, and ask for that endpoint.
Then using that endpoint, an NSXPCConnection can be created, which successfully established a bridge between the two applications. I have tested sending objects back and forth, and it all works as expected.
Note that if your application is sandboxed, you will have to create an XPCService, as a middle man between your Application and the Machservice
I'm pretty pumped that I got this working-- I'm fairly active in SO, so if anybody is interested in source code, just add a comment and I can go through the effort to post more details
Some hurdles I came across:
You have to launch your machservice, these are the lines:
OSStatus err;
AuthorizationExternalForm extForm;
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (err == errAuthorizationSuccess) {
NSLog(#"SUCCESS AUTHORIZING DAEMON");
}
assert(err == errAuthorizationSuccess);
Boolean success;
CFErrorRef error;
success = SMJobBless(
kSMDomainSystemLaunchd,
CFSTR("DAEMON IDENTIFIER HERE"),
self->_authRef,
&error
);
Also, every time you rebuild your daemon, you have to unload the previous launch agent, with these bash commands:
sudo launchctl unload /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/PrivilegedHelperTools/com.example.apple-samplecode.EBAS.HelperTool
(With your corresponding identifiers, of course)
Is there a way to interact with Yii on the command line with a console like rails console? I'd like to test DB and ActiveRecord calls.
You can install yii-shell. It's made by the Yii team. It works like rails console
EDIT:
Sorry, I worked from the documentation of yii-shell which is - at the time of this writing - but a promise.
This is how you can get a proper REPL working in Yii 2.
First, we would need to get Psysh. You can install it globally to play with it, but I recommend adding the following line to your composer.json
require-dev: {
// ... some other packages ...
"psy/psysh": "0.7.2"
}
Run composer update to get this package installed.
Now we need to add this to a controller. The way to call up the break point from a controller is eval(\Psy\sh());.
Note that this would invoke the console for debugging. So if your app is served with Apache, Nginx, or any other server which is not tied to an interactive console, this is pointless
For this to work, I have served the application using PHP's inbuilt server and Yii's wrapper for it.
In Yii Basic App template...
cd /path/to/application
./yii serve localhost:12345
In any controller, ...say controllers/SiteController.php
public function actionIndex()
{
eval(\Psy\sh()); // <-- debugger point
return $this->render('index');
}
When you access this action via tha URL, it would hang on your browser. If you check back in the console, you would see an interactive shell which should work like rails c. Checkout the Psysh Documentation for more details. To exit this interactive console, type exit; this should return control back to PHP's inbuilt server. Do not exit the interactive console with Ctrl-C as this would close the PHP's inbuilt server also.
In Yii Advanced App template...
Serving the application does not work at the moment since it defaults to serving the contents of console/web which doesn't exist. I have raised an issue with Yii; you can follow along there if it interests you.
However, if you have console controllers, you can do the same thing we did for the basic app. When you run the console command, you should be presented with the same interactive debug console.
1) Install https://github.com/yiisoft/yii2-shell
composer require yiisoft/yii2-shell
2) ./yii shell
I've finally managed to figure out how to invoke the VLC executable so that it attempts to run an interface, and debug messages show up in the output when they're included within the script itself.
However, when I use the following code:
require "common"
function test_f()
return 'this is a test'
end
h = vlc.httpd("localhost", 8090)
a = h:handler("/test",nil,nil,test_f,nil)
VLC blows up. It doesn't start an httpd on that port (or any other that I can determine). It claims permission is denied (and this on a port higher than 1024) and that it can't open the socket. The documentation is rather lackluster and I was hoping someone could shed light on how to use this portion of the API properly (I was going to do a rest service interface, if I can figure this out).
Is there a way to know the return code or process ID of the process which gets executed when the privileged helper tool is installed as a launchdaemon and launched via SMJobSubmit().
I have an application which to execute some tasks in privileged manner uses the SMJobSubmit API as mentioned here.
Now in order to know whether the tasks succeeded or not, I will have to do one of the following.
The best option is to get the return code of the executable that ran.
Another option would be if I could create a pipe between my application and the launchd.
If the above two are not possible, I will have to resort to some hack like writing a file in /tmp location and reading it from my app.
I guess SMJobSubmit internally submits the executable with a launchdaemon dictionary to the launchd which is then responsible for its execution. So is there a way I could query launchd to find out the return code for the executable run with the label "mylabel".
There is no way to do this directly.
SMJobSubmit is a simple wrapper around a complicated task. It also returns synchronously despite launching a task asynchronously. So, while it can give you an error if it fails to submit the job, if it successfully submits a job that fails to run, there is no way to find that out.
So, you will have to explicitly write some code to communicate from your helper to your app, to report that it's up and running.
If you've already built some communication mechanism (signals, files, Unix or TCP sockets, JSON-RPC over HTTP, whatever), just use that.
If you're designing something from scratch, XPC may be the best answer. You can't use XPC to launch your helper (since it's privileged), but you can manually create a connection by registering a Mach service and calling xpc_connection_create_mach_service.