Skip to content

Commit

Permalink
Merge pull request #54 from orlandos-nl/jo/direct-tcpip-server
Browse files Browse the repository at this point in the history
Add DirectTCPIP support to SSH servers
  • Loading branch information
Joannis authored Aug 30, 2024
2 parents f265b4e + 38c5d47 commit c11f16f
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/*.xcodeproj
xcuserdata/
Package.resolved
.vscode/launch.json
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "6fe203dc33195667ce1759bf0182975e4653ba1c",
"version": "1.4.4"
"revision": "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
"version": "1.5.4"
}
},
{
Expand Down
14 changes: 11 additions & 3 deletions Sources/Citadel/ClientSession.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import NIO
import NIOSSH
import Logging

final class ClientHandshakeHandler: ChannelInboundHandler {
typealias InboundIn = Any

private let promise: EventLoopPromise<Void>
let logger = Logger(label: "nl.orlandos.citadel.handshake")

/// A future that will be fulfilled when the handshake is complete.
public var authenticated: EventLoopFuture<Void> {
promise.futureResult
}

init(eventLoop: EventLoop) {
init(eventLoop: EventLoop, loginTimeout: TimeAmount) {
let promise = eventLoop.makePromise(of: Void.self)
self.promise = promise
}
Expand Down Expand Up @@ -54,7 +56,10 @@ final class SSHClientSession {
algorithms: SSHAlgorithms = SSHAlgorithms(),
protocolOptions: Set<SSHProtocolOption> = []
) async throws -> SSHClientSession {
let handshakeHandler = ClientHandshakeHandler(eventLoop: channel.eventLoop)
let handshakeHandler = ClientHandshakeHandler(
eventLoop: channel.eventLoop,
loginTimeout: .seconds(10)
)
var clientConfiguration = SSHClientConfiguration(
userAuthDelegate: authenticationMethod(),
serverAuthDelegate: hostKeyValidator
Expand Down Expand Up @@ -101,7 +106,10 @@ final class SSHClientSession {
group: EventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1),
connectTimeout: TimeAmount = .seconds(30)
) async throws -> SSHClientSession {
let handshakeHandler = ClientHandshakeHandler(eventLoop: group.next())
let handshakeHandler = ClientHandshakeHandler(
eventLoop: group.next(),
loginTimeout: .seconds(10)
)
var clientConfiguration = SSHClientConfiguration(
userAuthDelegate: authenticationMethod(),
serverAuthDelegate: hostKeyValidator
Expand Down
File renamed without changes.
62 changes: 62 additions & 0 deletions Sources/Citadel/DirectTCPIP/Server/DirectTCPIP+Server.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import NIO
import NIOSSH

fileprivate final class ProxyChannelHandler: ChannelOutboundHandler {
typealias OutboundIn = ByteBuffer

private let write: (ByteBuffer, EventLoopPromise<Void>?) -> Void

init(write: @escaping (ByteBuffer, EventLoopPromise<Void>?) -> Void) {
self.write = write
}

func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
let data = self.unwrapOutboundIn(data)
write(data, promise)
}
}

public protocol DirectTCPIPDelegate {
func initializeDirectTCPIPChannel(_ channel: Channel, request: SSHChannelType.DirectTCPIP, context: SSHContext) -> EventLoopFuture<Void>
}

public struct DirectTCPIPForwardingDelegate: DirectTCPIPDelegate {
internal enum Error: Swift.Error {
case forbidden
}

public var whitelistedHosts: [String]?
public var whitelistedPorts: [Int]?

public init() {}

public func initializeDirectTCPIPChannel(_ channel: Channel, request: SSHChannelType.DirectTCPIP, context: SSHContext) -> EventLoopFuture<Void> {
if let whitelistedHosts, !whitelistedHosts.contains(request.targetHost) {
return channel.eventLoop.makeFailedFuture(Error.forbidden)
}

if let whitelistedPorts, !whitelistedPorts.contains(request.targetPort) {
return channel.eventLoop.makeFailedFuture(Error.forbidden)
}

return ClientBootstrap(group: channel.eventLoop)
.connect(host: request.targetHost, port: request.targetPort)
.flatMap { remote in
channel.pipeline.addHandlers([
DataToBufferCodec()
]).flatMap {
channel.pipeline.addHandler(ProxyChannelHandler { data, promise in
remote.writeAndFlush(data, promise: promise)
})
}.flatMap {
remote.pipeline.addHandler(ProxyChannelHandler { [weak channel] data, promise in
guard let channel else {
promise?.fail(ChannelError.ioOnClosedChannel)
return
}
channel.writeAndFlush(data, promise: promise)
})
}
}
}
}
19 changes: 18 additions & 1 deletion Sources/Citadel/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ final class SubsystemHandler: ChannelDuplexHandler {
final class CitadelServerDelegate {
var sftp: SFTPDelegate?
var exec: ExecDelegate?
var directTCPIP: DirectTCPIPDelegate?

fileprivate init() {}

Expand All @@ -109,7 +110,19 @@ final class CitadelServerDelegate {
handlers.append(ExecHandler(delegate: exec, username: username))

return channel.pipeline.addHandlers(handlers)
case .directTCPIP, .forwardedTCPIP:
case .directTCPIP(let request):
guard let delegate = directTCPIP else {
return channel.eventLoop.makeFailedFuture(CitadelError.unsupported)
}

return channel.pipeline.addHandler(DataToBufferCodec()).flatMap {
return delegate.initializeDirectTCPIPChannel(
channel,
request: request,
context: SSHContext(username: username)
)
}
case .forwardedTCPIP:
return channel.eventLoop.makeFailedFuture(CitadelError.unsupported)
}
}
Expand Down Expand Up @@ -148,6 +161,10 @@ public final class SSHServer {
public func enableExec(withDelegate delegate: ExecDelegate) {
self.delegate.exec = delegate
}

public func enableDirectTCPIP(withDelegate delegate: DirectTCPIPDelegate) {
self.delegate.directTCPIP = delegate
}

/// Closes the SSH Server, stopping new connections from coming in.
public func close() async throws {
Expand Down

0 comments on commit c11f16f

Please sign in to comment.