99 lines
3.7 KiB
JavaScript
99 lines
3.7 KiB
JavaScript
import {ArgError, ArgParser} from './scripts/lib/arg-parser';
|
|
|
|
export async function main(ns) {
|
|
// Setup
|
|
ns.disableLog('ALL');
|
|
const argParser = new ArgParser('network-graph.js', 'Scan the network for devices and display as an ASCII tree:\n home\n ├─ n00dles (ROOTED)\n | └─ max-hardware (80|1)\n | └─ neo-net (50|1)\n ├─ foodnstuff (ROOTED)\n └─ sigma-cosmetics (ROOTED)', null, [
|
|
{name: 'device', desc: 'Point to start scan from, defaults to current machine', optional: true, default: ns.getHostname(), type: 'string'},
|
|
{name: 'depth', desc: 'Depth to scan to, defaults to 3', flags: ['-d', '--depth'], default: Infinity, type: 'num'},
|
|
{name: 'filter', desc: 'Display devices matching name', flags: ['-f', '--filter'], type: 'string'},
|
|
{name: 'regex', desc: 'Display devices matching pattern', flags: ['-r', '--regex'], type: 'string'},
|
|
{name: 'verbose', desc: 'Displays the required hack level & ports needed to root: (level|port)', flags: ['-v', '--verbose'], type: 'bool'},
|
|
]);
|
|
let args;
|
|
try {
|
|
args = argParser.parse(ns.args);
|
|
} catch(err) {
|
|
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
|
throw err;
|
|
}
|
|
|
|
/**
|
|
* Prune tree down to devices that match name or pattern.
|
|
* @param tree {object} - Tree to search
|
|
* @param find {string} - Device name or pattern to search for
|
|
* @param regex {boolean} - True to use regex, false for raw check
|
|
* @returns {object} - Pruned tree
|
|
*/
|
|
function filter(tree, find, regex = false) {
|
|
const found = new Set();
|
|
function buildWhitelist(tree, find, path = []) {
|
|
const keys = Object.keys(tree);
|
|
if(!keys.length) return;
|
|
Object.keys(tree).forEach(n => {
|
|
const matches = regex ? new RegExp(find).test(n) : n == find;
|
|
if(n == 'n00dles') console.log(n, find, matches);
|
|
if(matches) {
|
|
found.add(n);
|
|
path.forEach(p => found.add(p));
|
|
}
|
|
buildWhitelist(tree[n], find, [...path, n]);
|
|
})
|
|
}
|
|
function prune(tree, whitelist) {
|
|
Object.keys(tree).forEach(n => {
|
|
if(Object.keys(tree[n]).length) prune(tree[n], whitelist);
|
|
if(!whitelist.includes(n)) delete tree[n];
|
|
});
|
|
}
|
|
buildWhitelist(tree, find);
|
|
prune(tree, Array.from(found));
|
|
}
|
|
|
|
/**
|
|
* Recursively search network & build a tree
|
|
* @param host {string} - Point to scan from
|
|
* @param depth {number} - Current scanning depth
|
|
* @param blacklist {String[]} - Devices already discovered
|
|
* @returns Dicionary of discovered devices
|
|
*/
|
|
function scan(host, depth = 1, blacklist = [host]) {
|
|
if(depth > args['depth']) return {};
|
|
const localTargets = ns.scan(host).filter(target => !blacklist.includes(target));
|
|
blacklist = [...blacklist, ...localTargets];
|
|
return localTargets.reduce((acc, target) => {
|
|
const info = ns.getServer(target);
|
|
const verb = args['verbose'] ? ` (${info.hasAdminRights ? 'ROOTED' : `${info.requiredHackingSkill}|${info.numOpenPortsRequired}`})` : '';
|
|
const name = `${target}${verb}`;
|
|
acc[name] = scan(target, depth + 1, blacklist);
|
|
return acc;
|
|
}, {});
|
|
}
|
|
|
|
/**
|
|
* Iterate tree & print to screen
|
|
* @param tree {object} - Tree to parse
|
|
* @param spacer {string} - Spacer text for tree formatting
|
|
*/
|
|
function render(tree, spacer = ' ') {
|
|
Object.keys(tree).forEach((key, i, arr) => {
|
|
const last = i == arr.length - 1;
|
|
const branch = last ? '└─ ' : '├─ ';
|
|
ns.tprint(`${spacer}${branch}${key}`);
|
|
render(tree[key], spacer + (last ? ' ' : '| '));
|
|
});
|
|
}
|
|
|
|
// Run
|
|
ns.tprint(args['device']);
|
|
const found = scan(args['device']);
|
|
if(args['regex']) filter(found, args['regex'], true);
|
|
else if(args['filter']) filter(found, args['filter']);
|
|
render(found);
|
|
ns.tprint('');
|
|
}
|
|
|
|
export function autocomplete(data) {
|
|
return [...data.servers];
|
|
}
|