Recently, I've been trying to make a (very) simple program with Swift that lets you connect to a server through SSH and execute some files. Unfortunately, I could not figure out how to start an SSH session completely within the Swift app. Here is some code that I have written:
var sshConnectCommand = ["-c", "spawn ssh "+sshUsername+"#"+sshHost+"; expect assword:; send "+sshPassword+"\r"]
func sshIn() {
//starting ssh session
let sshConnect = NSTask()
sshConnect.arguments = [testCmd]
//rerouting output through a pipe
sshConnect.standardOutput = logAppend
//launch!
sshConnect.launch();
}
As you can see, I have used NSTask to try and run the 'expect' command to enter the password and everything. I would like to try and avoid using SSH-keygen as much as possible as this is intended to be used a server that the user does not have any access to.
So, to sum up:
How would you connect to SSH without SSH-keygen while remaining completely within the application code?
edit: I should also add, when trying to compile, I get this error:
[Swift._SwiftDeferredNSArray fileSystemRepresentation]: unrecognized selector sent to instance 0x600000032500. I'm not sure what this means.
I've been using something similar to this to ssh into my Raspberry Pi:
func sshIn() {
let task = CommandLine()
task.launchPath = "/usr/bin/ssh"
task.arguments = ["USERNAME#IPADDRESS", "-t", "sudo systemctl stop mediacenter; /opt/vc/bin/tvservice -o"]
task.launch()
}
-t closes the connection when the commands are finished running, and you can pass in all your commands like so command1; command 2 like where I've got sudo systemctl stop mediacenter; /opt/vc/bin/tvservice -o
As for the keygen thing, I don't think you have much of a choice. You can look into locking it down a bit though. Here's a good place to start looking.
Related
I am very new to using Apple let alone developing an app for them, but looking at creating a simple app to learn the language a little bit.
I am trying to open a file in an app, but the app needs to be opened with admin rights. I've tested running in the command to prove the concept works by running the following:
sudo open /private/etc/apache2/extra/httpd-vhosts.conf -a TextEdit
This as expected asks for the password in the terminal and then opens the app with the file to be editable.
I've then tried doing the following Swift to do the same thing:
let fileToOpen : String = txtFileToOpen.stringValue;
let task = Process();
task.launchPath = "/usr/bin/env"
task.arguments = ["sudo", "open", fileToOpen, "-a", "TextEdit"];
task.launch();
I was expecting that because I am running the above in app and not a terminal, that I would get the Apple dialog asking for my admin password to give permission for the above to run from my app, but unfortunately that doesn't work, instead I get the error:
env: sudo: Operation not permitted
Is there a way I can do the above and prompt the admin password to allow my process to run from my app?
I am looking to run a command on command line from my SWIFT app in MAC.
var resString = "open \(app.getLocation()) --args 25 40"
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", resString]
task.launch()
print (resString)
when I print the resString on console, I. get the following
open [path to app on my local drive] --args 25 40
which executes normally when I copy paste in the command line. but on the other side, the app is opened but arguments are being ignored.
I also tried this https://stackoverflow.com/a/53725745/12197482 still didn't work, the app is launched but the arguments don't get through
EDIT: Here's the funniest thing that's really frustrating, I created a small script that have a command, which contains
#! /bin/bash
open ./SimulatorDebug.app --args arg1 arg2
I ran it from terminal again, args are passed correctly with no issues. I tried to run at from my app and the same issue happened again, app run play BUT NO ARGS are being passed which is I find really weird.
Try using /bin/sh as executableURL instead:
task.executableURL = URL(fileURLWithPath: "/bin/sh")
That should do it.
You might also want to try using task.run() instead, and waiting for completion:
try task.run()
task.waitUntilExit()
I wrote System to make this much easier. Feel free to use it or copy/paste the code:
https://github.com/eneko/System/blob/master/Sources/System/System.swift#L107
Updated with additional info
Since you are trying to open a macOS app, there is no need to use sh or bash, you can invoke open directly.
To test this, I've written a sample macOS app in Xcode that all it does is display arguments on screen. Then, I proceeded to launch the app from a macOS Playground, as seen in the screenshot:
Here is the code to launch the app with arguments:
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/open")
process.arguments = [
"/Applications/ArgumentTest/ArgumentTest.app",
"-n",
"--args",
"Open",
"From",
"Playground"
]
try process.run()
-n is used to launch an instance of the app, even if the app is already running. Might not be needed in your case.
Hope this helps!
I want to execute commands below by Process Class:
let uninstallTask = Process()
uninstallTask.launchPath = "/bin/bash"
//MarkerPro is an app in launchpad
uninstallTask.arguments = ["-c","sudo rm -rf "+"/Applications/MarkerPro.app"]
uninstallTask.launch()
But get errors below:
sudo: no tty present and no askpass program specified
So probably need to enter password before i execute delete command.
I found the solution by using "STPrivilegedTask" which is a tool to get authorization by users.
Using FileManager API I think also a good idea
How can one run and interact with a program such as ssh in a bash terminal using swift 4 and playgrounds? Terminal commands can be executed in a Mac swift playground with code such as:
#discardableResult func shell(_ command: String) -> String {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
return output
}
// Example usage:
let t = shell("ls")
print("\(t)") //prints dir listing
However, I would like to be able to interact with programs like ssh versus a programs that aren't interactive such as ls. For example the command:
let t = shell("ssh xxx.xxx.xxx.xxx -T -l root -p 22")
will start ssh and ssh will via terminal prompt for the password. I want to be able to then programmatically provide the password and continue. It seems to me that since SSH is running in the context of terminal that highjacking terminals stdin and stdout should suffice however, I've not been successful with that so far. Here are some resources I found that provide some information on highjacking:
Big nerd on highjacking
Medium article
Any ideas about if and how this could be accomplished?
ssh reads passwords directly from /dev/tty rather than stdin. To programmatically control a tty you need the pty module/APIs which tools like Expect are using.
I know nothing about Swift so not sure if it has a similar pty module. If it does not then you can use Expect to run ssh.
I have made a cocoa swift application, in which there is a NSTextField. The textfield returns a password, which is then loaded in a Process(), that brings it into a bash command script.
The command is: Echo ${1} | sudo -S echo works
To the process I pass the argument of the password, so it goes instead of the ${1}. When I run the command, the console says that usr/bin/sudo: Operation not permitted.
Is there a way I can use sudo in my bash scripts?
Help would really be appreciated. Thanks.
This is not the correct way to escalate privileges on OS X, particularly because it's terrible security; your passing around the password in cleartext like this is a generally bad practice.
Let me illustrate why this is a problem. Suppose I have this bash script:
#!/bin/bash
sleep 10
echo ${1}
Then, I execute my script, using Process:
import Foundation
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["/path/to/foo.sh", "This is a parameter"]
process.launch()
process.waitUntilExit()
Run the script, it outputs "This is a parameter", all seems well. But, while the script is running, in a separate Terminal window, I run this command:
$ ps -ajxww | grep foo.sh
And get the output:
username 85281 85276 85281 0 1 S s006 0:00.00 /bin/bash /path/to/foo.sh This is a parameter
As you can see, the parameter is plainly visible in the process list. If the parameter to that script had been my admin password, I would have just broadcast that password to every interested process on the system.
Anyway, instead of doing things like this, you should create a privileged launchd helper tool, which you can bless using SMJobBless(). The OS will prompt for the password in a secure way, and install your helper tool in a secure location. You can then communicate with your tool via XPC to have it do things as root. See Apple's EvenBetterAuthorizationSample for an idea of how to do this.