typescript-virtual-container 1.2.5 → 1.2.6
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/README.md +387 -193
- package/benchmark-results.txt +21 -21
- package/dist/SSHMimic/exec.js +2 -2
- package/dist/SSHMimic/executor.d.ts +6 -7
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +77 -60
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +6 -20
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +14 -0
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +13 -36
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +19 -2
- package/dist/VirtualShell/shellParser.d.ts +20 -2
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualShell/shellParser.js +229 -120
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +2 -0
- package/dist/commands/awk.d.ts +3 -0
- package/dist/commands/awk.d.ts.map +1 -0
- package/dist/commands/awk.js +29 -0
- package/dist/commands/base64.d.ts +3 -0
- package/dist/commands/base64.d.ts.map +1 -0
- package/dist/commands/base64.js +20 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +2 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +2 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +2 -0
- package/dist/commands/clear.d.ts.map +1 -1
- package/dist/commands/clear.js +4 -1
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +2 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +2 -0
- package/dist/commands/cut.d.ts +3 -0
- package/dist/commands/cut.d.ts.map +1 -0
- package/dist/commands/cut.js +27 -0
- package/dist/commands/date.d.ts +3 -0
- package/dist/commands/date.d.ts.map +1 -0
- package/dist/commands/date.js +22 -0
- package/dist/commands/deluser.d.ts.map +1 -1
- package/dist/commands/deluser.js +2 -0
- package/dist/commands/df.d.ts +3 -0
- package/dist/commands/df.d.ts.map +1 -0
- package/dist/commands/df.js +16 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +40 -0
- package/dist/commands/du.d.ts +3 -0
- package/dist/commands/du.d.ts.map +1 -0
- package/dist/commands/du.js +39 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +2 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +6 -14
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +11 -21
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +2 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +4 -7
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +12 -0
- package/dist/commands/gzip.d.ts +4 -0
- package/dist/commands/gzip.d.ts.map +1 -0
- package/dist/commands/gzip.js +40 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +2 -0
- package/dist/commands/help.d.ts +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +75 -3
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +2 -0
- package/dist/commands/htop.d.ts.map +1 -1
- package/dist/commands/htop.js +2 -0
- package/dist/commands/id.d.ts +3 -0
- package/dist/commands/id.d.ts.map +1 -0
- package/dist/commands/id.js +14 -0
- package/dist/commands/index.d.ts +5 -2
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +89 -62
- package/dist/commands/kill.d.ts +3 -0
- package/dist/commands/kill.d.ts.map +1 -0
- package/dist/commands/kill.js +13 -0
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +2 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +2 -0
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +2 -0
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +2 -0
- package/dist/commands/nano.d.ts.map +1 -1
- package/dist/commands/nano.js +2 -0
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +2 -0
- package/dist/commands/passwd.d.ts.map +1 -1
- package/dist/commands/passwd.js +2 -0
- package/dist/commands/ping.d.ts +3 -0
- package/dist/commands/ping.d.ts.map +1 -0
- package/dist/commands/ping.js +18 -0
- package/dist/commands/ps.d.ts +3 -0
- package/dist/commands/ps.d.ts.map +1 -0
- package/dist/commands/ps.js +17 -0
- package/dist/commands/pwd.d.ts.map +1 -1
- package/dist/commands/pwd.js +2 -0
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +2 -0
- package/dist/commands/sed.d.ts +3 -0
- package/dist/commands/sed.d.ts.map +1 -0
- package/dist/commands/sed.js +47 -0
- package/dist/commands/set.d.ts +3 -0
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +19 -46
- package/dist/commands/sh.d.ts +0 -1
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +228 -35
- package/dist/commands/sleep.d.ts +3 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +13 -0
- package/dist/commands/sort.d.ts +3 -0
- package/dist/commands/sort.d.ts.map +1 -0
- package/dist/commands/sort.js +37 -0
- package/dist/commands/su.d.ts.map +1 -1
- package/dist/commands/su.js +2 -0
- package/dist/commands/sudo.d.ts.map +1 -1
- package/dist/commands/sudo.js +2 -0
- package/dist/commands/tail.d.ts.map +1 -1
- package/dist/commands/tail.js +2 -0
- package/dist/commands/tar.d.ts +3 -0
- package/dist/commands/tar.d.ts.map +1 -0
- package/dist/commands/tar.js +64 -0
- package/dist/commands/tee.d.ts +3 -0
- package/dist/commands/tee.d.ts.map +1 -0
- package/dist/commands/tee.js +29 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +2 -0
- package/dist/commands/tr.d.ts +3 -0
- package/dist/commands/tr.d.ts.map +1 -0
- package/dist/commands/tr.js +24 -0
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +2 -0
- package/dist/commands/uname.d.ts +3 -0
- package/dist/commands/uname.d.ts.map +1 -0
- package/dist/commands/uname.js +21 -0
- package/dist/commands/uniq.d.ts +3 -0
- package/dist/commands/uniq.d.ts.map +1 -0
- package/dist/commands/uniq.js +33 -0
- package/dist/commands/unset.d.ts.map +1 -1
- package/dist/commands/unset.js +6 -10
- package/dist/commands/wc.d.ts.map +1 -1
- package/dist/commands/wc.js +2 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +2 -0
- package/dist/commands/who.d.ts.map +1 -1
- package/dist/commands/who.js +2 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +2 -0
- package/dist/commands/xargs.d.ts +3 -0
- package/dist/commands/xargs.d.ts.map +1 -0
- package/dist/commands/xargs.js +16 -0
- package/dist/types/commands.d.ts +13 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/pipeline.d.ts +20 -0
- package/dist/types/pipeline.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/SSHMimic/exec.ts +2 -2
- package/src/SSHMimic/executor.ts +95 -98
- package/src/SSHMimic/index.ts +15 -49
- package/src/SSHMimic/sftp.ts +15 -0
- package/src/VirtualFileSystem/index.ts +27 -75
- package/src/VirtualShell/shell.ts +19 -2
- package/src/VirtualShell/shellParser.ts +202 -168
- package/src/VirtualUserManager/index.ts +2 -7
- package/src/commands/adduser.ts +2 -0
- package/src/commands/awk.ts +30 -0
- package/src/commands/base64.ts +18 -0
- package/src/commands/cat.ts +2 -0
- package/src/commands/cd.ts +2 -0
- package/src/commands/chmod.ts +2 -0
- package/src/commands/clear.ts +4 -1
- package/src/commands/cp.ts +2 -0
- package/src/commands/curl.ts +2 -0
- package/src/commands/cut.ts +29 -0
- package/src/commands/date.ts +24 -0
- package/src/commands/deluser.ts +2 -0
- package/src/commands/df.ts +18 -0
- package/src/commands/diff.ts +29 -0
- package/src/commands/du.ts +39 -0
- package/src/commands/echo.ts +2 -0
- package/src/commands/env.ts +6 -16
- package/src/commands/export.ts +11 -24
- package/src/commands/find.ts +2 -0
- package/src/commands/grep.ts +4 -7
- package/src/commands/groups.ts +14 -0
- package/src/commands/gzip.ts +31 -0
- package/src/commands/head.ts +2 -0
- package/src/commands/help.ts +81 -3
- package/src/commands/hostname.ts +2 -0
- package/src/commands/htop.ts +2 -0
- package/src/commands/id.ts +16 -0
- package/src/commands/index.ts +98 -99
- package/src/commands/kill.ts +14 -0
- package/src/commands/ln.ts +2 -0
- package/src/commands/ls.ts +2 -0
- package/src/commands/mkdir.ts +2 -0
- package/src/commands/mv.ts +2 -0
- package/src/commands/nano.ts +2 -0
- package/src/commands/neofetch.ts +2 -0
- package/src/commands/passwd.ts +2 -0
- package/src/commands/ping.ts +20 -0
- package/src/commands/ps.ts +19 -0
- package/src/commands/pwd.ts +2 -0
- package/src/commands/rm.ts +2 -0
- package/src/commands/sed.ts +45 -0
- package/src/commands/set.ts +19 -50
- package/src/commands/sh.ts +192 -43
- package/src/commands/sleep.ts +14 -0
- package/src/commands/sort.ts +37 -0
- package/src/commands/su.ts +2 -0
- package/src/commands/sudo.ts +2 -0
- package/src/commands/tail.ts +2 -0
- package/src/commands/tar.ts +58 -0
- package/src/commands/tee.ts +25 -0
- package/src/commands/touch.ts +2 -0
- package/src/commands/tr.ts +24 -0
- package/src/commands/tree.ts +2 -0
- package/src/commands/uname.ts +20 -0
- package/src/commands/uniq.ts +28 -0
- package/src/commands/unset.ts +5 -12
- package/src/commands/wc.ts +2 -0
- package/src/commands/wget.ts +2 -0
- package/src/commands/who.ts +2 -0
- package/src/commands/whoami.ts +2 -0
- package/src/commands/xargs.ts +17 -0
- package/src/types/commands.ts +14 -0
- package/src/types/pipeline.ts +23 -0
- package/standalone.js +92 -64
- package/standalone.js.map +4 -4
- package/tests/users.test.ts +5 -34
package/dist/commands/unset.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { setEnvVar } from "./set";
|
|
2
1
|
export const unsetCommand = {
|
|
3
2
|
name: "unset",
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
for (const varName of args) {
|
|
11
|
-
setEnvVar(varName, "");
|
|
12
|
-
}
|
|
3
|
+
description: "Remove shell variable",
|
|
4
|
+
category: "shell",
|
|
5
|
+
params: ["<VAR>"],
|
|
6
|
+
run: ({ args, env }) => {
|
|
7
|
+
for (const name of args)
|
|
8
|
+
delete env.vars[name];
|
|
13
9
|
return { exitCode: 0 };
|
|
14
10
|
},
|
|
15
11
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wc.d.ts","sourceRoot":"","sources":["../../src/commands/wc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"wc.d.ts","sourceRoot":"","sources":["../../src/commands/wc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,SAAS,EAAE,WA6CvB,CAAC"}
|
package/dist/commands/wc.js
CHANGED
|
@@ -2,6 +2,8 @@ import { ifFlag } from "./command-helpers";
|
|
|
2
2
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
3
|
export const wcCommand = {
|
|
4
4
|
name: "wc",
|
|
5
|
+
description: "Count words/lines/bytes",
|
|
6
|
+
category: "text",
|
|
5
7
|
params: ["[-l] [-w] [-c] [file...]"],
|
|
6
8
|
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
7
9
|
const lines = ifFlag(args, ["-l"]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wget.d.ts","sourceRoot":"","sources":["../../src/commands/wget.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA8ErD,eAAO,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"wget.d.ts","sourceRoot":"","sources":["../../src/commands/wget.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA8ErD,eAAO,MAAM,WAAW,EAAE,WA+DzB,CAAC"}
|
package/dist/commands/wget.js
CHANGED
|
@@ -61,6 +61,8 @@ function runHostWget(args) {
|
|
|
61
61
|
}
|
|
62
62
|
export const wgetCommand = {
|
|
63
63
|
name: "wget",
|
|
64
|
+
description: "File downloader",
|
|
65
|
+
category: "network",
|
|
64
66
|
params: ["[url]"],
|
|
65
67
|
run: async ({ authUser, cwd, args, shell }) => {
|
|
66
68
|
const { flagsWithValues, positionals } = parseArgs(args, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"who.d.ts","sourceRoot":"","sources":["../../src/commands/who.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"who.d.ts","sourceRoot":"","sources":["../../src/commands/who.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,UAAU,EAAE,WAgBxB,CAAC"}
|
package/dist/commands/who.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { formatLoginDate } from "../SSHMimic/loginFormat";
|
|
2
2
|
export const whoCommand = {
|
|
3
3
|
name: "who",
|
|
4
|
+
description: "Show active sessions",
|
|
5
|
+
category: "system",
|
|
4
6
|
params: [],
|
|
5
7
|
run: ({ shell }) => {
|
|
6
8
|
const lines = shell.users.listActiveSessions().map((session) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,aAAa,EAAE,WAM3B,CAAC"}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xargs.d.ts","sourceRoot":"","sources":["../../src/commands/xargs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,eAAO,MAAM,YAAY,EAAE,WAa1B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { runCommand } from "./index";
|
|
2
|
+
export const xargsCommand = {
|
|
3
|
+
name: "xargs",
|
|
4
|
+
description: "Build and execute command lines from stdin",
|
|
5
|
+
category: "text",
|
|
6
|
+
params: ["[command] [args...]"],
|
|
7
|
+
run: async ({ authUser, hostname, mode, cwd, args, stdin, shell, env }) => {
|
|
8
|
+
const baseCmd = args[0] ?? "echo";
|
|
9
|
+
const extraArgs = args.slice(1);
|
|
10
|
+
const items = (stdin ?? "").trim().split(/\s+/).filter(Boolean);
|
|
11
|
+
if (items.length === 0)
|
|
12
|
+
return { exitCode: 0 };
|
|
13
|
+
const fullCmd = [baseCmd, ...extraArgs, ...items].join(" ");
|
|
14
|
+
return runCommand(fullCmd, authUser, hostname, mode, cwd, shell, undefined, env);
|
|
15
|
+
},
|
|
16
|
+
};
|
package/dist/types/commands.d.ts
CHANGED
|
@@ -52,6 +52,13 @@ export interface NanoEditorSession {
|
|
|
52
52
|
/** Initial editor content shown to user. */
|
|
53
53
|
initialContent: string;
|
|
54
54
|
}
|
|
55
|
+
/** Per-session shell environment (variables, last exit code). */
|
|
56
|
+
export interface ShellEnv {
|
|
57
|
+
/** Environment variables visible to commands. */
|
|
58
|
+
vars: Record<string, string>;
|
|
59
|
+
/** Exit status of the last executed command. */
|
|
60
|
+
lastExitCode: number;
|
|
61
|
+
}
|
|
55
62
|
/** Runtime context object passed to each command module. */
|
|
56
63
|
export interface CommandContext {
|
|
57
64
|
/** Authenticated user currently bound to stream. */
|
|
@@ -72,6 +79,8 @@ export interface CommandContext {
|
|
|
72
79
|
stdin?: string;
|
|
73
80
|
/** Current working directory for command execution. */
|
|
74
81
|
cwd: string;
|
|
82
|
+
/** Per-session environment available to command modules. */
|
|
83
|
+
env: ShellEnv;
|
|
75
84
|
}
|
|
76
85
|
/** Contract implemented by each shell command module. */
|
|
77
86
|
export interface ShellModule {
|
|
@@ -83,6 +92,10 @@ export interface ShellModule {
|
|
|
83
92
|
run: (ctx: CommandContext) => CommandResult | Promise<CommandResult>;
|
|
84
93
|
/** Optional alternative command names. */
|
|
85
94
|
aliases?: string[];
|
|
95
|
+
/** Short description shown in `help`. */
|
|
96
|
+
description?: string;
|
|
97
|
+
/** Category used for grouped help output. */
|
|
98
|
+
category?: string;
|
|
86
99
|
}
|
|
87
100
|
/** Command return union allowing sync or async handlers. */
|
|
88
101
|
export type CommandOutcome = CommandResult | Promise<CommandResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/types/commands.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gDAAgD;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC7B,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2DAA2D;IAC3D,UAAU,EAAE,OAAO,CAAC;IACpB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;CACf;AAED,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IACjC,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC9B,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,cAAc,EAAE,oBAAoB,EAAE,CAAC;IACvC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,IAAI,EAAE,WAAW,CAAC;IAClB,kDAAkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,8BAA8B;IAC9B,KAAK,EAAE,YAAY,CAAC;IACpB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/types/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface PipelineCommand {
|
|
|
11
11
|
/** Append to output file (>> file) */
|
|
12
12
|
appendOutput?: boolean;
|
|
13
13
|
}
|
|
14
|
+
/** Logical operator connecting two statement groups. */
|
|
15
|
+
export type LogicalOp = "&&" | "||" | ";";
|
|
14
16
|
/** Represents a parsed shell pipeline */
|
|
15
17
|
export interface Pipeline {
|
|
16
18
|
/** List of commands in the pipeline */
|
|
@@ -20,4 +22,22 @@ export interface Pipeline {
|
|
|
20
22
|
/** Error message if parsing failed */
|
|
21
23
|
error?: string;
|
|
22
24
|
}
|
|
25
|
+
/** A statement: one pipeline optionally followed by && / || / ; and the next statement */
|
|
26
|
+
export interface Statement {
|
|
27
|
+
/** Pipeline to execute for this statement. */
|
|
28
|
+
pipeline: Pipeline;
|
|
29
|
+
/** Operator connecting this statement to the next one. */
|
|
30
|
+
op?: LogicalOp;
|
|
31
|
+
/** Optional next statement in sequence. */
|
|
32
|
+
next?: Statement;
|
|
33
|
+
}
|
|
34
|
+
/** Top-level parse result for a script. */
|
|
35
|
+
export interface Script {
|
|
36
|
+
/** Statements contained in the script. */
|
|
37
|
+
statements: Statement[];
|
|
38
|
+
/** Whether the script was parsed successfully. */
|
|
39
|
+
isValid: boolean;
|
|
40
|
+
/** Optional parse error message. */
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
23
43
|
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/types/pipeline.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC/B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,yCAAyC;AACzC,MAAM,WAAW,QAAQ;IACxB,uCAAuC;IACvC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CACf"}
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/types/pipeline.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC/B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wDAAwD;AACxD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AAE1C,yCAAyC;AACzC,MAAM,WAAW,QAAQ;IACxB,uCAAuC;IACvC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,0FAA0F;AAC1F,MAAM,WAAW,SAAS;IACzB,8CAA8C;IAC9C,QAAQ,EAAE,QAAQ,CAAC;IACnB,0DAA0D;IAC1D,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,2CAA2C;IAC3C,IAAI,CAAC,EAAE,SAAS,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,MAAM;IACtB,0CAA0C;IAC1C,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CACf"}
|
package/package.json
CHANGED
package/src/SSHMimic/exec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runCommand } from "../commands";
|
|
1
|
+
import { makeDefaultEnv, runCommand } from "../commands";
|
|
2
2
|
import type { ExecStream } from "../types/streams";
|
|
3
3
|
import type { VirtualShell } from "../VirtualShell";
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ export function runExec(
|
|
|
17
17
|
shell: VirtualShell,
|
|
18
18
|
): void {
|
|
19
19
|
Promise.resolve(
|
|
20
|
-
runCommand(cmd, authUser, hostname, "exec", `/home/${authUser}`, shell),
|
|
20
|
+
runCommand(cmd, authUser, hostname, "exec", `/home/${authUser}`, shell, undefined, makeDefaultEnv(authUser, hostname)),
|
|
21
21
|
)
|
|
22
22
|
.then((result) => {
|
|
23
23
|
if (result.stdout) {
|
package/src/SSHMimic/executor.ts
CHANGED
|
@@ -1,13 +1,79 @@
|
|
|
1
1
|
import { runCommand as runSingleCommand } from "../commands";
|
|
2
2
|
import { resolvePath } from "../commands/helpers";
|
|
3
|
-
import type { CommandMode, CommandResult } from "../types/commands";
|
|
4
|
-
import type { Pipeline, PipelineCommand } from "../types/pipeline";
|
|
3
|
+
import type { CommandMode, CommandResult, ShellEnv } from "../types/commands";
|
|
4
|
+
import type { Pipeline, PipelineCommand, Script, Statement } from "../types/pipeline";
|
|
5
5
|
import type { VirtualShell } from "../VirtualShell";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// ── Script executor (handles &&/||/;) ────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export async function executeScript(
|
|
10
|
+
script: Script,
|
|
11
|
+
authUser: string,
|
|
12
|
+
hostname: string,
|
|
13
|
+
mode: CommandMode,
|
|
14
|
+
cwd: string,
|
|
15
|
+
shell: VirtualShell,
|
|
16
|
+
env: ShellEnv,
|
|
17
|
+
): Promise<CommandResult> {
|
|
18
|
+
if (!script.isValid) return { stderr: script.error || "Syntax error", exitCode: 1 };
|
|
19
|
+
|
|
20
|
+
let lastResult: CommandResult = { exitCode: 0 };
|
|
21
|
+
|
|
22
|
+
for (const stmt of script.statements) {
|
|
23
|
+
// Decide whether to run this statement based on previous op
|
|
24
|
+
lastResult = await executePipeline(stmt.pipeline, authUser, hostname, mode, cwd, shell, env);
|
|
25
|
+
env.lastExitCode = lastResult.exitCode ?? 0;
|
|
26
|
+
|
|
27
|
+
// Propagate session-control signals
|
|
28
|
+
if (lastResult.closeSession || lastResult.switchUser || lastResult.nextCwd) {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return lastResult;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Execute statements connected by &&/||/; */
|
|
37
|
+
export async function executeStatements(
|
|
38
|
+
statements: Statement[],
|
|
39
|
+
authUser: string,
|
|
40
|
+
hostname: string,
|
|
41
|
+
mode: CommandMode,
|
|
42
|
+
cwd: string,
|
|
43
|
+
shell: VirtualShell,
|
|
44
|
+
env: ShellEnv,
|
|
45
|
+
): Promise<CommandResult> {
|
|
46
|
+
let last: CommandResult = { exitCode: 0 };
|
|
47
|
+
let i = 0;
|
|
48
|
+
|
|
49
|
+
while (i < statements.length) {
|
|
50
|
+
const stmt = statements[i]!;
|
|
51
|
+
last = await executePipeline(stmt.pipeline, authUser, hostname, mode, cwd, shell, env);
|
|
52
|
+
env.lastExitCode = last.exitCode ?? 0;
|
|
53
|
+
|
|
54
|
+
if (last.closeSession || last.switchUser) return last;
|
|
55
|
+
|
|
56
|
+
const op = stmt.op;
|
|
57
|
+
if (!op || op === ";") {
|
|
58
|
+
// always run next
|
|
59
|
+
} else if (op === "&&") {
|
|
60
|
+
if ((last.exitCode ?? 0) !== 0) {
|
|
61
|
+
// skip until next ; or end
|
|
62
|
+
while (i < statements.length && statements[i]?.op === "&&") i++;
|
|
63
|
+
}
|
|
64
|
+
} else if (op === "||") {
|
|
65
|
+
if ((last.exitCode ?? 0) === 0) {
|
|
66
|
+
// skip until next ; or end
|
|
67
|
+
while (i < statements.length && statements[i]?.op === "||") i++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
i++;
|
|
71
|
+
}
|
|
72
|
+
return last;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Pipeline executor ─────────────────────────────────────────────────────────
|
|
76
|
+
|
|
11
77
|
export async function executePipeline(
|
|
12
78
|
pipeline: Pipeline,
|
|
13
79
|
authUser: string,
|
|
@@ -15,37 +81,26 @@ export async function executePipeline(
|
|
|
15
81
|
mode: CommandMode,
|
|
16
82
|
cwd: string,
|
|
17
83
|
shell: VirtualShell,
|
|
84
|
+
env?: ShellEnv,
|
|
18
85
|
): Promise<CommandResult> {
|
|
19
|
-
if (pipeline.
|
|
20
|
-
|
|
21
|
-
|
|
86
|
+
if (!pipeline.isValid) return { stderr: pipeline.error || "Syntax error", exitCode: 1 };
|
|
87
|
+
if (pipeline.commands.length === 0) return { exitCode: 0 };
|
|
88
|
+
|
|
89
|
+
const shellEnv: ShellEnv = env ?? { vars: {}, lastExitCode: 0 };
|
|
22
90
|
|
|
23
91
|
if (pipeline.commands.length === 1) {
|
|
24
|
-
// Single command with possible redirections
|
|
25
92
|
return executeSingleCommandWithRedirections(
|
|
26
93
|
pipeline.commands[0] as PipelineCommand,
|
|
27
|
-
authUser,
|
|
28
|
-
hostname,
|
|
29
|
-
mode,
|
|
30
|
-
cwd,
|
|
31
|
-
shell,
|
|
94
|
+
authUser, hostname, mode, cwd, shell, shellEnv,
|
|
32
95
|
);
|
|
33
96
|
}
|
|
34
97
|
|
|
35
|
-
// Multiple commands in a pipeline
|
|
36
98
|
return executePipelineChain(
|
|
37
99
|
pipeline.commands as PipelineCommand[],
|
|
38
|
-
authUser,
|
|
39
|
-
hostname,
|
|
40
|
-
mode,
|
|
41
|
-
cwd,
|
|
42
|
-
shell,
|
|
100
|
+
authUser, hostname, mode, cwd, shell, shellEnv,
|
|
43
101
|
);
|
|
44
102
|
}
|
|
45
103
|
|
|
46
|
-
/**
|
|
47
|
-
* Execute a single command with input/output redirections
|
|
48
|
-
*/
|
|
49
104
|
async function executeSingleCommandWithRedirections(
|
|
50
105
|
cmd: PipelineCommand,
|
|
51
106
|
authUser: string,
|
|
@@ -53,66 +108,37 @@ async function executeSingleCommandWithRedirections(
|
|
|
53
108
|
mode: CommandMode,
|
|
54
109
|
cwd: string,
|
|
55
110
|
shell: VirtualShell,
|
|
111
|
+
env: ShellEnv,
|
|
56
112
|
): Promise<CommandResult> {
|
|
57
|
-
// Prepare input if input file specified
|
|
58
113
|
let stdin: string | undefined;
|
|
59
114
|
if (cmd.inputFile) {
|
|
60
115
|
const inputPath = resolvePath(cwd, cmd.inputFile);
|
|
61
|
-
try {
|
|
62
|
-
|
|
63
|
-
} catch {
|
|
64
|
-
return {
|
|
65
|
-
stderr: `cat: ${cmd.inputFile}: No such file or directory`,
|
|
66
|
-
exitCode: 1,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
116
|
+
try { stdin = shell.vfs.readFile(inputPath); }
|
|
117
|
+
catch { return { stderr: `${cmd.inputFile}: No such file or directory`, exitCode: 1 }; }
|
|
69
118
|
}
|
|
70
119
|
|
|
71
|
-
// Build raw input for the command
|
|
72
120
|
const rawInput = [cmd.name, ...cmd.args].join(" ");
|
|
121
|
+
const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin, env);
|
|
73
122
|
|
|
74
|
-
// Run the command with potential input
|
|
75
|
-
const result = await runSingleCommand(
|
|
76
|
-
rawInput,
|
|
77
|
-
authUser,
|
|
78
|
-
hostname,
|
|
79
|
-
mode,
|
|
80
|
-
cwd,
|
|
81
|
-
shell,
|
|
82
|
-
stdin,
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
// Handle output redirection
|
|
86
123
|
if (cmd.outputFile) {
|
|
87
124
|
const outputPath = resolvePath(cwd, cmd.outputFile);
|
|
88
125
|
const output = result.stdout || "";
|
|
89
126
|
try {
|
|
90
127
|
if (cmd.appendOutput) {
|
|
91
|
-
try {
|
|
92
|
-
|
|
93
|
-
shell.writeFileAsUser(authUser, outputPath, existing + output);
|
|
94
|
-
} catch {
|
|
95
|
-
shell.writeFileAsUser(authUser, outputPath, output);
|
|
96
|
-
}
|
|
128
|
+
const existing = (() => { try { return shell.vfs.readFile(outputPath); } catch { return ""; } })();
|
|
129
|
+
shell.writeFileAsUser(authUser, outputPath, existing + output);
|
|
97
130
|
} else {
|
|
98
131
|
shell.writeFileAsUser(authUser, outputPath, output);
|
|
99
132
|
}
|
|
100
133
|
return { ...result, stdout: "" };
|
|
101
134
|
} catch {
|
|
102
|
-
return {
|
|
103
|
-
...result,
|
|
104
|
-
stderr: `Failed to write to ${cmd.outputFile}`,
|
|
105
|
-
exitCode: 1,
|
|
106
|
-
};
|
|
135
|
+
return { ...result, stderr: `Failed to write to ${cmd.outputFile}`, exitCode: 1 };
|
|
107
136
|
}
|
|
108
137
|
}
|
|
109
138
|
|
|
110
139
|
return result;
|
|
111
140
|
}
|
|
112
141
|
|
|
113
|
-
/**
|
|
114
|
-
* Execute a chain of commands connected by pipes
|
|
115
|
-
*/
|
|
116
142
|
async function executePipelineChain(
|
|
117
143
|
commands: PipelineCommand[],
|
|
118
144
|
authUser: string,
|
|
@@ -120,6 +146,7 @@ async function executePipelineChain(
|
|
|
120
146
|
mode: CommandMode,
|
|
121
147
|
cwd: string,
|
|
122
148
|
shell: VirtualShell,
|
|
149
|
+
env: ShellEnv,
|
|
123
150
|
): Promise<CommandResult> {
|
|
124
151
|
let currentOutput = "";
|
|
125
152
|
let exitCode = 0;
|
|
@@ -127,66 +154,36 @@ async function executePipelineChain(
|
|
|
127
154
|
for (let i = 0; i < commands.length; i++) {
|
|
128
155
|
const cmd = commands[i] as PipelineCommand;
|
|
129
156
|
|
|
130
|
-
// Handle input file for first command
|
|
131
157
|
if (i === 0 && cmd.inputFile) {
|
|
132
158
|
const inputPath = resolvePath(cwd, cmd.inputFile);
|
|
133
|
-
try {
|
|
134
|
-
|
|
135
|
-
} catch {
|
|
136
|
-
return {
|
|
137
|
-
stderr: `cat: ${cmd.inputFile}: No such file or directory`,
|
|
138
|
-
exitCode: 1,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
159
|
+
try { currentOutput = shell.vfs.readFile(inputPath); }
|
|
160
|
+
catch { return { stderr: `${cmd.inputFile}: No such file or directory`, exitCode: 1 }; }
|
|
141
161
|
}
|
|
142
162
|
|
|
143
|
-
// Build raw input
|
|
144
163
|
const rawInput = [cmd.name, ...cmd.args].join(" ");
|
|
145
|
-
|
|
146
|
-
// Create a modified context that might accept stdin
|
|
147
|
-
// For now, we'll append input as an additional arg for commands that support it
|
|
148
|
-
const result = await runSingleCommand(
|
|
149
|
-
rawInput,
|
|
150
|
-
authUser,
|
|
151
|
-
hostname,
|
|
152
|
-
mode,
|
|
153
|
-
cwd,
|
|
154
|
-
shell,
|
|
155
|
-
currentOutput,
|
|
156
|
-
);
|
|
157
|
-
|
|
164
|
+
const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, currentOutput, env);
|
|
158
165
|
exitCode = result.exitCode ?? 0;
|
|
159
166
|
|
|
160
|
-
// Handle output redirection (only for last command)
|
|
161
167
|
if (i === commands.length - 1 && cmd.outputFile) {
|
|
162
168
|
const outputPath = resolvePath(cwd, cmd.outputFile);
|
|
163
169
|
const output = result.stdout || "";
|
|
164
170
|
try {
|
|
165
171
|
if (cmd.appendOutput) {
|
|
166
|
-
try {
|
|
167
|
-
|
|
168
|
-
shell.writeFileAsUser(authUser, outputPath, existing + output);
|
|
169
|
-
} catch {
|
|
170
|
-
shell.writeFileAsUser(authUser, outputPath, output);
|
|
171
|
-
}
|
|
172
|
+
const existing = (() => { try { return shell.vfs.readFile(outputPath); } catch { return ""; } })();
|
|
173
|
+
shell.writeFileAsUser(authUser, outputPath, existing + output);
|
|
172
174
|
} else {
|
|
173
175
|
shell.writeFileAsUser(authUser, outputPath, output);
|
|
174
176
|
}
|
|
175
177
|
currentOutput = "";
|
|
176
178
|
} catch {
|
|
177
|
-
return {
|
|
178
|
-
stderr: `Failed to write to ${cmd.outputFile}`,
|
|
179
|
-
exitCode: 1,
|
|
180
|
-
};
|
|
179
|
+
return { stderr: `Failed to write to ${cmd.outputFile}`, exitCode: 1 };
|
|
181
180
|
}
|
|
182
181
|
} else {
|
|
183
|
-
// Pass output to next command
|
|
184
182
|
currentOutput = result.stdout || "";
|
|
185
183
|
}
|
|
186
184
|
|
|
187
|
-
if (result.stderr && exitCode !== 0) {
|
|
188
|
-
|
|
189
|
-
}
|
|
185
|
+
if (result.stderr && exitCode !== 0) return { stderr: result.stderr, exitCode };
|
|
186
|
+
if (result.closeSession || result.switchUser) return result;
|
|
190
187
|
}
|
|
191
188
|
|
|
192
189
|
return { stdout: currentOutput, exitCode };
|
package/src/SSHMimic/index.ts
CHANGED
|
@@ -140,11 +140,7 @@ class SshMimic extends EventEmitter {
|
|
|
140
140
|
|
|
141
141
|
// Rate-limit check
|
|
142
142
|
if (this.isLockedOut(remoteAddress)) {
|
|
143
|
-
this.emit("auth:failure", {
|
|
144
|
-
username: candidateUser,
|
|
145
|
-
remoteAddress,
|
|
146
|
-
reason: "lockout",
|
|
147
|
-
});
|
|
143
|
+
this.emit("auth:failure", { username: candidateUser, remoteAddress, reason: "lockout" });
|
|
148
144
|
ctx.reject();
|
|
149
145
|
return;
|
|
150
146
|
}
|
|
@@ -156,10 +152,7 @@ class SshMimic extends EventEmitter {
|
|
|
156
152
|
`User ${candidateUser} has no password set, allowing login without verification`,
|
|
157
153
|
);
|
|
158
154
|
authUser = candidateUser;
|
|
159
|
-
sessionId = shell.users.registerSession(
|
|
160
|
-
authUser,
|
|
161
|
-
remoteAddress,
|
|
162
|
-
).id;
|
|
155
|
+
sessionId = shell.users.registerSession(authUser, remoteAddress).id;
|
|
163
156
|
this.recordSuccess(remoteAddress);
|
|
164
157
|
this.emit("auth:success", { username: authUser, remoteAddress });
|
|
165
158
|
this.ensureHomeDir(authUser);
|
|
@@ -173,10 +166,7 @@ class SshMimic extends EventEmitter {
|
|
|
173
166
|
!shell.users.verifyPassword(candidateUser, ctx.password)
|
|
174
167
|
) {
|
|
175
168
|
this.recordFailure(remoteAddress);
|
|
176
|
-
this.emit("auth:failure", {
|
|
177
|
-
username: candidateUser,
|
|
178
|
-
remoteAddress,
|
|
179
|
-
});
|
|
169
|
+
this.emit("auth:failure", { username: candidateUser, remoteAddress });
|
|
180
170
|
ctx.reject();
|
|
181
171
|
return;
|
|
182
172
|
}
|
|
@@ -202,16 +192,13 @@ class SshMimic extends EventEmitter {
|
|
|
202
192
|
const incomingKey = ctx.key;
|
|
203
193
|
const keyMatches = authorizedKeys.some(
|
|
204
194
|
(k) =>
|
|
205
|
-
k.algo === incomingKey.algo &&
|
|
195
|
+
k.algo === incomingKey.algo &&
|
|
196
|
+
k.data.equals(incomingKey.data),
|
|
206
197
|
);
|
|
207
198
|
|
|
208
199
|
if (!keyMatches) {
|
|
209
200
|
this.recordFailure(remoteAddress);
|
|
210
|
-
this.emit("auth:failure", {
|
|
211
|
-
username: candidateUser,
|
|
212
|
-
remoteAddress,
|
|
213
|
-
method: "publickey",
|
|
214
|
-
});
|
|
201
|
+
this.emit("auth:failure", { username: candidateUser, remoteAddress, method: "publickey" });
|
|
215
202
|
ctx.reject();
|
|
216
203
|
return;
|
|
217
204
|
}
|
|
@@ -219,16 +206,9 @@ class SshMimic extends EventEmitter {
|
|
|
219
206
|
// Key matched — if this is a signature check step, accept
|
|
220
207
|
if (ctx.signature) {
|
|
221
208
|
authUser = candidateUser;
|
|
222
|
-
sessionId = shell.users.registerSession(
|
|
223
|
-
authUser,
|
|
224
|
-
remoteAddress,
|
|
225
|
-
).id;
|
|
209
|
+
sessionId = shell.users.registerSession(authUser, remoteAddress).id;
|
|
226
210
|
this.recordSuccess(remoteAddress);
|
|
227
|
-
this.emit("auth:success", {
|
|
228
|
-
username: authUser,
|
|
229
|
-
remoteAddress,
|
|
230
|
-
method: "publickey",
|
|
231
|
-
});
|
|
211
|
+
this.emit("auth:success", { username: authUser, remoteAddress, method: "publickey" });
|
|
232
212
|
this.ensureHomeDir(authUser);
|
|
233
213
|
ctx.accept();
|
|
234
214
|
} else {
|
|
@@ -258,35 +238,20 @@ class SshMimic extends EventEmitter {
|
|
|
258
238
|
acceptPty();
|
|
259
239
|
});
|
|
260
240
|
|
|
261
|
-
session.on(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
terminalSize.rows = info?.rows ?? terminalSize.rows;
|
|
266
|
-
},
|
|
267
|
-
);
|
|
241
|
+
session.on("window-change", (_acceptChange, _rejectChange, info) => {
|
|
242
|
+
terminalSize.cols = info?.cols ?? terminalSize.cols;
|
|
243
|
+
terminalSize.rows = info?.rows ?? terminalSize.rows;
|
|
244
|
+
});
|
|
268
245
|
|
|
269
246
|
session.on("shell", (acceptShell) => {
|
|
270
247
|
const stream = acceptShell();
|
|
271
|
-
shell?.startInteractiveSession(
|
|
272
|
-
stream,
|
|
273
|
-
authUser,
|
|
274
|
-
sessionId,
|
|
275
|
-
remoteAddress,
|
|
276
|
-
terminalSize,
|
|
277
|
-
);
|
|
248
|
+
shell?.startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize);
|
|
278
249
|
});
|
|
279
250
|
|
|
280
251
|
session.on("exec", (acceptExec, _rejectExec, info) => {
|
|
281
252
|
const stream = acceptExec();
|
|
282
253
|
if (stream) {
|
|
283
|
-
runExec(
|
|
284
|
-
stream,
|
|
285
|
-
info.command.trim(),
|
|
286
|
-
authUser,
|
|
287
|
-
shell.hostname,
|
|
288
|
-
shell,
|
|
289
|
-
);
|
|
254
|
+
runExec(stream, info.command.trim(), authUser, shell.hostname, shell);
|
|
290
255
|
}
|
|
291
256
|
});
|
|
292
257
|
});
|
|
@@ -328,3 +293,4 @@ class SshMimic extends EventEmitter {
|
|
|
328
293
|
|
|
329
294
|
export { SftpMimic } from "./sftp";
|
|
330
295
|
export { SshMimic };
|
|
296
|
+
|
package/src/SSHMimic/sftp.ts
CHANGED
|
@@ -253,6 +253,14 @@ export class SftpMimic extends EventEmitter {
|
|
|
253
253
|
);
|
|
254
254
|
|
|
255
255
|
if (ctx.method === "password") {
|
|
256
|
+
// If no password is set for the user, allow login without verification
|
|
257
|
+
if (!this.getUsers().hasPassword(candidateUser)) {
|
|
258
|
+
acceptSession(candidateUser);
|
|
259
|
+
this.emit("auth:success", { username: authUser, remoteAddress });
|
|
260
|
+
ctx.accept();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
256
264
|
if (
|
|
257
265
|
!this.getUsers().verifyPassword(candidateUser, ctx.password ?? "")
|
|
258
266
|
) {
|
|
@@ -272,6 +280,13 @@ export class SftpMimic extends EventEmitter {
|
|
|
272
280
|
|
|
273
281
|
if (ctx.method === "keyboard-interactive") {
|
|
274
282
|
const keyboardCtx = ctx as KeyboardAuthContext;
|
|
283
|
+
// If no password is set, accept immediately
|
|
284
|
+
if (!this.getUsers().hasPassword(candidateUser)) {
|
|
285
|
+
acceptSession(candidateUser);
|
|
286
|
+
this.emit("auth:success", { username: authUser, remoteAddress });
|
|
287
|
+
keyboardCtx.accept();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
275
290
|
keyboardCtx.prompt(
|
|
276
291
|
[{ prompt: "Password: ", echo: false }],
|
|
277
292
|
(answers) => {
|