mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-12-22 21:31:35 +03:00
Added multi-agent upload feature.
This commit is contained in:
parent
b1e369aee0
commit
c24c8821fe
@ -43,6 +43,8 @@ var MESHRIGHT_NODESKTOP = 65536;
|
||||
|
||||
function createMeshCore(agent) {
|
||||
var obj = {};
|
||||
var agentFileHttpRequests = {}; // Currently active agent HTTPS GET requests from the server.
|
||||
|
||||
if (process.platform == 'win32' && require('user-sessions').isRoot()) {
|
||||
// Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
|
||||
try {
|
||||
@ -1140,6 +1142,33 @@ function createMeshCore(agent) {
|
||||
case 'meshToolInfo':
|
||||
if (data.pipe == true) { delete data.pipe; delete data.action; data.cmd = 'meshToolInfo'; broadcastToRegisteredApps(data); }
|
||||
break;
|
||||
case 'wget': // Server uses this command to tell the agent to download a file using HTTPS/GET and place it in a given path. This is used for one-to-many file uploads.
|
||||
if ((data.overwrite !== true) && fs.existsSync(data.path)) break; // Don't overwrite an existing file.
|
||||
data.url = 'http' + getServerTargetUrlEx('*/').substring(2);
|
||||
var agentFileHttpOptions = http.parseUri(data.url);
|
||||
agentFileHttpOptions.path = data.urlpath;
|
||||
agentFileHttpOptions.rejectUnauthorized = 0; // TODO: Check TLS cert
|
||||
if (agentFileHttpOptions == null) break;
|
||||
var agentFileHttpRequest = http.request(agentFileHttpOptions,
|
||||
function (response) {
|
||||
response.xparent = this;
|
||||
try {
|
||||
response.xfile = fs.createWriteStream(this.xpath, { flags: 'wbN' })
|
||||
response.pipe(response.xfile);
|
||||
response.end = function () { delete agentFileHttpRequests[this.xparent.xurlpath]; delete this.xparent; }
|
||||
} catch (ex) {
|
||||
delete agentFileHttpRequests[this.xurlpath];
|
||||
delete response.xparent;
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
agentFileHttpRequest.on('error', function (ex) { delete agentFileHttpRequests[this.xurlpath]; });
|
||||
agentFileHttpRequest.end();
|
||||
agentFileHttpRequest.xurlpath = data.urlpath;
|
||||
agentFileHttpRequest.xpath = data.path;
|
||||
agentFileHttpRequests[data.urlpath] = agentFileHttpRequest;
|
||||
break;
|
||||
default:
|
||||
// Unknown action, ignore it.
|
||||
break;
|
||||
|
@ -107,6 +107,13 @@ function CreateMeshCentralServer(config, args) {
|
||||
if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/emails'))) { obj.webEmailsOverridePath = obj.path.join(__dirname, '../meshcentral-web/emails'); }
|
||||
}
|
||||
|
||||
// Clean up any temporary files
|
||||
var removeTime = new Date(Date.now()).getTime() - (30 * 60 * 1000); // 30 minutes
|
||||
var dir = obj.fs.readdir(obj.path.join(obj.filespath, 'tmp'), function (err, files) {
|
||||
if (err != null) return;
|
||||
for (var i in files) { try { const filepath = obj.path.join(obj.filespath, 'tmp', files[i]); if (obj.fs.statSync(filepath).mtime.getTime() < removeTime) { obj.fs.unlink(filepath, function () { }); } } catch (ex) { } }
|
||||
});
|
||||
|
||||
// Look to see if data and/or file path is specified
|
||||
if (obj.config.settings && (typeof obj.config.settings.datapath == 'string')) { obj.datapath = obj.config.settings.datapath; }
|
||||
if (obj.config.settings && (typeof obj.config.settings.filespath == 'string')) { obj.filespath = obj.config.settings.filespath; }
|
||||
@ -1550,6 +1557,13 @@ function CreateMeshCentralServer(config, args) {
|
||||
// Perform database maintenance
|
||||
obj.db.maintenance();
|
||||
|
||||
// Clean up any temporary files
|
||||
var removeTime = new Date(Date.now()).getTime() - (30 * 60 * 1000); // 30 minutes
|
||||
var dir = obj.fs.readdir(obj.path.join(obj.filespath, 'tmp'), function (err, files) {
|
||||
if (err != null) return;
|
||||
for (var i in files) { try { const filepath = obj.path.join(obj.filespath, 'tmp', files[i]); if (obj.fs.statSync(filepath).mtime.getTime() < removeTime) { obj.fs.unlink(filepath, function () { }); } } catch (ex) { } }
|
||||
});
|
||||
|
||||
// Check for self-update that targets a specific version
|
||||
if ((typeof obj.args.selfupdate == 'string') && (getCurrentVerion() === obj.args.selfupdate)) { obj.args.selfupdate = false; }
|
||||
|
||||
|
2
public/scripts/amt-wsman-0.2.0-min.js
vendored
2
public/scripts/amt-wsman-0.2.0-min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -4440,6 +4440,7 @@
|
||||
if ((rights & 131072) && ((added & 16) == 0)) { added |= 16; addedOptions += '<option value=106>' + "Run commands" + '</option>'; }
|
||||
if ((rights & 16384) && ((added & 32) == 0)) { added |= 32; addedOptions += '<option value=108>' + "Device notification" + '</option>'; }
|
||||
if ((rights & 4) && ((added & 64) == 0)) { added |= 64; addedOptions += '<option value=107>' + "Edit tags" + '</option>'; }
|
||||
if ((rights & 8) && ((added & 256) == 0)) { added |= 256; addedOptions += '<option value=109>' + "Upload files" + '</option>'; }
|
||||
if ((rights & 32768) && ((added & 128) == 0)) { added |= 128; addedOptions += '<option value=101>' + "Delete devices" + '</option>'; }
|
||||
}
|
||||
|
||||
@ -4519,6 +4520,19 @@
|
||||
x += '<textarea id=d2notifyMsg style=background-color:#fcf3cf;width:100%;height:140px;resize:none;overflow-y:scroll></textarea>';
|
||||
setDialogMode(2, "Device Notification", 3, d2groupActionFunctionNotifyExec, x);
|
||||
Q('d2notifyMsg').focus();
|
||||
} else if (op == 109) {
|
||||
// Upload files
|
||||
var wintype = false, linuxtype = false, chkNodeIds = getCheckedDevices();
|
||||
for (var i in chkNodeIds) { var n = getNodeFromId(chkNodeIds[i]); if (n.agent) { if ((n.agent.id > 0) && (n.agent.id < 5)) { wintype = true; } else { linuxtype = true; } } }
|
||||
var x = "Upload selected files to all selected devices" + '<br /><br />';
|
||||
x += '<form method=post enctype=multipart/form-data action=uploadfilebatch.ashx target=fileUploadFrame>';
|
||||
x += '<input type=hidden name=authCookie value=' + authCookie + ' /><input type=hidden name=nodeIds value=' + chkNodeIds.join(',') + ' /><input type=submit id=d2batchUploadSubmit style=display:none />';
|
||||
x += '<input type=file name=files id=d2uploadinput style=width:100% multiple=multiple onchange="d2batchUploadValidate()" /><br /><br />';
|
||||
if (wintype) { x += addHtmlValue("Windows Path", '<input style=width:250px type=text onchange="d2batchUploadValidate()" onkeyup="d2batchUploadValidate()" name=winpath id=d2winuploadpath placeholder="C:\\temp" value="" />'); }
|
||||
if (linuxtype) { x += addHtmlValue("Linux Path", '<input style=width:250px type=text onchange="d2batchUploadValidate()" onkeyup="d2batchUploadValidate()" name=linuxpath id=d2linuxuploadpath placeholder="/tmp" value="" />'); }
|
||||
x += '<br /><label><input type=checkbox name=overwriteFiles />' + "Overwrite if file exists?" + '</label></form>';
|
||||
setDialogMode(2, "Batch File Upload", 3, d2batchUploadValidateOk, x);
|
||||
d2batchUploadValidate();
|
||||
} else {
|
||||
// Power operation
|
||||
meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: parseInt(op) });
|
||||
@ -4526,6 +4540,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
function d2batchUploadValidate() { QE('idx_dlgOkButton', (Q('d2uploadinput').files.length != 0) && ((Q('d2winuploadpath') == null) || (Q('d2winuploadpath').value != '')) && ((Q('d2linuxuploadpath') == null) || (Q('d2linuxuploadpath').value != ''))); }
|
||||
function d2batchUploadValidateOk() { Q('d2batchUploadSubmit').click(); }
|
||||
|
||||
function d2groupActionFunctionNotifyExec() {
|
||||
var op = Q('d2deviceop').value, title = Q('dp2notifyTitle').value, msg = Q('d2notifyMsg').value, chkNodeIds = getCheckedDevices();
|
||||
if (msg.length == 0) return;
|
||||
@ -10735,10 +10752,10 @@
|
||||
|
||||
if (overWriteCount == 0) {
|
||||
// If no overwrite, go ahead with upload
|
||||
p5PerformUpload(1, files);
|
||||
p5PerformUpload(1, e.dataTransfer.files);
|
||||
} else {
|
||||
// Otherwise, prompt for confirmation
|
||||
setDialogMode(2, "Upload File", 3, p5PerformUpload, format((overWriteCount == 1)?"Upload will overwrite 1 file. Continue?":"Upload will overwrite {0} files. Continue?", overWriteCount), files);
|
||||
setDialogMode(2, "Upload File", 3, p5PerformUpload, format((overWriteCount == 1)?"Upload will overwrite 1 file. Continue?":"Upload will overwrite {0} files. Continue?", overWriteCount), e.dataTransfer.files);
|
||||
}
|
||||
}
|
||||
|
||||
|
85
webserver.js
85
webserver.js
@ -2718,6 +2718,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, null, res, req, domain, user, meshid, nodeid);
|
||||
}
|
||||
|
||||
// Handle download of a server file by an agent
|
||||
function handleAgentDownloadFile(req, res) {
|
||||
const domain = checkUserIpAddress(req, res);
|
||||
if (domain == null) { return; }
|
||||
if (req.query.c == null) { res.sendStatus(404); return; }
|
||||
|
||||
// Check the inbound desktop sharing cookie
|
||||
var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 5); // 5 minute timeout
|
||||
if ((c == null) || (c.a != 'tmpdl') || (c.d != domain.id) || (c.nid == null) || (c.f == null) || (obj.common.IsFilenameValid(c.f) == false)) { res.sendStatus(404); return; }
|
||||
|
||||
// Send the file back
|
||||
try { res.sendFile(obj.path.join(obj.filespath, 'tmp', c.f)); return; } catch (ex) { res.sendStatus(404); }
|
||||
}
|
||||
|
||||
// Handle logo request
|
||||
function handleLogoRequest(req, res) {
|
||||
const domain = checkUserIpAddress(req, res);
|
||||
@ -3154,6 +3168,75 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
});
|
||||
}
|
||||
|
||||
// Upload a file to the server and then batch upload to many agents
|
||||
function handleUploadFileBatch(req, res) {
|
||||
const domain = checkUserIpAddress(req, res);
|
||||
if (domain == null) { return; }
|
||||
if (domain.userQuota == -1) { res.sendStatus(401); return; }
|
||||
var authUserid = null;
|
||||
if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
|
||||
const multiparty = require('multiparty');
|
||||
const form = new multiparty.Form();
|
||||
form.parse(req, function (err, fields, files) {
|
||||
// If an authentication cookie is embedded in the form, use that.
|
||||
if ((fields != null) && (fields.auth != null) && (fields.auth.length == 1) && (typeof fields.auth[0] == 'string')) {
|
||||
var loginCookie = obj.parent.decodeCookie(fields.auth[0], obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
|
||||
if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // Check cookie IP binding.
|
||||
if ((loginCookie != null) && (domain.id == loginCookie.domainid)) { authUserid = loginCookie.userid; } // Use cookie authentication
|
||||
}
|
||||
if (authUserid == null) { res.sendStatus(401); return; }
|
||||
|
||||
// Get the user
|
||||
const user = obj.users[authUserid];
|
||||
if ((user == null) || (user.siteadmin & 8) == 0) { res.sendStatus(401); return; } // Check if we have file rights
|
||||
|
||||
// Get fields
|
||||
if ((fields == null) || (fields.nodeIds == null) || (fields.nodeIds.length != 1)) { res.sendStatus(404); return; }
|
||||
var cmd = { nodeids: fields.nodeIds[0].split(','), files: [], user: user, domain: domain };
|
||||
if ((fields.winpath != null) && (fields.winpath.length == 1)) { cmd.windowsPath = fields.winpath[0]; }
|
||||
if ((fields.linuxpath != null) && (fields.linuxpath.length == 1)) { cmd.linuxPath = fields.linuxpath[0]; }
|
||||
if ((fields.overwriteFiles != null) && (fields.overwriteFiles.length == 1) && (fields.overwriteFiles[0] == 'on')) { cmd.overwrite = true; }
|
||||
|
||||
// Get server temporary path
|
||||
var serverpath = obj.path.join(obj.filespath, 'tmp')
|
||||
try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { }
|
||||
try { obj.fs.mkdirSync(serverpath); } catch (ex) { }
|
||||
|
||||
// More typical upload method, the file data is in a multipart mime post.
|
||||
for (var i in files.files) {
|
||||
var file = files.files[i], ftarget = getRandomPassword() + '-' + file.originalFilename, fpath = obj.path.join(serverpath, ftarget);
|
||||
cmd.files.push({ name: file.originalFilename, target: ftarget });
|
||||
// Rename the file
|
||||
obj.fs.rename(file.path, fpath, function (err) {
|
||||
if (err && (err.code === 'EXDEV')) {
|
||||
// On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
|
||||
obj.common.copyFile(file.path, fpath, function (err) { obj.fs.unlink(file.path, function (err) { handleUploadFileBatchEx(cmd); }); });
|
||||
} else {
|
||||
handleUploadFileBatchEx(cmd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.send('');
|
||||
});
|
||||
}
|
||||
|
||||
// Instruct one of more agents to download a URL to a given local drive location.
|
||||
function handleUploadFileBatchEx(cmd) {
|
||||
for (var i in cmd.nodeids) {
|
||||
obj.GetNodeWithRights(cmd.domain, cmd.user, cmd.nodeids[i], function (node, rights, visible) {
|
||||
if ((node == null) || ((rights & 8) == 0) || (visible == false)) return; // We don't have remote control rights to this device
|
||||
var agentPath = ((node.agent.id > 0) && (node.agent.id < 5)) ? cmd.windowsPath : cmd.linuxPath;
|
||||
for (var f in cmd.files) {
|
||||
const acmd = { action: 'wget', overwrite: cmd.overwrite, urlpath: '/agentdownload.ashx?c=' + obj.parent.encodeCookie({ a: 'tmpdl', d: cmd.domain.id, nid: node._id, f: cmd.files[f].target }, obj.parent.loginCookieEncryptionKey), path: obj.path.join(agentPath, cmd.files[f].name) };
|
||||
var agent = obj.wsagents[node._id];
|
||||
if (agent != null) { try { agent.send(JSON.stringify(acmd)); } catch (ex) { } }
|
||||
// TODO: Add support for peer servers.
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to all events we are allowed to receive
|
||||
obj.subscribe = function (userid, target) {
|
||||
const user = obj.users[userid];
|
||||
@ -4793,6 +4876,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
obj.app.get(url + 'devicepowerevents.ashx', obj.handleDevicePowerEvents);
|
||||
obj.app.get(url + 'downloadfile.ashx', handleDownloadFile);
|
||||
obj.app.post(url + 'uploadfile.ashx', handleUploadFile);
|
||||
obj.app.post(url + 'uploadfilebatch.ashx', handleUploadFileBatch);
|
||||
obj.app.post(url + 'uploadmeshcorefile.ashx', handleUploadMeshCoreFile);
|
||||
obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
|
||||
obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
|
||||
@ -4808,6 +4892,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
});
|
||||
obj.app.ws(url + 'devicefile.ashx', function (ws, req) { obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, ws, null, req, domain); });
|
||||
obj.app.get(url + 'devicefile.ashx', handleDeviceFile);
|
||||
obj.app.get(url + 'agentdownload.ashx', handleAgentDownloadFile);
|
||||
obj.app.get(url + 'logo.png', handleLogoRequest);
|
||||
obj.app.get(url + 'loginlogo.png', handleLoginLogoRequest);
|
||||
obj.app.post(url + 'translations', handleTranslationsRequest);
|
||||
|
Loading…
Reference in New Issue
Block a user