typescript-virtual-container 1.2.8 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +0 -1
- package/README.md +462 -44
- package/biome.json +7 -0
- package/dist/SSHMimic/exec.d.ts.map +1 -1
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +35 -21
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +20 -6
- package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
- package/dist/VirtualFileSystem/binaryPack.js +29 -6
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +36 -13
- package/dist/VirtualPackageManager/index.d.ts +202 -0
- package/dist/VirtualPackageManager/index.d.ts.map +1 -0
- package/dist/VirtualPackageManager/index.js +825 -0
- package/dist/VirtualShell/index.d.ts +93 -12
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +95 -13
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +3 -1
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.d.ts +52 -20
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +54 -20
- package/dist/commands/adduser.d.ts +6 -0
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +6 -0
- package/dist/commands/alias.d.ts +9 -0
- package/dist/commands/alias.d.ts.map +1 -0
- package/dist/commands/alias.js +63 -0
- package/dist/commands/apt.d.ts +9 -0
- package/dist/commands/apt.d.ts.map +1 -0
- package/dist/commands/apt.js +205 -0
- package/dist/commands/awk.d.ts +11 -0
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +15 -2
- package/dist/commands/base64.d.ts +5 -0
- package/dist/commands/base64.d.ts.map +1 -1
- package/dist/commands/base64.js +9 -1
- package/dist/commands/cat.d.ts +5 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +35 -8
- package/dist/commands/cd.d.ts +5 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +5 -0
- package/dist/commands/chmod.d.ts +5 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +57 -3
- package/dist/commands/command-helpers.d.ts +78 -4
- package/dist/commands/command-helpers.d.ts.map +1 -1
- package/dist/commands/command-helpers.js +78 -4
- package/dist/commands/cp.d.ts +5 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +5 -0
- package/dist/commands/curl.d.ts +5 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +106 -26
- package/dist/commands/cut.d.ts +5 -0
- package/dist/commands/cut.d.ts.map +1 -1
- package/dist/commands/cut.js +8 -1
- package/dist/commands/date.d.ts +5 -0
- package/dist/commands/date.d.ts.map +1 -1
- package/dist/commands/date.js +7 -1
- package/dist/commands/declare.d.ts +3 -0
- package/dist/commands/declare.d.ts.map +1 -0
- package/dist/commands/declare.js +39 -0
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +5 -0
- package/dist/commands/dpkg.d.ts +9 -0
- package/dist/commands/dpkg.d.ts.map +1 -0
- package/dist/commands/dpkg.js +161 -0
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +8 -2
- package/dist/commands/echo.d.ts +5 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +33 -12
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +11 -1
- package/dist/commands/exit.d.ts +5 -0
- package/dist/commands/exit.d.ts.map +1 -1
- package/dist/commands/exit.js +12 -2
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +3 -1
- package/dist/commands/find.d.ts +5 -0
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +5 -0
- package/dist/commands/free.d.ts +8 -0
- package/dist/commands/free.d.ts.map +1 -0
- package/dist/commands/free.js +43 -0
- package/dist/commands/grep.d.ts +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +12 -2
- package/dist/commands/gzip.d.ts +5 -0
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +18 -2
- package/dist/commands/head.d.ts +5 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +5 -0
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +98 -45
- package/dist/commands/helpers.d.ts +3 -0
- package/dist/commands/helpers.d.ts.map +1 -1
- package/dist/commands/helpers.js +3 -0
- package/dist/commands/history.d.ts +8 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +26 -0
- package/dist/commands/hostname.d.ts +5 -0
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +5 -0
- package/dist/commands/id.d.ts.map +1 -1
- package/dist/commands/id.js +4 -1
- package/dist/commands/index.d.ts +2 -10
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -231
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +6 -3
- package/dist/commands/lsb-release.d.ts +3 -0
- package/dist/commands/lsb-release.d.ts.map +1 -0
- package/dist/commands/lsb-release.js +56 -0
- package/dist/commands/man.d.ts +3 -0
- package/dist/commands/man.d.ts.map +1 -0
- package/dist/commands/man.js +155 -0
- package/dist/commands/nano.js +1 -1
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +6 -1
- package/dist/commands/node.d.ts +9 -0
- package/dist/commands/node.d.ts.map +1 -0
- package/dist/commands/node.js +316 -0
- package/dist/commands/npm.d.ts +19 -0
- package/dist/commands/npm.d.ts.map +1 -0
- package/dist/commands/npm.js +109 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +7 -2
- package/dist/commands/printf.d.ts +3 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +113 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +30 -6
- package/dist/commands/python.d.ts +30 -0
- package/dist/commands/python.d.ts.map +1 -0
- package/dist/commands/python.js +2058 -0
- package/dist/commands/read.d.ts +3 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +34 -0
- package/dist/commands/registry.d.ts +8 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +229 -0
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -0
- package/dist/commands/runtime.js +280 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +11 -3
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +9 -3
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +69 -30
- package/dist/commands/shift.d.ts +5 -0
- package/dist/commands/shift.d.ts.map +1 -0
- package/dist/commands/shift.js +52 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +4 -2
- package/dist/commands/source.d.ts +3 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +34 -0
- package/dist/commands/sudo.js +1 -1
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +11 -3
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +8 -6
- package/dist/commands/test.d.ts +3 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +114 -0
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +3 -1
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +14 -0
- package/dist/commands/type.d.ts +3 -0
- package/dist/commands/type.d.ts.map +1 -0
- package/dist/commands/type.js +34 -0
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +4 -1
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uptime.d.ts +3 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +43 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +92 -96
- package/dist/commands/which.d.ts +3 -0
- package/dist/commands/which.d.ts.map +1 -0
- package/dist/commands/which.js +32 -0
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +1 -1
- package/dist/index.d.ts +15 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/modules/linuxRootfs.d.ts +41 -0
- package/dist/modules/linuxRootfs.d.ts.map +1 -0
- package/dist/modules/linuxRootfs.js +440 -0
- package/dist/modules/neofetch.d.ts.map +1 -1
- package/dist/modules/neofetch.js +1 -0
- package/dist/standalone-wo-sftp.d.ts +2 -0
- package/dist/standalone-wo-sftp.d.ts.map +1 -0
- package/dist/standalone-wo-sftp.js +30 -0
- package/dist/utils/expand.d.ts +50 -0
- package/dist/utils/expand.d.ts.map +1 -0
- package/dist/utils/expand.js +183 -0
- package/dist/utils/vfsDiff.d.ts +90 -0
- package/dist/utils/vfsDiff.d.ts.map +1 -0
- package/dist/utils/vfsDiff.js +177 -0
- package/package.json +3 -1
- package/src/SSHMimic/exec.ts +10 -1
- package/src/SSHMimic/executor.ts +105 -21
- package/src/SSHMimic/index.ts +49 -15
- package/src/VirtualFileSystem/binaryPack.ts +35 -8
- package/src/VirtualFileSystem/index.ts +78 -28
- package/src/VirtualPackageManager/index.ts +979 -0
- package/src/VirtualShell/index.ts +133 -14
- package/src/VirtualShell/shell.ts +23 -3
- package/src/VirtualShell/shellParser.ts +134 -36
- package/src/VirtualUserManager/index.ts +62 -22
- package/src/commands/adduser.ts +6 -0
- package/src/commands/alias.ts +64 -0
- package/src/commands/apt.ts +228 -0
- package/src/commands/awk.ts +20 -6
- package/src/commands/base64.ts +13 -2
- package/src/commands/cat.ts +40 -8
- package/src/commands/cd.ts +5 -0
- package/src/commands/chmod.ts +53 -3
- package/src/commands/command-helpers.ts +78 -4
- package/src/commands/cp.ts +5 -0
- package/src/commands/curl.ts +118 -33
- package/src/commands/cut.ts +8 -1
- package/src/commands/date.ts +7 -1
- package/src/commands/declare.ts +44 -0
- package/src/commands/diff.ts +17 -3
- package/src/commands/dpkg.ts +180 -0
- package/src/commands/du.ts +17 -5
- package/src/commands/echo.ts +41 -12
- package/src/commands/env.ts +11 -1
- package/src/commands/exit.ts +12 -2
- package/src/commands/export.ts +3 -1
- package/src/commands/find.ts +5 -0
- package/src/commands/free.ts +47 -0
- package/src/commands/grep.ts +12 -2
- package/src/commands/gzip.ts +28 -4
- package/src/commands/head.ts +5 -0
- package/src/commands/help.ts +121 -47
- package/src/commands/helpers.ts +8 -0
- package/src/commands/history.ts +34 -0
- package/src/commands/hostname.ts +5 -0
- package/src/commands/id.ts +4 -1
- package/src/commands/index.ts +9 -255
- package/src/commands/ls.ts +6 -3
- package/src/commands/lsb-release.ts +58 -0
- package/src/commands/man.ts +166 -0
- package/src/commands/nano.ts +1 -1
- package/src/commands/neofetch.ts +6 -1
- package/src/commands/node.ts +341 -0
- package/src/commands/npm.ts +132 -0
- package/src/commands/ping.ts +10 -3
- package/src/commands/printf.ts +112 -0
- package/src/commands/ps.ts +40 -6
- package/src/commands/python.ts +2229 -0
- package/src/commands/read.ts +41 -0
- package/src/commands/registry.ts +244 -0
- package/src/commands/runtime.ts +353 -0
- package/src/commands/sed.ts +27 -9
- package/src/commands/set.ts +9 -3
- package/src/commands/sh.ts +170 -44
- package/src/commands/shift.ts +53 -0
- package/src/commands/sleep.ts +2 -1
- package/src/commands/sort.ts +10 -6
- package/src/commands/source.ts +47 -0
- package/src/commands/sudo.ts +1 -1
- package/src/commands/tar.ts +28 -7
- package/src/commands/tee.ts +7 -1
- package/src/commands/test.ts +135 -0
- package/src/commands/tr.ts +3 -1
- package/src/commands/true.ts +17 -0
- package/src/commands/type.ts +43 -0
- package/src/commands/uname.ts +5 -1
- package/src/commands/uniq.ts +8 -2
- package/src/commands/uptime.ts +49 -0
- package/src/commands/wget.ts +105 -119
- package/src/commands/which.ts +37 -0
- package/src/commands/xargs.ts +11 -2
- package/src/index.ts +27 -18
- package/src/modules/linuxRootfs.ts +642 -0
- package/src/modules/neofetch.ts +1 -0
- package/src/standalone-wo-sftp.ts +38 -0
- package/src/utils/expand.ts +238 -0
- package/src/utils/vfsDiff.ts +275 -0
- package/standalone-wo-sftp.js +507 -0
- package/standalone-wo-sftp.js.map +7 -0
- package/standalone.js +486 -109
- package/standalone.js.map +4 -4
- package/tests/bun-test-shim.ts +9 -1
- package/tests/command-helpers.test.ts +1 -5
- package/tests/new-features.test.ts +1036 -0
- package/tests/parser-executor.test.ts +27 -27
- package/tests/sftp.test.ts +122 -42
- package/tests/users.test.ts +23 -5
- package/CHANGELOG.md +0 -150
|
@@ -1,16 +1,42 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import { createCustomCommand, registerCommand
|
|
2
|
+
import { createCustomCommand, registerCommand } from "../commands/registry";
|
|
3
|
+
import { runCommand } from "../commands/runtime";
|
|
4
|
+
import {
|
|
5
|
+
bootstrapLinuxRootfs,
|
|
6
|
+
refreshProc,
|
|
7
|
+
syncEtcPasswd,
|
|
8
|
+
} from "../modules/linuxRootfs";
|
|
3
9
|
import type { CommandContext, CommandResult } from "../types/commands";
|
|
4
10
|
import type { ShellStream } from "../types/streams";
|
|
5
11
|
import type { PerfLogger } from "../utils/perfLogger";
|
|
6
12
|
import { createPerfLogger } from "../utils/perfLogger";
|
|
7
13
|
import VirtualFileSystem, { type VfsOptions } from "../VirtualFileSystem";
|
|
14
|
+
import { VirtualPackageManager } from "../VirtualPackageManager";
|
|
8
15
|
import { VirtualUserManager } from "../VirtualUserManager";
|
|
9
16
|
import { startShell } from "./shell";
|
|
10
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Virtual machine identity strings surfaced by system-info commands
|
|
20
|
+
* (`uname`, `neofetch`, `lsb_release`, `/proc/version`, `/etc/os-release`).
|
|
21
|
+
*
|
|
22
|
+
* Pass this as the second argument to `new VirtualShell()` to customise the
|
|
23
|
+
* distro name, kernel version, and CPU architecture reported inside the shell.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const shell = new VirtualShell("my-vm", {
|
|
28
|
+
* kernel: "6.1.0+custom-amd64",
|
|
29
|
+
* os: "Acme GNU/Linux x64",
|
|
30
|
+
* arch: "x86_64",
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
11
34
|
export interface ShellProperties {
|
|
35
|
+
/** Kernel version string (e.g. `"1.0.0+itsrealfortune+1-amd64"`). */
|
|
12
36
|
kernel: string;
|
|
37
|
+
/** Full OS description (e.g. `"Fortune GNU/Linux x64"`). */
|
|
13
38
|
os: string;
|
|
39
|
+
/** CPU architecture label (e.g. `"x86_64"`, `"aarch64"`). */
|
|
14
40
|
arch: string;
|
|
15
41
|
}
|
|
16
42
|
|
|
@@ -32,16 +58,40 @@ function resolveAutoSudoForNewUsers(): boolean {
|
|
|
32
58
|
}
|
|
33
59
|
|
|
34
60
|
/**
|
|
35
|
-
* Coordinates the virtual filesystem, user manager,
|
|
61
|
+
* Coordinates the virtual filesystem, user manager, package manager, and
|
|
62
|
+
* command runtime for a single isolated shell environment.
|
|
36
63
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
64
|
+
* Each instance owns its own VFS tree, user database, package registry, and
|
|
65
|
+
* session state — multiple instances are fully independent.
|
|
66
|
+
*
|
|
67
|
+
* Instances are consumed both by the SSH/SFTP server facades and directly via
|
|
68
|
+
* the programmatic `SshClient` API.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const shell = new VirtualShell("my-vm");
|
|
73
|
+
* await shell.ensureInitialized();
|
|
74
|
+
* const client = new SshClient(shell, "root");
|
|
75
|
+
* const result = await client.exec("uname -a");
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @fires VirtualShell#initialized Emitted once the VFS and users are ready.
|
|
79
|
+
* @fires VirtualShell#command Emitted after every command execution.
|
|
80
|
+
* @fires VirtualShell#session:start Emitted when an interactive session opens.
|
|
39
81
|
*/
|
|
40
82
|
class VirtualShell extends EventEmitter {
|
|
83
|
+
/** Backing virtual filesystem — use for direct path operations. */
|
|
41
84
|
vfs: VirtualFileSystem;
|
|
85
|
+
/** Virtual user database — use for auth, quotas, and session tracking. */
|
|
42
86
|
users: VirtualUserManager;
|
|
87
|
+
/** APT/dpkg package manager backed by the built-in package registry. */
|
|
88
|
+
packageManager: VirtualPackageManager;
|
|
89
|
+
/** Hostname shown in the shell prompt and SSH ident string. */
|
|
43
90
|
hostname: string;
|
|
91
|
+
/** Distro identity strings surfaced by `uname`, `neofetch`, etc. */
|
|
44
92
|
properties: ShellProperties;
|
|
93
|
+
/** Unix ms timestamp of shell creation — used by `uptime` and `/proc/uptime`. */
|
|
94
|
+
startTime: number;
|
|
45
95
|
private initialized: Promise<void>;
|
|
46
96
|
|
|
47
97
|
/**
|
|
@@ -60,17 +110,27 @@ class VirtualShell extends EventEmitter {
|
|
|
60
110
|
perf.mark("constructor");
|
|
61
111
|
this.hostname = hostname;
|
|
62
112
|
this.properties = properties || defaultShellProperties;
|
|
113
|
+
this.startTime = Date.now();
|
|
63
114
|
this.vfs = new VirtualFileSystem(vfsOptions ?? {});
|
|
64
115
|
this.users = new VirtualUserManager(this.vfs, resolveAutoSudoForNewUsers());
|
|
116
|
+
this.packageManager = new VirtualPackageManager(this.vfs, this.users);
|
|
65
117
|
|
|
66
118
|
// Store references to avoid TypeScript "used before assigned" errors
|
|
67
119
|
const vfs = this.vfs;
|
|
68
120
|
const users = this.users;
|
|
121
|
+
const pm = this.packageManager;
|
|
122
|
+
const shellProps = this.properties;
|
|
123
|
+
const shellHostname = this.hostname;
|
|
124
|
+
const startTime = this.startTime;
|
|
69
125
|
|
|
70
126
|
// Initialize both VFS mirror and users, ensuring all is ready before auth
|
|
71
127
|
this.initialized = (async () => {
|
|
72
128
|
await vfs.restoreMirror();
|
|
73
129
|
await users.initialize();
|
|
130
|
+
// Bootstrap Linux rootfs (idempotent)
|
|
131
|
+
bootstrapLinuxRootfs(vfs, users, shellHostname, shellProps, startTime);
|
|
132
|
+
// Load installed packages from dpkg status
|
|
133
|
+
pm.load();
|
|
74
134
|
this.emit("initialized");
|
|
75
135
|
})();
|
|
76
136
|
}
|
|
@@ -105,11 +165,16 @@ class VirtualShell extends EventEmitter {
|
|
|
105
165
|
}
|
|
106
166
|
|
|
107
167
|
/**
|
|
108
|
-
* Executes a command line string
|
|
168
|
+
* Executes a raw command line string programmatically.
|
|
169
|
+
*
|
|
170
|
+
* Supports the full shell operator set (`&&`, `||`, `;`, `|`, `>`, `<`,
|
|
171
|
+
* `$(cmd)`) and alias expansion. The result is emitted via the
|
|
172
|
+
* `"command"` event but not returned — use `SshClient.exec()` for a
|
|
173
|
+
* result-returning wrapper.
|
|
109
174
|
*
|
|
110
|
-
* @param rawInput
|
|
111
|
-
* @param authUser
|
|
112
|
-
* @param cwd
|
|
175
|
+
* @param rawInput Unparsed command line (e.g. `"ls -la /tmp"`).
|
|
176
|
+
* @param authUser Username to run the command as.
|
|
177
|
+
* @param cwd Current working directory for path resolution.
|
|
113
178
|
*/
|
|
114
179
|
executeCommand(rawInput: string, authUser: string, cwd: string): void {
|
|
115
180
|
perf.mark("executeCommand");
|
|
@@ -118,14 +183,19 @@ class VirtualShell extends EventEmitter {
|
|
|
118
183
|
}
|
|
119
184
|
|
|
120
185
|
/**
|
|
121
|
-
*
|
|
186
|
+
* Attaches an interactive PTY session to this shell instance.
|
|
122
187
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
188
|
+
* Called internally by `SshMimic` when a client opens a shell channel.
|
|
189
|
+
* The session reads from `stream` (user keystrokes) and writes back ANSI
|
|
190
|
+
* output. History, `.bashrc` sourcing, and Ctrl+W/Ctrl+U line editing are
|
|
191
|
+
* handled automatically.
|
|
192
|
+
*
|
|
193
|
+
* @param stream Bidirectional SSH channel stream.
|
|
194
|
+
* @param authUser Authenticated username bound to this session.
|
|
195
|
+
* @param sessionId Stable session UUID (used for `who` output), or `null`.
|
|
196
|
+
* @param remoteAddress IP or hostname of the connecting client.
|
|
197
|
+
* @param terminalSize Initial terminal dimensions in columns and rows.
|
|
127
198
|
*/
|
|
128
|
-
|
|
129
199
|
startInteractiveSession(
|
|
130
200
|
stream: ShellStream,
|
|
131
201
|
authUser: string,
|
|
@@ -146,6 +216,55 @@ class VirtualShell extends EventEmitter {
|
|
|
146
216
|
terminalSize,
|
|
147
217
|
this,
|
|
148
218
|
);
|
|
219
|
+
// Refresh /proc/<pid> and /proc/self after session is registered
|
|
220
|
+
this.refreshProcSessions();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Refreshes the `/proc` virtual filesystem with current system state.
|
|
225
|
+
*
|
|
226
|
+
* Updates `/proc/uptime`, `/proc/meminfo`, `/proc/cpuinfo`,
|
|
227
|
+
* `/proc/version`, `/proc/loadavg`, `/proc/self`, and per-session
|
|
228
|
+
* `/proc/<pid>` entries from live session and host data.
|
|
229
|
+
*
|
|
230
|
+
* Called automatically during `bootstrapLinuxRootfs`. Call again before
|
|
231
|
+
* reading `/proc` files for up-to-date values.
|
|
232
|
+
*/
|
|
233
|
+
public refreshProcFs(): void {
|
|
234
|
+
refreshProc(
|
|
235
|
+
this.vfs,
|
|
236
|
+
this.properties,
|
|
237
|
+
this.hostname,
|
|
238
|
+
this.startTime,
|
|
239
|
+
this.users.listActiveSessions(),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Updates only the session-dependent `/proc` entries (`/proc/<pid>`,
|
|
245
|
+
* `/proc/self`). Cheaper than a full `refreshProcFs()` — call this
|
|
246
|
+
* whenever a session is registered or unregistered.
|
|
247
|
+
*/
|
|
248
|
+
public refreshProcSessions(): void {
|
|
249
|
+
refreshProc(
|
|
250
|
+
this.vfs,
|
|
251
|
+
this.properties,
|
|
252
|
+
this.hostname,
|
|
253
|
+
this.startTime,
|
|
254
|
+
this.users.listActiveSessions(),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Syncs `/etc/passwd`, `/etc/group`, and `/etc/shadow` from the current
|
|
260
|
+
* `VirtualUserManager` state.
|
|
261
|
+
*
|
|
262
|
+
* Called automatically during `bootstrapLinuxRootfs`. Call again after
|
|
263
|
+
* `users.addUser()`, `users.deleteUser()`, or `users.addSudoer()` to keep
|
|
264
|
+
* the classic Unix credential files in sync with the user manager.
|
|
265
|
+
*/
|
|
266
|
+
public syncPasswd(): void {
|
|
267
|
+
syncEtcPasswd(this.vfs, this.users);
|
|
149
268
|
}
|
|
150
269
|
|
|
151
270
|
/**
|
|
@@ -73,9 +73,20 @@ export function startShell(
|
|
|
73
73
|
for (const line of bashrc.split("\n")) {
|
|
74
74
|
const l = line.trim();
|
|
75
75
|
if (!l || l.startsWith("#")) continue;
|
|
76
|
-
await runCommand(
|
|
76
|
+
await runCommand(
|
|
77
|
+
l,
|
|
78
|
+
authUser,
|
|
79
|
+
hostname,
|
|
80
|
+
"shell",
|
|
81
|
+
cwd,
|
|
82
|
+
shell,
|
|
83
|
+
undefined,
|
|
84
|
+
shellEnv,
|
|
85
|
+
);
|
|
77
86
|
}
|
|
78
|
-
} catch {
|
|
87
|
+
} catch {
|
|
88
|
+
/* ignore bashrc errors */
|
|
89
|
+
}
|
|
79
90
|
}
|
|
80
91
|
})();
|
|
81
92
|
|
|
@@ -585,7 +596,16 @@ export function startShell(
|
|
|
585
596
|
|
|
586
597
|
if (line.length > 0) {
|
|
587
598
|
const result = await Promise.resolve(
|
|
588
|
-
runCommand(
|
|
599
|
+
runCommand(
|
|
600
|
+
line,
|
|
601
|
+
authUser,
|
|
602
|
+
hostname,
|
|
603
|
+
"shell",
|
|
604
|
+
cwd,
|
|
605
|
+
shell,
|
|
606
|
+
undefined,
|
|
607
|
+
shellEnv,
|
|
608
|
+
),
|
|
589
609
|
);
|
|
590
610
|
|
|
591
611
|
pushHistory(line);
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Pipeline,
|
|
3
|
+
PipelineCommand,
|
|
4
|
+
Script,
|
|
5
|
+
Statement,
|
|
6
|
+
LogicalOp,
|
|
7
|
+
} from "../types/pipeline";
|
|
2
8
|
|
|
3
9
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
4
10
|
|
|
@@ -56,8 +62,9 @@ export function expandToken(
|
|
|
56
62
|
token = token.replace(/\$#/g, "0");
|
|
57
63
|
|
|
58
64
|
// ${VAR:-default} and ${VAR:+value}
|
|
59
|
-
token = token.replace(
|
|
60
|
-
|
|
65
|
+
token = token.replace(
|
|
66
|
+
/\$\{([^}:]+):-([^}]*)\}/g,
|
|
67
|
+
(_, name, def) => env[name] ?? def,
|
|
61
68
|
);
|
|
62
69
|
token = token.replace(/\$\{([^}:]+):\+([^}]*)\}/g, (_, name, val) =>
|
|
63
70
|
env[name] ? val : "",
|
|
@@ -67,8 +74,9 @@ export function expandToken(
|
|
|
67
74
|
token = token.replace(/\$\{([^}]+)\}/g, (_, name) => env[name] ?? "");
|
|
68
75
|
|
|
69
76
|
// $VAR (greedy: match longest valid identifier)
|
|
70
|
-
token = token.replace(
|
|
71
|
-
|
|
77
|
+
token = token.replace(
|
|
78
|
+
/\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
79
|
+
(_, name) => env[name] ?? "",
|
|
72
80
|
);
|
|
73
81
|
|
|
74
82
|
return token;
|
|
@@ -120,7 +128,10 @@ function parseStatements(input: string): Statement[] {
|
|
|
120
128
|
return statements;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
interface Segment {
|
|
131
|
+
interface Segment {
|
|
132
|
+
text: string;
|
|
133
|
+
op?: LogicalOp;
|
|
134
|
+
}
|
|
124
135
|
|
|
125
136
|
function splitByLogicalOps(input: string): Segment[] {
|
|
126
137
|
const segments: Segment[] = [];
|
|
@@ -139,19 +150,61 @@ function splitByLogicalOps(input: string): Segment[] {
|
|
|
139
150
|
const ch = input[i]!;
|
|
140
151
|
const ch2 = input.slice(i, i + 2);
|
|
141
152
|
|
|
142
|
-
if ((ch === '"' || ch === "'") && !inQ) {
|
|
143
|
-
|
|
144
|
-
|
|
153
|
+
if ((ch === '"' || ch === "'") && !inQ) {
|
|
154
|
+
inQ = true;
|
|
155
|
+
qChar = ch;
|
|
156
|
+
current += ch;
|
|
157
|
+
i++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (inQ && ch === qChar) {
|
|
161
|
+
inQ = false;
|
|
162
|
+
current += ch;
|
|
163
|
+
i++;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (inQ) {
|
|
167
|
+
current += ch;
|
|
168
|
+
i++;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
145
171
|
|
|
146
|
-
if (ch === "(") {
|
|
147
|
-
|
|
148
|
-
|
|
172
|
+
if (ch === "(") {
|
|
173
|
+
depth++;
|
|
174
|
+
current += ch;
|
|
175
|
+
i++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (ch === ")") {
|
|
179
|
+
depth--;
|
|
180
|
+
current += ch;
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (depth > 0) {
|
|
185
|
+
current += ch;
|
|
186
|
+
i++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
149
189
|
|
|
150
|
-
if (ch2 === "&&") {
|
|
151
|
-
|
|
152
|
-
|
|
190
|
+
if (ch2 === "&&") {
|
|
191
|
+
flush("&&");
|
|
192
|
+
i += 2;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (ch2 === "||") {
|
|
196
|
+
flush("||");
|
|
197
|
+
i += 2;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (ch === ";") {
|
|
201
|
+
flush(";");
|
|
202
|
+
i++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
153
205
|
|
|
154
|
-
current += ch;
|
|
206
|
+
current += ch;
|
|
207
|
+
i++;
|
|
155
208
|
}
|
|
156
209
|
flush();
|
|
157
210
|
return segments;
|
|
@@ -170,13 +223,26 @@ function splitByPipe(input: string): string[] {
|
|
|
170
223
|
|
|
171
224
|
for (let i = 0; i < input.length; i++) {
|
|
172
225
|
const ch = input[i]!;
|
|
173
|
-
if ((ch === '"' || ch === "'") && !inQ) {
|
|
174
|
-
|
|
175
|
-
|
|
226
|
+
if ((ch === '"' || ch === "'") && !inQ) {
|
|
227
|
+
inQ = true;
|
|
228
|
+
qChar = ch;
|
|
229
|
+
current += ch;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (inQ && ch === qChar) {
|
|
233
|
+
inQ = false;
|
|
234
|
+
current += ch;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (inQ) {
|
|
238
|
+
current += ch;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
176
241
|
|
|
177
242
|
// || was already consumed at statement level, bare | is pipe
|
|
178
243
|
if (ch === "|" && input[i + 1] !== "|") {
|
|
179
|
-
if (!current.trim())
|
|
244
|
+
if (!current.trim())
|
|
245
|
+
throw new Error("Syntax error near unexpected token '|'");
|
|
180
246
|
tokens.push(current.trim());
|
|
181
247
|
current = "";
|
|
182
248
|
} else {
|
|
@@ -185,7 +251,8 @@ function splitByPipe(input: string): string[] {
|
|
|
185
251
|
}
|
|
186
252
|
|
|
187
253
|
const tail = current.trim();
|
|
188
|
-
if (!tail && tokens.length > 0)
|
|
254
|
+
if (!tail && tokens.length > 0)
|
|
255
|
+
throw new Error("Syntax error near unexpected token '|'");
|
|
189
256
|
if (tail) tokens.push(tail);
|
|
190
257
|
return tokens;
|
|
191
258
|
}
|
|
@@ -204,19 +271,27 @@ function parseCommandWithRedirections(token: string): PipelineCommand {
|
|
|
204
271
|
const part = parts[i]!;
|
|
205
272
|
if (part === "<") {
|
|
206
273
|
i++;
|
|
207
|
-
if (i >= parts.length)
|
|
274
|
+
if (i >= parts.length)
|
|
275
|
+
throw new Error("Syntax error: expected filename after <");
|
|
208
276
|
inputFile = parts[i];
|
|
209
277
|
i++;
|
|
210
278
|
} else if (part === ">>") {
|
|
211
279
|
i++;
|
|
212
|
-
if (i >= parts.length)
|
|
213
|
-
|
|
280
|
+
if (i >= parts.length)
|
|
281
|
+
throw new Error("Syntax error: expected filename after >>");
|
|
282
|
+
outputFile = parts[i];
|
|
283
|
+
appendOutput = true;
|
|
284
|
+
i++;
|
|
214
285
|
} else if (part === ">") {
|
|
215
286
|
i++;
|
|
216
|
-
if (i >= parts.length)
|
|
217
|
-
|
|
287
|
+
if (i >= parts.length)
|
|
288
|
+
throw new Error("Syntax error: expected filename after >");
|
|
289
|
+
outputFile = parts[i];
|
|
290
|
+
appendOutput = false;
|
|
291
|
+
i++;
|
|
218
292
|
} else {
|
|
219
|
-
cmdParts.push(part);
|
|
293
|
+
cmdParts.push(part);
|
|
294
|
+
i++;
|
|
220
295
|
}
|
|
221
296
|
}
|
|
222
297
|
|
|
@@ -236,26 +311,49 @@ function tokenizeCommand(input: string): string[] {
|
|
|
236
311
|
const next = input[i + 1];
|
|
237
312
|
|
|
238
313
|
if ((ch === '"' || ch === "'") && !inQ) {
|
|
239
|
-
inQ = true;
|
|
314
|
+
inQ = true;
|
|
315
|
+
qChar = ch;
|
|
316
|
+
i++;
|
|
317
|
+
continue;
|
|
240
318
|
}
|
|
241
319
|
if (inQ && ch === qChar) {
|
|
242
|
-
inQ = false;
|
|
320
|
+
inQ = false;
|
|
321
|
+
qChar = "";
|
|
322
|
+
i++;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (inQ) {
|
|
326
|
+
current += ch;
|
|
327
|
+
i++;
|
|
328
|
+
continue;
|
|
243
329
|
}
|
|
244
|
-
if (inQ) { current += ch; i++; continue; }
|
|
245
330
|
|
|
246
331
|
if (ch === " ") {
|
|
247
|
-
if (current) {
|
|
248
|
-
|
|
332
|
+
if (current) {
|
|
333
|
+
tokens.push(current);
|
|
334
|
+
current = "";
|
|
335
|
+
}
|
|
336
|
+
i++;
|
|
337
|
+
continue;
|
|
249
338
|
}
|
|
250
339
|
|
|
251
340
|
if ((ch === ">" || ch === "<") && !inQ) {
|
|
252
|
-
if (current) {
|
|
253
|
-
|
|
254
|
-
|
|
341
|
+
if (current) {
|
|
342
|
+
tokens.push(current);
|
|
343
|
+
current = "";
|
|
344
|
+
}
|
|
345
|
+
if (ch === ">" && next === ">") {
|
|
346
|
+
tokens.push(">>");
|
|
347
|
+
i += 2;
|
|
348
|
+
} else {
|
|
349
|
+
tokens.push(ch);
|
|
350
|
+
i++;
|
|
351
|
+
}
|
|
255
352
|
continue;
|
|
256
353
|
}
|
|
257
354
|
|
|
258
|
-
current += ch;
|
|
355
|
+
current += ch;
|
|
356
|
+
i++;
|
|
259
357
|
}
|
|
260
358
|
if (current) tokens.push(current);
|
|
261
359
|
return tokens;
|
|
@@ -290,10 +290,11 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
|
-
* Updates password for an existing user account.
|
|
293
|
+
* Updates the password for an existing user account.
|
|
294
294
|
*
|
|
295
295
|
* @param username Username to update.
|
|
296
|
-
* @param password New plaintext password.
|
|
296
|
+
* @param password New plaintext password (must be non-empty).
|
|
297
|
+
* @throws When the user does not exist or the password is empty.
|
|
297
298
|
*/
|
|
298
299
|
public async setPassword(username: string, password: string): Promise<void> {
|
|
299
300
|
perf.mark("setPassword");
|
|
@@ -309,9 +310,10 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
309
310
|
}
|
|
310
311
|
|
|
311
312
|
/**
|
|
312
|
-
* Deletes existing non-root user account.
|
|
313
|
+
* Deletes an existing non-root user account and revokes sudo access.
|
|
313
314
|
*
|
|
314
315
|
* @param username Username to remove.
|
|
316
|
+
* @throws When `username` is `"root"` or the user does not exist.
|
|
315
317
|
*/
|
|
316
318
|
public async deleteUser(username: string): Promise<void> {
|
|
317
319
|
perf.mark("deleteUser");
|
|
@@ -343,9 +345,10 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
343
345
|
}
|
|
344
346
|
|
|
345
347
|
/**
|
|
346
|
-
* Grants sudo
|
|
348
|
+
* Grants sudo privileges to an existing user.
|
|
347
349
|
*
|
|
348
350
|
* @param username Username to promote.
|
|
351
|
+
* @throws When the user does not exist.
|
|
349
352
|
*/
|
|
350
353
|
public async addSudoer(username: string): Promise<void> {
|
|
351
354
|
perf.mark("addSudoer");
|
|
@@ -359,9 +362,10 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
359
362
|
}
|
|
360
363
|
|
|
361
364
|
/**
|
|
362
|
-
* Revokes sudo
|
|
365
|
+
* Revokes sudo privileges from a user. Root cannot be demoted.
|
|
363
366
|
*
|
|
364
367
|
* @param username Username to demote.
|
|
368
|
+
* @throws When `username` is `"root"`.
|
|
365
369
|
*/
|
|
366
370
|
public async removeSudoer(username: string): Promise<void> {
|
|
367
371
|
perf.mark("removeSudoer");
|
|
@@ -375,11 +379,14 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
375
379
|
}
|
|
376
380
|
|
|
377
381
|
/**
|
|
378
|
-
* Registers active session and allocates
|
|
382
|
+
* Registers a new active session and allocates a virtual TTY identifier.
|
|
379
383
|
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
384
|
+
* Called by the SSH server when a client is authenticated. The returned
|
|
385
|
+
* descriptor is visible in `who` output and `listActiveSessions()`.
|
|
386
|
+
*
|
|
387
|
+
* @param username Authenticated username bound to the session.
|
|
388
|
+
* @param remoteAddress IP address or hostname of the connecting client.
|
|
389
|
+
* @returns The newly created `VirtualActiveSession` descriptor.
|
|
383
390
|
*/
|
|
384
391
|
public registerSession(
|
|
385
392
|
username: string,
|
|
@@ -403,9 +410,11 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
403
410
|
}
|
|
404
411
|
|
|
405
412
|
/**
|
|
406
|
-
*
|
|
413
|
+
* Removes an active session record when the connection closes.
|
|
414
|
+
*
|
|
415
|
+
* Safe to call with a `null` or `undefined` session ID — it will be a no-op.
|
|
407
416
|
*
|
|
408
|
-
* @param sessionId Session
|
|
417
|
+
* @param sessionId Session UUID returned by `registerSession()`, or nullish.
|
|
409
418
|
*/
|
|
410
419
|
public unregisterSession(sessionId: string | null | undefined): void {
|
|
411
420
|
perf.mark("unregisterSession");
|
|
@@ -425,11 +434,15 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
425
434
|
}
|
|
426
435
|
|
|
427
436
|
/**
|
|
428
|
-
* Updates username
|
|
437
|
+
* Updates the username and remote address metadata for an active session.
|
|
429
438
|
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
439
|
+
* Called internally by `su` and `sudo` when the effective user changes
|
|
440
|
+
* within a session. Silently ignored when the session ID is nullish or
|
|
441
|
+
* unknown.
|
|
442
|
+
*
|
|
443
|
+
* @param sessionId Session UUID to update, or nullish for no-op.
|
|
444
|
+
* @param username New effective username.
|
|
445
|
+
* @param remoteAddress New remote address (usually unchanged).
|
|
433
446
|
*/
|
|
434
447
|
public updateSession(
|
|
435
448
|
sessionId: string | null | undefined,
|
|
@@ -454,9 +467,11 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
454
467
|
}
|
|
455
468
|
|
|
456
469
|
/**
|
|
457
|
-
*
|
|
470
|
+
* Returns a snapshot of all currently active sessions, sorted by start time.
|
|
471
|
+
*
|
|
472
|
+
* Used by `who`, `ps`, `uptime`, and the `HoneyPot` auditor.
|
|
458
473
|
*
|
|
459
|
-
* @returns
|
|
474
|
+
* @returns Array of `VirtualActiveSession` descriptors.
|
|
460
475
|
*/
|
|
461
476
|
public listActiveSessions(): VirtualActiveSession[] {
|
|
462
477
|
perf.mark("listActiveSessions");
|
|
@@ -465,6 +480,15 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
465
480
|
);
|
|
466
481
|
}
|
|
467
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Returns a sorted list of all registered usernames.
|
|
485
|
+
*
|
|
486
|
+
* @returns Array of username strings sorted alphabetically.
|
|
487
|
+
*/
|
|
488
|
+
public listUsers(): string[] {
|
|
489
|
+
return Array.from(this.users.keys()).sort();
|
|
490
|
+
}
|
|
491
|
+
|
|
468
492
|
private loadFromVfs(): void {
|
|
469
493
|
this.users.clear();
|
|
470
494
|
|
|
@@ -610,6 +634,14 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
610
634
|
return record;
|
|
611
635
|
}
|
|
612
636
|
|
|
637
|
+
/**
|
|
638
|
+
* Returns `true` when the user has a non-empty password set.
|
|
639
|
+
*
|
|
640
|
+
* A user with no password (or whose password hash matches the empty-string
|
|
641
|
+
* hash) is allowed to authenticate without a credential check.
|
|
642
|
+
*
|
|
643
|
+
* @param username Target username.
|
|
644
|
+
*/
|
|
613
645
|
public hasPassword(username: string): boolean {
|
|
614
646
|
perf.mark("hasPassword");
|
|
615
647
|
if (this.getPasswordHash(username) === this.hashPassword("")) {
|
|
@@ -620,10 +652,13 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
620
652
|
}
|
|
621
653
|
|
|
622
654
|
/**
|
|
623
|
-
* Hashes plaintext password
|
|
655
|
+
* Hashes a plaintext password using scrypt (or SHA-256 in fast-hash mode).
|
|
624
656
|
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
657
|
+
* Set `SSH_MIMIC_FAST_PASSWORD_HASH=1` to switch to SHA-256 for test
|
|
658
|
+
* environments where scrypt latency is undesirable.
|
|
659
|
+
*
|
|
660
|
+
* @param password Plaintext password string.
|
|
661
|
+
* @returns Hex-encoded hash string.
|
|
627
662
|
*/
|
|
628
663
|
public hashPassword(password: string): string {
|
|
629
664
|
if (VirtualUserManager.fastPasswordHash) {
|
|
@@ -648,7 +683,10 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
648
683
|
throw new Error("invalid password");
|
|
649
684
|
}
|
|
650
685
|
}
|
|
651
|
-
private readonly authorizedKeys = new Map<
|
|
686
|
+
private readonly authorizedKeys = new Map<
|
|
687
|
+
string,
|
|
688
|
+
Array<{ algo: string; data: Buffer }>
|
|
689
|
+
>();
|
|
652
690
|
|
|
653
691
|
/**
|
|
654
692
|
* Adds an SSH public key for a user, enabling public-key authentication.
|
|
@@ -681,7 +719,9 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
681
719
|
*
|
|
682
720
|
* @param username Target user.
|
|
683
721
|
*/
|
|
684
|
-
public getAuthorizedKeys(
|
|
722
|
+
public getAuthorizedKeys(
|
|
723
|
+
username: string,
|
|
724
|
+
): Array<{ algo: string; data: Buffer }> {
|
|
685
725
|
return this.authorizedKeys.get(username) ?? [];
|
|
686
726
|
}
|
|
687
727
|
}
|