add webrelay to websocket and meshctrl #6484

Signed-off-by: si458 <simonsmith5521@gmail.com>

si458 committed Nov 26, 2024 at 16:46 UTC dbb5b4ba1117528643855ad424b61f777ffd7acf
3 files changed +99 -2
meshctrl.js
+47 -1
@@ -16,7 +16,7 @@ var settings = {};
16 const crypto = require('crypto');
17 const args = require('minimist')(process.argv.slice(2));
18 const path = require('path');
19 -const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addlocaldevice', 'addamtdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload', 'report', 'grouptoast', 'groupmessage'];
19 +const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addlocaldevice', 'addamtdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload', 'report', 'grouptoast', 'groupmessage', 'webrelay'];
20 if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } }
21
22 if (args['_'].length == 0) {
@@ -65,6 +65,7 @@ if (args['_'].length == 0) {
65 console.log(" Shell - Access command shell of a remote device.");
66 console.log(" Upload - Upload a file to a remote device.");
67 console.log(" Download - Download a file from a remote device.");
68 + console.log(" WebRelay - Creates a HTTP/HTTPS webrelay link for a remote device.");
69 console.log(" DeviceOpenUrl - Open a URL on a remote device.");
70 console.log(" DeviceMessage - Open a message box on a remote device.");
71 console.log(" DeviceToast - Display a toast notification on a remote device.");
@@ -277,6 +278,12 @@ if (args['_'].length == 0) {
278 else { ok = true; }
279 break;
280 }
281 + case 'webrelay': {
282 + if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
283 + else if (args.type == null) { console.log(winRemoveSingleQuotes("Missing protocol type, use --type [http,https]")); }
284 + else { ok = true; }
285 + break;
286 + }
287 case 'deviceopenurl': {
288 if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
289 else if (args.openurl == null) { console.log("Remote URL, use --openurl [url] specify the link to open."); }
@@ -1015,6 +1022,21 @@ if (args['_'].length == 0) {
1022 console.log(" --target [localpath] - The local path to download the file to.");
1023 break;
1024 }
1025 + case 'webrelay': {
1026 + console.log("Generate a webrelay URL to access a HTTP/HTTPS service on a remote device, Example usages:\r\n");
1027 + console.log(winRemoveSingleQuotes(" MeshCtrl WebRelay --id 'deviceid' --type http --port 80"));
1028 + console.log(winRemoveSingleQuotes(" MeshCtrl WebRelay --id 'deviceid' --type https --port 443"));
1029 + console.log("\r\nRequired arguments:\r\n");
1030 + if (process.platform == 'win32') {
1031 + console.log(" --id [deviceid] - The device identifier.");
1032 + } else {
1033 + console.log(" --id '[deviceid]' - The device identifier.");
1034 + }
1035 + console.log(" --type [http,https] - Type of relay from remote device, http or https.");
1036 + console.log("\r\nOptional arguments:\r\n");
1037 + console.log(" --port [portnumber] - Set alternative port for http or https, default is 80 for http and 443 for https.");
1038 + break;
1039 + }
1040 case 'deviceopenurl': {
1041 console.log("Open a web page on a remote device, Example usages:\r\n");
1042 console.log(winRemoveSingleQuotes(" MeshCtrl DeviceOpenUrl --id 'deviceid' --openurl http://meshcentral.com"));
@@ -1804,6 +1826,29 @@ function serverConnect() {
1826 req.end()
1827 break;
1828 }
1829 + case 'webrelay': {
1830 + var protocol = null;
1831 + if (args.type != null) {
1832 + if (args.type == 'http') {
1833 + protocol = 1;
1834 + } else if (args.type == 'https') {
1835 + protocol = 2;
1836 + } else {
1837 + console.log("Unknown protocol type: " + args.type); process.exit(1);
1838 + }
1839 + }
1840 + var port = null;
1841 + if (typeof args.port == 'number') {
1842 + if ((args.port < 1) || (args.port > 65535)) { console.log("Port number must be between 1 and 65535."); process.exit(1); }
1843 + port = args.port;
1844 + } else if (protocol == 1) {
1845 + port = 80;
1846 + } else if (protocol == 2) {
1847 + port = 443;
1848 + }
1849 + ws.send(JSON.stringify({ action: 'webrelay', nodeid: args.id, port: port, appid: protocol, responseid: 'meshctrl' }));
1850 + break;
1851 + }
1852 case 'devicesharing': {
1853 if (args.add) {
1854 if (args.add.length == 0) { console.log("Invalid guest name."); process.exit(1); }
@@ -2208,6 +2253,7 @@ function serverConnect() {
2253 break;
2254 }
2255 case 'createDeviceShareLink':
2256 + case 'webrelay':
2257 if (data.result == 'OK') {
2258 if (data.publicid) { console.log('ID: ' + data.publicid); }
2259 console.log('URL: ' + data.url);
meshuser.js
+42
@@ -2948,6 +2948,48 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2948 }
2949 break;
2950 }
2951 + case 'webrelay':
2952 + {
2953 + if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
2954 + else if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
2955 + else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
2956 + else if ((command.port != null) && (common.validateInt(command.port, 1, 65535) == false)) { err = 'Invalid port value'; } // Check the port if present
2957 + else {
2958 + if (command.nodeid.split('/').length == 1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
2959 + var snode = command.nodeid.split('/');
2960 + if ((snode.length != 3) || (snode[0] != 'node') || (snode[1] != domain.id)) { err = 'Invalid node id'; }
2961 + }
2962 + // Handle any errors
2963 + if (err != null) {
2964 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: err })); } catch (ex) { } }
2965 + break;
2966 + }
2967 + // Get the device rights
2968 + parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
2969 + // If node not found or we don't have remote control, reject.
2970 + if (node == null) {
2971 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: 'Invalid node id' })); } catch (ex) { } }
2972 + return;
2973 + }
2974 + var relayid = null;
2975 + var addr = null;
2976 + if (node.mtype == 3) { // Setup device relay if needed
2977 + var mesh = parent.meshes[node.meshid];
2978 + if (mesh && mesh.relayid) { relayid = mesh.relayid; addr = node.host; }
2979 + }
2980 + var webRelayDns = (args.relaydns != null) ? args.relaydns[0] : obj.getWebServerName(domain, req);
2981 + var webRelayPort = ((args.relaydns != null) ? ((typeof args.aliasport == 'number') ? args.aliasport : args.port) : ((parent.webrelayserver != null) ? ((typeof args.relayaliasport == 'number') ? args.relayaliasport : parent.webrelayserver.port) : 0));
2982 + if (webRelayPort == 0) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: 'WebRelay Disabled' })); return; } catch (ex) { } }
2983 + const authRelayCookie = parent.parent.encodeCookie({ ruserid: user._id, x: req.session.x }, parent.parent.loginCookieEncryptionKey);
2984 + var url = 'https://' + webRelayDns + ':' + webRelayPort + '/control-redirect.ashx?n=' + command.nodeid + '&p=' + command.port + '&appid=' + command.appid + '&c=' + authRelayCookie;
2985 + if (addr != null) { url += '&addr=' + addr; }
2986 + if (relayid != null) { url += '&relayid=' + relayid }
2987 + command.url = url;
2988 + if (command.responseid != null) { command.result = 'OK'; }
2989 + try { ws.send(JSON.stringify(command)); } catch (ex) { }
2990 + });
2991 + break;
2992 + }
2993 case 'runcommands':
2994 {
2995 if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
webserver.js
+10 -1
@@ -8036,6 +8036,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
8036 parent.debug('web', 'Invalid login, asking for email validation');
8037 try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, sms2fa: sms2fa, msg2fa: msg2fa, email2fasent: true })); ws.close(); } catch (e) { }
8038 } else {
8039 + req.session.userid = user._id;
8040 + req.session.ip = req.clientIp;
8041 + setSessionRandom(req);
8042 func(ws, req, domain, user, null, authData);
8043 }
8044 }
@@ -8051,6 +8054,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
8054 try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, sms2fa: sms2fa, msg2fa: msg2fa, email2fasent: true })); ws.close(); } catch (e) { }
8055 } else {
8056 // We are authenticated
8057 + req.session.userid = user._id;
8058 + req.session.ip = req.clientIp;
8059 + setSessionRandom(req);
8060 func(ws, req, domain, user);
8061 }
8062 }
@@ -8179,7 +8185,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
8185 if (emailcheck && (user.email != null) && (!(user._id.split('/')[2].startsWith('~'))) && (user.emailVerified !== true)) {
8186 parent.debug('web', 'Invalid login, asking for email validation');
8187 try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, email2fasent: true })); ws.close(); } catch (e) { }
8182 - } else {
8188 + } else {
8189 + req.session.userid = user._id;
8190 + req.session.ip = req.clientIp;
8191 + setSessionRandom(req);
8192 func(ws, req, domain, user);
8193 }
8194 }