0

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?

  • What do you get for the umlauted characters? Is it just umlauts that are the problem? Have you tried other diacritics for example `é` or `ñ`? – Chip Jarred Jul 01 '21 at 12:23
  • @ChipJarred I just tried the password "aaaé" and that doesn't work either. So I assume other diacritics will not work too. – user10802841 Jul 01 '21 at 12:28
  • 1
    What do you mean by "doesn't work"? What happens exactly? – Sweeper Jul 01 '21 at 12:28
  • Just a thought. `sudo` is a very old part of `Unix`. It might not take `.utf8` encoded characters. I'm pretty sure `.ascii` won't work because technically ASCII is only 7 bit characters, but you could try `.isoLatin1`, `.isoLatin2`, `.macRoman`, or `.nextstep` – Chip Jarred Jul 01 '21 at 12:30
  • I ran your code in a playground. Replaced the password with `"ü"`. The output correctly indicates that my password is incorrect. – Sweeper Jul 01 '21 at 12:38
  • Completely unrelated but the `NSString - String` dance is horrible. Replace the line with `let output = String(data: data, encoding: .utf8)!` – vadian Jul 01 '21 at 12:49
  • Does `echo yourPassword | sudo -S xattr…` on the command line work with umlauts? – Martin R Jul 01 '21 at 12:50
  • @ChipJarred How would you do that? I tried it with this: `String(data: "+ü#ä".data(using: .macOSRoman)!, encoding: .macOSRoman)!` but it doesn't work. Output: `Password:Sorry, try again. Password: sudo: no password was provided sudo: 1 incorrect password attempt` – user10802841 Jul 01 '21 at 13:10
  • Actually the problem will be where you set the password, because that's where it will be sent through the pipe to `sudo`. I think you first need to determine see what's coming out the other end of the pipe... so maybe replace `task2` with `/usr/bin/cat` to just dump the pipe to stdout. Or even better to `/usr/bin/tee`. For `tee` you'll need to provide a file path as a parameter, just a file in your home directory will work. Then you can open it to see exactly what is being received through the pipe without worry about how Swift decodes it. – Chip Jarred Jul 01 '21 at 13:25
  • I hope it was obvious but use a temporary file name for the argument to `tee` in my last comment, because it will overwrite the file, so you don't want to use path to an existing file you want to keep. – Chip Jarred Jul 01 '21 at 13:31
  • @ChipJarred I tried that one and dumped the output of echo to test.txt using tee. The output of test.txt is: `+ü#ä` so the exact same text I put in :/ (See https://imgur.com/a/598Tu9s) – user10802841 Jul 01 '21 at 13:59

1 Answers1

1

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!)

idz
  • 12,825
  • 1
  • 29
  • 40
  • 1
    However, I noticed that if you enter a wrong password, the process runs indefinitely. – user10802841 Jul 02 '21 at 12:59
  • 1
    Hmm, not the behavior I observed, but easily fixed if you parse the output. It will hang if you do not include the new line. – idz Jul 02 '21 at 14:47
  • 1
    Whoops! my apologies, I just rechecked my code and I left out a line in my answer (this is why I was not seeing a hang on an incorrect password). I close the input stream after writing the password. I will update my answer! – idz Jul 02 '21 at 17:55
  • Thank you! Is there an alternative for ```sudoIn.fileHandleForWriting.close()``` because this function is only avaiable in macOS 10.15 or higher. – user10802841 Jul 09 '21 at 20:32
  • Would ```sudoIn.fileHandleForWriting.closeFile()``` work? – user10802841 Jul 09 '21 at 20:51
  • 1
    Should be fine, I guess they changed the name? Try it and see! – idz Jul 09 '21 at 20:53