Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apprelays.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const MESHRIGHT_RESETOFF = 0x00040000; // 262144
const MESHRIGHT_GUESTSHARING = 0x00080000; // 524288
const MESHRIGHT_DEVICEDETAILS = 0x00100000; // 1048576
const MESHRIGHT_RELAY = 0x00200000; // 2097152
const MESHRIGHT_HIDERDPSESSIONS = 0x00400000; // 4194304
const MESHRIGHT_ADMIN = 0xFFFFFFFF;

// SerialTunnel object is used to embed TLS within another connection.
Expand Down
7 changes: 7 additions & 0 deletions meshagent.js
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
obj.consoleKickValue = command.value;
}
}
else if (command.type == 'userSessions') {
const rights = parent.GetNodeRights(parent.wssessions2[command.sessionid].userid, obj.dbMeshKey, obj.dbNodeKey);
if ((rights !== 0xFFFFFFFF) && (rights & 0x00400000)) {
command.data = Object.fromEntries(Object.entries(command.data).filter(([k,v]) => (!v.StationName.toUpperCase().startsWith('RDP-'))));
console.log(rights.toString(16).padStart(8,'0'), command);
}
}

// Route a message
parent.routeAgentCommand(command, obj.domain.id, obj.dbNodeKey, obj.dbMeshKey);
Expand Down
16 changes: 15 additions & 1 deletion meshcentral-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,11 @@
]
}
},
"applyFeaturePermissionsToRouterAndWebTools": {
"type": "boolean",
"default": true,
"description": "Hides RDP, SSH or SCP links if access to Desktop, Terminal or Files is denied."
},
"deviceMeshRouterLinks": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -1929,6 +1934,15 @@
"default": true,
"description": "When enabled, activates the built-in web-based VNC client."
},
"novncargs": {
"type": "string",
"default": "show_dot=1",
"description": "Additional arguments for nVNC. URL-encoded and sparated by ampersand (&)."
},
"novncViewOnlyPort": {
"type": ["number", "array"],
"description": "TCP port where a VNC server is listening in view-only mode. Single number or array in Object.fromEntries()-compatible format."
},
"mstsc": {
"type": "boolean",
"default": true,
Expand Down Expand Up @@ -2324,7 +2338,7 @@
"viewonly": {
"type": "boolean",
"description": "When set to true, the remote desktop feature is view only.",
"default": "false"
"default": false
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions meshcentral.js
Original file line number Diff line number Diff line change
Expand Up @@ -3216,6 +3216,8 @@ function CreateMeshCentralServer(config, args) {

// Sign windows agents
obj.signMeshAgents = function (domain, func) {
// Skip signing to speed up debugging
if ('MESHCENTRAL_DEV_NO_AGENT_SIGNING' in process.env && (()=> { try { return require('node:inspector').url() !== undefined; } catch (_) { return false; } }).call()) { return void func(); }
// Setup the domain is specified
var objx = domain, suffix = '';
if (domain.id == '') { objx = obj; } else { suffix = '-' + domain.id; objx.meshAgentBinaries = {}; }
Expand Down
2 changes: 2 additions & 0 deletions meshipkvm.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ function CreateIPKVMManager(parent) {
const MESHRIGHT_RESETOFF = 0x00040000; // 262144
const MESHRIGHT_GUESTSHARING = 0x00080000; // 524288
const MESHRIGHT_DEVICEDETAILS = 0x00100000; // ?1048576?
const MESHRIGHT_RELAY = 0x00200000; // 2097152
const MESHRIGHT_HIDERDPSESSIONS = 0x00400000; // 4194304
const MESHRIGHT_ADMIN = 0xFFFFFFFF;

// Subscribe for mesh creation events
Expand Down
20 changes: 19 additions & 1 deletion meshrelay.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const MESHRIGHT_RESETOFF = 0x00040000; // 262144
const MESHRIGHT_GUESTSHARING = 0x00080000; // 524288
const MESHRIGHT_DEVICEDETAILS = 0x00100000; // 1048576
const MESHRIGHT_RELAY = 0x00200000; // 2097152
const MESHRIGHT_HIDERDPSESSIONS = 0x00400000; // 4194304
const MESHRIGHT_ADMIN = 0xFFFFFFFF;

// Protocol:
Expand Down Expand Up @@ -874,14 +875,31 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
parent.db.Get(cookie.nodeid, function (err, docs) {
if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket
const node = docs[0];
const rights = parent.GetNodeRights(obj.user, node.meshid, node._id);

// Check if this user has permission to relay thru this computer (MESHRIGHT_REMOTECONTROL or MESHRIGHT_RELAY rights)
if ((obj.nouser !== true) && ((parent.GetNodeRights(obj.user, node.meshid, node._id) & 0x00200008) == 0)) { console.log('ERR: Access denied (1)'); try { obj.close(); } catch (ex) { } return; }
if ((obj.nouser !== true) && ((rights & 0x00200008) == 0)) { console.log('ERR: Access denied (1)'); try { obj.close(); } catch (ex) { } return; }

// Set nodeid and meshid
obj.nodeid = node._id;
obj.meshid = node.meshid;

function getNoVncViewOnlyPort(rfbport = 5900) {
let novncvop = domain.novncviewonlyport;
if (typeof novncvop === 'number') { return novncvop; }
if (Array.isArray(novncvop)) { novncvop = domain.novncviewonlyport = Object.fromEntries(novncvop); }
const tokens = [obj.meshid, `mesh/${domain.id}/${parent.meshes[obj.meshid].name}`, obj.nodeid, `node/${domain.id}/${node.name}`, '*'];
tokens.some((val) => ((val = +novncvop[val]) ? ((rfbport = val), true) : false));
return rfbport;
}
if (domain.applyfeaturepermissionstorouterandwebtools !== false) {
switch (cookie.tcpport) {
// don't know why, but we have to terminate() the websocket to close the connection. or is a feature to avoid DoS?
case node.rdpport ?? 3389: { if ((rights !== 0xFFFFFFFF) && (rights & MESHRIGHT_REMOTEVIEWONLY)) { console.log('ERR: Access denied (1)'); try { obj.close(); /*ws.terminate();*/ } catch (ex) { } return; } break; }
case node.rfbport ?? 5900: { if ((rights !== 0xFFFFFFFF) && (rights & MESHRIGHT_REMOTEVIEWONLY)) { cookie.tcpport = getNoVncViewOnlyPort(node.rfbport); } break; }
}
}

// Send connection request to agent
const rcookieData = {};
if (user != null) { rcookieData.ruserid = user._id; } else if (obj.nouser === true) { rcookieData.nouser = 1; }
Expand Down
17 changes: 15 additions & 2 deletions meshuser.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
const MESHRIGHT_GUESTSHARING = 0x00080000; // 524288
const MESHRIGHT_DEVICEDETAILS = 0x00100000; // 1048576
const MESHRIGHT_RELAY = 0x00200000; // 2097152
const MESHRIGHT_HIDERDPSESSIONS = 0x00400000; // 4194304
const MESHRIGHT_ADMIN = 0xFFFFFFFF;

// Site rights
Expand Down Expand Up @@ -547,7 +548,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use

// Build server information object
const allFeatures = parent.getDomainUserFeatures(domain, user, req);
var serverinfo = { domain: domain.id, name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((domain.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (args.lanonly != true) && (parent.certificates.CommonName != null) && (parent.certificates.CommonName.indexOf('.') != -1) && (user._id.split('/')[2].startsWith('~') == false)), domainauth: (domain.auth == 'sspi'), serverTime: Date.now(), features: allFeatures.features, features2: allFeatures.features2 };
var serverinfo = { domain: domain.id, name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((domain.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (args.lanonly != true) && (parent.certificates.CommonName != null) && (parent.certificates.CommonName.indexOf('.') != -1) && (user._id.split('/')[2].startsWith('~') == false)), domainauth: (domain.auth == 'sspi'), serverTime: Date.now(), features: allFeatures.features, features2: allFeatures.features2, features3: allFeatures.features3 };
serverinfo.languages = parent.renderLanguages;
serverinfo.tlshash = Buffer.from(parent.webCertificateFullHashs[domain.id], 'binary').toString('hex').toUpperCase(); // SHA384 of server HTTPS certificate
serverinfo.agentCertHash = parent.agentCertificateHashBase64;
Expand Down Expand Up @@ -4722,7 +4723,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
} else {
try {
parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
} catch (ex) { console.log('Error loading plugin handler (' + ex + ')'); }
} catch (ex) { console.log('Error executing plugin serveraction (' + ex + ')', ex.stack); }
}
break;
}
Expand Down Expand Up @@ -6411,6 +6412,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
if ((node == null) || ((rights & MESHRIGHT_REMOTECONTROL) == 0) || (visible == false)) return; // Access denied.

const applyfeaturepermissions = obj.domain.applyfeaturepermissionstorouterandwebtools !== false;
switch (command.tag) {
case 'novnc': {
let novncargs = domain.novncargs ?? 'show_dot=1';
if (applyfeaturepermissions) { novncargs += '&view_only=' +!!(rights & MESHRIGHT_REMOTEVIEWONLY); }
command.qsappend = novncargs.charAt(0) === '&' ? novncargs.slice(1) : novncargs;
break;
}
case 'mstsc': { if (applyfeaturepermissions && (rights & MESHRIGHT_REMOTEVIEWONLY)) { return; } break; }
case 'ssh': { if (applyfeaturepermissions && (rights & MESHRIGHT_NOTERMINAL)) { return; }; break; }
}

// Add a user authentication cookie to a url
var cookieContent = { userid: user._id, domainid: user.domain };
if (command.nodeid) { cookieContent.nodeid = command.nodeid; }
Expand Down
11 changes: 11 additions & 0 deletions sample-config-advanced.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,17 @@
"_footer": "Test",
"_certUrl": "https://192.168.2.106:443/"
},
"_domain_with_novncViewOnlyPort": {
"_novnc": true,
"_1_novncViewOnlyPort": 5901,
"_2_novncViewOnlyPort": [
["#_node//Oqj4ExG@mcz9ZmXMuy7YncqEKuVA1rD5o8ZUWQ8F$mA$33zwfXz$XQ5mrUYdJbss", 5901],
["#_node//my-dev-box", 5902],
["#_mesh//KbvW2V18kiZSNQ5zkT8Qk2s7aADf0MwS1cXUlc$WzqwbwEVYMTopJFR1uxxZzE79", 5903],
["#_mesh//playground--connect", 5904],
["*", 5905]
]
},
"_info": {
"_share": "C:\\ExtraWebSite"
}
Expand Down
Loading
Loading