Launch sudo command from MacOS App Swift - swift

I need to launch a terminal command to xcode.
This is the command:
sudo xattr -d -r com.test.exemple /Desktop/file.extension
I tried so
let task = Process()
task.launchPath = "/usr/sbin/xattr"
task.arguments = ["-d","-r", "com.test.exemple"," /Desktop/file.extension"]
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output : String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
print(output)

Here's one way to do it using a pipe between commands. I verified that when I use the arguments in the commented out line that the file gets created by the super user.
What it is doing is this:
echo 'password' | sudo -S /usr/bin/xattr -d -r com.test.exemple
/Desktop/file.extension
func doTask(_ password:String) {
let taskOne = Process()
taskOne.launchPath = "/bin/echo"
taskOne.arguments = [password]
let taskTwo = Process()
taskTwo.launchPath = "/usr/bin/sudo"
taskTwo.arguments = ["-S", "/usr/bin/xattr", "-d", "-r", "com.test.exemple", " /Desktop/file.extension"]
//taskTwo.arguments = ["-S", "/usr/bin/touch", "/tmp/foo.bar.baz"]
let pipeBetween:Pipe = Pipe()
taskOne.standardOutput = pipeBetween
taskTwo.standardInput = pipeBetween
let pipeToMe = Pipe()
taskTwo.standardOutput = pipeToMe
taskTwo.standardError = pipeToMe
taskOne.launch()
taskTwo.launch()
let data = pipeToMe.fileHandleForReading.readDataToEndOfFile()
let output : String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
print(output)
}

I came across this question after reading this newer question. Just in case somebody arrives here via search, here's the code from my answer to that question.
There's no real need to pipe through echo; the following works just fine:
The following more direct approach is tested and working:
import Foundation
let password = "äëïöü"
let passwordWithNewline = password + "\n"
let sudo = Process()
sudo.launchPath = "/usr/bin/sudo"
sudo.arguments = ["-S", "/bin/ls"]
let sudoIn = Pipe()
let sudoOut = Pipe()
sudo.standardOutput = sudoOut
sudo.standardError = sudoOut
sudo.standardInput = sudoIn
sudo.launch()
// Show the output as it is produced
sudoOut.fileHandleForReading.readabilityHandler = { fileHandle in
let data = fileHandle.availableData
if (data.count == 0) { return }
print("read \(data.count)")
print("\(String(bytes: data, encoding: .utf8) ?? "<UTF8 conversion failed>")")
}
// Write the password
sudoIn.fileHandleForWriting.write(passwordWithNewline.data(using: .utf8)!)
// Close the file handle after writing the password; avoids a
// hang for incorrect password.
try? sudoIn.fileHandleForWriting.close()
// Make sure we don't disappear while output is still being produced.
sudo.waitUntilExit()
print("Process did exit")
The crux is that you must add a newline after the password.

In Xcode's "Signing & Capabilities" tab, disable "App Sandbox"
Use AppleScript:
func runScriptThatNeedsSudo() {
let myAppleScript = """
do shell script \"sudo touch /Library/hello
sudo launchctl kickstart -k system/com.apple.audio.coreaudiod" with administrator privileges
"""
var error: NSDictionary?
let scriptObject = NSAppleScript(source: myAppleScript)!
scriptObject.executeAndReturnError(&error)
}
This will prompt the user for their password.
Consider this a security issue because it will indiscriminately run any tool or application, severely increasing the user's security risk. Always inform the user about what your application is about to do. You should avoid the use of this functionality if possible.

Related

How to launch terminal and pass it a command using Swift

I want to programmatically open a terminal and paste a command into it like "cd /Users/...".
I can start a terminal using this code, but I don't know how to execute command
guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.Terminal") else { return }
let path = "/bin"
let configuration = NSWorkspace.OpenConfiguration()
configuration.arguments = \[path\]
NSWorkspace.shared.openApplication(at: url, configuration: configuration, completionHandler: nil)
It is very important to use the sandbox, the Process command is not suitable.
If all you are trying to achieve is to open a terminal on a specific location all you have to do is to use this code:
let pathToOpen = "/Users/admin/Desktop"
let url = URL(string:"terminal://"+pathToOpen)!
NSWorkspace.shared.open(url)
If you are trying just to run a command in terminal and show the output in your app, here is another useful code snippet:
func shell(_ command: String) -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
task.standardInput = nil
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
//Usage:
shell("yourCommandHere")
//please note that you are not allowed to use commands that require sudo

Problems when passing password containing umlauts to Process as argument in Swift

