I’d like to execute a shell command as root using “sudo” using the shell class inside a desktop app.
For this, I’d have to either ask the user’s password and pass it to the shell, or “elevate” the shell as root.
In both cases, the authentication dialog would seem to be the logical choice, but it would neither give the user’s password back to my app (fortunately, I’d say…) nor let me run a single shell with privileges (I think).
The only way I think of would be to present a dialog asking the user for his/her password and give it to the shell, but that’s generally said to be something to avoid as much as possible, for reasons that make sense.
This will show the correct macOS password request window and then echoes the password to the shell.
var Author as AuthorizationMBS
author=new AuthorizationMBS
if Author.SimpleNewAuthorization then
var s(-1) as String
s.add "" // put here the commands like sudo ...
Author.Execute("/bin/echo",s,true)
end
So I tried, but I couldn’t figure out how to get the result. Here’s my current code:
var Author as new AuthorizationMBS
Author.KeepRights=True
if Author.SimpleNewAuthorization then
var s(-1) as String
s.add CmdStr '"lsof"
Author.Execute("/bin/echo",s,true)
Var pid As Integer=Author.Wait
do
if Author.EOFStream then Exit
ResultWindow.TAResult.AddText Author.ReadStream(1000) 'Add to a text area
Loop
end if
The result I get is just “lsof” (the command). I first tried including the “sudo” word, and the result was equivalent (“sudo lsof”).
Another question I have regarding this class: is it equivalent to pass a single string with parameters vs separated parameters? The documentation doesn’t mention this and I can’t test until I’m getting results.
E.g.:
Example 1:
var s(-1) as String
s.add "diskutil apfs list"
Author.Execute("/bin/echo",s,true)
Example 2:
var s(-1) as String
s.add "diskutil"
s.add "apfs"
s.add "list"
Author.Execute("/bin/echo",s,true)
You are doing this completely wrong. The script goes into a file, you don’t need to use the s array and there is no need for a loop. My example may a bit too complex:
dim plistFile as String = "/Users/" + getUsername + "/Library/LaunchAgents/" + BundleID + "-helper.launchd.plist"
TempBinary.Write("#!/bin/sh" + EndOfLine.UNIX + "launchctl bootstrap gui/" + getUserID + " " + plistFile)
TempBinary.Close
'set permissions for shell script
dim theShell as new Shell
theShell.Execute("chmod 777 " + TempFile.ShellPath)
if theShell.ExitCode <> 0 then
globals.theErrorLog.DialogErrorProceed(kErrorAddScheduler + " " + "xxx")
globals.theErrorLog.LogItem(CurrentMethodName + " error chmod " + str(theShell.ExitCode))
Return
end if
'do Authorization
if theAuthorisation = nil then
theAuthorisation = new AuthorizationMBS
theAuthorisation.KeepRights = true
if not theAuthorisation.SimpleNewAuthorization then Return
end if
dim s(-1) as string
theAuthorisation.Execute(TempFile.UnixpathMBS, s, true)
if theAuthorisation.LastError = 0 then
dim theResult as Integer = theAuthorisation.Wait
#Pragma Unused theResult
else
globals.theErrorLog.DialogErrorProceed(kErrorAddScheduler + " " + Str(theAuthorisation.LastError))
end if
dim s(-1) as string
s.Add(tempFile.UnixpathMBS)
s.Add(theRegisterFile.Parent.UnixpathMBS)
theAuthorisation.Execute(InternalFolder.UnixpathMBS, s, true)
Is this for a shipping application? Or something internal? Because if an app asked for root access, it would be deleted off by hard drive faster than you can say “botnet”. There is no justification for virtually any app requiring root access - the scenarios are very, very few that require this.
Sorry, I was not clear how to use this trick.
The first part trickery is just to ask the users password with the official macOS window.
When the password is correct, you can put your code, for example the Xojo Shell that would need admin passwords, between 'if Author.LastError= 0 then / end’
That code will have admin users permission like you would use sudo.
It is an odd bit but it does work.
var Author as AuthorizationMBS
author=new AuthorizationMBS
if Author.SimpleNewAuthorization then
var s(-1) as String
s.add "-R"
Author.Execute("/bin/echo",s,true)
if Author.LastError= 0 then
// do whatever you want that needs the users password.
// for example using the Xojo Shell that would need sudo
var terminal as new shell
terminal.Execute "diskutil apfs list"
do
loop until not terminal.IsRunning
msgbox terminal.ReadAll
end
end
You’re right; the app will be released only for me and possibly friends.
It’s just a helper app which will show various shell commands in a “gui way”, so users are freed from remembering the syntax or making spelling mistakes. Nothing fancy.
Ah yes, brilliant, and working!
The shell can even be asynchronous while the original AuthorizationMBS object goes out of scope (which seems logical).
For instance, one function I use often is “lsof” when a volume can’t be unmounted. The culprit is usually the “mds” process keeping some files open, which runs as root. “lsof” won’t list those processes if ran as the current user.
The goal of my app is to run commands both as the current user and as root (provided the current user is an administrator, of course), because that’s how I work in the Terminal (and I’m trying to avoid it whenever possible).