diff --git a/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift b/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift index 4921aad..dc41ce1 100644 --- a/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift +++ b/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift @@ -26,6 +26,9 @@ public struct SSHChildChannelOptions { /// - seealso: `SSHChannelTypeOption`. public static let sshChannelType: SSHChildChannelOptions.Types.SSHChannelTypeOption = .init() + + /// - seealso: `UsernameOption`. + public static let username: SSHChildChannelOptions.Types.UsernameOption = .init() } extension SSHChildChannelOptions { @@ -53,4 +56,11 @@ extension SSHChildChannelOptions.Types { public init() {} } + + /// `UsernameOption` allows users to query the authenticated username of the channel. + public struct UsernameOption: ChannelOption { + public typealias Value = String? + + public init() {} + } } diff --git a/Sources/NIOSSH/Child Channels/SSHChannelMultiplexer.swift b/Sources/NIOSSH/Child Channels/SSHChannelMultiplexer.swift index 1638460..ef23a38 100644 --- a/Sources/NIOSSH/Child Channels/SSHChannelMultiplexer.swift +++ b/Sources/NIOSSH/Child Channels/SSHChannelMultiplexer.swift @@ -93,6 +93,9 @@ extension SSHChannelMultiplexer { self.erroredChannels.append(channelID) } } + + // The username which the server accepted in authorization + var username: String? { delegate?.username } } // MARK: Calls from SSH handlers. @@ -218,6 +221,8 @@ extension SSHChannelMultiplexer { protocol SSHMultiplexerDelegate { var channel: Channel? { get } + var username: String? { get } + func writeFromChildChannel(_: SSHMessage, _: EventLoopPromise?) func flushFromChildChannel() diff --git a/Sources/NIOSSH/Child Channels/SSHChildChannel.swift b/Sources/NIOSSH/Child Channels/SSHChildChannel.swift index d4988be..fe1d2e2 100644 --- a/Sources/NIOSSH/Child Channels/SSHChildChannel.swift +++ b/Sources/NIOSSH/Child Channels/SSHChildChannel.swift @@ -222,6 +222,8 @@ extension SSHChildChannel: Channel, ChannelCore { // This force-unwrap is safe: we set type before we call the initializer, so // users can only get this after this value is set. return self.type! as! Option.Value + case _ as SSHChildChannelOptions.Types.UsernameOption: + return multiplexer.username as! Option.Value case _ as ChannelOptions.Types.AutoReadOption: return self.autoRead as! Option.Value case _ as ChannelOptions.Types.AllowRemoteHalfClosureOption: diff --git a/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift b/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift index af676ae..dee58c0 100644 --- a/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift +++ b/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift @@ -14,6 +14,7 @@ protocol AcceptsUserAuthMessages { var userAuthStateMachine: UserAuthenticationStateMachine { get set } + var connectionAttributes: SSHConnectionStateMachine.Attributes? { get } } extension AcceptsUserAuthMessages { @@ -39,9 +40,21 @@ extension AcceptsUserAuthMessages { mutating func receiveUserAuthRequest(_ message: SSHMessage.UserAuthRequestMessage) throws -> SSHConnectionStateMachine.StateMachineInboundProcessResult { let result = try self.userAuthStateMachine.receiveUserAuthRequest(message) - + + let attr = connectionAttributes + if let future = result { - return .possibleFutureMessage(future.map(Self.transform(_:))) + return .possibleFutureMessage(future.map{ + switch $0 { + case .success: + attr?.username = message.username + return SSHMultiMessage(.userAuthSuccess) + case .failure(let message): + return SSHMultiMessage(.userAuthFailure(message)) + case .publicKeyOK(let message): + return SSHMultiMessage(.userAuthPKOK(message)) + } + }) } else { return .noMessage } @@ -65,17 +78,6 @@ extension AcceptsUserAuthMessages { } } - private static func transform(_ result: NIOSSHUserAuthenticationResponseMessage) -> SSHMultiMessage { - switch result { - case .success: - return SSHMultiMessage(.userAuthSuccess) - case .failure(let message): - return SSHMultiMessage(.userAuthFailure(message)) - case .publicKeyOK(let message): - return SSHMultiMessage(.userAuthPKOK(message)) - } - } - private static func transform(_ result: SSHMessage.UserAuthRequestMessage?) -> SSHMultiMessage? { result.map { SSHMultiMessage(.userAuthRequest($0)) } } diff --git a/Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift b/Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift index 2556353..d61ea59 100644 --- a/Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift +++ b/Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift @@ -57,15 +57,25 @@ struct SSHConnectionStateMachine { case sentDisconnect(SSHConnectionRole) } + class Attributes { + var username: String? = nil + } + /// The state of this state machine. private var state: State - + + /// Attributes of the connection which can be changed by messages handlers + private let attributes: Attributes + + var username: String? { attributes.username } + private static let defaultTransportProtectionSchemes: [NIOSSHTransportProtection.Type] = [ AES256GCMOpenSSHTransportProtection.self, AES128GCMOpenSSHTransportProtection.self, ] init(role: SSHConnectionRole, protectionSchemes: [NIOSSHTransportProtection.Type] = Self.defaultTransportProtectionSchemes) { - self.state = .idle(IdleState(role: role, protectionSchemes: protectionSchemes)) + self.attributes = Attributes() + self.state = .idle(IdleState(role: role, protectionSchemes: protectionSchemes, attributes:self.attributes)) } func start() -> SSHMultiMessage? { diff --git a/Sources/NIOSSH/Connection State Machine/States/ActiveState.swift b/Sources/NIOSSH/Connection State Machine/States/ActiveState.swift index c243fac..efeb069 100644 --- a/Sources/NIOSSH/Connection State Machine/States/ActiveState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/ActiveState.swift @@ -31,6 +31,8 @@ extension SSHConnectionStateMachine { internal var sessionIdentifier: ByteBuffer + internal weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(_ previous: UserAuthenticationState) { self.role = previous.role self.serializer = previous.serializer @@ -38,6 +40,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = previous.remoteVersion self.protectionSchemes = previous.protectionSchemes self.sessionIdentifier = previous.sessionIdentifier + self.connectionAttributes = previous.connectionAttributes } init(_ previous: RekeyingReceivedNewKeysState) { @@ -47,6 +50,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = previous.remoteVersion self.protectionSchemes = previous.protectionSchemes self.sessionIdentifier = previous.sessionIdentifier + self.connectionAttributes = previous.connectionAttributes } init(_ previous: RekeyingSentNewKeysState) { @@ -56,6 +60,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = previous.remoteVersion self.protectionSchemes = previous.protectionSchemes self.sessionIdentifier = previous.sessionIdentifier + self.connectionAttributes = previous.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/IdleState.swift b/Sources/NIOSSH/Connection State Machine/States/IdleState.swift index 0ea83d3..e6cd20c 100644 --- a/Sources/NIOSSH/Connection State Machine/States/IdleState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/IdleState.swift @@ -23,10 +23,13 @@ extension SSHConnectionStateMachine { internal var protectionSchemes: [NIOSSHTransportProtection.Type] - init(role: SSHConnectionRole, protectionSchemes: [NIOSSHTransportProtection.Type]) { + internal weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + + init(role: SSHConnectionRole, protectionSchemes: [NIOSSHTransportProtection.Type], attributes: SSHConnectionStateMachine.Attributes) { self.role = role self.serializer = SSHPacketSerializer() self.protectionSchemes = protectionSchemes + self.connectionAttributes = attributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/KeyExchangeState.swift b/Sources/NIOSSH/Connection State Machine/States/KeyExchangeState.swift index 983326a..f9e80b0 100644 --- a/Sources/NIOSSH/Connection State Machine/States/KeyExchangeState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/KeyExchangeState.swift @@ -33,6 +33,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var keyExchangeStateMachine: SSHKeyExchangeStateMachine + weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(sentVersionState state: SentVersionState, allocator: ByteBufferAllocator, loop: EventLoop, remoteVersion: String) { self.role = state.role self.parser = state.parser @@ -40,6 +42,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = remoteVersion self.protectionSchemes = state.protectionSchemes self.keyExchangeStateMachine = SSHKeyExchangeStateMachine(allocator: allocator, loop: loop, role: state.role, remoteVersion: remoteVersion, protectionSchemes: state.protectionSchemes, previousSessionIdentifier: nil) + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/ReceivedKexInitWhenActiveState.swift b/Sources/NIOSSH/Connection State Machine/States/ReceivedKexInitWhenActiveState.swift index 751b078..f112201 100644 --- a/Sources/NIOSSH/Connection State Machine/States/ReceivedKexInitWhenActiveState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/ReceivedKexInitWhenActiveState.swift @@ -34,6 +34,8 @@ extension SSHConnectionStateMachine { internal var sessionIdentifier: ByteBuffer + internal weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(_ previous: ActiveState, allocator: ByteBufferAllocator, loop: EventLoop) { self.role = previous.role self.serializer = previous.serializer @@ -42,6 +44,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previous.protectionSchemes self.sessionIdentifier = previous.sessionIdentifier self.keyExchangeStateMachine = SSHKeyExchangeStateMachine(allocator: allocator, loop: loop, role: previous.role, remoteVersion: previous.remoteVersion, protectionSchemes: previous.protectionSchemes, previousSessionIdentifier: self.sessionIdentifier) + self.connectionAttributes = previous.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/ReceivedNewKeysState.swift b/Sources/NIOSSH/Connection State Machine/States/ReceivedNewKeysState.swift index e7ac34a..b5e2f76 100644 --- a/Sources/NIOSSH/Connection State Machine/States/ReceivedNewKeysState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/ReceivedNewKeysState.swift @@ -39,6 +39,8 @@ extension SSHConnectionStateMachine { /// The user auth state machine that drives user authentication. var userAuthStateMachine: UserAuthenticationStateMachine + weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(keyExchangeState state: KeyExchangeState, loop: EventLoop) { self.role = state.role @@ -53,6 +55,7 @@ extension SSHConnectionStateMachine { self.userAuthStateMachine = UserAuthenticationStateMachine(role: self.role, loop: loop, sessionID: self.sessionIdentifier) + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/RekeyingReceivedNewKeysState.swift b/Sources/NIOSSH/Connection State Machine/States/RekeyingReceivedNewKeysState.swift index 37a9c5b..e2421fb 100644 --- a/Sources/NIOSSH/Connection State Machine/States/RekeyingReceivedNewKeysState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/RekeyingReceivedNewKeysState.swift @@ -36,6 +36,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var keyExchangeStateMachine: SSHKeyExchangeStateMachine + weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(_ previousState: RekeyingState) { self.role = previousState.role self.parser = previousState.parser @@ -44,6 +46,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previousState.protectionSchemes self.sessionIdentifier = previousState.sessionIdentifier self.keyExchangeStateMachine = previousState.keyExchangeStateMachine + self.connectionAttributes = previousState.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/RekeyingSentNewKeysState.swift b/Sources/NIOSSH/Connection State Machine/States/RekeyingSentNewKeysState.swift index 9e6af70..52dcaa1 100644 --- a/Sources/NIOSSH/Connection State Machine/States/RekeyingSentNewKeysState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/RekeyingSentNewKeysState.swift @@ -36,6 +36,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var keyExchangeStateMachine: SSHKeyExchangeStateMachine + weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(_ previousState: RekeyingState) { self.role = previousState.role self.parser = previousState.parser @@ -44,6 +46,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previousState.protectionSchemes self.sessionIdentifier = previousState.sessionIdentifier self.keyExchangeStateMachine = previousState.keyExchangeStateMachine + self.connectionAttributes = previousState.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/RekeyingState.swift b/Sources/NIOSSH/Connection State Machine/States/RekeyingState.swift index 000e415..059fdc7 100644 --- a/Sources/NIOSSH/Connection State Machine/States/RekeyingState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/RekeyingState.swift @@ -35,6 +35,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var keyExchangeStateMachine: SSHKeyExchangeStateMachine + weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(_ previousState: ReceivedKexInitWhenActiveState) { self.role = previousState.role self.parser = previousState.parser @@ -43,6 +45,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previousState.protectionSchemes self.sessionIdentifier = previousState.sessionIdentifier self.keyExchangeStateMachine = previousState.keyExchangeStateMachine + self.connectionAttributes = previousState.connectionAttributes } init(_ previousState: SentKexInitWhenActiveState) { @@ -53,6 +56,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previousState.protectionSchemes self.sessionIdentifier = previousState.sessionIdentitifier self.keyExchangeStateMachine = previousState.keyExchangeStateMachine + self.connectionAttributes = previousState.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/SentKexInitWhenActiveState.swift b/Sources/NIOSSH/Connection State Machine/States/SentKexInitWhenActiveState.swift index df89697..0450e04 100644 --- a/Sources/NIOSSH/Connection State Machine/States/SentKexInitWhenActiveState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/SentKexInitWhenActiveState.swift @@ -34,6 +34,8 @@ extension SSHConnectionStateMachine { internal var keyExchangeStateMachine: SSHKeyExchangeStateMachine + internal weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(_ previous: ActiveState, allocator: ByteBufferAllocator, loop: EventLoop) { self.role = previous.role self.serializer = previous.serializer @@ -42,6 +44,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previous.protectionSchemes self.sessionIdentitifier = previous.sessionIdentifier self.keyExchangeStateMachine = SSHKeyExchangeStateMachine(allocator: allocator, loop: loop, role: self.role, remoteVersion: self.remoteVersion, protectionSchemes: self.protectionSchemes, previousSessionIdentifier: previous.sessionIdentifier) + self.connectionAttributes = previous.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/SentNewKeysState.swift b/Sources/NIOSSH/Connection State Machine/States/SentNewKeysState.swift index 5452fb5..460888e 100644 --- a/Sources/NIOSSH/Connection State Machine/States/SentNewKeysState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/SentNewKeysState.swift @@ -39,6 +39,8 @@ extension SSHConnectionStateMachine { /// The user auth state machine that drives user authentication. var userAuthStateMachine: UserAuthenticationStateMachine + weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(keyExchangeState state: KeyExchangeState, loop: EventLoop) { self.role = state.role @@ -53,6 +55,7 @@ extension SSHConnectionStateMachine { self.userAuthStateMachine = UserAuthenticationStateMachine(role: self.role, loop: loop, sessionID: self.sessionIdentifier) + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/SentVersionState.swift b/Sources/NIOSSH/Connection State Machine/States/SentVersionState.swift index a7a83fb..37d23c8 100644 --- a/Sources/NIOSSH/Connection State Machine/States/SentVersionState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/SentVersionState.swift @@ -28,6 +28,8 @@ extension SSHConnectionStateMachine { var protectionSchemes: [NIOSSHTransportProtection.Type] + weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + private let allocator: ByteBufferAllocator init(idleState state: IdleState, allocator: ByteBufferAllocator) { @@ -37,6 +39,7 @@ extension SSHConnectionStateMachine { self.parser = SSHPacketParser(allocator: allocator) self.allocator = allocator + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/UserAuthenticationState.swift b/Sources/NIOSSH/Connection State Machine/States/UserAuthenticationState.swift index 7b6d2b7..5aec764 100644 --- a/Sources/NIOSSH/Connection State Machine/States/UserAuthenticationState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/UserAuthenticationState.swift @@ -35,6 +35,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var userAuthStateMachine: UserAuthenticationStateMachine + weak var connectionAttributes: SSHConnectionStateMachine.Attributes? + init(sentNewKeysState state: SentNewKeysState) { self.role = state.role self.parser = state.parser @@ -43,6 +45,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = state.remoteVersion self.protectionSchemes = state.protectionSchemes self.sessionIdentifier = state.sessionIdentifier + self.connectionAttributes = state.connectionAttributes } init(receivedNewKeysState state: ReceivedNewKeysState) { @@ -53,6 +56,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = state.remoteVersion self.protectionSchemes = state.protectionSchemes self.sessionIdentifier = state.sessionIdentifier + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/NIOSSHHandler.swift b/Sources/NIOSSH/NIOSSHHandler.swift index 50f1fa1..e02e6bf 100644 --- a/Sources/NIOSSH/NIOSSHHandler.swift +++ b/Sources/NIOSSH/NIOSSHHandler.swift @@ -57,6 +57,9 @@ public final class NIOSSHHandler { private var pendingGlobalRequestResponses: CircularBuffer + // The authenticated username, if there was one. + var username: String? { stateMachine.username } + public init(role: SSHConnectionRole, allocator: ByteBufferAllocator, inboundChildChannelInitializer: ((Channel, SSHChannelType) -> EventLoopFuture)?) { self.stateMachine = SSHConnectionStateMachine(role: role) self.pendingWrite = false diff --git a/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift b/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift index 065ee65..e3576a2 100644 --- a/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift +++ b/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift @@ -21,6 +21,8 @@ import XCTest /// This reduces the testing surface area somewhat, which greatly helps us to test the /// implementation of the multiplexer and child channels. final class DummyDelegate: SSHMultiplexerDelegate { + var username : String? = "dummy" + var _channel: EmbeddedChannel = EmbeddedChannel() var writes: MarkedCircularBuffer<(SSHMessage, EventLoopPromise?)> = MarkedCircularBuffer(initialCapacity: 8)