-
-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support an SSH (SFTP) server #16
Changes from 1 commit
12fa76c
d24936d
fd83a94
d9716ae
f8c7566
f40d5f1
2a22932
ad137ae
d9ad237
eb45f7e
90ffb2d
19a5715
2eb9763
e5ad069
d5f9f65
48c43e4
5d5b3fb
85e2069
2756bd1
366e359
47f41e1
98c9dca
d485a38
dfdc513
13f7f9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import Foundation | ||
import NIO | ||
import NIOSSH | ||
import Logging | ||
|
||
final class SFTPClientInboundHandler: ChannelInboundHandler { | ||
typealias InboundIn = SFTPMessage | ||
|
||
let responses: SFTPResponses | ||
let logger: Logger | ||
|
||
init(responses: SFTPResponses, logger: Logger) { | ||
self.responses = responses | ||
self.logger = logger | ||
} | ||
|
||
func channelRead(context: ChannelHandlerContext, data: NIOAny) { | ||
let message = unwrapInboundIn(data) | ||
|
||
if !self.responses.isInitialized, case .version(let version) = message { | ||
if version.version != .v3 { | ||
logger.warning("SFTP ERROR: Server version is unrecognized or incompatible: \(version.version.rawValue)") | ||
context.fireErrorCaught(SFTPError.unsupportedVersion(version.version)) | ||
} else { | ||
responses.initialized.succeed(version) | ||
} | ||
} else if let response = SFTPResponse(message: message) { | ||
if let promise = responses.responses.removeValue(forKey: response.requestId) { | ||
if case .status(let status) = response, status.errorCode != .ok { | ||
// logged as debug rather than warning because there are many cases in which a protocol error is | ||
// not only nonfatal, but even expected (such as SSH_FX_EOF). | ||
self.logger.debug("SFTP error received: \(status)") | ||
promise.fail(status) | ||
} else { | ||
promise.succeed(response) | ||
} | ||
} else { | ||
self.logger.warning("SFTP response received for nonexistent request, this is a protocol error") | ||
context.fireErrorCaught(SFTPError.noResponseTarget) | ||
} | ||
} else { | ||
self.logger.warning("SFTP received unrecognized response message, this is a protocol error") | ||
context.fireErrorCaught(SFTPError.invalidResponse) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -126,6 +126,11 @@ public struct SFTPFileAttributes: CustomDebugStringConvertible { | |
// let extended_count: UInt32? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two TODO's above are already public api? |
||
|
||
public static let none = SFTPFileAttributes() | ||
public static let all: SFTPFileAttributes = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer a more descriptive name since SFTPFileAttributes holds more than readwrite permissions so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should really be implementing this for permissions: http://learn.leighcotnoir.com/wp-content/uploads/2016/08/POSIX-file-permissions.pdf as optionset . Then we don't need this |
||
var attr = SFTPFileAttributes() | ||
attr.permissions = 777 | ||
return attr | ||
}() | ||
|
||
public var debugDescription: String { "unimplemented" } | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,7 +95,7 @@ enum SFTPResponse { | |
self = .data(message) | ||
case .mkdir(let message): | ||
self = .mkdir(message) | ||
case .openFile, .closeFile, .read, .write, .initialize, .version: | ||
case .attributes, .openFile, .closeFile, .read, .write, .initialize, .version, .stat, .lstat: | ||
return nil | ||
} | ||
} | ||
|
@@ -234,6 +234,40 @@ public enum SFTPMessage { | |
fileprivate var debugVariantWithoutLargeData: Self { self } | ||
} | ||
|
||
public struct Stat: SFTPMessageContent { | ||
public static let id = SFTPMessageType.stat | ||
|
||
public let requestId: UInt32 | ||
public let path: String | ||
|
||
public var debugDescription: String { "{\(self.requestId)}('\(self.path)'" } | ||
|
||
fileprivate var debugVariantWithoutLargeData: Self { self } | ||
} | ||
|
||
public struct LStat: SFTPMessageContent { | ||
public static let id = SFTPMessageType.lstat | ||
|
||
public let requestId: UInt32 | ||
public let path: String | ||
|
||
public var debugDescription: String { "{\(self.requestId)}('\(self.path)'" } | ||
|
||
fileprivate var debugVariantWithoutLargeData: Self { self } | ||
} | ||
|
||
public struct Attributes: SFTPMessageContent { | ||
public static let id = SFTPMessageType.attributes | ||
|
||
public let requestId: UInt32 | ||
public let attributes: SFTPFileAttributes | ||
|
||
public var debugDescription: String { "{\(self.requestId)}('\(self.attributes)'" } | ||
|
||
fileprivate var debugVariantWithoutLargeData: Self { self } | ||
} | ||
|
||
|
||
/// Client. | ||
/// | ||
/// Starts SFTP session and indicates client version. | ||
|
@@ -286,13 +320,26 @@ public enum SFTPMessage { | |
/// No response, directory gets created or an error is thrown. | ||
case mkdir(MkDir) | ||
|
||
case stat(Stat) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally some nice comments like the other enums :) (Since I don't immediately know what stat and lstat are or what attributes we're talking about here) |
||
case lstat(LStat) | ||
case attributes(Attributes) | ||
|
||
public var messageType: SFTPMessageType { | ||
switch self { | ||
case .initialize(let message as SFTPMessageContent), .version(let message as SFTPMessageContent), | ||
.openFile(let message as SFTPMessageContent), .closeFile(let message as SFTPMessageContent), | ||
.read(let message as SFTPMessageContent), .write(let message as SFTPMessageContent), | ||
.handle(let message as SFTPMessageContent), .status(let message as SFTPMessageContent), | ||
.data(let message as SFTPMessageContent), .mkdir(let message as SFTPMessageContent): | ||
case | ||
.initialize(let message as SFTPMessageContent), | ||
Joannis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.version(let message as SFTPMessageContent), | ||
.openFile(let message as SFTPMessageContent), | ||
.closeFile(let message as SFTPMessageContent), | ||
.read(let message as SFTPMessageContent), | ||
.write(let message as SFTPMessageContent), | ||
.handle(let message as SFTPMessageContent), | ||
.status(let message as SFTPMessageContent), | ||
.data(let message as SFTPMessageContent), | ||
.mkdir(let message as SFTPMessageContent), | ||
.stat(let message as SFTPMessageContent), | ||
.lstat(let message as SFTPMessageContent), | ||
.attributes(let message as SFTPMessageContent): | ||
return message.id | ||
} | ||
} | ||
|
@@ -303,7 +350,9 @@ public enum SFTPMessage { | |
.openFile(let message as SFTPMessageContent), .closeFile(let message as SFTPMessageContent), | ||
.read(let message as SFTPMessageContent), .write(let message as SFTPMessageContent), | ||
.handle(let message as SFTPMessageContent), .status(let message as SFTPMessageContent), | ||
.data(let message as SFTPMessageContent), .mkdir(let message as SFTPMessageContent): | ||
.data(let message as SFTPMessageContent), .mkdir(let message as SFTPMessageContent), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer these on their own lines as well same as above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me know if you agree on style things like that btw |
||
.stat(let message as SFTPMessageContent), .lstat(let message as SFTPMessageContent), | ||
.attributes(let message as SFTPMessageContent): | ||
return "\(message.id)\(message.debugDescription)" | ||
} | ||
} | ||
|
@@ -320,6 +369,9 @@ public enum SFTPMessage { | |
case .status(let message): return Self.status(message.debugVariantWithoutLargeData) | ||
case .data(let message): return Self.data(message.debugVariantWithoutLargeData) | ||
case .mkdir(let message): return Self.mkdir(message.debugVariantWithoutLargeData) | ||
case .stat(let message): return Self.stat(message.debugVariantWithoutLargeData) | ||
case .lstat(let message): return Self.lstat(message.debugVariantWithoutLargeData) | ||
case .attributes(let message): return Self.attributes(message.debugVariantWithoutLargeData) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this logging line no longer needed in the renamed SFTPClientInboundHandler? (It seems like a copy and rename and this is the only line that is missing in that new file)