Added device sharing link revocation.

This commit is contained in:
Ylian Saint-Hilaire 2020-10-30 17:08:07 -07:00
parent 4b72c2d526
commit 64c8e21713
12 changed files with 1829 additions and 1539 deletions

View File

@ -2590,7 +2590,7 @@ function createMeshCore(agent) {
var response = null;
switch (cmd) {
case 'help': { // Displays available commands
var fin = '', f = '', availcommands = 'coredump,service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,wallpaper,agentmsg';
var fin = '', f = '', availcommands = 'coreinfo, coredump,service,fdsnapshot,fdcount,startupoptions,alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,wallpaper,agentmsg';
if (process.platform == 'win32') { availcommands += ',safemode,wpfhwacceleration,uac'; }
if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; }
if (process.platform != 'freebsd') { availcommands += ',vm';}
@ -2606,6 +2606,10 @@ function createMeshCore(agent) {
response = "Available commands: \r\n" + fin + ".";
break;
}
case 'coreinfo': {
response = JSON.stringify(meshCoreObj, null, 2);
break;
}
case 'agentmsg': {
if (args['_'].length == 0) {
response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage

View File

@ -432,11 +432,11 @@ module.exports.CreateAmtManager = function (parent) {
// Connect now
var comm;
if (dev.tlsfail !== true) {
parent.debug('amt', 'Relay-Connect', "TLS", dev.name, user, pass);
parent.debug('amt', (dev.connType == 1) ? 'Relay-Connect' : 'LMS-Connect', "TLS", dev.name, user, pass);
comm = CreateWsmanComm(dev.nodeid, 16993, user, pass, 1, null, ciraconn); // Perform TLS
comm.xtlsFingerprint = 0; // Perform no certificate checking
} else {
parent.debug('amt', 'Relay-Connect', "NoTLS", dev.name, user, pass);
parent.debug('amt', (dev.connType == 1) ? 'Relay-Connect' : 'LMS-Connect', "NoTLS", dev.name, user, pass);
comm = CreateWsmanComm(dev.nodeid, 16992, user, pass, 0, null, ciraconn); // No TLS
}
var wsstack = WsmanStackCreateService(comm);
@ -608,7 +608,7 @@ module.exports.CreateAmtManager = function (parent) {
if ((typeof dev.aquired.user == 'string') && (dev.aquired.user != device.intelamt.user)) { change = 1; log = 1; device.intelamt.user = dev.aquired.user; changes.push('AMT user'); }
if ((typeof dev.aquired.pass == 'string') && (dev.aquired.pass != device.intelamt.pass)) { change = 1; log = 1; device.intelamt.pass = dev.aquired.pass; changes.push('AMT pass'); }
if ((typeof dev.aquired.mpspass == 'string') && (dev.aquired.mpspass != device.intelamt.mpspass)) { change = 1; log = 1; device.intelamt.mpspass = dev.aquired.mpspass; changes.push('AMT MPS pass'); }
if ((typeof dev.aquired.host == 'string') && (dev.aquired.host != device.host)) { change = 1; log = 1; device.host = dev.aquired.host; changes.push('host'); }
if ((typeof dev.aquired.host == 'string') && (dev.aquired.host != device.intelamt.host)) { change = 1; log = 1; device.intelamt.host = dev.aquired.host; changes.push('AMT host'); }
if ((typeof dev.aquired.realm == 'string') && (dev.aquired.realm != device.intelamt.realm)) { change = 1; log = 1; device.intelamt.realm = dev.aquired.realm; changes.push('AMT realm'); }
if ((typeof dev.aquired.hash == 'string') && (dev.aquired.hash != device.intelamt.hash)) { change = 1; log = 1; device.intelamt.hash = dev.aquired.hash; changes.push('AMT hash'); }
if ((typeof dev.aquired.tls == 'number') && (dev.aquired.tls != device.intelamt.tls)) { change = 1; log = 1; device.intelamt.tls = dev.aquired.tls; changes.push('AMT TLS'); }

17
db.js
View File

@ -851,6 +851,13 @@ module.exports.CreateDB = function (parent, func) {
}
}
};
obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
if (id && (id != '')) {
sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ? AND type = ? AND domain = ? AND nodeid IN (?)', [id, type, domain, nodes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
} else {
sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ? AND nodeid IN (?)', [type, domain, nodes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
}
};
obj.GetAllType = function (type, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ?', [type], func); }
obj.GetAllIdsOfType = function (ids, domain, type, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id IN (?) AND domain = ? AND type = ?', [ids, domain, type], func); }
obj.GetUserWithEmail = function (domain, email, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE domain = ? AND extra = ?', [domain, 'email/' + email], func); }
@ -1010,6 +1017,11 @@ module.exports.CreateDB = function (parent, func) {
obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
}
};
obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
var x = { type: type, domain: domain, nodeid: { $in: nodes } };
if (id) { x._id = id; }
obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
};
obj.GetAllType = function (type, func) { obj.file.find({ type: type }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
@ -1163,6 +1175,11 @@ module.exports.CreateDB = function (parent, func) {
obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
}
};
obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
var x = { type: type, domain: domain, nodeid: { $in: nodes } };
if (id) { x._id = id; }
obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
};
obj.GetAllType = function (type, func) { obj.file.find({ type: type }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };

View File

@ -1135,6 +1135,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
case 'coreinfo':
{
// Sent by the agent to update agent information
// TODO: Check that this does not include outdates Intel AMT information
ChangeAgentCoreInfo(command);
break;
}
@ -1255,34 +1256,6 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
}
break;
}
case 'acmactivate':
{
if (obj.agentInfo.capabilities & 0x20) return; // If this is a temporary device, don't do ACM activation.
// Get the current Intel AMT policy
var mesh = parent.meshes[obj.dbMeshKey];
if ((mesh == null) || (mesh.amt == null) || (mesh.amt.type != 3) || (domain.amtacmactivation == null) || (domain.amtacmactivation.acmmatch == null) || (mesh.amt.password == null)) break; // If this is not the right policy, ignore this.
// Get the Intel AMT admin password, randomize if needed.
var amtpassword = ((mesh.amt.password == '') ? getRandomAmtPassword() : mesh.amt.password);
if (checkAmtPassword(amtpassword) == false) return; // Invalid Intel AMT password, this should never happen.
// Agent is asking the server to sign an Intel AMT ACM activation request
var signResponse = parent.parent.certificateOperations.signAcmRequest(domain, command, 'admin', amtpassword, obj.remoteaddrport, obj.dbNodeKey, obj.dbMeshKey, obj.agentInfo.computerName, obj.agentInfo.agentId); // TODO: Place account credentials!!!
if ((signResponse != null) && (signResponse.error == null)) {
// Log this activation event
var event = { etype: 'node', action: 'amtactivate', nodeid: obj.dbNodeKey, domain: domain.id, msgid: 58, msgArgs: [command.fqdn], msg: 'Device requested Intel(R) AMT ACM activation, FQDN: ' + command.fqdn, ip: obj.remoteaddrport };
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
// Update the device Intel AMT information
ChangeAgentCoreInfo({ 'intelamt': { user: 'admin', pass: amtpassword, uuid: command.uuid, realm: command.realm } });
// Send the activation response
obj.send(JSON.stringify(signResponse));
}
break;
}
case 'diagnostic':
{
if (typeof command.value == 'object') {
@ -1482,13 +1455,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if (!device.intelamt) { device.intelamt = {}; }
if ((command.intelamt.ver != null) && (typeof command.intelamt.ver == 'string') && (command.intelamt.ver.length < 12) && (device.intelamt.ver != command.intelamt.ver)) { changes.push('AMT version'); device.intelamt.ver = command.intelamt.ver; change = 1; log = 1; }
if ((command.intelamt.sku != null) && (typeof command.intelamt.sku == 'number') && (device.intelamt.sku !== command.intelamt.sku)) { changes.push('AMT SKU'); device.intelamt.sku = command.intelamt.sku; change = 1; log = 1; }
if ((command.intelamt.state != null) && (typeof command.intelamt.state == 'number') && (device.intelamt.state != command.intelamt.state)) { changes.push('AMT state'); device.intelamt.state = command.intelamt.state; change = 1; log = 1; }
if ((command.intelamt.state != null) && (typeof command.intelamt.state == 'number') && (device.intelamt.state != command.intelamt.state)) { changes.push('AMT state'); console.log('agent', device.intelamt.state, command.intelamt.state); device.intelamt.state = command.intelamt.state; change = 1; log = 1; }
if ((command.intelamt.flags != null) && (typeof command.intelamt.flags == 'number') && (device.intelamt.flags != command.intelamt.flags)) {
if (device.intelamt.flags) { changes.push('AMT flags (' + device.intelamt.flags + ' --> ' + command.intelamt.flags + ')'); } else { changes.push('AMT flags (' + command.intelamt.flags + ')'); }
device.intelamt.flags = command.intelamt.flags; change = 1; log = 1;
}
if ((command.intelamt.realm != null) && (typeof command.intelamt.realm == 'string') && (device.intelamt.realm != command.intelamt.realm)) { changes.push('AMT realm'); device.intelamt.realm = command.intelamt.realm; change = 1; log = 1; }
if ((command.intelamt.host != null) && (typeof command.intelamt.host == 'string') && (device.intelamt.host != command.intelamt.host)) { changes.push('AMT host'); device.intelamt.host = command.intelamt.host; change = 1; log = 1; }
//if ((command.intelamt.host != null) && (typeof command.intelamt.host == 'string') && (device.intelamt.host != command.intelamt.host)) { changes.push('AMT host'); device.intelamt.host = command.intelamt.host; change = 1; log = 1; }
if ((command.intelamt.uuid != null) && (typeof command.intelamt.uuid == 'string') && (device.intelamt.uuid != command.intelamt.uuid)) { changes.push('AMT uuid'); device.intelamt.uuid = command.intelamt.uuid; change = 1; log = 1; }
if ((command.intelamt.user != null) && (typeof command.intelamt.user == 'string') && (device.intelamt.user != command.intelamt.user)) { changes.push('AMT user'); device.intelamt.user = command.intelamt.user; change = 1; log = 1; }
if ((command.intelamt.pass != null) && (typeof command.intelamt.pass == 'string') && (device.intelamt.pass != command.intelamt.pass)) { changes.push('AMT pass'); device.intelamt.pass = command.intelamt.pass; change = 1; log = 1; }

View File

@ -803,7 +803,32 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
return obj;
}
function checkDeviceSharePublicIdentifier(parent, domain, nodeid, pid, func) {
// Check the public id
parent.db.GetAllTypeNodeFiltered([nodeid], domain.id, 'deviceshare', null, function (err, docs) {
if ((err != null) || (docs.length == 0)) { func(false); return; }
// Search for the device share public identifier
var found = false;
for (var i = 0; i < docs.length; i++) { if (docs[i].publicid == pid) { found = true; } }
func(found);
});
}
module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie) {
if ((cookie != null) && (typeof cookie.nid == 'string') && (typeof cookie.pid == 'string')) {
checkDeviceSharePublicIdentifier(parent, domain, cookie.nid, cookie.pid, function (result) {
// If the identifier if not found, close the connection
if (result == false) { try { ws.close(); } catch (e) { } return; }
// Public device sharing identifier found, continue as normal.
CreateMeshRelayEx(parent, ws, req, domain, user, cookie);
});
} else {
CreateMeshRelayEx(parent, ws, req, domain, user, cookie);
}
}
function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
const currentTime = Date.now();
if (cookie) {
if ((typeof cookie.expire == 'number') && (cookie.expire <= currentTime)) { delete req.query.nodeid; }
@ -819,6 +844,14 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
obj.ruserid = null;
obj.req = req; // Used in multi-server.js
// Setup subscription for desktop sharing public identifier
// If the identifier is removed, drop the connection
if ((cookie != null) && (typeof cookie.pid == 'string')) {
obj.pid = cookie.pid;
parent.parent.AddEventDispatch([obj.nodeid], obj);
obj.HandleEvent = function (source, event, ids, id) { if ((event.action == 'removedDeviceShare') && (obj.pid == event.publicid)) { obj.close(); } }
}
// Check relay authentication
if ((user == null) && (obj.req.query != null) && (obj.req.query.rauth != null)) {
const rcookie = parent.parent.decodeCookie(obj.req.query.rauth, parent.parent.loginCookieEncryptionKey, 240); // Cookie with 4 hour timeout
@ -878,6 +911,9 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
// Clear timers if present
if (obj.pingtimer != null) { clearInterval(obj.pingtimer); delete obj.pingtimer; }
if (obj.pongtimer != null) { clearInterval(obj.pongtimer); delete obj.pongtimer; }
// Unsubscribe
if (obj.pid != null) { parent.parent.RemoveAllEventDispatch(obj); }
};
obj.sendAgentMessage = function (command, userid, domainid) {

View File

@ -13,7 +13,33 @@
/*jshint esversion: 6 */
"use strict";
function checkDeviceSharePublicIdentifier(parent, domain, nodeid, pid, func) {
// Check the public id
parent.db.GetAllTypeNodeFiltered([nodeid], domain.id, 'deviceshare', null, function (err, docs) {
if ((err != null) || (docs.length == 0)) { func(false); return; }
// Search for the device share public identifier
var found = false;
for (var i = 0; i < docs.length; i++) { if (docs[i].publicid == pid) { found = true; } }
func(found);
});
}
module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie) {
if ((cookie != null) && (typeof cookie.nid == 'string') && (typeof cookie.pid == 'string')) {
checkDeviceSharePublicIdentifier(parent, domain, cookie.nid, cookie.pid, function (result) {
// If the identifier if not found, close the connection
if (result == false) { try { ws.close(); } catch (e) { } return; }
// Public device sharing identifier found, continue as normal.
CreateMeshRelayEx(parent, ws, req, domain, user, cookie);
});
} else {
CreateMeshRelayEx(parent, ws, req, domain, user, cookie);
}
}
function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
const currentTime = Date.now();
if (cookie) {
if ((typeof cookie.expire == 'number') && (cookie.expire <= currentTime)) { try { ws.close(); parent.parent.debug('relay', 'Relay: Expires cookie (' + req.clientIp + ')'); } catch (e) { console.log(e); } return; }
@ -26,6 +52,14 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
obj.ruserid = null;
obj.req = req; // Used in multi-server.js
// Setup subscription for desktop sharing public identifier
// If the identifier is removed, drop the connection
if ((cookie != null) && (typeof cookie.pid == 'string')) {
obj.pid = cookie.pid;
parent.parent.AddEventDispatch([cookie.nid], obj);
obj.HandleEvent = function (source, event, ids, id) { if ((event.action == 'removedDeviceShare') && (obj.pid == event.publicid)) { closeBothSides(); } }
}
// Check relay authentication
if ((user == null) && (obj.req.query != null) && (obj.req.query.rauth != null)) {
const rcookie = parent.parent.decodeCookie(obj.req.query.rauth, parent.parent.loginCookieEncryptionKey, 240); // Cookie with 4 hour timeout
@ -78,6 +112,9 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
delete obj.ws;
delete obj.peer;
delete obj.expireTimer;
// Unsubscribe
if (obj.pid != null) { parent.parent.RemoveAllEventDispatch(obj); }
};
obj.sendAgentMessage = function (command, userid, domainid) {
@ -465,6 +502,9 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
delete obj.peer;
if (obj.pingtimer != null) { clearInterval(obj.pingtimer); delete obj.pingtimer; }
if (obj.pongtimer != null) { clearInterval(obj.pongtimer); delete obj.pongtimer; }
// Unsubscribe
if (obj.pid != null) { parent.parent.RemoveAllEventDispatch(obj); }
}
// Record a new entry in a recording log

View File

@ -4577,9 +4577,107 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
ws.send(JSON.stringify({ action: 'createInviteLink', meshid: command.meshid, url: url, expire: command.expire, cookie: inviteCookie, responseid: command.responseid, tag: command.tag }));
break;
}
case 'deviceShares': {
var err = null;
if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
// Handle any errors
if (err != null) {
if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: err })); } catch (ex) { } }
break;
}
// Get the device rights
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
// If node not found or we don't have remote control, reject.
if ((node == null) || ((rights & 8) == 0)) return;
// If there is MESHRIGHT_DESKLIMITEDINPUT or MESHRIGHT_REMOTEVIEWONLY on this account, reject this request.
if ((rights != 0xFFFFFFFF) && ((rights & 4352) != 0)) return;
parent.db.GetAllTypeNodeFiltered([command.nodeid], domain.id, 'deviceshare', null, function (err, docs) {
if (err != null) return;
var now = Date.now(), removed = false, okDocs = [];
for (var i = 0; i < docs.length; i++) {
const doc = docs[i];
if (doc.expireTime < now) {
// This share is expired.
parent.db.Remove(doc._id, function () { }); delete docs[i]; removed = true;
} else {
// This share is ok, remove extra data we don't need to send.
delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
okDocs.push(doc);
}
}
try { ws.send(JSON.stringify({ action: 'deviceShares', nodeid: command.nodeid, deviceShares: okDocs })); } catch (ex) { }
// If we removed any shares, send device share update
if (removed == true) {
var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: node._id, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 });
}
});
});
break;
}
case 'removeDeviceShare': {
var err = null;
if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
else if (common.validateString(command.publicid, 1, 128) == false) { err = 'Invalid public id'; } // Check the public identifier
// Handle any errors
if (err != null) {
if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removeDeviceShare', responseid: command.responseid, result: err })); } catch (ex) { } }
break;
}
// Get the device rights
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
// If node not found or we don't have remote control, reject.
if ((node == null) || ((rights & 8) == 0)) return;
// If there is MESHRIGHT_DESKLIMITEDINPUT or MESHRIGHT_REMOTEVIEWONLY on this account, reject this request.
if ((rights != 0xFFFFFFFF) && ((rights & 4352) != 0)) return;
parent.db.GetAllTypeNodeFiltered([command.nodeid], domain.id, 'deviceshare', null, function (err, docs) {
if (err != null) return;
// Remove device sharing
var now = Date.now(), removedExact = null, removed = false, okDocs = [];
for (var i = 0; i < docs.length; i++) {
const doc = docs[i];
if (doc.publicid == command.publicid) { parent.db.Remove(doc._id, function () { }); removedExact = doc; removed = true; }
else if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); removed = true; } else {
// This share is ok, remove extra data we don't need to send.
delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
okDocs.push(doc);
}
}
// Confirm removal if requested
if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removeDeviceShare', responseid: command.responseid, nodeid: command.nodeid, publicid: doc.publicid, removed: removedExact })); } catch (ex) { } }
// Event device share removal
if (removedExact != null) {
// Send out an event that we removed a device share
var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'removedDeviceShare', msg: 'Removed Device Share', msgid: 102, msgArgs: [removedExact.guestName], domain: domain.id, publicid: command.publicid };
parent.parent.DispatchEvent(targets, obj, event);
}
// If we removed any shares, send device share update
if (removed == true) {
var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: node._id, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 });
}
});
});
break;
}
case 'createDeviceShareLink': {
var err = null, maxExpireMinutes = (typeof domain.maxguestsessionsharingtime == 'number') ? domain.maxguestsessionsharingtime : 60;
if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the meshid
if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
else if (common.validateString(command.guestname, 1, 128) == false) { err = 'Invalid guest name'; } // Check the guest name
else if (common.validateInt(command.expire, 1, maxExpireMinutes) == false) { err = 'Invalid expire time'; } // Check the expire time in hours
else if (common.validateInt(command.consent, 0, 256) == false) { err = 'Invalid flags'; } // Check the flags
@ -4595,7 +4693,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
break;
}
// Get the device from the database
// Get the device rights
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
// If node not found or we don't have remote control, reject.
if ((node == null) || ((rights & 8) == 0)) return;
@ -4604,8 +4702,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if ((rights != 0xFFFFFFFF) && ((rights & 4352) != 0)) return;
// Create cookie
var expireTime = Date.now() + (60000 * command.expire);
const inviteCookie = parent.parent.encodeCookie({ a: 5, uid: user._id, gn: command.guestname, nid: node._id, cf: command.consent, expire: expireTime }, parent.parent.invitationLinkEncryptionKey);
var publicid = getRandomPassword();
var startTime = Date.now(), expireTime = Date.now() + (60000 * command.expire);
const inviteCookie = parent.parent.encodeCookie({ a: 5, uid: user._id, gn: command.guestname, nid: node._id, cf: command.consent, start: startTime, expire: expireTime, pid: publicid }, parent.parent.invitationLinkEncryptionKey);
if (inviteCookie == null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createDeviceShareLink', responseid: command.responseid, result: 'Unable to generate shareing cookie' })); } catch (ex) { } } return; }
command.expire = expireTime;
@ -4618,6 +4717,33 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (serverName.split('.') == 1) { url = '/' + xdomain + 'desktop?c=' + inviteCookie; }
command.url = url;
ws.send(JSON.stringify(command));
// Create a device sharing database entry
parent.db.Set({ type: 'deviceshare', nodeid: node._id, domain: node.domain, publicid: publicid, startTime: startTime, expireTime: expireTime, userid: user._id, guestName: command.guestname, consent: command.consent, url: url });
// Send out an event that we added a device share
var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'addedDeviceShare', msg: 'Added Device Share', msgid: 101, msgArgs: [command.guestname, 'DATETIME:' + startTime, 'DATETIME:' + expireTime], domain: domain.id };
parent.parent.DispatchEvent(targets, obj, event);
// Send device share update
parent.db.GetAllTypeNodeFiltered([command.nodeid], domain.id, 'deviceshare', null, function (err, docs) {
if (err != null) return;
// Check device sharing
var now = Date.now();
for (var i = 0; i < docs.length; i++) {
const doc = docs[i];
if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); delete docs[i]; } else {
// This share is ok, remove extra data we don't need to send.
delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
}
}
// Send device share update
var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: node._id, action: 'deviceShareUpdate', domain: domain.id, deviceShares: docs, nolog: 1 });
});
});
break;
}

