fauxnix-cli 0.1.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/LICENSE +21 -0
- package/README.md +179 -0
- package/dist/ast.d.ts +72 -0
- package/dist/ast.js +43 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +91 -0
- package/dist/commands/archive.d.ts +2 -0
- package/dist/commands/archive.js +385 -0
- package/dist/commands/files.d.ts +2 -0
- package/dist/commands/files.js +721 -0
- package/dist/commands/install-all.d.ts +2 -0
- package/dist/commands/install-all.js +17 -0
- package/dist/commands/net.d.ts +2 -0
- package/dist/commands/net.js +533 -0
- package/dist/commands/sysinfo.d.ts +2 -0
- package/dist/commands/sysinfo.js +1138 -0
- package/dist/commands/text-filters.d.ts +2 -0
- package/dist/commands/text-filters.js +2281 -0
- package/dist/commands/text-io.d.ts +2 -0
- package/dist/commands/text-io.js +1164 -0
- package/dist/encoding.d.ts +7 -0
- package/dist/encoding.js +29 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +103 -0
- package/dist/executor.d.ts +27 -0
- package/dist/executor.js +299 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +79 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.js +400 -0
- package/dist/registry.d.ts +79 -0
- package/dist/registry.js +145 -0
- package/dist/translator.d.ts +55 -0
- package/dist/translator.js +318 -0
- package/package.json +57 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { registerAll } from '../registry.js';
|
|
2
|
+
import { handlers as files } from './files.js';
|
|
3
|
+
import { handlers as textFilters } from './text-filters.js';
|
|
4
|
+
import { handlers as textIo } from './text-io.js';
|
|
5
|
+
import { handlers as sysinfo } from './sysinfo.js';
|
|
6
|
+
import { handlers as net } from './net.js';
|
|
7
|
+
import { handlers as archive } from './archive.js';
|
|
8
|
+
/** Register every built-in Linux command translator. Side-effectful import. */
|
|
9
|
+
export function installAll() {
|
|
10
|
+
registerAll(files);
|
|
11
|
+
registerAll(textFilters);
|
|
12
|
+
registerAll(textIo);
|
|
13
|
+
registerAll(sysinfo);
|
|
14
|
+
registerAll(net);
|
|
15
|
+
registerAll(archive);
|
|
16
|
+
}
|
|
17
|
+
installAll();
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import { wordToString } from '../ast.js';
|
|
2
|
+
import { parseWords, psErr, psStr } from '../registry.js';
|
|
3
|
+
import { exprOfWord, literalOfWord, operandExpr } from '../translator.js';
|
|
4
|
+
/* ------------------------------------------------------------------ */
|
|
5
|
+
/* Shared PS snippets */
|
|
6
|
+
/* ------------------------------------------------------------------ */
|
|
7
|
+
/**
|
|
8
|
+
* Mimosa security guard for HTTP(S) client commands (curl/wget): refuse
|
|
9
|
+
* loopback / private / reserved destinations before any request is made.
|
|
10
|
+
* Applied to every argv element that starts with http:// or https://.
|
|
11
|
+
*/
|
|
12
|
+
const PS_NETGUARD_FNS = [
|
|
13
|
+
'function fx-badhost($h) {',
|
|
14
|
+
' $h = ([string]$h).Trim(\'[]\').ToLower()',
|
|
15
|
+
" if ($h -eq '') { return $false }",
|
|
16
|
+
" if ($h -eq 'localhost' -or $h -eq '::1') { return $true }",
|
|
17
|
+
" if ($h.StartsWith('127.') -or $h.StartsWith('10.') -or $h.StartsWith('192.168.') -or $h.StartsWith('169.254.')) { return $true }",
|
|
18
|
+
" if ($h.StartsWith('172.')) {",
|
|
19
|
+
" $fx_p = $h.Split('.')",
|
|
20
|
+
' if ($fx_p.Count -ge 2) { $fx_n = 0; if ([int]::TryParse($fx_p[1], [ref]$fx_n)) { if ($fx_n -ge 16 -and $fx_n -le 31) { return $true } } }',
|
|
21
|
+
' }',
|
|
22
|
+
' return $false',
|
|
23
|
+
'}',
|
|
24
|
+
'function fx-urlhost($u) {',
|
|
25
|
+
' $fx_r = [string]$u',
|
|
26
|
+
" $fx_i = $fx_r.IndexOf('://')",
|
|
27
|
+
' if ($fx_i -lt 0) { return \'\' }',
|
|
28
|
+
' $fx_r = $fx_r.Substring($fx_i + 3)',
|
|
29
|
+
" foreach ($fx_c in @('/', '?', '#')) { $fx_i = $fx_r.IndexOf($fx_c); if ($fx_i -ge 0) { $fx_r = $fx_r.Substring(0, $fx_i) } }",
|
|
30
|
+
" $fx_i = $fx_r.LastIndexOf('@')",
|
|
31
|
+
' if ($fx_i -ge 0) { $fx_r = $fx_r.Substring($fx_i + 1) }',
|
|
32
|
+
" if ($fx_r.StartsWith('[')) {",
|
|
33
|
+
" $fx_j = $fx_r.IndexOf(']')",
|
|
34
|
+
' if ($fx_j -ge 0) { $fx_r = $fx_r.Substring(0, $fx_j + 1) }',
|
|
35
|
+
' } else {',
|
|
36
|
+
" $fx_i = $fx_r.IndexOf(':')",
|
|
37
|
+
' if ($fx_i -ge 0) { $fx_r = $fx_r.Substring(0, $fx_i) }',
|
|
38
|
+
' }',
|
|
39
|
+
" return $fx_r.Trim('[]')",
|
|
40
|
+
'}',
|
|
41
|
+
'function fx-netguard($cmd, $a) {',
|
|
42
|
+
' $fx_s = [string]$a',
|
|
43
|
+
" if ($fx_s.StartsWith('http://', [System.StringComparison]::OrdinalIgnoreCase) -or $fx_s.StartsWith('https://', [System.StringComparison]::OrdinalIgnoreCase)) {",
|
|
44
|
+
' $fx_h = fx-urlhost $fx_s',
|
|
45
|
+
' if (fx-badhost $fx_h) {',
|
|
46
|
+
" [Console]::Error.WriteLine($cmd + ': fauxnix refused private/loopback address ' + $fx_h)",
|
|
47
|
+
' return $true',
|
|
48
|
+
' }',
|
|
49
|
+
' }',
|
|
50
|
+
' return $false',
|
|
51
|
+
'}',
|
|
52
|
+
].join('\n');
|
|
53
|
+
/** IPv4 netmask math helpers (prefix → mask/broadcast), shared by ip/ifconfig. */
|
|
54
|
+
const PS_IP_MASK_FNS = [
|
|
55
|
+
'function fx-mask($len, $oct) {',
|
|
56
|
+
' $fx_rem = $len - ($oct * 8)',
|
|
57
|
+
' if ($fx_rem -le 0) { return 0 }',
|
|
58
|
+
' if ($fx_rem -ge 8) { return 255 }',
|
|
59
|
+
' return ((255 -shl (8 - $fx_rem)) -band 255)',
|
|
60
|
+
'}',
|
|
61
|
+
'function fx-nm($len) {',
|
|
62
|
+
' $fx_o = @()',
|
|
63
|
+
' for ($fx_i = 0; $fx_i -lt 4; $fx_i++) { $fx_o += [string](fx-mask $len $fx_i) }',
|
|
64
|
+
" return ($fx_o -join '.')",
|
|
65
|
+
'}',
|
|
66
|
+
'function fx-brd($ip, $len) {',
|
|
67
|
+
" $fx_b = ([string]$ip).Split('.')",
|
|
68
|
+
' $fx_o = @()',
|
|
69
|
+
' for ($fx_i = 0; $fx_i -lt 4; $fx_i++) {',
|
|
70
|
+
' $fx_m = fx-mask $len $fx_i',
|
|
71
|
+
' $fx_o += [string](([int]$fx_b[$fx_i]) -bor (255 -bxor $fx_m))',
|
|
72
|
+
' }',
|
|
73
|
+
" return ($fx_o -join '.')",
|
|
74
|
+
'}',
|
|
75
|
+
].join('\n');
|
|
76
|
+
/** Synthetic literal Word (for values extracted out of a larger argument). */
|
|
77
|
+
function synthWord(text) {
|
|
78
|
+
return [{ kind: 'Text', text }];
|
|
79
|
+
}
|
|
80
|
+
/** A native-exe invocation obeying the fauxnix contract (string lines + exit code). */
|
|
81
|
+
function nativeCall(exe, argArray) {
|
|
82
|
+
return [
|
|
83
|
+
'& ' + psStr(exe) + ' @(' + argArray + ') | ForEach-Object { [string]$_ }',
|
|
84
|
+
'if ($LASTEXITCODE -gt 0) { $script:fx_exit = $LASTEXITCODE }',
|
|
85
|
+
].join('\n');
|
|
86
|
+
}
|
|
87
|
+
/* ------------------------------------------------------------------ */
|
|
88
|
+
/* curl */
|
|
89
|
+
/* ------------------------------------------------------------------ */
|
|
90
|
+
const curl = (args) => {
|
|
91
|
+
// CRITICAL: in PS 5.1 `curl` is an alias for Invoke-WebRequest — the real
|
|
92
|
+
// curl must be invoked explicitly as curl.exe. All args pass through
|
|
93
|
+
// untouched (Windows curl.exe is real curl); the runtime host guard below
|
|
94
|
+
// refuses private/loopback URLs before the process is even started.
|
|
95
|
+
return [
|
|
96
|
+
PS_NETGUARD_FNS,
|
|
97
|
+
"$fx_args = @(" + args.map(exprOfWord).join(', ') + ')',
|
|
98
|
+
'$fx_bad = $false',
|
|
99
|
+
"foreach ($fx_a in $fx_args) { if (fx-netguard 'curl' $fx_a) { $fx_bad = $true } }",
|
|
100
|
+
'if ($fx_bad) { $script:fx_exit = 1 }',
|
|
101
|
+
'else {',
|
|
102
|
+
" " + nativeCall('curl.exe', '$fx_args'),
|
|
103
|
+
'}',
|
|
104
|
+
].join('\n');
|
|
105
|
+
};
|
|
106
|
+
/* ------------------------------------------------------------------ */
|
|
107
|
+
/* wget */
|
|
108
|
+
/* ------------------------------------------------------------------ */
|
|
109
|
+
/** GNU wget default output filename derived from a literal URL (index.html when bare). */
|
|
110
|
+
function urlFileName(u) {
|
|
111
|
+
// keep only the path component (drop scheme, host, query, fragment)
|
|
112
|
+
let p = u.split('#')[0].split('?')[0];
|
|
113
|
+
const sm = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.exec(p);
|
|
114
|
+
if (sm)
|
|
115
|
+
p = p.slice(sm[0].length);
|
|
116
|
+
const slash = p.indexOf('/');
|
|
117
|
+
if (slash >= 0)
|
|
118
|
+
p = p.slice(slash + 1);
|
|
119
|
+
else
|
|
120
|
+
p = '';
|
|
121
|
+
if (p === '')
|
|
122
|
+
return 'index.html'; // no path (or trailing /) → GNU default
|
|
123
|
+
return p;
|
|
124
|
+
}
|
|
125
|
+
/** Map GNU wget argv → curl.exe argv (only literal words are rewritten). */
|
|
126
|
+
function mapWgetArgs(args) {
|
|
127
|
+
const margs = [];
|
|
128
|
+
const urls = [];
|
|
129
|
+
let sawOutput = false;
|
|
130
|
+
const raw = args.map(wordToString);
|
|
131
|
+
let i = 0;
|
|
132
|
+
while (i < args.length) {
|
|
133
|
+
const lit = literalOfWord(args[i]);
|
|
134
|
+
if (lit === null) {
|
|
135
|
+
margs.push(exprOfWord(args[i]));
|
|
136
|
+
i++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// bundles like -q, -qO-, -O-
|
|
140
|
+
const m = /^-(q*)O(.*)$/.exec(lit);
|
|
141
|
+
if (m) {
|
|
142
|
+
if (m[1].length > 0)
|
|
143
|
+
margs.push("'-s'");
|
|
144
|
+
const rest = m[2];
|
|
145
|
+
if (rest === '') {
|
|
146
|
+
if (i + 1 < args.length) {
|
|
147
|
+
const v = wordToString(args[i + 1]);
|
|
148
|
+
if (v === '-') {
|
|
149
|
+
sawOutput = true; // -O - → curl writes to stdout by default
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
margs.push("'-o'", operandExpr(args[i + 1]));
|
|
153
|
+
sawOutput = true;
|
|
154
|
+
}
|
|
155
|
+
i++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else if (rest === '-') {
|
|
159
|
+
sawOutput = true; // -O- → stdout passthrough (curl default)
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
margs.push("'-o'", operandExpr(synthWord(rest)));
|
|
163
|
+
sawOutput = true;
|
|
164
|
+
}
|
|
165
|
+
i++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (lit === '--output-document' && i + 1 < args.length) {
|
|
169
|
+
const v = wordToString(args[i + 1]);
|
|
170
|
+
if (v === '-') {
|
|
171
|
+
sawOutput = true;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
margs.push("'-o'", operandExpr(args[i + 1]));
|
|
175
|
+
sawOutput = true;
|
|
176
|
+
}
|
|
177
|
+
i += 2;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const eq = /^--output-document=(.*)$/.exec(lit);
|
|
181
|
+
if (eq) {
|
|
182
|
+
if (eq[1] === '') {
|
|
183
|
+
sawOutput = true;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
margs.push("'-o'", operandExpr(synthWord(eq[1])));
|
|
187
|
+
sawOutput = true;
|
|
188
|
+
}
|
|
189
|
+
i++;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (lit === '-q' || lit === '--quiet') {
|
|
193
|
+
margs.push("'-s'");
|
|
194
|
+
i++;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (lit === '--spider') {
|
|
198
|
+
margs.push("'-I'");
|
|
199
|
+
i++;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (/^https?:\/\//i.test(lit))
|
|
203
|
+
urls.push(lit);
|
|
204
|
+
margs.push(exprOfWord(args[i]));
|
|
205
|
+
i++;
|
|
206
|
+
}
|
|
207
|
+
// GNU wget saves the document in the current directory when -O is absent;
|
|
208
|
+
// curl would dump it on stdout, so derive the filename from the URL.
|
|
209
|
+
if (!sawOutput && urls.length === 1) {
|
|
210
|
+
margs.push("'-o'", operandExpr(synthWord(urlFileName(urls[0]))));
|
|
211
|
+
}
|
|
212
|
+
return { margs, sawOutput, urls };
|
|
213
|
+
}
|
|
214
|
+
const wget = (args) => {
|
|
215
|
+
const orig = args.map(exprOfWord);
|
|
216
|
+
const mapped = mapWgetArgs(args);
|
|
217
|
+
return [
|
|
218
|
+
PS_NETGUARD_FNS,
|
|
219
|
+
'$fx_args = @(' + orig.join(', ') + ')',
|
|
220
|
+
'$fx_margs = @(' + mapped.margs.join(', ') + ')',
|
|
221
|
+
'$fx_bad = $false',
|
|
222
|
+
"foreach ($fx_a in $fx_args) { if (fx-netguard 'wget' $fx_a) { $fx_bad = $true } }",
|
|
223
|
+
'if ($fx_bad) { $script:fx_exit = 1 }',
|
|
224
|
+
// real GNU wget on PATH (Git Bash etc.) → use it natively with the original argv
|
|
225
|
+
"elseif (Get-Command 'wget.exe' -ErrorAction SilentlyContinue) {",
|
|
226
|
+
" " + nativeCall('wget.exe', '$fx_args'),
|
|
227
|
+
'}',
|
|
228
|
+
// otherwise map onto curl.exe
|
|
229
|
+
'else {',
|
|
230
|
+
" " + nativeCall('curl.exe', '$fx_margs'),
|
|
231
|
+
'}',
|
|
232
|
+
].join('\n');
|
|
233
|
+
};
|
|
234
|
+
/* ------------------------------------------------------------------ */
|
|
235
|
+
/* ping */
|
|
236
|
+
/* ------------------------------------------------------------------ */
|
|
237
|
+
const ping = (args) => {
|
|
238
|
+
const raw = args.map(wordToString);
|
|
239
|
+
const out = [];
|
|
240
|
+
let hasCount = false;
|
|
241
|
+
let i = 0;
|
|
242
|
+
while (i < args.length) {
|
|
243
|
+
const t = raw[i];
|
|
244
|
+
let handled = false;
|
|
245
|
+
let m = /^-c(\d+)$/.exec(t);
|
|
246
|
+
if (m) {
|
|
247
|
+
out.push("'-n'", psStr(m[1]));
|
|
248
|
+
hasCount = true;
|
|
249
|
+
handled = true;
|
|
250
|
+
}
|
|
251
|
+
if (!handled && t === '-c' && i + 1 < args.length) {
|
|
252
|
+
out.push("'-n'", exprOfWord(args[i + 1]));
|
|
253
|
+
hasCount = true;
|
|
254
|
+
i++;
|
|
255
|
+
handled = true;
|
|
256
|
+
}
|
|
257
|
+
m = /^-W(\d+(?:\.\d+)?)$/.exec(t);
|
|
258
|
+
if (!handled && m) {
|
|
259
|
+
out.push("'-w'", psStr(String(Math.round(parseFloat(m[1]) * 1000))));
|
|
260
|
+
handled = true;
|
|
261
|
+
}
|
|
262
|
+
if (!handled && t === '-W' && i + 1 < args.length && /^\d+(\.\d+)?$/.test(raw[i + 1])) {
|
|
263
|
+
out.push("'-w'", psStr(String(Math.round(parseFloat(raw[i + 1]) * 1000))));
|
|
264
|
+
i++;
|
|
265
|
+
handled = true;
|
|
266
|
+
}
|
|
267
|
+
// -i interval: no equivalent — dropped (with its value)
|
|
268
|
+
if (!handled && /^-i\d+(\.\d+)?$/.test(t)) {
|
|
269
|
+
handled = true;
|
|
270
|
+
}
|
|
271
|
+
if (!handled && t === '-i' && i + 1 < args.length && /^\d+(\.\d+)?$/.test(raw[i + 1])) {
|
|
272
|
+
i++;
|
|
273
|
+
handled = true;
|
|
274
|
+
}
|
|
275
|
+
if (!handled)
|
|
276
|
+
out.push(exprOfWord(args[i]));
|
|
277
|
+
i++;
|
|
278
|
+
}
|
|
279
|
+
// Linux ping defaults to endless echoing → Windows needs -t
|
|
280
|
+
if (!hasCount)
|
|
281
|
+
out.unshift("'-t'");
|
|
282
|
+
return nativeCall('ping.exe', out.join(', '));
|
|
283
|
+
};
|
|
284
|
+
/* ------------------------------------------------------------------ */
|
|
285
|
+
/* netstat / ss */
|
|
286
|
+
/* ------------------------------------------------------------------ */
|
|
287
|
+
const netstat = (args) => {
|
|
288
|
+
const raw = args.map(wordToString);
|
|
289
|
+
const out = [];
|
|
290
|
+
const notes = [];
|
|
291
|
+
let i = 0;
|
|
292
|
+
while (i < args.length) {
|
|
293
|
+
const t = raw[i];
|
|
294
|
+
// listening-socket combos (-tlnp -tlpn -tulpn -tln -tl ...) → -ano
|
|
295
|
+
const body = t.slice(1);
|
|
296
|
+
if (/^-[tulnp]+$/.test(t) &&
|
|
297
|
+
body.includes('l') &&
|
|
298
|
+
(body.includes('n') || body.includes('p'))) {
|
|
299
|
+
if (!out.includes("'-ano'"))
|
|
300
|
+
out.push("'-ano'");
|
|
301
|
+
i++;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (t === '-an' || t === '-na') {
|
|
305
|
+
if (!out.includes("'-ano'"))
|
|
306
|
+
out.push("'-ano'");
|
|
307
|
+
i++;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (t === '-p') {
|
|
311
|
+
if (i + 1 < args.length && !raw[i + 1].startsWith('-'))
|
|
312
|
+
i++;
|
|
313
|
+
notes.push('netstat: fauxnix: -p is not supported on Windows (all sockets shown)');
|
|
314
|
+
i++;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
out.push(exprOfWord(args[i]));
|
|
318
|
+
i++;
|
|
319
|
+
}
|
|
320
|
+
// if -p swallowed everything, still list sockets as -ano — a bare
|
|
321
|
+
// `netstat` (no flags) reverse-DNSes every address and takes minutes
|
|
322
|
+
if (out.length === 0)
|
|
323
|
+
out.push("'-ano'");
|
|
324
|
+
return [
|
|
325
|
+
...notes.map((n) => '[Console]::Error.WriteLine(' + psStr(n) + ')'),
|
|
326
|
+
nativeCall('netstat.exe', out.join(', ')),
|
|
327
|
+
].join('\n');
|
|
328
|
+
};
|
|
329
|
+
const ss = () => {
|
|
330
|
+
// Windows has no ss; netstat -ano is the closest (all sockets + PIDs).
|
|
331
|
+
return [
|
|
332
|
+
'[Console]::Error.WriteLine(' + psStr('ss: fauxnix maps ss → netstat -ano (Windows)') + ')',
|
|
333
|
+
nativeCall('netstat.exe', "'-ano'"),
|
|
334
|
+
].join('\n');
|
|
335
|
+
};
|
|
336
|
+
/* ------------------------------------------------------------------ */
|
|
337
|
+
/* ip / ifconfig */
|
|
338
|
+
/* ------------------------------------------------------------------ */
|
|
339
|
+
/** PS: unique interface alias list, loopback first (GNU puts lo first). */
|
|
340
|
+
const PS_IFLIST = [
|
|
341
|
+
"$fx_addrs = @(Get-NetIPAddress -ErrorAction SilentlyContinue |",
|
|
342
|
+
" Sort-Object -Property @{e={ if ($_.InterfaceAlias -like '*Loopback*') { 0 } else { 1 } }}, InterfaceAlias, AddressFamily)",
|
|
343
|
+
"$fx_ifs = @($fx_addrs | Select-Object -ExpandProperty InterfaceAlias -Unique)",
|
|
344
|
+
].join('\n');
|
|
345
|
+
const ip = (args) => {
|
|
346
|
+
const raw = args.map(wordToString);
|
|
347
|
+
let sub = '';
|
|
348
|
+
for (const t of raw) {
|
|
349
|
+
if (!t.startsWith('-')) {
|
|
350
|
+
sub = t;
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const fam = raw.includes('-6') ? 'IPv6' : raw.includes('-4') ? 'IPv4' : '';
|
|
355
|
+
const famFilter = fam ? " | Where-Object { $_.AddressFamily -eq '" + fam + "' }" : '';
|
|
356
|
+
if (sub === 'addr' || sub === 'a' || sub === 'address') {
|
|
357
|
+
return [
|
|
358
|
+
PS_IP_MASK_FNS,
|
|
359
|
+
'$fx_addrs = @(Get-NetIPAddress -ErrorAction SilentlyContinue' + famFilter + ' |',
|
|
360
|
+
" Sort-Object -Property @{e={ if ($_.InterfaceAlias -like '*Loopback*') { 0 } else { 1 } }}, InterfaceAlias, AddressFamily)",
|
|
361
|
+
"$fx_ifs = @($fx_addrs | Select-Object -ExpandProperty InterfaceAlias -Unique)",
|
|
362
|
+
'$fx_n = 0',
|
|
363
|
+
'foreach ($fx_name in $fx_ifs) {',
|
|
364
|
+
' $fx_n = $fx_n + 1',
|
|
365
|
+
" $fx_lo = ($fx_name -like '*Loopback*')",
|
|
366
|
+
" if ($fx_lo) { ('' + $fx_n + ': ' + $fx_name + ': <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000') }",
|
|
367
|
+
" else { ('' + $fx_n + ': ' + $fx_name + ': <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000') }",
|
|
368
|
+
' foreach ($fx_a in @($fx_addrs | Where-Object { $_.InterfaceAlias -eq $fx_name })) {',
|
|
369
|
+
" if ($fx_a.AddressFamily -eq 'IPv4') {",
|
|
370
|
+
" $fx_scope = 'global'",
|
|
371
|
+
" if ($fx_lo) { $fx_scope = 'host' } elseif ($fx_a.IPAddress.StartsWith('169.254.')) { $fx_scope = 'link' }",
|
|
372
|
+
" (' inet ' + $fx_a.IPAddress + '/' + $fx_a.PrefixLength + ' brd ' + (fx-brd $fx_a.IPAddress $fx_a.PrefixLength) + ' scope ' + $fx_scope + ' ' + $fx_name)",
|
|
373
|
+
' } else {',
|
|
374
|
+
" $fx_scope = 'global'",
|
|
375
|
+
" if ($fx_a.IPAddress -eq '::1') { $fx_scope = 'host' } elseif ($fx_a.IPAddress.ToLower().StartsWith('fe80:')) { $fx_scope = 'link' }",
|
|
376
|
+
" (' inet6 ' + $fx_a.IPAddress + '/' + $fx_a.PrefixLength + ' scope ' + $fx_scope + ' ' + $fx_name)",
|
|
377
|
+
' }',
|
|
378
|
+
' }',
|
|
379
|
+
'}',
|
|
380
|
+
].join('\n');
|
|
381
|
+
}
|
|
382
|
+
if (sub === 'link' || sub === 'l') {
|
|
383
|
+
return [
|
|
384
|
+
PS_IFLIST,
|
|
385
|
+
'$fx_mac = @{}',
|
|
386
|
+
'foreach ($fx_ad in @(Get-NetAdapter -ErrorAction SilentlyContinue)) { if ($fx_ad.MacAddress) { $fx_mac[$fx_ad.Name] = $fx_ad.MacAddress.Replace(\'-\', \':\').ToLower() } }',
|
|
387
|
+
'$fx_n = 0',
|
|
388
|
+
'foreach ($fx_name in $fx_ifs) {',
|
|
389
|
+
' $fx_n = $fx_n + 1',
|
|
390
|
+
" $fx_lo = ($fx_name -like '*Loopback*')",
|
|
391
|
+
" if ($fx_lo) { ('' + $fx_n + ': ' + $fx_name + ': <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000') }",
|
|
392
|
+
" else { ('' + $fx_n + ': ' + $fx_name + ': <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000') }",
|
|
393
|
+
' if ($fx_lo) {',
|
|
394
|
+
" ' link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00'",
|
|
395
|
+
' } else {',
|
|
396
|
+
' $fx_m = $fx_mac[$fx_name]',
|
|
397
|
+
" if (-not $fx_m) { $fx_m = '00:00:00:00:00:00' }",
|
|
398
|
+
" (' link/ether ' + $fx_m + ' brd ff:ff:ff:ff:ff:ff')",
|
|
399
|
+
' }',
|
|
400
|
+
'}',
|
|
401
|
+
].join('\n');
|
|
402
|
+
}
|
|
403
|
+
if (sub === 'route' || sub === 'r') {
|
|
404
|
+
return [
|
|
405
|
+
'$fx_a4 = @(Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue)',
|
|
406
|
+
'$fx_rts = @(Get-NetRoute -ErrorAction SilentlyContinue |',
|
|
407
|
+
" Where-Object { $_.DestinationPrefix -match '\\.' -and $_.DestinationPrefix -ne '255.255.255.255/32' } |",
|
|
408
|
+
' Sort-Object -Property DestinationPrefix)',
|
|
409
|
+
'foreach ($fx_r in @($fx_rts | Where-Object { $_.DestinationPrefix -eq \'0.0.0.0/0\' })) {',
|
|
410
|
+
" $fx_l = 'default'",
|
|
411
|
+
" if ($null -ne $fx_r.NextHop -and $fx_r.NextHop -ne '' -and $fx_r.NextHop -ne '0.0.0.0') { $fx_l = $fx_l + ' via ' + $fx_r.NextHop }",
|
|
412
|
+
" $fx_l = $fx_l + ' dev ' + $fx_r.InterfaceAlias",
|
|
413
|
+
" $fx_c = @($fx_a4 | Where-Object { $_.InterfaceAlias -eq $fx_r.InterfaceAlias })",
|
|
414
|
+
" if ($fx_c.Count -gt 0) { $fx_l = $fx_l + ' proto dhcp src ' + $fx_c[0].IPAddress }",
|
|
415
|
+
' $fx_l',
|
|
416
|
+
'}',
|
|
417
|
+
'foreach ($fx_r in @($fx_rts | Where-Object { $_.DestinationPrefix -ne \'0.0.0.0/0\' })) {',
|
|
418
|
+
' $fx_l = $fx_r.DestinationPrefix',
|
|
419
|
+
" if ($null -ne $fx_r.NextHop -and $fx_r.NextHop -ne '' -and $fx_r.NextHop -ne '0.0.0.0') { $fx_l = $fx_l + ' via ' + $fx_r.NextHop }",
|
|
420
|
+
" $fx_l = $fx_l + ' dev ' + $fx_r.InterfaceAlias + ' proto kernel scope link'",
|
|
421
|
+
" $fx_c = @($fx_a4 | Where-Object { $_.InterfaceAlias -eq $fx_r.InterfaceAlias })",
|
|
422
|
+
" if ($fx_c.Count -gt 0) { $fx_l = $fx_l + ' src ' + $fx_c[0].IPAddress }",
|
|
423
|
+
' $fx_l',
|
|
424
|
+
'}',
|
|
425
|
+
].join('\n');
|
|
426
|
+
}
|
|
427
|
+
return psErr('ip', 'fauxnix does not support "ip ' + sub + '"');
|
|
428
|
+
};
|
|
429
|
+
const ifconfig = (args) => {
|
|
430
|
+
const { operandWords } = parseWords(args);
|
|
431
|
+
const filter = operandWords.length
|
|
432
|
+
? "$fx_ifs = @($fx_ifs | Where-Object { $_ -like ('*' + (" + exprOfWord(operandWords[0]) + ") + '*') })\n"
|
|
433
|
+
: '';
|
|
434
|
+
return [
|
|
435
|
+
PS_IP_MASK_FNS,
|
|
436
|
+
PS_IFLIST,
|
|
437
|
+
filter,
|
|
438
|
+
'foreach ($fx_name in $fx_ifs) {',
|
|
439
|
+
" $fx_lo = ($fx_name -like '*Loopback*')",
|
|
440
|
+
" if ($fx_lo) { ($fx_name + ': flags=73<UP,LOOPBACK,RUNNING> mtu 65536') } else { ($fx_name + ': flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500') }",
|
|
441
|
+
' foreach ($fx_a in @($fx_addrs | Where-Object { $_.InterfaceAlias -eq $fx_name })) {',
|
|
442
|
+
" if ($fx_a.AddressFamily -eq 'IPv4') {",
|
|
443
|
+
" $fx_l = ' inet addr:' + $fx_a.IPAddress + ' netmask:' + (fx-nm $fx_a.PrefixLength)",
|
|
444
|
+
" if (-not $fx_lo) { $fx_l = $fx_l + ' broadcast:' + (fx-brd $fx_a.IPAddress $fx_a.PrefixLength) }",
|
|
445
|
+
' $fx_l',
|
|
446
|
+
' } else {',
|
|
447
|
+
" $fx_sc = 'Scope:Global'",
|
|
448
|
+
" if ($fx_a.IPAddress -eq '::1') { $fx_sc = 'Scope:Host' } elseif ($fx_a.IPAddress.ToLower().StartsWith('fe80:')) { $fx_sc = 'Scope:Link' }",
|
|
449
|
+
" ' inet6 addr: ' + $fx_a.IPAddress + '/' + $fx_a.PrefixLength + ' ' + $fx_sc",
|
|
450
|
+
' }',
|
|
451
|
+
' }',
|
|
452
|
+
" ''",
|
|
453
|
+
'}',
|
|
454
|
+
].join('\n');
|
|
455
|
+
};
|
|
456
|
+
/* ------------------------------------------------------------------ */
|
|
457
|
+
/* nslookup / dig / host */
|
|
458
|
+
/* ------------------------------------------------------------------ */
|
|
459
|
+
const nslookup = (args) => nativeCall('nslookup.exe', args.map(exprOfWord).join(', '));
|
|
460
|
+
const dig = (args) => {
|
|
461
|
+
const raw = args.map(wordToString);
|
|
462
|
+
let short = false;
|
|
463
|
+
const ops = [];
|
|
464
|
+
for (let i = 0; i < raw.length; i++) {
|
|
465
|
+
const t = raw[i];
|
|
466
|
+
if (t === '+short') {
|
|
467
|
+
short = true;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (t.startsWith('-') || t.startsWith('+'))
|
|
471
|
+
continue; // other dig options dropped
|
|
472
|
+
if (t.startsWith('@'))
|
|
473
|
+
continue; // @server dropped
|
|
474
|
+
ops.push(args[i]);
|
|
475
|
+
}
|
|
476
|
+
const note = '[Console]::Error.WriteLine(' + psStr('dig: fauxnix maps dig → nslookup') + ')';
|
|
477
|
+
if (short) {
|
|
478
|
+
// dig +short HOST → only the answer addresses, no server preamble
|
|
479
|
+
if (ops.length === 0) {
|
|
480
|
+
return note + '; ' + psErr('dig', 'fauxnix: no host given');
|
|
481
|
+
}
|
|
482
|
+
return [
|
|
483
|
+
note,
|
|
484
|
+
'$fx_h = ' + exprOfWord(ops[0]),
|
|
485
|
+
'try {',
|
|
486
|
+
" foreach ($fx_a in @([System.Net.Dns]::GetHostAddresses($fx_h) | Sort-Object -Property @{e={ if ($_.AddressFamily -eq 'InterNetwork') { 0 } else { 1 } }})) { $fx_a.IPAddressToString }",
|
|
487
|
+
'} catch { }',
|
|
488
|
+
].join('\n');
|
|
489
|
+
}
|
|
490
|
+
const nsArgs = [];
|
|
491
|
+
if (ops.length >= 2)
|
|
492
|
+
nsArgs.push(psStr('-type=' + wordToString(ops[1])));
|
|
493
|
+
if (ops.length >= 1)
|
|
494
|
+
nsArgs.push(exprOfWord(ops[0]));
|
|
495
|
+
return [
|
|
496
|
+
note,
|
|
497
|
+
nativeCall('nslookup.exe', nsArgs.join(', ')),
|
|
498
|
+
].join('\n');
|
|
499
|
+
};
|
|
500
|
+
const host = (args) => {
|
|
501
|
+
const { operandWords } = parseWords(args);
|
|
502
|
+
if (operandWords.length === 0) {
|
|
503
|
+
return psErr('host', 'you must specify a host name (usage: host NAME)');
|
|
504
|
+
}
|
|
505
|
+
return [
|
|
506
|
+
'$fx_name = ' + exprOfWord(operandWords[0]),
|
|
507
|
+
'try {',
|
|
508
|
+
" $fx_addrs = @([System.Net.Dns]::GetHostAddresses($fx_name) | Sort-Object -Property @{e={ if ($_.AddressFamily -eq 'InterNetwork') { 0 } else { 1 } }})",
|
|
509
|
+
' foreach ($fx_a in $fx_addrs) {',
|
|
510
|
+
" if ($fx_a.AddressFamily -eq 'InterNetwork') { ($fx_name + ' has address ' + $fx_a.IPAddressToString) }",
|
|
511
|
+
" else { ($fx_name + ' has IPv6 address ' + $fx_a.IPAddressToString) }",
|
|
512
|
+
' }',
|
|
513
|
+
'} catch {',
|
|
514
|
+
" [Console]::Error.WriteLine('Host ' + $fx_name + ' not found: 3(NXDOMAIN)')",
|
|
515
|
+
' $script:fx_exit = 1',
|
|
516
|
+
'}',
|
|
517
|
+
].join('\n');
|
|
518
|
+
};
|
|
519
|
+
/* ------------------------------------------------------------------ */
|
|
520
|
+
/* exports */
|
|
521
|
+
/* ------------------------------------------------------------------ */
|
|
522
|
+
export const handlers = {
|
|
523
|
+
curl,
|
|
524
|
+
wget,
|
|
525
|
+
ping,
|
|
526
|
+
netstat,
|
|
527
|
+
ss,
|
|
528
|
+
ip,
|
|
529
|
+
ifconfig,
|
|
530
|
+
nslookup,
|
|
531
|
+
dig,
|
|
532
|
+
host,
|
|
533
|
+
};
|