196 lines
9.7 KiB
JavaScript
196 lines
9.7 KiB
JavaScript
var fs = require('fs');
|
|
var os = require('os');
|
|
var net = require('net');
|
|
var http = require('http');
|
|
var dgram = require('dgram');
|
|
var httpHeaders = require('http-headers');
|
|
var tcpserver = null;
|
|
var broadcastSockets = {};
|
|
var multicastSockets = {};
|
|
var discoveryInterval = null;
|
|
var membershipIPv4 = '239.255.255.235';
|
|
var membershipIPv6 = 'FF02:0:0:0:0:0:0:FE';
|
|
|
|
/*
|
|
// Route Settings
|
|
var settings = {
|
|
action: 'route',
|
|
localPort: 1234,
|
|
remoteName: 'AmtMachine7',
|
|
remoteNodeId: 'node//nmiPnDhT3vHKu$zg296YC5RjK53Trgh3Cimx3K8GVrFh$xch0UAAett2rbJpeddc',
|
|
remotePort: 3389,
|
|
username: 'a',
|
|
password: 'a',
|
|
serverUrl: 'wss://devbox.mesh.meshcentral.com:443/meshrelay.ashx',
|
|
serverId: 'D99362D5ED8BAEA8BF9E743B34B242256370C460FD66CB62373C6CFCB204D6D707403E396CF0EF6DC2B3A42F735135FD', // SHA384 of server HTTPS public key
|
|
serverHttpsHash: 'D9DE9E27A229B5355708A3672FB23237CC994A680B3570D242A91E36B4AE5BC9', // SHA256 of server HTTPS certificate
|
|
debugLevel: 0
|
|
}
|
|
*/
|
|
|
|
// Check the server certificate fingerprint
|
|
function onVerifyServer(clientName, certs) {
|
|
try { for (var i in certs) { if (certs[i].fingerprint.replace(/:/g, '') == settings.serverHttpsHash) { return; } } } catch (e) { }
|
|
if (serverhash != null) { console.log('Error: Failed to verify server certificate.'); return false; }
|
|
}
|
|
|
|
// Print a debug message
|
|
function debug(level, message) { if ((settings.debugLevel != null) && (settings.debugLevel >= level)) { console.log(message); } }
|
|
|
|
// Parse the input arguments into an object
|
|
function parceArguments(argv) {
|
|
var r = {};
|
|
for (var i in argv) {
|
|
i = parseInt(i);
|
|
if (argv[i].startsWith('--') == true) {
|
|
var key = argv[i].substring(2).toLowerCase(), val = true;
|
|
if (((i + 1) < argv.length) && (argv[i + 1].startsWith('--') == false)) { val = argv[i + 1]; }
|
|
r[key] = val;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Start the router, start by listening to the local port
|
|
function run(argv) {
|
|
var args = parceArguments(argv);
|
|
console.log('MeshCentral Command v1.0b');
|
|
var actionpath = 'meshaction.txt';
|
|
if (args.actionfile != null) { actionpath = args.actionfile; }
|
|
// Load the action file
|
|
var actionfile = null;
|
|
try { actionfile = fs.readFileSync(actionpath); } catch (e) { }
|
|
if (actionfile == null) { console.log('Unable to load \"' + actionpath + '\". Create this file or specify the location using --actionfile [filename].'); process.exit(1); }
|
|
try { settings = JSON.parse(actionfile); } catch (e) { console.log(actionpath, e); process.exit(1); }
|
|
|
|
// Set the arguments
|
|
if ((typeof args.localport) == 'string') { settings.localport = parseInt(args.localport); }
|
|
if ((typeof args.remotenodeid) == 'string') { settings.remoteNodeId = args.remotenodeid; }
|
|
if ((typeof args.username) == 'string') { settings.username = args.username; }
|
|
if ((typeof args.password) == 'string') { settings.password = args.password; }
|
|
if ((typeof args.user) == 'string') { settings.username = args.user; }
|
|
if ((typeof args.pass) == 'string') { settings.password = args.pass; }
|
|
if ((typeof args.serverid) == 'string') { settings.serverId = args.serverid; }
|
|
if ((typeof args.serverhttpshash) == 'string') { settings.serverHttpsHash = args.serverhttpshash; }
|
|
if ((typeof args.remoteport) == 'string') { settings.remotePort = parseInt(args.remoteport); }
|
|
|
|
// Validate meshaction.txt
|
|
if (settings.action == null) { console.log('No \"action\" specified.'); process.exit(1); }
|
|
settings.action = settings.action.toLowerCase();
|
|
if (settings.action == 'route') {
|
|
if ((settings.localPort == null) || (typeof settings.localPort != 'number') || (settings.localPort < 0) || (settings.localPort > 65535)) { console.log('No or invalid \"localPort\" specified, use --localport [localport].'); process.exit(1); }
|
|
if ((settings.remoteNodeId == null) || (typeof settings.remoteNodeId != 'string')) { console.log('No or invalid \"remoteNodeId\" specified.'); process.exit(1); }
|
|
if ((settings.username == null) || (typeof settings.username != 'string') || (settings.username == '')) { console.log('No or invalid \"username\" specified, use --username [username].'); process.exit(1); }
|
|
if ((settings.password == null) || (typeof settings.password != 'string') || (settings.password == '')) { console.log('No or invalid \"password\" specified, use --password [password].'); process.exit(1); }
|
|
if ((settings.serverId == null) || (typeof settings.serverId != 'string') || (settings.serverId.length != 96)) { console.log('No or invalid \"serverId\" specified.'); process.exit(1); }
|
|
if ((settings.serverHttpsHash == null) || (typeof settings.serverHttpsHash != 'string') || (settings.serverHttpsHash.length != 96)) { console.log('No or invalid \"serverHttpsHash\" specified.'); process.exit(1); }
|
|
if ((settings.remotePort == null) || (typeof settings.remotePort != 'number') || (settings.remotePort < 0) || (settings.remotePort > 65535)) { console.log('No or invalid \"remotePort\" specified, use --remoteport [remoteport].'); process.exit(1); }
|
|
} else {
|
|
console.log('Invalid \"action\" specified.'); process.exit(1);
|
|
}
|
|
|
|
debug(1, "Settings: " + JSON.stringify(settings));
|
|
if (settings.serverUrl != null) { startRouter(); } else { discoverMeshServer(); }
|
|
}
|
|
|
|
// Starts the router
|
|
function startRouter() {
|
|
tcpserver = net.createServer(OnTcpClientConnected);
|
|
tcpserver.on('error', function (err) { console.log(err); process.exit(0); });
|
|
tcpserver.listen(settings.localPort, function () {
|
|
// We started listening.
|
|
if (settings.remoteName == null) {
|
|
console.log('Redirecting local port ' + settings.localPort + ' to remote port ' + settings.remotePort + '.');
|
|
} else {
|
|
console.log('Redirecting local port ' + settings.localPort + ' to ' + settings.remoteName + ':' + settings.remotePort + '.');
|
|
}
|
|
console.log('Press ctrl-c to terminal.');
|
|
|
|
// If settings has a "cmd", run it now.
|
|
//process.exec("notepad.exe");
|
|
});
|
|
}
|
|
|
|
// Called when a TCP connect is received on the local port. Launch a tunnel.
|
|
function OnTcpClientConnected(c) {
|
|
try {
|
|
// 'connection' listener
|
|
debug(1, 'Client connected');
|
|
c.on('end', function () { disconnectTunnel(this, this.websocket, 'Client closed'); });
|
|
c.pause();
|
|
|
|
try {
|
|
options = http.parseUri(settings.serverUrl + '?user=' + settings.username + '&pass=' + settings.password + '&nodeid=' + settings.remoteNodeId + '&tcpport=' + settings.remotePort);
|
|
} catch (e) { console.log('Unable to parse \"serverUrl\".'); process.exit(1); }
|
|
options.checkServerIdentity = onVerifyServer;
|
|
c.websocket = http.request(options);
|
|
c.websocket.tcp = c;
|
|
c.websocket.tunneling = false;
|
|
c.websocket.upgrade = OnWebSocket;
|
|
c.websocket.on('error', function (msg) { console.log(msg); });
|
|
c.websocket.end();
|
|
} catch (e) { debug(2, e); }
|
|
}
|
|
|
|
// Disconnect both TCP & WebSocket connections and display a message.
|
|
function disconnectTunnel(tcp, ws, msg) {
|
|
if (ws != null) { try { ws.end(); } catch (e) { debug(2, e); } }
|
|
if (tcp != null) { try { tcp.end(); } catch (e) { debug(2, e); } }
|
|
debug(1, 'Tunnel disconnected: ' + msg);
|
|
}
|
|
|
|
// Called when the web socket gets connected
|
|
function OnWebSocket(msg, s, head) {
|
|
debug(1, 'Websocket connected');
|
|
s.on('data', function (msg) {
|
|
if (this.parent.tunneling == false) {
|
|
msg = msg.toString();
|
|
if (msg == 'c') {
|
|
this.parent.tunneling = true; this.pipe(this.parent.tcp); this.parent.tcp.pipe(this); debug(1, 'Tunnel active');
|
|
} else if ((msg.length > 6) && (msg.substring(0, 6) == 'error:')) {
|
|
console.log(msg.substring(6));
|
|
disconnectTunnel(this.tcp, this, msg.substring(6));
|
|
}
|
|
}
|
|
});
|
|
s.on('error', function (msg) { disconnectTunnel(this.tcp, this, 'Websocket error'); });
|
|
s.on('close', function (msg) { disconnectTunnel(this.tcp, this, 'Websocket closed'); });
|
|
s.parent = this;
|
|
}
|
|
|
|
// Try to discover the location of the mesh server
|
|
function discoverMeshServer() { console.log('Looking for server...'); discoveryInterval = setInterval(discoverMeshServerOnce, 5000); discoverMeshServerOnce(); }
|
|
|
|
// Try to discover the location of the mesh server only once
|
|
function discoverMeshServerOnce() {
|
|
var interfaces = os.networkInterfaces();
|
|
for (var adapter in interfaces) {
|
|
if (interfaces.hasOwnProperty(adapter)) {
|
|
for (var i = 0 ; i < interfaces[adapter].length; ++i) {
|
|
var addr = interfaces[adapter][i];
|
|
multicastSockets[i] = dgram.createSocket({ type: (addr.family == "IPv4" ? "udp4" : "udp6") });
|
|
multicastSockets[i].bind({ address: addr.address, exclusive: false });
|
|
if (addr.family == "IPv4") {
|
|
multicastSockets[i].addMembership(membershipIPv4);
|
|
//multicastSockets[i].setMulticastLoopback(true);
|
|
multicastSockets[i].once('message', OnMulticastMessage);
|
|
multicastSockets[i].send(settings.serverId, 16989, membershipIPv4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Called when a multicast packet is received
|
|
function OnMulticastMessage(msg, rinfo) {
|
|
var m = msg.toString().split('|');
|
|
if ((m.length == 3) && (m[0] == 'MeshCentral2') && (m[1] == settings.serverId)) {
|
|
settings.serverUrl = m[2].replace('%s', rinfo.address).replace('/agent.ashx', '/meshrelay.ashx');
|
|
console.log('Found server at ' + settings.serverUrl + '.');
|
|
if (discoveryInterval != null) { clearInterval(discoveryInterval); discoveryInterval = null; }
|
|
startRouter();
|
|
}
|
|
}
|
|
|
|
try { run(process.argv); } catch (e) { console.log(e); }
|