0
# SSH Server
1
2
Complete SSH server implementation for hosting SSH services with comprehensive authentication handling, session management, and client connection support.
3
4
## Capabilities
5
6
### Server Class
7
8
Creates and manages SSH server instances with full protocol support.
9
10
```javascript { .api }
11
/**
12
* SSH server for hosting SSH services
13
* Extends net.Server for network connection handling
14
*/
15
class Server extends net.Server {
16
constructor(config: ServerConfig, connectionListener?: ConnectionListener);
17
maxConnections: number;
18
}
19
20
interface ServerConfig {
21
// Required: Host keys for server identity
22
hostKeys: Array<Buffer | string | ParsedKey>;
23
24
// Protocol settings
25
algorithms?: AlgorithmList;
26
ident?: string;
27
banner?: string;
28
greeting?: string;
29
keepaliveInterval?: number;
30
keepaliveCountMax?: number;
31
32
// Debugging
33
debug?: DebugFunction;
34
}
35
36
type ConnectionListener = (client: IncomingClient, info: ClientInfo) => void;
37
38
interface ClientInfo {
39
ip: string;
40
header: {
41
identRaw: string;
42
versions: {
43
protocol: string;
44
software: string;
45
};
46
comments?: string;
47
};
48
}
49
```
50
51
**Usage Examples:**
52
53
```javascript
54
const { Server } = require('ssh2');
55
const fs = require('fs');
56
57
// Basic server setup
58
const hostKey = fs.readFileSync('host.key');
59
60
const server = new Server({
61
hostKeys: [hostKey]
62
}, (client) => {
63
console.log('Client connected!');
64
65
client.on('authentication', (ctx) => {
66
console.log(`Auth attempt: ${ctx.method} for ${ctx.username}`);
67
68
if (ctx.method === 'password') {
69
if (ctx.username === 'admin' && ctx.password === 'secret') {
70
ctx.accept();
71
} else {
72
ctx.reject();
73
}
74
} else {
75
ctx.reject(['password']);
76
}
77
});
78
});
79
80
server.listen(2222, '0.0.0.0', () => {
81
console.log('SSH server listening on port 2222');
82
});
83
```
84
85
### IncomingClient Class
86
87
Represents a connected SSH client with authentication and session management.
88
89
```javascript { .api }
90
/**
91
* Server-side representation of connected SSH client
92
* Extends EventEmitter for client lifecycle events
93
*/
94
class IncomingClient extends EventEmitter {
95
// Connection properties
96
authenticated: boolean;
97
noMoreSessions: boolean;
98
99
// Connection management
100
end(): boolean;
101
setNoDelay(noDelay?: boolean): boolean;
102
rekey(callback?: StatusCallback): boolean;
103
104
// Port forwarding
105
forwardOut(
106
boundAddr: string,
107
boundPort: number,
108
remoteAddr: string,
109
remotePort: number,
110
callback: ForwardOutCallback
111
): boolean;
112
113
// X11 forwarding
114
x11(
115
originAddr: string,
116
originPort: number,
117
callback: X11ForwardCallback
118
): boolean;
119
120
// OpenSSH extensions
121
openssh_forwardOutStreamLocal(
122
socketPath: string,
123
callback: ForwardOutCallback
124
): boolean;
125
}
126
127
type ForwardOutCallback = (err: Error | null, stream?: ServerChannel) => void;
128
type X11ForwardCallback = (err: Error | null, stream?: ServerChannel) => void;
129
type StatusCallback = (err?: Error | null) => void;
130
```
131
132
### Authentication Context Classes
133
134
Comprehensive authentication handling for different authentication methods.
135
136
```javascript { .api }
137
/**
138
* Base authentication context for all authentication methods
139
*/
140
class AuthContext {
141
username: string;
142
user: string; // Alias for username
143
service: string;
144
method: string;
145
146
/**
147
* Accept the authentication attempt
148
*/
149
accept(): void;
150
151
/**
152
* Reject the authentication attempt
153
* @param methodsLeft - Optional array of allowed methods for continuation
154
* @param isPartial - Whether this is partial authentication success
155
*/
156
reject(methodsLeft?: string[], isPartial?: boolean): void;
157
}
158
159
/**
160
* Password authentication context
161
*/
162
class PwdAuthContext extends AuthContext {
163
password: string;
164
165
/**
166
* Request password change from client
167
* @param prompt - Password change prompt message
168
* @param callback - Callback receiving new password
169
*/
170
requestChange(prompt: string, callback: PasswordChangeCallback): void;
171
}
172
173
/**
174
* Public key authentication context
175
*/
176
class PKAuthContext extends AuthContext {
177
key: ParsedKey;
178
signature?: Buffer;
179
blob: Buffer;
180
hashAlgo?: string;
181
}
182
183
/**
184
* Host-based authentication context
185
*/
186
class HostbasedAuthContext extends PKAuthContext {
187
localHostname: string;
188
localUsername: string;
189
}
190
191
/**
192
* Keyboard-interactive authentication context
193
*/
194
class KeyboardAuthContext extends AuthContext {
195
submethods: string[];
196
197
/**
198
* Send prompts to client for keyboard-interactive authentication
199
* @param prompts - Array of prompts to send
200
* @param title - Optional title for prompt dialog
201
* @param instructions - Optional instructions
202
* @param callback - Callback receiving user responses
203
*/
204
prompt(
205
prompts: KeyboardPrompt[],
206
title?: string,
207
instructions?: string,
208
callback: KeyboardResponseCallback
209
): void;
210
}
211
212
type PasswordChangeCallback = (newPassword: string) => void;
213
type KeyboardResponseCallback = (responses: string[]) => void;
214
```
215
216
**Authentication Examples:**
217
218
```javascript
219
client.on('authentication', (ctx) => {
220
console.log(`Authentication attempt: ${ctx.method} for user ${ctx.username}`);
221
222
switch (ctx.method) {
223
case 'password':
224
if (ctx.username === 'admin' && ctx.password === 'secret') {
225
ctx.accept();
226
} else {
227
ctx.reject();
228
}
229
break;
230
231
case 'publickey':
232
// Verify public key against authorized keys
233
const authorizedKey = getAuthorizedKey(ctx.username);
234
if (ctx.key.type === authorizedKey.type &&
235
ctx.key.public.equals(authorizedKey.public)) {
236
if (ctx.signature) {
237
// Verify signature if provided
238
const sigOK = ctx.key.verify(getAuthData(ctx), ctx.signature, ctx.hashAlgo);
239
if (sigOK) {
240
ctx.accept();
241
} else {
242
ctx.reject();
243
}
244
} else {
245
ctx.accept(); // Key is acceptable, signature will be checked on next attempt
246
}
247
} else {
248
ctx.reject();
249
}
250
break;
251
252
case 'keyboard-interactive':
253
ctx.prompt([
254
{ prompt: 'Password: ', echo: false },
255
{ prompt: 'Token: ', echo: false }
256
], 'Two-Factor Authentication', 'Please provide password and token', (responses) => {
257
if (responses[0] === 'secret' && responses[1] === '123456') {
258
ctx.accept();
259
} else {
260
ctx.reject();
261
}
262
});
263
break;
264
265
default:
266
ctx.reject(['password', 'publickey']);
267
}
268
});
269
```
270
271
### Session Management
272
273
Handle client session requests for shells, commands, and subsystems.
274
275
```javascript { .api }
276
/**
277
* Session represents a client session request
278
*/
279
interface Session extends EventEmitter {
280
// Session type events
281
on(event: 'shell', listener: (accept: AcceptSession, reject: RejectSession) => void): this;
282
on(event: 'exec', listener: (accept: AcceptSession, reject: RejectSession, info: ExecInfo) => void): this;
283
on(event: 'subsystem', listener: (accept: AcceptSession, reject: RejectSession, info: SubsystemInfo) => void): this;
284
on(event: 'sftp', listener: (accept: AcceptSession, reject: RejectSession) => void): this;
285
286
// Session control events
287
on(event: 'pty', listener: (accept: AcceptPty, reject: RejectPty, info: PtyInfo) => void): this;
288
on(event: 'window-change', listener: (accept: AcceptWindowChange, reject: RejectWindowChange, info: WindowChangeInfo) => void): this;
289
on(event: 'x11', listener: (accept: AcceptX11, reject: RejectX11, info: X11RequestInfo) => void): this;
290
on(event: 'env', listener: (accept: AcceptEnv, reject: RejectEnv, info: EnvInfo) => void): this;
291
on(event: 'signal', listener: (accept: AcceptSignal, reject: RejectSignal, info: SignalInfo) => void): this;
292
on(event: 'auth-agent', listener: (accept: AcceptAgent, reject: RejectAgent) => void): this;
293
294
// Session lifecycle
295
on(event: 'end', listener: () => void): this;
296
on(event: 'close', listener: () => void): this;
297
}
298
299
interface ExecInfo {
300
command: string;
301
}
302
303
interface SubsystemInfo {
304
name: string;
305
}
306
307
interface PtyInfo {
308
cols: number;
309
rows: number;
310
width: number;
311
height: number;
312
term: string;
313
modes: { [mode: string]: number };
314
}
315
316
interface WindowChangeInfo {
317
cols: number;
318
rows: number;
319
width: number;
320
height: number;
321
}
322
323
interface X11RequestInfo {
324
single: boolean;
325
protocol: string;
326
cookie: string;
327
screen: number;
328
}
329
330
interface EnvInfo {
331
key: string;
332
val: string;
333
}
334
335
interface SignalInfo {
336
name: string;
337
}
338
339
type AcceptSession = () => ServerChannel;
340
type RejectSession = () => boolean;
341
type AcceptPty = () => void;
342
type RejectPty = () => boolean;
343
type AcceptWindowChange = () => void;
344
type RejectWindowChange = () => boolean;
345
type AcceptX11 = () => void;
346
type RejectX11 = () => boolean;
347
type AcceptEnv = () => void;
348
type RejectEnv = () => boolean;
349
type AcceptSignal = () => void;
350
type RejectSignal = () => boolean;
351
type AcceptAgent = () => void;
352
type RejectAgent = () => boolean;
353
```
354
355
**Session Handling Examples:**
356
357
```javascript
358
client.on('ready', () => {
359
console.log('Client authenticated!');
360
361
client.on('session', (accept, reject) => {
362
const session = accept();
363
364
session.on('shell', (accept, reject) => {
365
const stream = accept();
366
console.log('Client requested shell');
367
368
// Set up shell interaction
369
stream.write('Welcome to SSH server!\n$ ');
370
371
stream.on('data', (data) => {
372
const command = data.toString().trim();
373
console.log('Command received:', command);
374
375
if (command === 'exit') {
376
stream.exit(0);
377
stream.end();
378
} else {
379
stream.write(`Command output for: ${command}\n$ `);
380
}
381
});
382
});
383
384
session.on('exec', (accept, reject, info) => {
385
const stream = accept();
386
console.log('Client wants to execute:', info.command);
387
388
// Execute command and send output
389
stream.write(`Output of ${info.command}\n`);
390
stream.exit(0);
391
stream.end();
392
});
393
394
session.on('sftp', (accept, reject) => {
395
console.log('Client requested SFTP subsystem');
396
const sftpStream = accept();
397
// Set up SFTP handling...
398
});
399
});
400
});
401
```
402
403
## Events
404
405
### Server Events
406
407
```javascript { .api }
408
interface ServerEvents {
409
/**
410
* Emitted when a new client connects
411
*/
412
'connection': (client: IncomingClient, info: ClientInfo) => void;
413
414
/**
415
* Emitted when server error occurs
416
*/
417
'error': (err: Error) => void;
418
419
/**
420
* Emitted when server starts listening
421
*/
422
'listening': () => void;
423
424
/**
425
* Emitted when server is closed
426
*/
427
'close': () => void;
428
}
429
```
430
431
### Client Events
432
433
```javascript { .api }
434
interface IncomingClientEvents {
435
/**
436
* Emitted for each authentication attempt
437
*/
438
'authentication': (ctx: AuthContext) => void;
439
440
/**
441
* Emitted when client is successfully authenticated
442
*/
443
'ready': () => void;
444
445
/**
446
* Emitted when client requests a new session
447
*/
448
'session': (accept: AcceptSession, reject: RejectSession) => void;
449
450
/**
451
* Emitted for direct TCP/IP connection requests (from local forwarding)
452
*/
453
'tcpip': (
454
accept: AcceptTcpip,
455
reject: RejectTcpip,
456
info: TcpipInfo
457
) => void;
458
459
/**
460
* Emitted for OpenSSH streamlocal connection requests
461
*/
462
'openssh.streamlocal': (
463
accept: AcceptStreamLocal,
464
reject: RejectStreamLocal,
465
info: StreamLocalInfo
466
) => void;
467
468
/**
469
* Emitted for global requests
470
*/
471
'request': (
472
accept: AcceptRequest,
473
reject: RejectRequest,
474
name: string,
475
info: RequestInfo
476
) => void;
477
478
/**
479
* Emitted when key re-exchange is initiated
480
*/
481
'rekey': () => void;
482
483
/**
484
* Emitted when client connection ends
485
*/
486
'end': () => void;
487
488
/**
489
* Emitted when client connection is closed
490
*/
491
'close': () => void;
492
493
/**
494
* Emitted when client connection error occurs
495
*/
496
'error': (err: Error) => void;
497
}
498
```
499
500
## Type Definitions
501
502
### Server Channel Types
503
504
```javascript { .api }
505
interface ServerChannel extends Duplex {
506
stderr: Writable;
507
508
/**
509
* Send exit status to client
510
*/
511
exit(status: number): boolean;
512
513
/**
514
* Send signal to client
515
*/
516
signal(signal: string): boolean;
517
518
/**
519
* Close the channel
520
*/
521
close(): boolean;
522
523
/**
524
* Set window size
525
*/
526
setWindow(rows: number, cols: number, height?: number, width?: number): boolean;
527
}
528
```
529
530
### Connection Request Types
531
532
```javascript { .api }
533
interface TcpipInfo {
534
srcIP: string;
535
srcPort: number;
536
destIP: string;
537
destPort: number;
538
}
539
540
interface StreamLocalInfo {
541
socketPath: string;
542
}
543
544
interface RequestInfo {
545
bindAddr?: string;
546
bindPort?: number;
547
socketPath?: string;
548
[key: string]: any;
549
}
550
551
type AcceptTcpip = () => ServerChannel;
552
type RejectTcpip = () => boolean;
553
type AcceptStreamLocal = () => ServerChannel;
554
type RejectStreamLocal = () => boolean;
555
type AcceptRequest = () => void;
556
type RejectRequest = () => boolean;
557
```
558
559
### Keyboard Interactive Types
560
561
```javascript { .api }
562
interface KeyboardPrompt {
563
prompt: string;
564
echo: boolean;
565
}
566
```