So I am trying to execute a command using sudo from Swift using the following code (as suggested here:
func doTask(_ password:String) {
let taskOne = Process()
taskOne.launchPath = "/bin/echo"
taskOne.arguments = [password]
let taskTwo = Process()
taskTwo.launchPath = "/usr/bin/sudo"
taskTwo.arguments = ["-S", "/usr/bin/xattr", "-d", "-r", "com.test.exemple", " /Desktop/file.extension"]
//taskTwo.arguments = ["-S", "/usr/bin/touch", "/tmp/foo.bar.baz"]
let pipeBetween:Pipe = Pipe()
taskOne.standardOutput = pipeBetween
taskTwo.standardInput = pipeBetween
let pipeToMe = Pipe()
taskTwo.standardOutput = pipeToMe
taskTwo.standardError = pipeToMe
taskOne.launch()
taskTwo.launch()
let data = pipeToMe.fileHandleForReading.readDataToEndOfFile()
let output : String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
print(output)
}
It works just fine for passwords such as "test", "password#123" etc. But when I try a password containing an umlaut such as "ä","ü" or "ö" in doesn't work. Any ideas why?
I'm not sure why the answer to the other question piped through echo... seems to introduce unnecessary complications and unknowns.
The following more direct approach is tested and working:
import Foundation
let password = "äëïöü"
let passwordWithNewline = password + "\n"
let sudo = Process()
sudo.launchPath = "/usr/bin/sudo"
sudo.arguments = ["-S", "/bin/ls"]
let sudoIn = Pipe()
let sudoOut = Pipe()
sudo.standardOutput = sudoOut
sudo.standardError = sudoOut
sudo.standardInput = sudoIn
sudo.launch()
// Show the output as it is produced
sudoOut.fileHandleForReading.readabilityHandler = { fileHandle in
let data = fileHandle.availableData
if (data.count == 0) { return }
print("read \(data.count)")
print("\(String(bytes: data, encoding: .utf8) ?? "<UTF8 conversion failed>")")
}
// Write the password
sudoIn.fileHandleForWriting.write(passwordWithNewline.data(using: .utf8)!)
// Close the file handle after writing the password; avoids a
// hang for incorrect password.
try? sudoIn.fileHandleForWriting.close()
// Make sure we don't disappear while output is still being produced.
sudo.waitUntilExit()
print("Process did exit")
The crux is that you must add a newline after the password. (I suppose in some ways echo is just an overly complicated way of doing that!)

swift command line tool for git commands, but its no

I would like to create a tool for git commands using swift.
I'm getting git error usage: git [--version] [--help] [-C ]
[-c =].
Is there any way to distribute it as a command-line package and using in the swift file?
import Foundation
struct Task {
static let shared = Task()
func run(with args: String...){
let task = Process()
task.launchPath = "/usr/bin/git"
task.arguments = ["-c", args.joined(separator: " ")]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
print(output)
task.waitUntilExit()
}
}
let task = Task.shared
task.run(with: "status")
task.run(with: "fetch --all")
task.run(with: "add --all")
You are using deprecated methods in your code and there are some other things missing.
First we should set the shell to use
func run(with args: String...){
let task = Process()
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
Then instead of using the deprecated launchPath we build a string with the full command and set it as the arguments for the task
let arguments = "/usr/bin/git \(args.joined(separator: " "))"
task.arguments = ["-c", arguments]
I also think it is a good idea to handle any errors by checking standard error
let pipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = pipe
task.standardError = errorPipe
Instead of using the deprecated launch method use run and read both standard out and standard error
do {
try task.run()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if !data.isEmpty {
if let output = String(data: data, encoding: .utf8) {
print(output)
}
}
let error = errorPipe.fileHandleForReading.readDataToEndOfFile()
if !error.isEmpty {
if let errorMessage = String(data: error, encoding: .utf8) {
print(errorMessage)
}
}
} catch {
print(error)
}
For a simple command it might be worth having the handling of standard output and standard error in an if/else so feel free to change that but for more complicated commands dealing for example with multiple files it might produce both output and errors

Launch Safari (not default browser) at URL in Swift?

I need to open Safari specifically, not the default browser. If Chrome/Firefox/whatever is the default browser my app still needs to open the URL in Safari.
I've looked online but all I can see is for opening in default browser like this...
let url = URL(string: host)!
NSWorkspace.shared.open(url)
...which works for me only when Safari is the users default.
How can I open a URL in Safari specifically?
Not duplicate - the other question is not in Swift...
Okay I found a solution but it's a bit of a hack...
Shell function for making bash commands:
func shell(command: String) -> String {
var output = ""
var error = ""
do {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", command]
let outputPipe = Pipe()
task.standardOutput = outputPipe
let errorPipe = Pipe()
task.standardError = errorPipe
try task.run()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
output = NSString(data: outputData, encoding: String.Encoding.utf8.rawValue)! as String
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
error = NSString(data: errorData, encoding: String.Encoding.utf8.rawValue)! as String
}
catch let err as NSError{
output = err.localizedDescription
}
return error + "\n" + output + "\n"
}
In bash you can use: open -a safari www.blahblahblah.com
So in Swift I can implement this like so:
shell(command: "open -a safari " + host)
Bit of a hack but it works

swift process standart input pipe

I want to create an encrypted .dmg on macOS using swift
I haven't found an implementation for hdiutil, so I'm forced to use terminal commands for that.
Unfortunately, I don't know how to work with pipes (which should be used regarding apples documentation)
The command i want to trigger is the following:
printf 'password' | hdiutil create -encryption AES-256 -volname backupname -stdinpass -size 1GB backupname.dmg
The code, I've tried is the following (nothing worked):
func shell(_ args: String...) -> Int32 {
let task = Process()
let inputpipe = Pipe()
task.standardInput = inputpipe
//1. try: inputpipe.fileHandleForReading.write("password".data(using: .utf8)!)
//2. try: inputpipe.fileHandleForWriting.write("password".data(using: .utf8)!)
/*3. try: inputpipe.fileHandleForWriting.writeabilityHandler = {
pipe in
let inputString = "password"
let data = inputString.data(using: .utf8)!
pipe.write(data)
}*/
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
print(shell("hdiutil", "create", "-encryption", "AES-256", "-volname", "backupname", "-stdinpass", "-size", "1MB", "~/Documents/backupname.dmg"))
How do i correctly provide the password using Process()