Skip to content

Instantly share code, notes, and snippets.

@sarensw
Last active August 6, 2024 13:45
Show Gist options
  • Save sarensw/7016a2271b504dfd8518d7ecd1227ced to your computer and use it in GitHub Desktop.
Save sarensw/7016a2271b504dfd8518d7ecd1227ced to your computer and use it in GitHub Desktop.
A wrapper for the NIOSSHClient example found in the apple/swift-nio-ssh repo. It can be used to call commands on the connected ssh server.
let ssh = SwiftNioSshWrapper(host: "...", port: 22, user: "...", password: "...")
do {
try ssh.run(command: "pwd")
} catch {
print(error)
}
import Foundation
import Dispatch
import NIO
import NIOSSH
public class SwiftNioSshWrapper {
let host: String
let port: Int
let user: String
let password: String
public init(host: String, port: Int, user: String, password: String) {
print("Initializing the wrapper")
self.host = host
self.port = port
self.user = user
self.password = password
}
// Just prints the config
public func printConfig() {
print("Config: \(host), \(port), \(user), \(password)")
}
// This file contains an example NIO SSH client. As NIO SSH is currently under active
// development this file doesn't currently do all that much, but it does provide a binary you
// can kick off to get a feel for how NIO SSH drives the connection live. As the feature set of
// NIO SSH increases we'll be adding to this client to try to make it a better example of what you
// can do with NIO SSH.
final class ErrorHandler: ChannelInboundHandler {
typealias InboundIn = Any
func errorCaught(context: ChannelHandlerContext, error: Error) {
print("Error in pipeline: \(error)")
context.close(promise: nil)
}
}
final class AcceptAllHostKeysDelegate: NIOSSHClientServerAuthenticationDelegate {
func validateHostKey(hostKey: NIOSSHPublicKey, validationCompletePromise: EventLoopPromise<Void>) {
// Do not replicate this in your own code: validate host keys! This is a
// choice made for expedience, not for any other reason.
validationCompletePromise.succeed(())
}
}
public func run(command: String) throws -> Void {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
try! group.syncShutdownGracefully()
}
let bootstrap = ClientBootstrap(group: group)
.channelInitializer { channel in
channel.pipeline.addHandlers([NIOSSHHandler(role: .client(.init(userAuthDelegate: InteractivePasswordPromptDelegate(username: self.user, password: self.password), serverAuthDelegate: AcceptAllHostKeysDelegate())), allocator: channel.allocator, inboundChildChannelInitializer: nil), ErrorHandler()])
}
.channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
.channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
do {
print("connect via ssh")
let channel = try bootstrap.connect(host: self.host, port: self.port).wait()
// We've been asked to exec.
print("channel created")
let exitStatusPromise = channel.eventLoop.makePromise(of: Int.self)
let childChannel: Channel = try! channel.pipeline.handler(type: NIOSSHHandler.self).flatMap { sshHandler in
let promise = channel.eventLoop.makePromise(of: Channel.self)
sshHandler.createChannel(promise) { childChannel, channelType in
guard channelType == .session else {
return channel.eventLoop.makeFailedFuture(SSHClientError.invalidChannelType)
}
return childChannel.pipeline.addHandlers([ExampleExecHandler(command: command, completePromise: exitStatusPromise), ErrorHandler()])
}
return promise.futureResult
}.wait()
// Wait for the connection to close
print("closing")
try! childChannel.closeFuture.wait()
print("child channel closed")
let exitStatus = try! exitStatusPromise.futureResult.wait()
print("exist status promise awaited")
try! channel.close().wait()
print("all closed")
print(exitStatus)
// Exit like we're the command.
// exit(Int32(exitStatus))
} catch {
print("could not connect via SSH")
return
}
}
}
@alexe100
Copy link

Any way to upload file to ssh server from iOS app?

@NaveenKVN
Copy link

NaveenKVN commented Aug 6, 2024

import Foundation
import Dispatch
import NIO
import NIOSSH
import NIOCore
import NIOPosix

