@takodotid/circuit 0.31.0 → 0.31.1
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/dist/cli/index.js +31 -22
- package/dist/types/adapters/devices/vrp/index.d.ts +1 -1
- package/dist/types/schema/device.d.ts +2 -0
- package/dist/types/transport/console.d.ts +4 -2
- package/package.json +1 -1
- package/src/adapters/devices/fortios/index.ts +2 -2
- package/src/adapters/devices/junos/index.ts +1 -1
- package/src/adapters/devices/raisecom-ros/index.ts +2 -2
- package/src/adapters/devices/routeros/index.ts +2 -2
- package/src/adapters/devices/vrp/TRAPS.md +2 -0
- package/src/adapters/devices/vrp/index.ts +5 -3
- package/src/schema/device.ts +2 -0
- package/src/transport/console.ts +30 -15
package/dist/cli/index.js
CHANGED
|
@@ -425,7 +425,7 @@ function expect(channel, write, patterns, timeout = 30000) {
|
|
|
425
425
|
channel.write(write);
|
|
426
426
|
});
|
|
427
427
|
}
|
|
428
|
-
async function consoleLogin(channel, dialect, user, password) {
|
|
428
|
+
async function consoleLogin(channel, dialect, user, password, passwordOnly = false) {
|
|
429
429
|
const others = dialect.others ?? [];
|
|
430
430
|
const states = [dialect.login, dialect.password, ...others.map(([pattern]) => pattern), dialect.prompt];
|
|
431
431
|
const toward = (index) => {
|
|
@@ -438,32 +438,38 @@ async function consoleLogin(channel, dialect, user, password) {
|
|
|
438
438
|
`;
|
|
439
439
|
};
|
|
440
440
|
await quiet(channel, 1000);
|
|
441
|
+
const start = passwordOnly ? 1 : 0;
|
|
441
442
|
let state = (await expect(channel, `
|
|
442
443
|
`, states)).index;
|
|
443
|
-
for (let step = 0;state !==
|
|
444
|
+
for (let step = 0;state !== start; step++) {
|
|
445
|
+
if (passwordOnly && state === 0)
|
|
446
|
+
throw new Error("the console asks for a name, but its console is set to ask only for a password");
|
|
444
447
|
if (step >= 10)
|
|
445
|
-
throw new Error(
|
|
448
|
+
throw new Error(`the console never reached a ${passwordOnly ? "password" : "login"} prompt`);
|
|
446
449
|
state = (await expect(channel, toward(state), states)).index;
|
|
447
450
|
}
|
|
448
451
|
await quiet(channel, 1000);
|
|
449
|
-
|
|
450
|
-
|
|
452
|
+
if (!passwordOnly) {
|
|
453
|
+
const name = dialect.user ? dialect.user(user) : user;
|
|
454
|
+
if ((await expect(channel, `${name}
|
|
451
455
|
`, [dialect.password, dialect.login, dialect.prompt])).index !== 0) {
|
|
452
|
-
|
|
456
|
+
throw new Error("the console did not ask for a password");
|
|
457
|
+
}
|
|
453
458
|
}
|
|
459
|
+
const refusal = dialect.refused ? [dialect.refused] : [];
|
|
460
|
+
const first = 2 + refusal.length;
|
|
461
|
+
const answers = [dialect.prompt, dialect.login, ...refusal, ...others.map(([pattern]) => pattern)];
|
|
454
462
|
let answer = await expect(channel, `${password}
|
|
455
|
-
`,
|
|
456
|
-
for (let step = 0;answer.index >=
|
|
463
|
+
`, answers, 60000);
|
|
464
|
+
for (let step = 0;answer.index >= first; step++) {
|
|
457
465
|
if (step >= 5)
|
|
458
466
|
throw new Error("the console kept asking after the login");
|
|
459
|
-
answer = await expect(channel, others[answer.index -
|
|
460
|
-
dialect.prompt,
|
|
461
|
-
dialect.login,
|
|
462
|
-
...others.map(([pattern]) => pattern)
|
|
463
|
-
]);
|
|
467
|
+
answer = await expect(channel, others[answer.index - first][1], answers);
|
|
464
468
|
}
|
|
465
469
|
if (answer.index === 1)
|
|
466
470
|
throw new Error(`the console refused the login of ${user}`);
|
|
471
|
+
if (answer.index === 2 && dialect.refused)
|
|
472
|
+
throw new Error("the console refused the password");
|
|
467
473
|
await quiet(channel, 800);
|
|
468
474
|
}
|
|
469
475
|
function quiet(channel, silence) {
|
|
@@ -2761,9 +2767,9 @@ async function consoleSession(device, work) {
|
|
|
2761
2767
|
const { console: console2, user, password } = consoleOf(device);
|
|
2762
2768
|
const opened = await openConsole(console2);
|
|
2763
2769
|
try {
|
|
2764
|
-
await consoleLogin(opened.channel, FORTIOS_CONSOLE, user, password);
|
|
2770
|
+
await consoleLogin(opened.channel, FORTIOS_CONSOLE, user, password, console2.passwordOnly);
|
|
2765
2771
|
const cli = new Cli(opened.channel);
|
|
2766
|
-
const relogin = () => consoleLogin(opened.channel, FORTIOS_CONSOLE, user, password);
|
|
2772
|
+
const relogin = () => consoleLogin(opened.channel, FORTIOS_CONSOLE, user, password, console2.passwordOnly);
|
|
2767
2773
|
const result = await work(cli, relogin);
|
|
2768
2774
|
await consoleLogout(cli);
|
|
2769
2775
|
return result;
|
|
@@ -4657,7 +4663,7 @@ async function onConsole(device, work) {
|
|
|
4657
4663
|
const { console: console2, user, password } = consoleOf(device);
|
|
4658
4664
|
const opened = await openConsole(console2);
|
|
4659
4665
|
const login = async () => {
|
|
4660
|
-
await consoleLogin(opened.channel, JUNOS_CONSOLE, user, password);
|
|
4666
|
+
await consoleLogin(opened.channel, JUNOS_CONSOLE, user, password, console2.passwordOnly);
|
|
4661
4667
|
const ready = openCli(opened.channel);
|
|
4662
4668
|
opened.channel.write(`
|
|
4663
4669
|
`);
|
|
@@ -5529,7 +5535,7 @@ async function consoleShell(device) {
|
|
|
5529
5535
|
const { console: console2, user, password } = consoleOf(device);
|
|
5530
5536
|
const opened = await openConsole(console2);
|
|
5531
5537
|
try {
|
|
5532
|
-
await consoleLogin(opened.channel, RAISECOM_CONSOLE, user, password);
|
|
5538
|
+
await consoleLogin(opened.channel, RAISECOM_CONSOLE, user, password, console2.passwordOnly);
|
|
5533
5539
|
} catch (error) {
|
|
5534
5540
|
opened.close();
|
|
5535
5541
|
throw error;
|
|
@@ -5542,7 +5548,7 @@ async function consoleShell(device) {
|
|
|
5542
5548
|
opened.close();
|
|
5543
5549
|
},
|
|
5544
5550
|
fresh: async () => {
|
|
5545
|
-
await consoleLogin(opened.channel, RAISECOM_CONSOLE, user, password);
|
|
5551
|
+
await consoleLogin(opened.channel, RAISECOM_CONSOLE, user, password, console2.passwordOnly);
|
|
5546
5552
|
await send(opened.channel, "terminal page-break disable", 15000, 1000);
|
|
5547
5553
|
return session;
|
|
5548
5554
|
}
|
|
@@ -7478,7 +7484,7 @@ async function consoleSession2(device) {
|
|
|
7478
7484
|
const { console: console2, user, password } = consoleOf(device);
|
|
7479
7485
|
const opened = await openConsole(console2);
|
|
7480
7486
|
try {
|
|
7481
|
-
await consoleLogin(opened.channel, ROUTEROS_CONSOLE, user, password);
|
|
7487
|
+
await consoleLogin(opened.channel, ROUTEROS_CONSOLE, user, password, console2.passwordOnly);
|
|
7482
7488
|
} catch (error) {
|
|
7483
7489
|
opened.close();
|
|
7484
7490
|
throw error;
|
|
@@ -7487,7 +7493,7 @@ async function consoleSession2(device) {
|
|
|
7487
7493
|
const session = {
|
|
7488
7494
|
run: (command, timeout) => shell.run(command, timeout),
|
|
7489
7495
|
fresh: async () => {
|
|
7490
|
-
await consoleLogin(opened.channel, ROUTEROS_CONSOLE, user, password);
|
|
7496
|
+
await consoleLogin(opened.channel, ROUTEROS_CONSOLE, user, password, console2.passwordOnly);
|
|
7491
7497
|
return session;
|
|
7492
7498
|
},
|
|
7493
7499
|
end: () => {
|
|
@@ -7726,7 +7732,7 @@ var DEVICE_FACTS3 = [
|
|
|
7726
7732
|
"domain default",
|
|
7727
7733
|
"Info:"
|
|
7728
7734
|
];
|
|
7729
|
-
var UNDO_BY_KEYWORD2 = /^(description|name|port default vlan|port link-type|port trunk pvid vlan|set authentication password|eth-trunk|ip binding vpn-instance)\b/;
|
|
7735
|
+
var UNDO_BY_KEYWORD2 = /^(description|name|port default vlan|port link-type|port trunk pvid vlan|set authentication password|eth-trunk|ip binding vpn-instance|ssh server cipher|ssh server hmac|ssh server key-exchange)\b/;
|
|
7730
7736
|
function undo2(line) {
|
|
7731
7737
|
if (line.startsWith("undo "))
|
|
7732
7738
|
return line.slice("undo ".length);
|
|
@@ -8176,7 +8182,10 @@ var VRP_CONSOLE = {
|
|
|
8176
8182
|
password: /Password:\s*$/i,
|
|
8177
8183
|
prompt: /<[^>\n]+>\s*$/,
|
|
8178
8184
|
logout: "quit",
|
|
8185
|
+
refused: /The password is invalid/i,
|
|
8179
8186
|
others: [
|
|
8187
|
+
[/Please Press ENTER\.?\s*$/i, `
|
|
8188
|
+
`],
|
|
8180
8189
|
[/Change now\? ?\[Y\/N\]:?\s*$/i, `N
|
|
8181
8190
|
`],
|
|
8182
8191
|
[/\[~?\*?[^\]\n]+\]\s*$/, `return
|
|
@@ -8187,7 +8196,7 @@ async function onConsole2(device, work) {
|
|
|
8187
8196
|
const { console: console2, user, password } = consoleOf(device);
|
|
8188
8197
|
const opened = await openConsole(console2);
|
|
8189
8198
|
const login = async () => {
|
|
8190
|
-
await consoleLogin(opened.channel, VRP_CONSOLE, user, password);
|
|
8199
|
+
await consoleLogin(opened.channel, VRP_CONSOLE, user, password, console2.passwordOnly);
|
|
8191
8200
|
await send(opened.channel, "screen-length 0 temporary", 1e4, 1500);
|
|
8192
8201
|
};
|
|
8193
8202
|
try {
|
|
@@ -18,7 +18,7 @@ export declare function login(device: Device): Promise<Client>;
|
|
|
18
18
|
* Every login asks whether to change the password. Answering yes replaces it at once and every stored credential with it, so the answer is always no. Anything sent before the question is answered is taken as the answer.
|
|
19
19
|
*/
|
|
20
20
|
export declare function openShell(client: Client): Promise<ClientChannel>;
|
|
21
|
-
/** How the console asks for a login: the same question to change the password, always answered no,
|
|
21
|
+
/** How the console asks for a login: the same question to change the password, always answered no, a system view left with `return`, and the line's banner after a logout or a refused password answered with Enter. Not yet proved on a device. */
|
|
22
22
|
export declare const VRP_CONSOLE: ConsoleDialect;
|
|
23
23
|
/** What the device runs, credentials scrubbed. */
|
|
24
24
|
export declare function read(device: Device, _network?: readonly Device[], options?: ReadOptions): Promise<string>;
|
|
@@ -36,6 +36,8 @@ export type Console = {
|
|
|
36
36
|
port: string;
|
|
37
37
|
/** Line speed in bits per second, such as 9600 or 115200. */
|
|
38
38
|
baud: number;
|
|
39
|
+
/** The console asks only for a password, with no name first. Circuit then types no name; `connection.user` still picks the password. */
|
|
40
|
+
passwordOnly?: true;
|
|
39
41
|
};
|
|
40
42
|
/** A separate routing table, with the interfaces that route in it. */
|
|
41
43
|
export type Vrf = {
|
|
@@ -27,6 +27,8 @@ export type ConsoleDialect = {
|
|
|
27
27
|
others?: readonly (readonly [RegExp, string])[];
|
|
28
28
|
/** The name typed at the login prompt, when the platform adds to it. */
|
|
29
29
|
user?: (user: string) => string;
|
|
30
|
+
/** What the console prints when it refuses a password, for a console that then shows no login prompt. */
|
|
31
|
+
refused?: RegExp;
|
|
30
32
|
};
|
|
31
33
|
/** The channel a console speaks on: an SSH stream from the relay, or anything shaped like one. */
|
|
32
34
|
type Channel = Pick<ClientChannel, "write"> & {
|
|
@@ -39,9 +41,9 @@ export declare function expect(channel: Channel, write: string, patterns: readon
|
|
|
39
41
|
text: string;
|
|
40
42
|
}>;
|
|
41
43
|
/**
|
|
42
|
-
* Log in on a console, whatever state the last session left it in: what `others` names is moved on, a session left open is logged out, so every session starts from a fresh login. Returns once the CLI's prompt shows.
|
|
44
|
+
* Log in on a console, whatever state the last session left it in: what `others` names is moved on, a session left open is logged out, so every session starts from a fresh login. Returns once the CLI's prompt shows. A console that asks only for a password (`passwordOnly`) is logged in at its password prompt, with no name typed.
|
|
43
45
|
*/
|
|
44
|
-
export declare function consoleLogin(channel: Channel, dialect: ConsoleDialect, user: string, password: string): Promise<void>;
|
|
46
|
+
export declare function consoleLogin(channel: Channel, dialect: ConsoleDialect, user: string, password: string, passwordOnly?: boolean): Promise<void>;
|
|
45
47
|
/** The text after a command's echo. A terminal wraps a long echo where it likes, so the echo is matched character by character, line breaks it added skipped. */
|
|
46
48
|
export declare function afterEcho(text: string, command: string): string;
|
|
47
49
|
/** A CLI on a console that runs one command at a time and returns what it printed, as an SSH exec would: no echo, no prompt. */
|
package/package.json
CHANGED
|
@@ -173,9 +173,9 @@ async function consoleSession<T>(device: Device, work: (cli: Cli, relogin: () =>
|
|
|
173
173
|
const opened = await openConsole(console);
|
|
174
174
|
|
|
175
175
|
try {
|
|
176
|
-
await consoleLogin(opened.channel, FORTIOS_CONSOLE, user, password);
|
|
176
|
+
await consoleLogin(opened.channel, FORTIOS_CONSOLE, user, password, console.passwordOnly);
|
|
177
177
|
const cli = new Cli(opened.channel);
|
|
178
|
-
const relogin = () => consoleLogin(opened.channel, FORTIOS_CONSOLE, user, password);
|
|
178
|
+
const relogin = () => consoleLogin(opened.channel, FORTIOS_CONSOLE, user, password, console.passwordOnly);
|
|
179
179
|
|
|
180
180
|
const result = await work(cli, relogin);
|
|
181
181
|
await consoleLogout(cli);
|
|
@@ -375,7 +375,7 @@ async function onConsole<T>(device: Device, work: (channel: ClientChannel, relog
|
|
|
375
375
|
|
|
376
376
|
// The CLI is opened on a prompt asked for after the login, since the login's own prompt is already read.
|
|
377
377
|
const login = async () => {
|
|
378
|
-
await consoleLogin(opened.channel, JUNOS_CONSOLE, user, password);
|
|
378
|
+
await consoleLogin(opened.channel, JUNOS_CONSOLE, user, password, console.passwordOnly);
|
|
379
379
|
const ready = openCli(opened.channel);
|
|
380
380
|
opened.channel.write("\n");
|
|
381
381
|
await ready;
|
|
@@ -371,7 +371,7 @@ async function consoleShell(device: Device): Promise<Session> {
|
|
|
371
371
|
const opened = await openConsole(console);
|
|
372
372
|
|
|
373
373
|
try {
|
|
374
|
-
await consoleLogin(opened.channel, RAISECOM_CONSOLE, user, password);
|
|
374
|
+
await consoleLogin(opened.channel, RAISECOM_CONSOLE, user, password, console.passwordOnly);
|
|
375
375
|
} catch (error) {
|
|
376
376
|
opened.close();
|
|
377
377
|
throw error;
|
|
@@ -384,7 +384,7 @@ async function consoleShell(device: Device): Promise<Session> {
|
|
|
384
384
|
opened.close();
|
|
385
385
|
},
|
|
386
386
|
fresh: async () => {
|
|
387
|
-
await consoleLogin(opened.channel, RAISECOM_CONSOLE, user, password);
|
|
387
|
+
await consoleLogin(opened.channel, RAISECOM_CONSOLE, user, password, console.passwordOnly);
|
|
388
388
|
await send(opened.channel, "terminal page-break disable", 15_000, 1000);
|
|
389
389
|
return session;
|
|
390
390
|
},
|
|
@@ -68,7 +68,7 @@ async function consoleSession(device: Device): Promise<Session> {
|
|
|
68
68
|
const opened = await openConsole(console);
|
|
69
69
|
|
|
70
70
|
try {
|
|
71
|
-
await consoleLogin(opened.channel, ROUTEROS_CONSOLE, user, password);
|
|
71
|
+
await consoleLogin(opened.channel, ROUTEROS_CONSOLE, user, password, console.passwordOnly);
|
|
72
72
|
} catch (error) {
|
|
73
73
|
opened.close();
|
|
74
74
|
throw error;
|
|
@@ -79,7 +79,7 @@ async function consoleSession(device: Device): Promise<Session> {
|
|
|
79
79
|
const session: Session = {
|
|
80
80
|
run: (command, timeout) => shell.run(command, timeout),
|
|
81
81
|
fresh: async () => {
|
|
82
|
-
await consoleLogin(opened.channel, ROUTEROS_CONSOLE, user, password);
|
|
82
|
+
await consoleLogin(opened.channel, ROUTEROS_CONSOLE, user, password, console.passwordOnly);
|
|
83
83
|
return session;
|
|
84
84
|
},
|
|
85
85
|
end: () => {
|
|
@@ -21,3 +21,5 @@ Behaviour that looks like something else. Each one has been hit on a CE6855 runn
|
|
|
21
21
|
- sFlow samples per port, `sflow sampling rate N`, N from 4096; the default is 8192 and an explicit 8192 is kept as written. A port can point to a collector only once the collector exists globally; until then it is refused with "collector does not exist".
|
|
22
22
|
- `vlan batch` has every VLAN on one line, and undoing a list that includes a VLAN with an L3 interface is refused. A change sends only the VLANs added and `undo vlan batch` for the ones removed.
|
|
23
23
|
- A VLAN name is unique across VLANs: a new VLAN cannot take a name another still has. VLANs no longer used, and their VLAN interfaces, are removed first, in the same candidate.
|
|
24
|
+
- `undo ssh server key-exchange <algorithms>` is refused with "Too many parameters"; `undo ssh server key-exchange` takes none. `hmac` was proved the same way. `cipher` is undone the same way but was not proved on its own.
|
|
25
|
+
- A console on a password only (`passwordOnly`) shows `Please Press ENTER.` after a logout or a refused password, and `Error: The password is invalid.` for a wrong one.
|
|
@@ -37,7 +37,7 @@ const DEVICE_FACTS = [
|
|
|
37
37
|
|
|
38
38
|
/** Lines whose removal takes only their keyword, not their value. */
|
|
39
39
|
const UNDO_BY_KEYWORD =
|
|
40
|
-
/^(description|name|port default vlan|port link-type|port trunk pvid vlan|set authentication password|eth-trunk|ip binding vpn-instance)\b/;
|
|
40
|
+
/^(description|name|port default vlan|port link-type|port trunk pvid vlan|set authentication password|eth-trunk|ip binding vpn-instance|ssh server cipher|ssh server hmac|ssh server key-exchange)\b/;
|
|
41
41
|
|
|
42
42
|
function undo(line: string): string {
|
|
43
43
|
// A negated line is undone by stating it.
|
|
@@ -571,13 +571,15 @@ export async function openShell(client: Client): Promise<ClientChannel> {
|
|
|
571
571
|
return channel;
|
|
572
572
|
}
|
|
573
573
|
|
|
574
|
-
/** How the console asks for a login: the same question to change the password, always answered no,
|
|
574
|
+
/** How the console asks for a login: the same question to change the password, always answered no, a system view left with `return`, and the line's banner after a logout or a refused password answered with Enter. Not yet proved on a device. */
|
|
575
575
|
export const VRP_CONSOLE: ConsoleDialect = {
|
|
576
576
|
login: /Username:\s*$/i,
|
|
577
577
|
password: /Password:\s*$/i,
|
|
578
578
|
prompt: /<[^>\n]+>\s*$/,
|
|
579
579
|
logout: "quit",
|
|
580
|
+
refused: /The password is invalid/i,
|
|
580
581
|
others: [
|
|
582
|
+
[/Please Press ENTER\.?\s*$/i, "\n"],
|
|
581
583
|
[/Change now\? ?\[Y\/N\]:?\s*$/i, "N\n"],
|
|
582
584
|
[/\[~?\*?[^\]\n]+\]\s*$/, "return\n"],
|
|
583
585
|
],
|
|
@@ -589,7 +591,7 @@ async function onConsole<T>(device: Device, work: (channel: ClientChannel, relog
|
|
|
589
591
|
const opened = await openConsole(console);
|
|
590
592
|
|
|
591
593
|
const login = async () => {
|
|
592
|
-
await consoleLogin(opened.channel, VRP_CONSOLE, user, password);
|
|
594
|
+
await consoleLogin(opened.channel, VRP_CONSOLE, user, password, console.passwordOnly);
|
|
593
595
|
await send(opened.channel, "screen-length 0 temporary", 10_000, 1500);
|
|
594
596
|
};
|
|
595
597
|
|
package/src/schema/device.ts
CHANGED
|
@@ -39,6 +39,8 @@ export type Console = {
|
|
|
39
39
|
port: string;
|
|
40
40
|
/** Line speed in bits per second, such as 9600 or 115200. */
|
|
41
41
|
baud: number;
|
|
42
|
+
/** The console asks only for a password, with no name first. Circuit then types no name; `connection.user` still picks the password. */
|
|
43
|
+
passwordOnly?: true;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
/** A separate routing table, with the interfaces that route in it. */
|
package/src/transport/console.ts
CHANGED
|
@@ -250,6 +250,8 @@ export type ConsoleDialect = {
|
|
|
250
250
|
others?: readonly (readonly [RegExp, string])[];
|
|
251
251
|
/** The name typed at the login prompt, when the platform adds to it. */
|
|
252
252
|
user?: (user: string) => string;
|
|
253
|
+
/** What the console prints when it refuses a password, for a console that then shows no login prompt. */
|
|
254
|
+
refused?: RegExp;
|
|
253
255
|
};
|
|
254
256
|
|
|
255
257
|
/** Terminal escape sequences: cursor moves, screen clears, character sets and modes. */
|
|
@@ -293,9 +295,15 @@ export function expect(channel: Channel, write: string, patterns: readonly RegEx
|
|
|
293
295
|
}
|
|
294
296
|
|
|
295
297
|
/**
|
|
296
|
-
* Log in on a console, whatever state the last session left it in: what `others` names is moved on, a session left open is logged out, so every session starts from a fresh login. Returns once the CLI's prompt shows.
|
|
298
|
+
* Log in on a console, whatever state the last session left it in: what `others` names is moved on, a session left open is logged out, so every session starts from a fresh login. Returns once the CLI's prompt shows. A console that asks only for a password (`passwordOnly`) is logged in at its password prompt, with no name typed.
|
|
297
299
|
*/
|
|
298
|
-
export async function consoleLogin(
|
|
300
|
+
export async function consoleLogin(
|
|
301
|
+
channel: Channel,
|
|
302
|
+
dialect: ConsoleDialect,
|
|
303
|
+
user: string,
|
|
304
|
+
password: string,
|
|
305
|
+
passwordOnly = false
|
|
306
|
+
): Promise<void> {
|
|
299
307
|
const others = dialect.others ?? [];
|
|
300
308
|
const states = [dialect.login, dialect.password, ...others.map(([pattern]) => pattern), dialect.prompt];
|
|
301
309
|
|
|
@@ -309,32 +317,39 @@ export async function consoleLogin(channel: Channel, dialect: ConsoleDialect, us
|
|
|
309
317
|
// What the last session printed as it left, such as a login prompt after its logout, arrives late; it must not be taken for an answer.
|
|
310
318
|
await quiet(channel, 1_000);
|
|
311
319
|
|
|
320
|
+
// The prompt a fresh login starts at: the login prompt, or the password prompt when there is no name to type.
|
|
321
|
+
const start = passwordOnly ? 1 : 0;
|
|
322
|
+
|
|
312
323
|
let state = (await expect(channel, "\n", states)).index;
|
|
313
|
-
for (let step = 0; state !==
|
|
314
|
-
if (
|
|
324
|
+
for (let step = 0; state !== start; step++) {
|
|
325
|
+
if (passwordOnly && state === 0) throw new Error("the console asks for a name, but its console is set to ask only for a password");
|
|
326
|
+
if (step >= 10) throw new Error(`the console never reached a ${passwordOnly ? "password" : "login"} prompt`);
|
|
315
327
|
state = (await expect(channel, toward(state), states)).index;
|
|
316
328
|
}
|
|
317
329
|
|
|
318
330
|
// A console can print the login prompt more than once; a name typed while it does is lost.
|
|
319
331
|
await quiet(channel, 1_000);
|
|
320
332
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
333
|
+
if (!passwordOnly) {
|
|
334
|
+
const name = dialect.user ? dialect.user(user) : user;
|
|
335
|
+
if ((await expect(channel, `${name}\n`, [dialect.password, dialect.login, dialect.prompt])).index !== 0) {
|
|
336
|
+
throw new Error("the console did not ask for a password");
|
|
337
|
+
}
|
|
324
338
|
}
|
|
325
339
|
|
|
326
|
-
// A platform may ask something once logged in, such as whether to read a licence; `others` answers it.
|
|
327
|
-
|
|
328
|
-
|
|
340
|
+
// A platform may ask something once logged in, such as whether to read a licence; `others` answers it. A refusal is checked before `others`, since the banner that follows it matches one of them.
|
|
341
|
+
const refusal = dialect.refused ? [dialect.refused] : [];
|
|
342
|
+
const first = 2 + refusal.length;
|
|
343
|
+
const answers = [dialect.prompt, dialect.login, ...refusal, ...others.map(([pattern]) => pattern)];
|
|
344
|
+
|
|
345
|
+
let answer = await expect(channel, `${password}\n`, answers, 60_000);
|
|
346
|
+
for (let step = 0; answer.index >= first; step++) {
|
|
329
347
|
if (step >= 5) throw new Error("the console kept asking after the login");
|
|
330
|
-
answer = await expect(channel, others[answer.index -
|
|
331
|
-
dialect.prompt,
|
|
332
|
-
dialect.login,
|
|
333
|
-
...others.map(([pattern]) => pattern),
|
|
334
|
-
]);
|
|
348
|
+
answer = await expect(channel, others[answer.index - first]![1], answers);
|
|
335
349
|
}
|
|
336
350
|
|
|
337
351
|
if (answer.index === 1) throw new Error(`the console refused the login of ${user}`);
|
|
352
|
+
if (answer.index === 2 && dialect.refused) throw new Error("the console refused the password");
|
|
338
353
|
|
|
339
354
|
// Some consoles print the prompt twice after a login; what arrives late must not reach the first command.
|
|
340
355
|
await quiet(channel, 800);
|