### SSH Server Setup Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ShellDelegate.md Configure and start an SSH server using SSHServer. This snippet shows how to host the server, enable shell functionality with a delegate, and manage its lifecycle. ```swift let server = try await SSHServer.host( host: "0.0.0.0", port: 22, hostKeys: hostKeys, authenticationDelegate: authDelegate ) server.enableShell(withDelegate: SimpleShellDelegate()) try await server.closeFuture.get() ``` -------------------------------- ### SFTPFileAttributes Example Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/types.md Example of creating SFTPFileAttributes to set directory permissions before creating a directory. Ensure SFTP client is initialized. ```swift let attrs = SFTPFileAttributes(permissions: 0o755) try await sftp.createDirectory(atPath: "bin", attributes: attrs) ``` -------------------------------- ### Build Dockerfile for SSH Example Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/ExampleClient/README.md Builds the Docker image for the SSH example server. Ensure you are in the directory containing the Dockerfile. ```bash docker build --file ExampleServer.dockerfile --tag sshd-example . ``` -------------------------------- ### Swift MyExecDelegate Example Source: https://github.com/orlandos-nl/citadel/blob/main/README.md An example custom ExecDelegate that uses bash to execute commands and manages environment variables. ```swift import Foundation /// An example of a custom ExecDelegate that uses bash as the shell to execute commands public final class MyExecDelegate: ExecDelegate { var environment: [String: String] = [: ] public func setEnvironmentValue(_ value: String, forKey key: String) async throws { // Set the environment variable environment[key] = value } public func start(command: String, outputHandler: ExecOutputHandler) async throws -> ExecCommandContext { // Start the command let process = Process() // This uses bash as the shell to execute the command // You can use any shell you want, or even a custom one // This is just an example, you can do whatever you want // as long as you provide an exit code process.executableURL = URL(fileURLWithPath: "/bin/bash") process.arguments = ["-c", command] process.environment = environment process.standardInput = outputHandler.stdin process.standardOutput = outputHandler.stdout process.standardError = outputHandler.stderr process.terminationHandler = { process in // Send the exit code outputHandler.exit(code: Int(process.terminationStatus)) } // Start the process try process.run() return ExecProcessContext(process: process) } } ``` -------------------------------- ### ExecDelegate start() Method Signature Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ExecDelegate.md Signature for the start method, which executes a command and returns a context for managing the process. The outputHandler is used for I/O and exit status. ```swift func start( command: String, outputHandler: ExecOutputHandler ) async throws -> ExecCommandContext ``` -------------------------------- ### Start Local HTTP Server Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/RemotePortForwardExample/README.md Starts a simple HTTP server on the local machine to be exposed remotely. Ensure this service is running before initiating the port forward. ```bash # Terminal 1 - Start a local HTTP server python3 -m http.server 3000 ``` -------------------------------- ### Initialize and Start SSH Server Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHServer.md Creates and starts an SSH server on the specified host and port. Ensure you provide valid host keys and an authentication delegate. The server binds to the given host and port, using default algorithms and protocol options unless specified otherwise. ```swift public static func host( host: String, port: Int, hostKeys: [NIOSSHPrivateKey], algorithms: SSHAlgorithms = SSHAlgorithms(), protocolOptions: Set = [], logger: Logger = Logger(label: "nl.orlandos.citadel.server"), authenticationDelegate: NIOSSHServerUserAuthenticationDelegate, group: MultiThreadedEventLoopGroup = .init(numberOfThreads: 1) ) async throws -> SSHServer ``` ```swift let server = try await SSHServer.host( host: "0.0.0.0", port: 22, hostKeys: [NIOSSHPrivateKey(ed25519Key: .init())], authenticationDelegate: MyAuthDelegate() ) ``` -------------------------------- ### ShellOutboundWriter Write Example Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ShellDelegate.md Demonstrates how to use the ShellOutboundWriter to send welcome messages and prompts to the client. ```swift try outbound.write("Welcome to MyShell!\n") try outbound.write(ByteBuffer(string: "prompt> ")) ``` -------------------------------- ### Run Remote Port Forward Example (SSH Key Auth) Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/RemotePortForwardExample/README.md Executes the Citadel remote port forward example using SSH key authentication, which is recommended for security. Ensure your private key is accessible. ```bash # Or with SSH key authentication (recommended) swift run remote-forward \ --host your-ssh-server.com \ --username your-username \ --private-key ~/.ssh/id_rsa ``` -------------------------------- ### Swift ExecProcessContext Implementation Source: https://github.com/orlandos-nl/citadel/blob/main/README.md An example implementation of ExecCommandContext for managing a child process. ```swift import Foundation /// A context that represents a process that is being executed. /// This can receive remote `terminate` signals, or receive a notification that `stdin` was closed through `inputClosed`. struct ExecProcessContext: ExecCommandContext { let process: Process func terminate() async throws { process.terminate() } func inputClosed() async throws { try process.stdin.close() } } ``` -------------------------------- ### Run Remote Port Forward Example (Password Auth) Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/RemotePortForwardExample/README.md Executes the Citadel remote port forward example using password authentication for the SSH connection. Replace placeholders with your actual server details. ```bash # Terminal 2 - Run the remote port forward example cd Examples/RemotePortForwardExample # Run with password authentication swift run remote-forward \ --host your-ssh-server.com \ --username your-username \ --password your-password ``` -------------------------------- ### Enable Direct TCPIP Forwarding Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/Delegates.md Example of how to enable direct TCP/IP forwarding on the server using a DirectTCPIPForwardingDelegate with whitelisting. ```swift let delegate = DirectTCPIPForwardingDelegate() delegate.whitelistedHosts = ["internal.example.com"] delegate.whitelistedPorts = [5432, 3306] server.enableDirectTCPIP(withDelegate: delegate) ``` -------------------------------- ### SSHServer.host() Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/configuration.md Configures and starts an SSH server instance, binding to a specified host and port, and handling user authentication. ```APIDOC ## SSHServer.host() ### Description Configures and starts an SSH server instance, binding to a specified host and port, and handling user authentication. ### Method `host( host: String, port: Int, hostKeys: [NIOSSHPrivateKey], algorithms: SSHAlgorithms = SSHAlgorithms(), protocolOptions: Set = [], logger: Logger = Logger(label: "nl.orlandos.citadel.server"), authenticationDelegate: NIOSSHServerUserAuthenticationDelegate, group: MultiThreadedEventLoopGroup = .init(numberOfThreads: 1) ) async throws -> SSHServer` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```swift let hostKey = NIOSSHPrivateKey(ed25519Key: Curve25519.Signing.PrivateKey()) let server = try await SSHServer.host( host: "0.0.0.0", port: 2222, hostKeys: [hostKey], authenticationDelegate: MyAuthDelegate(), group: MultiThreadedEventLoopGroup(numberOfThreads: 4) ) ``` ### Response #### Success Response (200) `SSHServer` - An instance of the configured SSH server. #### Response Example None provided. ``` -------------------------------- ### Run Remote Port Forward Example with Docker Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/RemotePortForwardExample/README.md Demonstrates testing remote port forwarding locally using Docker to simulate an SSH server. This avoids the need for a separate remote server during development or testing. ```bash # Start an SSH server in Docker docker run -d -p 2222:2222 \ -e USER_NAME=testuser \ -e USER_PASSWORD=testpass \ -e PASSWORD_ACCESS=true \ lscr.io/linuxserver/openssh-server:latest # Run the example swift run remote-forward \ --host localhost \ --port 2222 \ --username testuser \ --password testpass \ --insecure ``` -------------------------------- ### Example MyExecDelegate Implementation Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ExecDelegate.md A concrete implementation of the ExecDelegate protocol using Swift's Process class. It sets up environment variables and pipes for standard input, output, and error streams. ```swift public final class MyExecDelegate: ExecDelegate { var environment: [String: String] = [: ] public func setEnvironmentValue(_ value: String, forKey key: String) async throws { environment[key] = value } public func start( command: String, outputHandler: ExecOutputHandler ) async throws -> ExecCommandContext { let process = Process() process.executableURL = URL(fileURLWithPath: "/bin/bash") process.arguments = ["-c", command] process.environment = environment process.standardInput = outputHandler.stdinPipe process.standardOutput = outputHandler.stdoutPipe process.standardError = outputHandler.stderrPipe process.terminationHandler = { p in outputHandler.succeed(exitCode: Int(p.terminationStatus)) } try process.run() return MyExecContext(process: process) } } struct MyExecContext: ExecCommandContext { let process: Process func terminate() async throws { process.terminate() } func inputClosed() async throws { try process.standardInput?.close() } } ``` -------------------------------- ### SSHServer.host Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHServer.md Creates and starts an SSH server on the specified host and port. This is the primary method for initializing the server. ```APIDOC ## SSHServer.host ### Description Creates and starts an SSH server on the specified host and port. ### Method `public static func host(...) async throws -> SSHServer` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **host** (`String`) - Yes - Bind address (0.0.0.0 for all interfaces) - **port** (`Int`) - Yes - Listen port (typically 22) - **hostKeys** (`[NIOSSHPrivateKey]`) - Yes - Server private keys for authentication - **algorithms** (`SSHAlgorithms`) - No - Default - Custom cryptographic algorithms - **protocolOptions** (`Set`) - No - `[]` - SSH protocol configuration - **logger** (`Logger`) - No - Default - Logger instance - **authenticationDelegate** (`NIOSSHServerUserAuthenticationDelegate`) - Yes - User authentication handler - **group** (`MultiThreadedEventLoopGroup`) - No - Single-threaded - Event loop group for networking ### Returns An active `SSHServer` instance ### Throws Networking errors during server binding ### Example ```swift let server = try await SSHServer.host( host: "0.0.0.0", port: 22, hostKeys: [NIOSSHPrivateKey(ed25519Key: .init())], authenticationDelegate: MyAuthDelegate() ) ``` ``` -------------------------------- ### SFTP Error Handling Example Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/errors.md Demonstrates how to catch and handle specific SFTP errors, including `.errorStatus` and `.fileHandleInvalid`, using a switch statement. ```swift do { try await sftp.openFile(filePath: "/etc/passwd", flags: .write) } catch let error as SFTPError { switch error { case .errorStatus(let status): print("SFTP error: \(status.errorCode.debugDescription)") case .fileHandleInvalid: print("File could not be opened") default: print("SFTP operation failed: \(error)") } } ``` -------------------------------- ### Command Execution Failure Example Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/errors.md Shows how to catch a `CommandFailed` error after executing a command that returns a non-zero exit code. ```swift do { try await client.executeCommand("false") } catch let error as SSHClient.CommandFailed { print("Command failed with exit code: \(error.exitCode)") } ``` -------------------------------- ### Command Stderr Output Example Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/errors.md Demonstrates catching `TT জৈSTDError` to access and print the stderr output from a command. ```swift do { try await client.executeCommand("cat /nonexistent") } catch let error as TTYSTDError { let message = String(buffer: error.message) print("Stderr: \(message)") } ``` -------------------------------- ### startShell Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ShellDelegate.md Initiates an interactive SSH shell session. This method is called by the system to start a new shell and should be implemented by the user to handle the session's lifecycle, processing client input and sending output. ```APIDOC ## startShell(inbound:outbound:context:) ### Description Initiates an interactive SSH shell session. This function runs for the lifetime of the shell session, processing client input from the inbound stream and sending output to the client via the outbound writer. The context provides window size changes and session information. ### Method `async throws` ### Parameters #### Path Parameters - **inbound** (`AsyncStream`) - Required - Stream of client input events - **outbound** (`ShellOutboundWriter`) - Required - Writer for sending output to client - **context** (`SSHShellContext`) - Required - Shell session context ### Throws Any errors that terminate the shell. ### Details - Process client input from the inbound stream - Send output to client via the outbound writer - The context provides window size changes and session information ``` -------------------------------- ### Connect and Run Command with Swift Client Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/ExampleClient/README.md Executes the Swift client code to connect to the running SSH server and run a command. This requires the Swift toolchain to be installed. ```swift swift run ``` -------------------------------- ### Run SSH Example Docker Image Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/ExampleClient/README.md Runs the built Docker image, exposing the SSH port. This makes the server accessible on localhost:2323. ```bash docker run -p 2323:22 sshd-example ``` -------------------------------- ### SimpleShellDelegate Implementation Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ShellDelegate.md An example implementation of the ShellDelegate protocol that handles basic commands like 'exit', 'pwd', and 'echo'. It processes client input and sends responses. ```swift public class SimpleShellDelegate: ShellDelegate { public func startShell( inbound: AsyncStream, outbound: ShellOutboundWriter, context: SSHShellContext ) async throws { try outbound.write("Welcome to SSH Shell!\n") try outbound.write("Type 'exit' to quit.\n\n") try outbound.write("> ") var inputBuffer = "" for await event in inbound { switch event { case .stdin(let buffer): if let data = buffer.getData(at: 0, length: buffer.readableBytes) { inputBuffer += String(bytes: data, encoding: .utf8) ?? "" } // Check for complete lines if inputBuffer.contains("\n") { let line = inputBuffer .trimmingCharacters(in: .whitespacesAndNewlines) if line == "exit" { try outbound.write("Goodbye!\n") try await context.close() return } else if line == "pwd" { try outbound.write("/home/user\n") } else if line.hasPrefix("echo ") { let text = String(line.dropFirst(5)) try outbound.write(text + "\n") } else { try outbound.write("Unknown command: \(line)\n") } inputBuffer = "" try outbound.write("> ") } } } } } ``` -------------------------------- ### Run Remote Port Forward Example with Local SSH (macOS) Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/RemotePortForwardExample/README.md Shows how to test remote port forwarding using the local SSH server on macOS. This requires enabling remote login and using your system password for authentication. ```bash # Enable SSH on macOS (if not already enabled) sudo systemsetup -setremotelogin on # Run the example swift run remote-forward \ --host localhost \ --username $(whoami) \ --password your-mac-password \ --insecure ``` -------------------------------- ### Enable Remote Port Forwarding Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/Delegates.md Example of enabling remote port forwarding with an AsyncRemotePortForwardDelegate, specifying allowed ports and a handler for accepted connections. ```swift let delegate = AsyncRemotePortForwardDelegate( allowedPorts: [8080, 8081], onAccept: { channel, addr in try await channel.executeThenClose { inbound, outbound in for try await data in inbound { try await outbound.write(data) } } } ) server.enableRemotePortForward(withDelegate: delegate) ``` -------------------------------- ### Server Usage of ExecDelegate Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ExecDelegate.md Example of how to enable and use an ExecDelegate with an SSHServer. This involves creating an SSH server instance and then enabling the exec functionality with a custom delegate. ```swift let server = try await SSHServer.host( host: "0.0.0.0", port: 22, hostKeys: hostKeys, authenticationDelegate: authDelegate ) server.enableExec(withDelegate: MyExecDelegate()) try await server.closeFuture.get() ``` -------------------------------- ### Execute Command and Get Separate Output Streams Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Executes a command and returns separate streams for standard output and standard error. Ideal for processing stdout and stderr independently. ```swift public func executeCommandPair( _ command: String, inShell: Bool = false ) async throws -> ExecCommandStream ``` ```swift let streams = try await client.executeCommandPair("make") Task { for try await output in streams.stdout { print("make output:", String(buffer: output)) } } Task { for try await error in streams.stderr { print("make error:", String(buffer: error)) } } ``` -------------------------------- ### Execute Command and Get Output Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Executes a shell command on the remote server and retrieves its complete output. Supports options to merge standard error and execute within a shell context. ```swift let output = try await client.executeCommand("ls -la /home") let text = String(buffer: output) print(text) ``` -------------------------------- ### Create and Configure SSH Server Source: https://github.com/orlandos-nl/citadel/blob/main/README.md Initializes an SSH server listening on a specified host and port, using a custom authentication delegate and enabling SFTP and command execution. ```swift let server = try await SSHServer.host( host: "0.0.0.0", port: 22, hostKeys: [ // This hostkey changes every app boot, it's more practical to use a pre-generated one NIOSSHPrivateKey(ed25519Key: .init()) ], authenticationDelegate: MyCustomMongoDBAuthDelegate(db: mongokitten) ) server.enableExec(withDelegate: MyExecDelegate()) server.enableSFTP(withDelegate: MySFTPDelegate()) ``` -------------------------------- ### readAttributes() Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SFTPFile.md Gets the file's current attributes, including size, permissions, and timestamps. ```APIDOC ## readAttributes() ### Description Gets the file's current attributes, including size, permissions, and timestamps. ### Method `async throws` ### Response #### Success Response - **SFTPFileAttributes** - With size, permissions, timestamps #### Throws - `SFTPError` on failure ### Example ```swift let file = try await sftp.openFile(filePath: "document.pdf", flags: .read) let attrs = try await file.readAttributes() if let size = attrs.size { print("File size: \(size) bytes") } if let perms = attrs.permissions { print("Permissions: \(String(format: "%o", perms))") } try await file.close() ``` ``` -------------------------------- ### ExecDelegate.start Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ExecDelegate.md Executes a command and returns a context for managing the process. The command string is typically passed to a shell. stdout and stderr should be written to the handler's pipes. Process exit code must be reported via outputHandler.succeed(exitCode:). Errors should be reported via outputHandler.fail(_:). ```APIDOC ## ExecDelegate.start ### Description Executes a command and returns a context for managing the process. ### Method Signature ```swift func start( command: String, outputHandler: ExecOutputHandler ) async throws -> ExecCommandContext ``` ### Parameters #### Path Parameters - **command** (String) - Required - The command string to execute - **outputHandler** (ExecOutputHandler) - Required - Handler for I/O and exit status ### Returns - **ExecCommandContext** - Context for process control ### Throws - Any errors that prevent command execution ``` -------------------------------- ### Connect and Execute Command with SSHClient Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/README.md Establishes an SSH connection, executes a command, prints the output, and closes the connection. Requires host, authentication method, and host key validator. ```swift let client = try await SSHClient.connect( host: "example.com", authenticationMethod: .passwordBased(username: "alice", password: "secret"), hostKeyValidator: .acceptAnything() ) let output = try await client.executeCommand("ls -la") print(String(buffer: output)) try await client.close() ``` -------------------------------- ### SSH Server with Command Execution Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/README.md Sets up an SSH server that supports password authentication and enables command execution. Requires a custom authentication delegate and an exec delegate. ```swift struct MyAuthDelegate: NIOSSHServerUserAuthenticationDelegate { let supportedAuthenticationMethods: NIOSSHAvailableUserAuthenticationMethods = [.password] func requestReceived( request: NIOSSHUserAuthenticationRequest, responsePromise: EventLoopPromise ) { if case .password(let pwd) = request.request, pwd.password == "correctpassword" { responsePromise.succeed(.success) } else { responsePromise.succeed(.failure) } } } let server = try await SSHServer.host( host: "0.0.0.0", port: 22, hostKeys: [NIOSSHPrivateKey(ed25519Key: .init())], authenticationDelegate: MyAuthDelegate() ) server.enableExec(withDelegate: MyExecDelegate()) try await server.closeFuture.get() ``` -------------------------------- ### Connect to SSH Server with Detailed Options Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Establishes an SSH connection with granular control over host, port, authentication, host key validation, reconnection, algorithms, and protocol options. Useful for custom configurations. ```swift let client = try await SSHClient.connect( host: "example.com", authenticationMethod: .ed25519(username: "user", privateKey: key), hostKeyValidator: .trustedKeys(publicKeys), reconnect: .always ) ``` -------------------------------- ### read(from:length:) Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SFTPFile.md Reads bytes from the file at a specific offset. Allows specifying a starting offset and the number of bytes to read. ```APIDOC ## read(from:length:) ### Description Reads bytes from the file at a specific offset. Allows specifying a starting offset and the number of bytes to read. ### Method `async throws` ### Parameters #### Path Parameters - **offset** (`UInt64`) - Optional - Byte offset to start reading (Default: 0) - **length** (`UInt32`) - Optional - Maximum bytes to read (Default: .max) ### Response #### Success Response - **ByteBuffer** - Containing the read data #### Throws - `SFTPError.fileHandleInvalid` if file is closed - `SFTPError.invalidResponse` if server returns unexpected response - `SFTPError` for I/O failures ### Example ```swift let file = try await sftp.openFile(filePath: "data.bin", flags: .read) // Read first 1024 bytes let chunk1 = try await file.read(from: 0, length: 1024) // Read next 1024 bytes let chunk2 = try await file.read(from: 1024, length: 1024) try await file.close() ``` ``` -------------------------------- ### Create Directory with Permissions Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SFTPClient.md Creates a new directory on the remote SFTP server with specified permissions. Ensure the parent directory exists and the client has write permissions. ```swift try await sftp.createDirectory( atPath: "backups", attributes: SFTPFileAttributes(permissions: 0o755) ) ``` -------------------------------- ### ExecDelegate setEnvironmentValue() Method Signature Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ExecDelegate.md Signature for the setEnvironmentValue method, used to set an environment variable for the command execution. This should be called before the command starts. ```swift func setEnvironmentValue(_ value: String, forKey key: String) async throws ``` -------------------------------- ### Connect to Jump Host Source: https://github.com/orlandos-nl/citadel/blob/main/README.md Establishes an initial SSH connection to a jump host, which can then be used to connect to a target host. ```swift let jumpHostSettings = SSHClientSettings( host: "jump.example.com", authenticationMethod: .passwordBased(username: "joannis", password: "s3cr3t"), hostKeyValidator: .acceptAnything() ) let jumpHostClient = try await SSHClient.connect(to: jumpHostSettings) ``` -------------------------------- ### Reading Bytes from SFTP File Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SFTPFile.md Reads a specified number of bytes from an SFTP file starting at a given offset. Useful for processing files in chunks. ```swift let file = try await sftp.openFile(filePath: "data.bin", flags: .read) // Read first 1024 bytes let chunk1 = try await file.read(from: 0, length: 1024) // Read next 1024 bytes let chunk2 = try await file.read(from: 1024, length: 1024) try await file.close() ``` -------------------------------- ### SFTP Get Real Path Source: https://github.com/orlandos-nl/citadel/blob/main/README.md Obtains the real, canonical path of a given file or directory path, resolving symbolic links and relative paths. ```swift // Get the current working directory let cwd = try await sftp.getRealPath(atPath: ".") //Obtain the real path of the directory eg "/opt/vulscan/.. -> /opt" let truePath = try await sftp.getRealPath(atPath: "/opt/vulscan/..") ``` -------------------------------- ### Get Real Path Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SFTPClient.md Resolves a given path to its canonical absolute form on the SFTP server. Useful for normalizing paths or resolving symbolic links. ```swift let canonical = try await sftp.getRealPath(atPath: "/tmp/..") print(canonical) // "/tmp" or "/" ``` -------------------------------- ### Open and Read File Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SFTPClient.md Opens a file for reading, reads its entire content, and then closes the file. Ensure the file exists and the client has read permissions. ```swift let file = try await sftp.openFile( filePath: "document.txt", flags: .read ) let data = try await file.readToEnd() try await file.close() ``` -------------------------------- ### SSHClient.connect(to:) Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Establishes an SSH connection to a remote server using provided settings. This is a convenient static method for initiating a connection. ```APIDOC ## SSHClient.connect(to:) ### Description Connects to an SSH server using the provided settings. ### Method `static func connect(to settings: SSHClientSettings) async throws -> SSHClient` ### Parameters #### Path Parameters - **settings** (`SSHClientSettings`) - Required - Configuration for the SSH connection ### Returns An authenticated `SSHClient` instance. ### Throws - `SSHClientError.allAuthenticationOptionsFailed` if all authentication methods fail - `ChannelError.connectTimeout` if connection times out - Various networking errors ### Example ```swift let settings = SSHClientSettings( host: "example.com", authenticationMethod: { .passwordBased(username: "user", password: "pass") }, hostKeyValidator: .acceptAnything() ) let client = try await SSHClient.connect(to: settings) ``` ``` -------------------------------- ### RemotePortForwardDelegate Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/Delegates.md Manages reverse port forwarding, where the server listens for incoming connections and forwards them back to the client. It provides methods for starting and stopping the listening process. ```APIDOC ## RemotePortForwardDelegate ### Description Handles reverse port forwarding (server listens and forwards connections back to client). ### Methods #### startListening Starts listening on a specified host and port for incoming connections to be forwarded. - **Method Signature**: `func startListening(host: String, port: Int, handler: NIOSSHHandler, eventLoop: EventLoop, context: SSHContext) -> EventLoopFuture` - **Parameters**: - **host** (`String`) - Host to listen on. - **port** (`Int`) - Port to listen on (0 indicates auto-selection). - **handler** (`NIOSSHHandler`) - Used for creating forwarded channels. - **eventLoop** (`EventLoop`) - The event loop for performing operations. - **context** (`SSHContext`) - The SSH connection context. - **Returns**: The bound port number, or `nil` if the request is rejected. #### stopListening Stops the listening process on a specified host and port. - **Method Signature**: `func stopListening(host: String, port: Int, eventLoop: EventLoop, context: SSHContext) -> EventLoopFuture` - **Parameters**: - **host** (`String`) - Host to stop listening on. - **port** (`Int`) - Port to stop listening on. - **eventLoop** (`EventLoop`) - The event loop for performing operations. - **context** (`SSHContext`) - The SSH connection context. ### AsyncRemotePortForwardDelegate Implementation A high-level asynchronous implementation using async/await. #### Initialization ```swift public init( eventLoopGroup: EventLoopGroup = MultiThreadedEventLoopGroup.singleton, allowedHosts: [String]? = nil, allowedPorts: [Int]? = nil, logger: Logger = Logger(label: "nl.orlandos.citadel.server.remote-forward"), onAccept: @escaping @Sendable ( NIOAsyncChannel, SocketAddress ) async throws -> Void ) ``` #### Parameters - **eventLoopGroup** (`EventLoopGroup`) - The event loop group to use (defaults to singleton). - **allowedHosts** (`[String]?`) - Optional list of allowed hosts for forwarding. - **allowedPorts** (`[Int]?`) - Optional list of allowed ports for forwarding. - **logger** (`Logger`) - A logger instance for the delegate. - **onAccept** (`@escaping @Sendable (NIOAsyncChannel, SocketAddress) async throws -> Void`) - A closure executed when a new connection is accepted, providing the channel and socket address. #### Example Usage ```swift let delegate = AsyncRemotePortForwardDelegate( allowedPorts: [8080, 8081], onAccept: { channel, addr in try await channel.executeThenClose { inbound, outbound in for try await data in inbound { try await outbound.write(data) } } } ) server.enableRemotePortForward(withDelegate: delegate) ``` ``` -------------------------------- ### Execute Command with Options Source: https://github.com/orlandos-nl/citadel/blob/main/README.md Executes a command with options to set a maximum response size and merge standard error with standard output. ```swift let stdoutAndStderr = try await client.executeCommand("ls -la ~", maxResponseSize: 42, mergeStreams: true) ``` -------------------------------- ### SSHReconnectMode for Auto-Reconnection Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/configuration.md Define the auto-reconnection behavior when an SSH connection drops. Options include reconnecting once, always, or never. This example shows setting reconnection to always and logging on disconnect. ```swift public struct SSHReconnectMode: Equatable, Sendable { public static let once: SSHReconnectMode public static let always: SSHReconnectMode public static let never: SSHReconnectMode } ``` ```swift let client = try await SSHClient.connect( host: "example.com", authenticationMethod: auth, hostKeyValidator: validator, reconnect: .always ) client.onDisconnect { print("Connection dropped") } ``` -------------------------------- ### SSHClient.connect(on:settings:) Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Establishes an SSH client connection over an existing NIO Channel. ```APIDOC ## SSHClient.connect(on:settings:) ### Description Establishes an SSH client on an existing NIO Channel. ### Method `static func connect(on channel: Channel, settings: SSHClientSettings) async throws -> SSHClient` ### Parameters #### Path Parameters - **channel** (`Channel`) - Required - An active NIO Channel - **settings** (`SSHClientSettings`) - Required - SSH configuration ### Returns An authenticated `SSHClient` instance. ``` -------------------------------- ### SSHProtocolOption for Maximum Packet Size Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/configuration.md Configure SSH protocol-level options, such as setting the maximum packet size. This example demonstrates setting the maximum packet size to 1 MB. ```swift public struct SSHProtocolOption: Hashable, Sendable { public static func maximumPacketSize(_ size: Int) -> Self } ``` ```swift let options: Set = [ .maximumPacketSize(1 << 20) // 1 MB ] let client = try await SSHClient.connect( host: "example.com", authenticationMethod: .passwordBased(username: "user", password: "pass"), hostKeyValidator: .acceptAnything(), protocolOptions: options ) ``` -------------------------------- ### init(sshRsa:) Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHAuthenticationMethod.md Parses an OpenSSH format RSA private key from a string. ```APIDOC ## init(sshRsa:) ### Description Parses an OpenSSH format RSA private key from a string. ### Method Signature ```swift init(sshRsa: String) throws ``` ### Parameters #### Path Parameters - **sshRsa** (`String`) - Required - The content of the OpenSSH format RSA private key. ### Throws This initializer can throw an error if the provided string is not a valid OpenSSH format RSA private key. ### Example ```swift let keyContent = try String(contentsOf: keyPath) let privateKey = try Insecure.RSA.PrivateKey(sshRsa: keyContent) let auth = SSHAuthenticationMethod.rsa(username: "user", privateKey: privateKey) ``` ``` -------------------------------- ### Connect to SSH Server with Settings Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Connects to an SSH server using provided settings, including host, authentication, and host key validation. Requires SSHClientSettings. ```swift let settings = SSHClientSettings( host: "example.com", authenticationMethod: { .passwordBased(username: "user", password: "pass") }, hostKeyValidator: .acceptAnything() ) let client = try await SSHClient.connect(to: settings) ``` -------------------------------- ### Run Remote Port Forwarding (Convenience) Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md A high-level method for setting up remote port forwarding that automatically handles the connection to a local service. Use this for simpler remote port forwarding configurations. ```swift public func runRemotePortForward( host: String, port: Int, forwardingTo localHost: String, port localPort: Int, onOpen: @escaping @Sendable (SSHRemotePortForward) async throws -> Void = { _ in } ) async throws ``` ```swift try await client.runRemotePortForward( host: "0.0.0.0", port: 8080, forwardingTo: "127.0.0.1", port: 3000 ) ``` -------------------------------- ### SSHClientSettings Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/types.md Configuration for establishing SSH client connections. It includes details like host, port, authentication method, host key validation, algorithms, protocol options, event loop group, and connection timeout. ```APIDOC ## Struct: SSHClientSettings ### Description Configuration for establishing SSH client connections. ### Fields - **host** (`String`) - Required - Remote hostname or IP address - **port** (`Int`) - Required - Remote SSH port - **authenticationMethod** (Closure returning `SSHAuthenticationMethod`) - Required - Returns `SSHAuthenticationMethod` - **hostKeyValidator** (`SSHHostKeyValidator`) - Required - Host key validation strategy - **algorithms** (`SSHAlgorithms`) - Optional - Default: Default `SSHAlgorithms` - Custom cryptographic algorithms - **protocolOptions** (`Set`) - Optional - Default: `[]` - SSH protocol options - **group** (`EventLoopGroup`) - Optional - Default: Singleton `EventLoopGroup` - Event loop group - **connectTimeout** (`TimeAmount`) - Optional - Default: 30s - Connection timeout ``` -------------------------------- ### SFTP Create Directory Source: https://github.com/orlandos-nl/citadel/blob/main/README.md Creates a new directory at the specified path on the SFTP server. ```swift // Create a directory try await sftp.createDirectory(atPath: "/etc/custom-folder") ``` -------------------------------- ### Connect to SSH Server Source: https://github.com/orlandos-nl/citadel/blob/main/README.md Establishes a connection to an SSH server using provided settings. Requires username, password, and a host key validator. ```swift let settings = SSHClientSettings( host: "example.com", authenticationMethod: { .passwordBased(username: "joannis", password: "s3cr3t") }, // Please use another validator if at all possible, it's insecure // But it's an easy way to try out Citadel hostKeyValidator: .acceptAnything() ) let client = try await SSHClient.connect(to: settings) ``` -------------------------------- ### withTTY Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Creates an interactive shell session. This method simplifies setting up and interacting with a remote shell. ```APIDOC ## withTTY(environment:perform:) ### Description Creates an interactive shell session. ### Method `@available(macOS 15.0, *) public func withTTY( environment: [SSHChannelRequestEvent.EnvironmentRequest] = [], perform: (_ inbound: TTYOutput, _ outbound: TTYStdinWriter) async throws -> Void ) async throws` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None #### Parameters - **environment** (`[SSHChannelRequestEvent.EnvironmentRequest]`) - Optional - Environment variables (default: `[]`) - **perform** (`Closure`) - Required - Async closure for shell interaction ### Example ```swift try await client.withTTY { inbound, outbound in try await outbound.write(ByteBuffer(string: "ls -la\nexit\n")) for try await output in inbound { if case .stdout(let buf) = output { print(String(buffer: buf)) } } } ``` ``` -------------------------------- ### withPTY Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Creates a pseudo-terminal (PTY) session, providing full terminal control. This is suitable for interactive shell-like experiences. ```APIDOC ## withPTY(_:environment:perform:) ### Description Creates a pseudo-terminal (PTY) session with full terminal control. ### Method `@available(macOS 15.0, *) public func withPTY( _ request: SSHChannelRequestEvent.PseudoTerminalRequest, environment: [SSHChannelRequestEvent.EnvironmentRequest] = [], perform: (_ inbound: TTYOutput, _ outbound: TTYStdinWriter) async throws -> Void ) async throws` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None #### Parameters - **request** (`SSHChannelRequestEvent.PseudoTerminalRequest`) - Required - PTY configuration (size, terminal type) - **environment** (`[SSHChannelRequestEvent.EnvironmentRequest]`) - Optional - Environment variables (default: `[]`) - **perform** (`Closure`) - Required - Async closure for terminal interaction ### Throws Any errors from terminal operations ### Example ```swift try await client.withPTY( SSHChannelRequestEvent.PseudoTerminalRequest( wantReply: true, term: "xterm", terminalCharacterWidth: 80, terminalRowHeight: 24, terminalPixelWidth: 0, terminalPixelHeight: 0, terminalModes: .init([.ECHO: 1]) ) ) { inbound, outbound in try await outbound.write(ByteBuffer(string: "whoami\n")) for try await output in inbound { switch output { case .stdout(let buffer): print(String(buffer: buffer)) default: break } } } ``` ``` -------------------------------- ### SFTPOpenFileFlags OptionSet Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/types.md Specifies flags to control how files are opened via SFTP. Supports read, write, append, create, truncate, and force create options. ```swift public struct SFTPOpenFileFlags: OptionSet, CustomDebugStringConvertible, Sendable { public static let read: SFTPOpenFileFlags public static let write: SFTPOpenFileFlags public static let append: SFTPOpenFileFlags public static let create: SFTPOpenFileFlags public static let truncate: SFTPOpenFileFlags public static let forceCreate: SFTPOpenFileFlags } ``` ```swift let flags: SFTPOpenFileFlags = [.read, .write] let file = try await sftp.openFile(filePath: "data.bin", flags: flags) ``` -------------------------------- ### Configure SSH Server Host Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/configuration.md Sets up the SSH server to listen on a specific host and port, requiring host keys and an authentication delegate. An event loop group can also be provided. ```swift public static func host( host: String, port: Int, hostKeys: [NIOSSHPrivateKey], algorithms: SSHAlgorithms = SSHAlgorithms(), protocolOptions: Set = [], logger: Logger = Logger(label: "nl.orlandos.citadel.server"), authenticationDelegate: NIOSSHServerUserAuthenticationDelegate, group: MultiThreadedEventLoopGroup = .init(numberOfThreads: 1) ) async throws -> SSHServer ``` ```swift let hostKey = NIOSSHPrivateKey(ed25519Key: Curve25519.Signing.PrivateKey()) let server = try await SSHServer.host( host: "0.0.0.0", port: 2222, hostKeys: [hostKey], authenticationDelegate: MyAuthDelegate(), group: MultiThreadedEventLoopGroup(numberOfThreads: 4) ) ``` -------------------------------- ### executeCommand Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/configuration.md Executes a shell command on the server and returns its output. Supports options for maximum output size, merging standard error with standard output, and executing within a shell context. ```APIDOC ## executeCommand ### Description Executes a shell command on the server and returns its output. Supports options for maximum output size, merging standard error with standard output, and executing within a shell context. ### Method `executeCommand( _ command: String, maxResponseSize: Int = .max, mergeStreams: Bool = false, inShell: Bool = false ) async throws -> ByteBuffer` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```swift let output = try await client.executeCommand( "cat /var/log/messages", maxResponseSize: 10 * 1024 * 1024, // 10 MB limit mergeStreams: true, inShell: false ) ``` ### Response #### Success Response (200) `ByteBuffer` - The output of the executed command. #### Response Example None provided. ``` -------------------------------- ### Execute Simple Command Source: https://github.com/orlandos-nl/citadel/blob/main/README.md Executes a single command on the SSH server and retrieves its standard output. ```swift let stdout = try await client.executeCommand("ls -la ~") ``` -------------------------------- ### Open SFTP Session Source: https://github.com/orlandos-nl/citadel/blob/main/README.md Opens an SFTP session on an existing SSH client connection. This is the first step for performing SFTP operations. ```swift // Open an SFTP session on the SSH client let sftp = try await client.openSFTP() ``` -------------------------------- ### Execute Command and Stream Output Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Executes a command and streams its standard output and standard error as they become available. Useful for real-time monitoring of command execution. ```swift public func executeCommandStream( _ command: String, environment: [SSHChannelRequestEvent.EnvironmentRequest] = [], inShell: Bool = false ) async throws -> AsyncThrowingStream ``` ```swift let stream = try await client.executeCommandStream("tail -f /var/log/system.log") for try await output in stream { switch output { case .stdout(let buffer): print("out:", String(buffer: buffer)) case .stderr(let buffer): print("err:", String(buffer: buffer)) } } ``` -------------------------------- ### Display Help for Remote Forward Command Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/RemotePortForwardExample/README.md Shows the available command-line options for the `remote-forward` tool. This is useful for understanding all configurable parameters. ```bash swift run remote-forward --help ``` -------------------------------- ### Establish SSH Tunneling Connection Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHClient.md Use this method to establish an SSH connection to a target host through an existing SSH client (jump host). Requires SSHClientSettings for both the jump host and the target host. ```swift let jumpClient = try await SSHClient.connect(to: jumpHostSettings) let targetClient = try await jumpClient.jump(to: targetHostSettings) ``` -------------------------------- ### ShellDelegate startShell Method Signature Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/ShellDelegate.md The signature for initiating an interactive shell session. It takes streams for inbound client events and outbound server responses, along with shell context. ```swift func startShell( inbound: AsyncStream, outbound: ShellOutboundWriter, context: SSHShellContext ) async throws ``` -------------------------------- ### Swift Code to Use Ports >= 1024 Source: https://github.com/orlandos-nl/citadel/blob/main/Examples/RemotePortForwardExample/README.md Demonstrates how to select a port number greater than or equal to 1024 to avoid 'Permission denied' errors, which typically occur when trying to bind to privileged ports (< 1024) without root access. ```swift let forward = try await client.createRemotePortForward( host: "0.0.0.0", port: 8080 // Use ports >= 1024 ) { ... } ``` -------------------------------- ### acceptAnything() Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/SSHHostKeyValidator.md Creates a validator that accepts any host key without verification. This method is insecure and intended for testing or development purposes. ```APIDOC ## acceptAnything() ### Description Creates a validator that accepts any host key without verification. This is insecure and should only be used for testing/development. ### Method `public static func acceptAnything() -> SSHHostKeyValidator` ### Returns `SSHHostKeyValidator` that accepts all keys. ### Example ```swift let validator = SSHHostKeyValidator.acceptAnything() let client = try await SSHClient.connect( host: "example.com", authenticationMethod: .passwordBased(username: "user", password: "pass"), hostKeyValidator: validator ) ``` ``` -------------------------------- ### AsyncRemotePortForwardDelegate Implementation Source: https://github.com/orlandos-nl/citadel/blob/main/_autodocs/Delegates.md A high-level async/await implementation for remote port forwarding. Configure allowed hosts/ports and provide an onAccept handler for incoming connections. ```swift public final class AsyncRemotePortForwardDelegate: RemotePortForwardDelegate { public init( eventLoopGroup: EventLoopGroup = MultiThreadedEventLoopGroup.singleton, allowedHosts: [String]? = nil, allowedPorts: [Int]? = nil, logger: Logger = Logger(label: "nl.orlandos.citadel.server.remote-forward"), onAccept: @escaping @Sendable ( NIOAsyncChannel, SocketAddress ) async throws -> Void ) } ```