public class SshClient {
let host: String
let port: Int

var user: String {
    hardcodedClientPasswordDelegate.user
}
var password: String {
    hardcodedClientPasswordDelegate.password
}

let hardcodedClientPasswordDelegate : HardcodedClientPasswordDelegate

public init(host: String, port: Int = 22, user: String, password: String) {
    print("Initializing the wrapper")
    self.host = host
    self.port = port
    self.hardcodedClientPasswordDelegate = .init(user: user, password: password)
}

// Just prints the config
public func printConfig() {
  print("Config: \(host), \(port), \(user)")
}

// This file contains an example NIO SSH client. As NIO SSH is currently under active
// development this file doesn't currently do all that much, but it does provide a binary you
// can kick off to get a feel for how NIO SSH drives the connection live. As the feature set of
// NIO SSH increases we'll be adding to this client to try to make it a better example of what you
// can do with NIO SSH.
final class ErrorHandler: ChannelInboundHandler {
    typealias InboundIn = Any

    func errorCaught(context: ChannelHandlerContext, error: Error) {
        print("Error in pipeline: \(error)")
        context.close(promise: nil)
    }
}

final class AcceptAllHostKeysDelegate: NIOSSHClientServerAuthenticationDelegate {
    func validateHostKey(hostKey: NIOSSHPublicKey, validationCompletePromise: EventLoopPromise<Void>) {
        // Do not replicate this in your own code: validate host keys! This is a
        // choice made for expedience, not for any other reason.
        validationCompletePromise.succeed(())
    }
}

struct HardcodedClientPasswordDelegate: NIOSSHClientUserAuthenticationDelegate {
    var user: String
    var password: String
    
    init(user:String, password:String) {
        self.user = user
        self.password = password
    }
    
    func nextAuthenticationType(availableMethods: NIOSSHAvailableUserAuthenticationMethods, nextChallengePromise: EventLoopPromise<NIOSSHUserAuthenticationOffer?>) {
        precondition(availableMethods.contains(.password))
        nextChallengePromise.succeed(NIOSSHUserAuthenticationOffer(username: user,
                                                                   serviceName: "",
                                                                   offer: .password(.init(password: password))))
    }
}

public func run(command: String) throws -> Void {
    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
    defer {
        try! group.syncShutdownGracefully()
    }

    let bootstrap = ClientBootstrap(group: group)
        .channelInitializer { channel in
            channel.pipeline.addHandlers([NIOSSHHandler(role: .client(.init(userAuthDelegate: self.hardcodedClientPasswordDelegate, serverAuthDelegate: AcceptAllHostKeysDelegate())), allocator: channel.allocator, inboundChildChannelInitializer: nil), ErrorHandler()])
        }
        .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
        .channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
        print("connecting via ssh ...")
        let channel = try bootstrap.connect(host: self.host, port: self.port).wait()
        
        // We've been asked to exec.
        print("connected: channel created.")
        let exitStatusPromise = channel.eventLoop.makePromise(of: Int.self)
        let childChannel: Channel = try! channel.pipeline.handler(type: NIOSSHHandler.self).flatMap { sshHandler in
            let promise = channel.eventLoop.makePromise(of: Channel.self)
            sshHandler.createChannel(promise) { childChannel, channelType in
                guard channelType == .session else {
                    return channel.eventLoop.makeFailedFuture(SSHClientError.invalidChannelType)
                }
                return childChannel.pipeline.addHandlers([
                    ExampleExecHandler(command: command,
                                       completePromise: exitStatusPromise),
                    ErrorHandler(),
                ])
            }
            return promise.futureResult
        }.wait() 

// Fatal error: 'try!' expression unexpectedly raised an error: NIOCore.ChannelError.eof

        // Wait for the connection to close
        print("closing")
        
        try! childChannel.closeFuture.wait()
        Swift.print("child channel closed")
        
        let exitStatus = try exitStatusPromise.futureResult.wait()
        print("exist status promise awaited")
        
        try! channel.close().wait()
        print("all closed")
        print(exitStatus)

    
}

enum SSHClientError: Swift.Error {
    case passwordAuthenticationNotSupported
    case commandExecFailed
    case invalidChannelType
    case invalidData
}

}

I am also getting the error Fatal error: 'try!' expression unexpectedly raised an error: NIOCore.ChannelError.eof

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment