-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmain.swift
139 lines (125 loc) · 5.38 KB
/
main.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//
// Echo Example for SSHConsole - main.swift
//
// Created by Jim Studt on 11/21/20.
//
// SPDX-License-Identifier: MIT
//
import Foundation
import SSHConsole
import Dispatch
///
/// This conforms to the `SSHPublicKeyDelegate` protocol and enables public key authentication.
///
/// This one's policy is to look in the process owner's `~/.ssh/authorized_keys` file for a
/// OpenSSH formatted key, which SwiftNIO SSH can handle (no RSA), that matches the `publicKey`.
///
/// - Attention: `completion()` must be called exactly once.
///
/// - Note: You probably want to push this off onto a work queue somewhere if it does file I/O like this one.
///
struct Authenticator : SSHPublicKeyDelegate {
//
// Scan the user's ~/.ssh/authorized_keys file for OpenSSH format keys
// Ignore username, we know who we are.
//
// Not all formats are supported, like RSA apparently. ssh-ed25519 is.
//
func authenticate( username:String, publicKey:SSHConsole.PublicKey, completion: @escaping ((Bool)->Void)) {
DispatchQueue.global().async {
let homeDirURL = FileManager.default.homeDirectoryForCurrentUser
let authorizedKeysURL = URL(fileURLWithPath: ".ssh/authorized_keys", relativeTo: homeDirURL)
if let file = try? String(contentsOfFile: authorizedKeysURL.path) {
//
// The actual work: We are passing the entire file to publicKey.isIn(file:)
completion( publicKey.isIn(file:file))
}
return completion(false)
}
}
}
///
/// Implement a password policy. This one is especially terrible and you should not do this.
///
/// There is a single authorized user named *password* with a password of *admin*. Just
/// confusing enough to shake the stupid bots if they crawl you while running the example.
///
/// Your `ssh` command won't offer a password unless it fails at public key authentication.
/// Your `ssh` command might let you add a ` -o PubkeyAuthentication=no` to
/// forgo your public key and let you test passwords.
///
/// - Attention: `completion()` must be called exactly once.
///
/// - Warning: You probably don't want to put hardcoded passwords in your source code.
/// Someone will find it in a repository.
///
/// - Note: You probably want to push this off onto a work queue somewhere if it does file I/O like this one.
///
struct TrivialPassword : SSHPasswordDelegate {
func authenticate(username: String, password: String, completion: @escaping ((Bool) -> Void)) {
DispatchQueue.global().async {
completion( username == "password" && password == "admin")
}
}
}
/// Process a command from an SSH connection. SSHConsole does not accept input, so this is the
/// command part of the `ssh` command as one solid string.
///
/// - Parameters:
/// - command: The command from the SSH session
/// - to: Use this to send output or error back to the remote SSH client
/// - user: The username from the authentication !! SEE NOTE!!!
/// - environment: The environment variables sent by the SSH client
///
/// - Note: For now, I don't think NIOSSH can track the user from authentication to the ChannelHandler.
/// as such, there is no way to know the `user`, so this is always `"???".
/// `
/// You will want to get onto your own work queue to do the work. Don't hang up SSHConsole's
/// thread. It is left to you since SSHConsole doesn't know what your app uses and maybe you have
/// synchronization issues.
///
/// - Attention: The SSH output is flushed and the connection closed when the `to` parameter
/// deinitializes. If you hold a reference after your command is done, then your command
/// is *not* done. In practice this takes care of itself unless you are too clever.
///
func doEcho(command: String, to: SSHConsole.CommandHandler.Output, user:String, environment: [String : String]) {
DispatchQueue.global().async {
let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines)
switch trimmed {
case "exit":
to.write("Goodbye\r\n")
terminate.signal()
default:
to.write("echo: \(trimmed)\r\n")
}
}
}
///
/// When this semaphore is signalled, we will cleanly terminate the program.
///
let terminate = DispatchSemaphore(value: 0)
///
/// Please excuse the "!", this is a demo. You will need to first make one, then store it somewhere, then
/// retrieve it at startup in your real code.
///
/// Something like `saveMyNewKey( SSHConsole.PrivateKey().string )` will get you your own key.
/// You might just print it in the debugger and paste it in like this for testing.
///
let hostKeys = [ SSHConsole.PrivateKey(string:"ed25519 IWK76Glc2Dh7BeaSJrErVAndP6QWHZ06Wk9U5aeoaEI=")! ]
///
/// At last! We create our console on our port with out identity and security policy., then start listening with our command handler.
///
let console = SSHConsole( port:2525, hostKeys:hostKeys, passwordDelegate: TrivialPassword() , publicKeyDelegate: Authenticator() )
try console.listen( runner: doEcho )
///
/// Hang around and let things happen. Eventually someone will send an `exit` command and
/// this will return.
///
/// Notice that this is *your* program doing the waiting it *its* own special way. Nothing to do with SSHConsole.
///
terminate.wait()
///
/// Do a clean shutdown
///
try console.stop()
print("Echo is done.")