View File

@ -701,7 +701,6 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen);
var port = common.ReadInt(data, 10 + requestLen + addrLen);
parent.debug('mpscmd', '--> GLOBAL_REQUEST', request, addr + ':' + port);
ChangeHostname(socket, addr, socket.tag.SystemId);
if (socket.tag.boundPorts.indexOf(port) == -1) { socket.tag.boundPorts.push(port); }
SendTcpForwardSuccessReply(socket, port);
return 14 + requestLen + addrLen;
@ -1131,44 +1130,6 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
return cirachannel;
};
function ChangeHostname(socket, host, systemid) {
if (socket.tag.host === host) return; // Nothing to change
socket.tag.host = host;
// Change the device
obj.db.Get(socket.tag.nodeid, function (err, nodes) {
if ((nodes == null) || (nodes.length !== 1)) return;
var node = nodes[0];
// See if any changes need to be made
if ((node.intelamt != null) && (node.intelamt.host == host) && (node.name != null) && (node.name != '') && (node.intelamt.state == 2)) return;
// Get the mesh for this device
obj.db.Get(node.meshid, function (err, meshes) {
if ((meshes == null) || (meshes.length !== 1)) return;
var mesh = meshes[0];
// Ready the node change event
var changes = ['host'], event = { etype: 'node', action: 'changenode', nodeid: node._id };
event.msg = +": ";
// Make the change & save
if (node.intelamt == null) node.intelamt = {};
node.intelamt.host = host;
node.intelamt.state = 2; // Set the state to activated, since this is pretty obvious, we have a CIRA connection.
if (((node.name == null) || (node.name == '')) && (host != null) && (host != '')) { node.name = host.split('.')[0]; } // If this system has no name, set it to the start of the domain name.
if (((node.name == null) || (node.name == '')) && (systemid != null)) { node.name = systemid; } // If this system still has no name, set it to the system GUID.
obj.db.Set(node);
// Event the node change
event.msg = 'CIRA changed device ' + node.name + ' from group ' + mesh.name + ': ' + changes.join(', ');
event.node = parent.webserver.CloneSafeNode(node);
if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
obj.parent.DispatchEvent(['*', node.meshid], obj, event);
});
});
}
// Change a node to a new meshid, this is called when a node changes groups.
obj.changeDeviceMesh = function (nodeid, newMeshId) {
var connectionArray = obj.ciraConnections[nodeid];

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1795,6 +1795,7 @@
powerTimelineReq = null;
powerTimelineNode = null;
powerTimelineUpdate = null;
deviceShares = null;
deleteAllNotifications(); // Close and clear notifications if present
hideContextMenu(); // Hide the context menu if present
QV('verifyEmailId2', false);
@ -2116,6 +2117,13 @@
if (currentNode._id == message.nodeid) { mainUpdate(256); }
break;
}
case 'deviceShares': {
if (message.nodeid != deviceSharesReq) break;
deviceSharesNode = message.nodeid;
deviceShares = message.deviceShares;
if (currentNode._id == message.nodeid) { gotoDevice(currentNode._id, xxcurrentView, true); }
break;
}
case 'getsysinfo': {
if (message.nodeid != powerTimelineReq) break;
if (message.noinfo === true) {
@ -2495,7 +2503,7 @@
}
case 'event': {
if (!message.event.nolog) {
if (currentNode && (message.event.nodeid == currentNode._id)) {
if (currentNode && (message.event.nodeid == currentNode._id) && (currentDeviceEvents != null)) {
// If this event has a nodeid and we are looking at this node, update the log in real time.
currentDeviceEvents.unshift(message.event);
var eventLimit = parseInt(p16limitdropdown.value);
@ -2520,6 +2528,13 @@
if (message.event.noact) break; // Take no action on this event
switch (message.event.action) {
case 'deviceShareUpdate': {
if (message.event.nodeid != deviceSharesReq) break;
deviceSharesNode = message.event.nodeid;
deviceShares = message.event.deviceShares;
if (currentNode._id == deviceSharesNode) { gotoDevice(currentNode._id, xxcurrentView, true); }
break;
}
case 'agentlog': {
if (message.event.msgid == 98) {
// This is a agent help request, popup a notification.
@ -5525,6 +5540,11 @@
var powerTimelineReq = null;
var powerTimelineUpdate = null;
var powerTimeline = null;
var deviceSharesNode = null;
var deviceSharesReq = null;
var deviceShares = null;
function getCurrentNode() { return currentNode; };
function gotoDevice(nodeid, panel, refresh, event) {
// Remind the user to verify the email address
@ -5881,6 +5901,12 @@
QH('p17info', '');
}
// Request device sharing
if ((deviceSharesNode != currentNode._id) && (deviceSharesReq != currentNode._id)) {
deviceSharesReq = currentNode._id;
meshserver.send({ action: 'deviceShares', nodeid: currentNode._id });
}
// Reset the desktop tools
QV('DeskTools', false);
showDeskToolsProcesses();
@ -5957,8 +5983,23 @@
}
if (count == 1) { x += '<tr><td><div style=padding:6px>&nbsp;<i>' + "No users with special device permissions" + '</i><div></div></div></td><td></td></tr>'; }
x += '</tbody></table>';
// Show device shares
if ((deviceShares != null) && (deviceSharesNode == currentNode._id) && (deviceShares.length > 0)) {
x += '<br /><table style="color:black;background-color:#EEE;border-color:#AAA;border-width:1px;border-style:solid;border-collapse:collapse" border=0 cellpadding=2 cellspacing=0 width=100%><tbody><tr style=background-color:#AAAAAA;font-weight:bold><th scope=col style=text-align:left;width:430px>' + "Active Device Sharing" + '</th><th scope=col style=text-align:left></th></tr>';
count = 1;
for (var i = 0; i < deviceShares.length; i++) {
var dshare = deviceShares[i];
var trash = '<a href="' + dshare.url + '" rel="noreferrer noopener" target=_blank title="' + "Device Sharing Link" + '" style=cursor:pointer><img src=images/link2.png border=0 height=10 width=10></a> <a href=# onclick=\'return p30removeDeviceSharing(event,"' + encodeURIComponentEx(currentNode._id) + '","' + encodeURIComponentEx(dshare.publicid) + '","' + encodeURIComponentEx(dshare.guestName) + '")\' title="' + "Remove device sharing" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
var details = printFlexDateTime(new Date(dshare.startTime)) + ' to ' + printFlexDateTime(new Date(dshare.expireTime));
if (dshare.consent) { if ((dshare.consent & 8) != 0) { details += ', Prompt for consent'; } }
x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%><div class=m' + 2 + '></div><div>&nbsp;' + dshare.guestName + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + details + '</div></td></tr>';
}
x += '</tbody></table>';
}
QH('p10html4', x);
// Change the URL
var urlviewmode = '';
if (((features & 0x10000000) == 0) && (xxcurrentView >= 10) && (xxcurrentView <= 19) && (currentNode != null)) {
@ -8415,7 +8456,12 @@
msg = EscapeHtml(event.msg).split('(R)').join('&reg;');
} else {
msg = eventsMessageId[event.msgid];
if (event.msgArgs != null) { for (var i in event.msgArgs) { msg = msg.split('{' + i + '}').join(event.msgArgs[i]); } }
for (var i in event.msgArgs) {
var xx = event.msgArgs[i];
if ((typeof xx == 'string') && (xx.indexOf('DATETIME:') == 0)) { xx = printFlexDateTime(new Date(parseInt(xx.substring(9)))); }
msg = msg.split('{' + i + '}').join(xx);
}
//if (event.msgArgs != null) { for (var i in event.msgArgs) { msg = msg.split('{' + i + '}').join(event.msgArgs[i]); } }
msg = EscapeHtml(msg).split('(R)').join('&reg;');
}
if (event.username) {
@ -10828,7 +10874,9 @@
97: "Removed phone number of user {0}",
98: "Help Requested, user: {0}, details: {1}",
99: "Running commands as user",
100: "Running commands as user if possible"
100: "Running commands as user if possible",
101: "Added device share {0} from {1} to {2}",
102: "Removed device share {0}"
};
// Highlights the device being hovered
@ -10861,7 +10909,13 @@
msg = EscapeHtml(event.msg).split('(R)').join('&reg;');
} else {
msg = eventsMessageId[event.msgid];
if (event.msgArgs != null) { for (var i in event.msgArgs) { msg = msg.split('{' + i + '}').join(event.msgArgs[i]); } }
if (event.msgArgs != null) {
for (var i in event.msgArgs) {
var xx = event.msgArgs[i];
if ((typeof xx == 'string') && (xx.indexOf('DATETIME:') == 0)) { xx = printFlexDateTime(new Date(parseInt(xx.substring(9)))); }
msg = msg.split('{' + i + '}').join(xx);
}
}
msg = EscapeHtml(msg).split('(R)').join('&reg;');
}
if (event.nodeid) {
@ -12357,6 +12411,11 @@
QH('p30html2', x);
}
function p30removeDeviceSharing(event, nodeid, publicid, guestname) {
if (xxdialogMode) return;
setDialogMode(2, "Remove Device Share", 3, function(b, tag) { meshserver.send({ action: 'removeDeviceShare', nodeid: tag[0], publicid: tag[1] }); }, format("Confirm removal of device share \"{0}\"?", decodeURIComponent(guestname)), [ decodeURIComponent(nodeid), decodeURIComponent(publicid) ]);
}
function p30removeNodeFromUser(event, nodeid) {
if (xxdialogMode) return;
var node = getNodeFromId(decodeURIComponent(nodeid));
@ -12441,7 +12500,12 @@
msg = EscapeHtml(event.msg).split('(R)').join('&reg;');
} else {
msg = eventsMessageId[event.msgid];
if (event.msgArgs != null) { for (var i in event.msgArgs) { msg = msg.split('{' + i + '}').join(event.msgArgs[i]); } }
for (var i in event.msgArgs) {
var xx = event.msgArgs[i];
if ((typeof xx == 'string') && (xx.indexOf('DATETIME:') == 0)) { xx = printFlexDateTime(new Date(parseInt(xx.substring(9)))); }
msg = msg.split('{' + i + '}').join(xx);
}
//if (event.msgArgs != null) { for (var i in event.msgArgs) { msg = msg.split('{' + i + '}').join(event.msgArgs[i]); } }
msg = EscapeHtml(msg).split('(R)').join('&reg;');
}
if (event.nodeid) {
@ -13700,6 +13764,7 @@
function printDate(d) { return d.toLocaleDateString(args.locale); }
function printTime(d) { return d.toLocaleTimeString(args.locale); }
function printDateTime(d) { return d.toLocaleString(args.locale); }
function printFlexDateTime(d) { if (printDate(new Date()) == printDate(d)) { return printTime(d); } else { return printDateTime(d); } }
function addDetailItem(title, value, state) { return '<table style=width:100%><td>' + nobreak(title) + '<td style=text-align:right>' + value + '</table>'; }
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
function addTextLink(subtext, text, link) { var i = text.toLowerCase().indexOf(subtext.toLowerCase()); if (i == -1) { return text; } return text.substring(0, i) + '<a href="' + link + '">' + subtext + '</a>' + text.substring(i + subtext.length); }

View File

@ -2922,17 +2922,30 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check the inbound desktop sharing cookie
var c = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey, 60); // 60 minute timeout
if ((c == null) || (c.a !== 5) || (typeof c.uid != 'string') || (typeof c.nid != 'string') || (typeof c.gn != 'string') || (typeof c.cf != 'number') || (typeof c.expire != 'number') || (c.expire <= Date.now())) { res.sendStatus(404); return; }
if ((c == null) || (c.a !== 5) || (typeof c.uid != 'string') || (typeof c.nid != 'string') || (typeof c.gn != 'string') || (typeof c.cf != 'number') || (typeof c.start != 'number') || (typeof c.expire != 'number') || (typeof c.pid != 'string') || (c.expire <= Date.now())) { res.sendStatus(404); return; }
// Looks good, let's create the outbound session cookies.
// Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
const authCookie = obj.parent.encodeCookie({ userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, gn: c.gn, cf: 65 | c.cf, r: 8, expire: c.expire }, obj.parent.loginCookieEncryptionKey);
// Check the start time
if ((c.start > Date.now()) || (c.start > c.expire)) { res.sendStatus(404); return; }
// Lets respond by sending out the desktop viewer.
var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
parent.debug('web', 'handleDesktopRequest: Sending guest desktop page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
res.set({ 'Cache-Control': 'no-store' });
render(req, res, getRenderPage('desktop', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: '', domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), nodeid: c.nid, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, expire: c.expire }, req, domain));
// Check the public id
obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) {
if ((err != null) || (docs.length == 0)) { res.sendStatus(404); return; }
// Search for the device share public identifier
var found = false;
for (var i = 0; i < docs.length; i++) { if (docs[i].publicid == c.pid) { found = true; } }
if (found == false) { res.sendStatus(404); return; }
// Looks good, let's create the outbound session cookies.
// Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
const authCookie = obj.parent.encodeCookie({ userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, gn: c.gn, cf: 65 | c.cf, r: 8, expire: c.expire, pid: c.pid }, obj.parent.loginCookieEncryptionKey);
// Lets respond by sending out the desktop viewer.
var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
parent.debug('web', 'handleDesktopRequest: Sending guest desktop page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
res.set({ 'Cache-Control': 'no-store' });
render(req, res, getRenderPage('desktop', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: '', domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), nodeid: c.nid, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, expire: c.expire }, req, domain));
});
}
// Handle domain redirection