Improved MongoDB handling.

Ylian Saint-Hilaire committed Jan 26, 2021 at 13:34 UTC 89d5e8bbbd4bc3ae82e227b887a6b43379d3529d
2 files changed +11 -1829
db-good.js deleted
-1826
@@ -1,1826 +0,0 @@
1 -/**
2 -* @description MeshCentral database module
3 -* @author Ylian Saint-Hilaire
4 -* @copyright Intel Corporation 2018-2020
5 -* @license Apache-2.0
6 -* @version v0.0.2
7 -*/
8 -
9 -/*xjslint node: true */
10 -/*xjslint plusplus: true */
11 -/*xjslint maxlen: 256 */
12 -/*jshint node: true */
13 -/*jshint strict: false */
14 -/*jshint esversion: 6 */
15 -"use strict";
16 -
17 -//
18 -// Construct Meshcentral database object
19 -//
20 -// The default database is NeDB
21 -// https://github.com/louischatriot/nedb
22 -//
23 -// Alternativety, MongoDB can be used
24 -// https://www.mongodb.com/
25 -// Just run with --mongodb [connectionstring], where the connection string is documented here: https://docs.mongodb.com/manual/reference/connection-string/
26 -// The default collection is "meshcentral", but you can override it using --mongodbcol [collection]
27 -//
28 -module.exports.CreateDB = function (parent, func) {
29 - var obj = {};
30 - var Datastore = null;
31 - var expireEventsSeconds = (60 * 60 * 24 * 20); // By default, expire events after 20 days (1728000). (Seconds * Minutes * Hours * Days)
32 - var expirePowerEventsSeconds = (60 * 60 * 24 * 10); // By default, expire power events after 10 days (864000). (Seconds * Minutes * Hours * Days)
33 - var expireServerStatsSeconds = (60 * 60 * 24 * 30); // By default, expire power events after 30 days (2592000). (Seconds * Minutes * Hours * Days)
34 - const common = require('./common.js');
35 - obj.identifier = null;
36 - obj.dbKey = null;
37 - obj.dbRecordsEncryptKey = null;
38 - obj.dbRecordsDecryptKey = null;
39 - obj.changeStream = false;
40 - obj.pluginsActive = ((parent.config) && (parent.config.settings) && (parent.config.settings.plugins != null) && (parent.config.settings.plugins != false) && ((typeof parent.config.settings.plugins != 'object') || (parent.config.settings.plugins.enabled != false)));
41 -
42 - obj.SetupDatabase = function (func) {
43 - // Check if the database unique identifier is present
44 - // This is used to check that in server peering mode, everyone is using the same database.
45 - obj.Get('DatabaseIdentifier', function (err, docs) {
46 - if (err != null) { parent.debug('db', 'ERROR (Get DatabaseIdentifier): ' + err); }
47 - if ((err == null) && (docs.length == 1) && (docs[0].value != null)) {
48 - obj.identifier = docs[0].value;
49 - } else {
50 - obj.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
51 - obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
52 - }
53 - });
54 -
55 - // Load database schema version and check if we need to update
56 - obj.Get('SchemaVersion', function (err, docs) {
57 - if (err != null) { parent.debug('db', 'ERROR (Get SchemaVersion): ' + err); }
58 - var ver = 0;
59 - if ((err == null) && (docs.length == 1)) { ver = docs[0].value; }
60 - if (ver == 1) { console.log('This is an unsupported beta 1 database, delete it to create a new one.'); process.exit(0); }
61 -
62 - // TODO: Any schema upgrades here...
63 - obj.Set({ _id: 'SchemaVersion', value: 2 });
64 -
65 - func(ver);
66 - });
67 - };
68 -
69 - // Perform database maintenance
70 - obj.maintenance = function () {
71 - if (obj.databaseType == 1) { // NeDB will not remove expired records unless we try to access them. This will force the removal.
72 - obj.eventsfile.remove({ time: { '$lt': new Date(Date.now() - (expireEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
73 - obj.powerfile.remove({ time: { '$lt': new Date(Date.now() - (expirePowerEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
74 - obj.serverstatsfile.remove({ time: { '$lt': new Date(Date.now() - (expireServerStatsSeconds * 1000)) } }, { multi: true }); // Force delete older events
75 - }
76 - }
77 -
78 - obj.cleanup = function (func) {
79 - // TODO: Remove all mesh links to invalid users
80 - // TODO: Remove all meshes that dont have any links
81 -
82 - // Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
83 - if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
84 - // MariaDB or MySQL
85 - obj.RemoveAllOfType('event', function () { });
86 - obj.RemoveAllOfType('power', function () { });
87 - obj.RemoveAllOfType('smbios', function () { });
88 - } else if (obj.databaseType == 3) {
89 - // MongoDB
90 - obj.file.deleteMany({ type: 'event' }, { multi: true });
91 - obj.file.deleteMany({ type: 'power' }, { multi: true });
92 - obj.file.deleteMany({ type: 'smbios' }, { multi: true });
93 - } else {
94 - // NeDB or MongoJS
95 - obj.file.remove({ type: 'event' }, { multi: true });
96 - obj.file.remove({ type: 'power' }, { multi: true });
97 - obj.file.remove({ type: 'smbios' }, { multi: true });
98 - }
99 -
100 - // List of valid identifiers
101 - var validIdentifiers = {}
102 -
103 - // Load all user groups
104 - obj.GetAllType('ugrp', function (err, docs) {
105 - if (err != null) { parent.debug('db', 'ERROR (GetAll user): ' + err); }
106 - if ((err == null) && (docs.length > 0)) {
107 - for (var i in docs) {
108 - // Add this as a valid user identifier
109 - validIdentifiers[docs[i]._id] = 1;
110 - }
111 - }
112 -
113 - // Fix all of the creating & login to ticks by seconds, not milliseconds.
114 - obj.GetAllType('user', function (err, docs) {
115 - if (err != null) { parent.debug('db', 'ERROR (GetAll user): ' + err); }
116 - if ((err == null) && (docs.length > 0)) {
117 - for (var i in docs) {
118 - var fixed = false;
119 -
120 - // Add this as a valid user identifier
121 - validIdentifiers[docs[i]._id] = 1;
122 -
123 - // Fix email address capitalization
124 - if (docs[i].email && (docs[i].email != docs[i].email.toLowerCase())) {
125 - docs[i].email = docs[i].email.toLowerCase(); fixed = true;
126 - }
127 -
128 - // Fix account creation
129 - if (docs[i].creation) {
130 - if (docs[i].creation > 1300000000000) { docs[i].creation = Math.floor(docs[i].creation / 1000); fixed = true; }
131 - if ((docs[i].creation % 1) != 0) { docs[i].creation = Math.floor(docs[i].creation); fixed = true; }
132 - }
133 -
134 - // Fix last account login
135 - if (docs[i].login) {
136 - if (docs[i].login > 1300000000000) { docs[i].login = Math.floor(docs[i].login / 1000); fixed = true; }
137 - if ((docs[i].login % 1) != 0) { docs[i].login = Math.floor(docs[i].login); fixed = true; }
138 - }
139 -
140 - // Fix last password change
141 - if (docs[i].passchange) {
142 - if (docs[i].passchange > 1300000000000) { docs[i].passchange = Math.floor(docs[i].passchange / 1000); fixed = true; }
143 - if ((docs[i].passchange % 1) != 0) { docs[i].passchange = Math.floor(docs[i].passchange); fixed = true; }
144 - }
145 -
146 - // Fix subscriptions
147 - if (docs[i].subscriptions != null) { delete docs[i].subscriptions; fixed = true; }
148 -
149 - // Save the user if needed
150 - if (fixed) { obj.Set(docs[i]); }
151 - }
152 -
153 - // Remove all objects that have a "meshid" that no longer points to a valid mesh.
154 - // Fix any incorrectly escaped user identifiers
155 - obj.GetAllType('mesh', function (err, docs) {
156 - if (err != null) { parent.debug('db', 'ERROR (GetAll mesh): ' + err); }
157 - var meshlist = [];
158 - if ((err == null) && (docs.length > 0)) {
159 - for (var i in docs) {
160 - var meshChange = false;
161 - docs[i] = common.unEscapeLinksFieldName(docs[i]);
162 - meshlist.push(docs[i]._id);
163 -
164 - // Make sure all mesh types are number type, if not, fix it.
165 - if (typeof docs[i].mtype == 'string') { docs[i].mtype = parseInt(docs[i].mtype); meshChange = true; }
166 -
167 - // Take a look at the links
168 - if (docs[i].links != null) {
169 - for (var j in docs[i].links) {
170 - if (validIdentifiers[j] == null) {
171 - // This identifier is not known, let see if we can fix it.
172 - var xid = j, xid2 = common.unEscapeFieldName(xid);
173 - while ((xid != xid2) && (validIdentifiers[xid2] == null)) { xid = xid2; xid2 = common.unEscapeFieldName(xid2); }
174 - if (validIdentifiers[xid2] == 1) {
175 - //console.log('Fixing id: ' + j + ' to ' + xid2);
176 - docs[i].links[xid2] = docs[i].links[j];
177 - delete docs[i].links[j];
178 - meshChange = true;
179 - } else {
180 - // TODO: here, we may want to clean up links to users and user groups that do not exist anymore.
181 - //console.log('Unknown id: ' + j);
182 - }
183 - }
184 - }
185 - }
186 -
187 - // Save the updated device group if needed
188 - if (meshChange) { obj.Set(docs[i]); }
189 - }
190 - }
191 - if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
192 - // MariaDB
193 - sqlDbQuery('DELETE FROM MeshCentral.Main WHERE (extra LIKE ("mesh/%") AND (extra NOT IN ?)', [meshlist], func);
194 - } else if (obj.databaseType == 3) {
195 - // MongoDB
196 - obj.file.deleteMany({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
197 - } else {
198 - // NeDB or MongoJS
199 - obj.file.remove({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
200 - }
201 -
202 - // We are done
203 - validIdentifiers = null;
204 - if (func) { func(); }
205 - });
206 - }
207 - });
208 - });
209 - };
210 -
211 - // Get encryption key
212 - obj.getEncryptDataKey = function (password) {
213 - if (typeof password != 'string') return null;
214 - return parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
215 - }
216 -
217 - // Encrypt data
218 - obj.encryptData = function (password, plaintext) {
219 - var key = obj.getEncryptDataKey(password);
220 - if (key == null) return null;
221 - const iv = parent.crypto.randomBytes(16);
222 - const aes = parent.crypto.createCipheriv('aes-256-cbc', key, iv);
223 - var ciphertext = aes.update(plaintext);
224 - ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
225 - return ciphertext.toString('base64');
226 - }
227 -
228 - // Decrypt data
229 - obj.decryptData = function (password, ciphertext) {
230 - try {
231 - var key = obj.getEncryptDataKey(password);
232 - if (key == null) return null;
233 - const ciphertextBytes = Buffer.from(ciphertext, 'base64');
234 - const iv = ciphertextBytes.slice(0, 16);
235 - const data = ciphertextBytes.slice(16);
236 - const aes = parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
237 - var plaintextBytes = Buffer.from(aes.update(data));
238 - plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
239 - return plaintextBytes;
240 - } catch (ex) { return null; }
241 - }
242 -
243 - // Get the number of records in the database for various types, this is the slow NeDB way.
244 - // WARNING: This is a terrible query for database performance. Only do this when needed. This query will look at almost every document in the database.
245 - obj.getStats = function (func) {
246 - if (obj.databaseType == 3) {
247 - // MongoDB
248 - obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }]).toArray(function (err, docs) {
249 - var counters = {}, totalCount = 0;
250 - if (err == null) { for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } } }
251 - func(counters);
252 - });
253 - } else if (obj.databaseType == 2) {
254 - // MongoJS
255 - obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
256 - var counters = {}, totalCount = 0;
257 - if (err == null) { for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } } }
258 - func(counters);
259 - });
260 - } else if (obj.databaseType == 1) {
261 - // NeDB version
262 - obj.file.count({ type: 'node' }, function (err, nodeCount) {
263 - obj.file.count({ type: 'mesh' }, function (err, meshCount) {
264 - obj.file.count({ type: 'user' }, function (err, userCount) {
265 - obj.file.count({ type: 'sysinfo' }, function (err, sysinfoCount) {
266 - obj.file.count({ type: 'note' }, function (err, noteCount) {
267 - obj.file.count({ type: 'iploc' }, function (err, iplocCount) {
268 - obj.file.count({ type: 'ifinfo' }, function (err, ifinfoCount) {
269 - obj.file.count({ type: 'cfile' }, function (err, cfileCount) {
270 - obj.file.count({ type: 'lastconnect' }, function (err, lastconnectCount) {
271 - obj.file.count({}, function (err, totalCount) {
272 - func({ node: nodeCount, mesh: meshCount, user: userCount, sysinfo: sysinfoCount, iploc: iplocCount, note: noteCount, ifinfo: ifinfoCount, cfile: cfileCount, lastconnect: lastconnectCount, total: totalCount });
273 - });
274 - });
275 - });
276 - });
277 - });
278 - });
279 - });
280 - });
281 - });
282 - });
283 - }
284 - }
285 -
286 - // This is used to rate limit a number of operation per day. Returns a startValue each new days, but you can substract it and save the value in the db.
287 - obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if ((err == null) && (docs.length == 1)) { var r = docs[0]; if (r.day == t) { func({ _id: id, value: r.value, day: t }); return; } } func({ _id: id, value: startValue, day: t }); }); };
288 - obj.escapeBase64 = function escapeBase64(val) { return (val.replace(/\+/g, '@').replace(/\//g, '$')); }
289 -
290 - // Encrypt an database object
291 - obj.performRecordEncryptionRecode = function (func) {
292 - var count = 0;
293 - obj.GetAllType('user', function (err, docs) {
294 - if (err != null) { parent.debug('db', 'ERROR (performRecordEncryptionRecode): ' + err); }
295 - if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
296 - obj.GetAllType('node', function (err, docs) {
297 - if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
298 - obj.GetAllType('mesh', function (err, docs) {
299 - if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
300 - if (obj.databaseType == 1) { // If we are using NeDB, compact the database.
301 - obj.file.persistence.compactDatafile();
302 - obj.file.on('compaction.done', function () { func(count); }); // It's important to wait for compaction to finish before exit, otherwise NeDB may corrupt.
303 - } else {
304 - func(count); // For all other databases, normal exit.
305 - }
306 - });
307 - });
308 - });
309 - }
310 -
311 - // Encrypt an database object
312 - function performTypedRecordDecrypt(data) {
313 - if ((data == null) || (obj.dbRecordsDecryptKey == null) || (typeof data != 'object')) return data;
314 - for (var i in data) {
315 - if (data[i] == null) continue;
316 - if (data[i].type == 'user') {
317 - data[i] = performPartialRecordDecrypt(data[i]);
318 - } else if ((data[i].type == 'node') && (data[i].intelamt != null)) {
319 - data[i].intelamt = performPartialRecordDecrypt(data[i].intelamt);
320 - } else if ((data[i].type == 'mesh') && (data[i].amt != null)) {
321 - data[i].amt = performPartialRecordDecrypt(data[i].amt);
322 - }
323 - }
324 - return data;
325 - }
326 -
327 - // Encrypt an database object
328 - function performTypedRecordEncrypt(data) {
329 - if (obj.dbRecordsEncryptKey == null) return data;
330 - if (data.type == 'user') { return performPartialRecordEncrypt(Clone(data), ['otpkeys', 'otphkeys', 'otpsecret', 'salt', 'hash', 'oldpasswords']); }
331 - else if ((data.type == 'node') && (data.intelamt != null)) { var xdata = Clone(data); xdata.intelamt = performPartialRecordEncrypt(xdata.intelamt, ['user', 'pass', 'mpspass']); return xdata; }
332 - else if ((data.type == 'mesh') && (data.amt != null)) { var xdata = Clone(data); xdata.amt = performPartialRecordEncrypt(xdata.amt, ['password']); return xdata; }
333 - return data;
334 - }
335 -
336 - // Encrypt an object and return a buffer.
337 - function performPartialRecordEncrypt(plainobj, encryptNames) {
338 - if (typeof plainobj != 'object') return plainobj;
339 - var enc = {}, enclen = 0;
340 - for (var i in encryptNames) { if (plainobj[encryptNames[i]] != null) { enclen++; enc[encryptNames[i]] = plainobj[encryptNames[i]]; delete plainobj[encryptNames[i]]; } }
341 - if (enclen > 0) { plainobj._CRYPT = performRecordEncrypt(enc); } else { delete plainobj._CRYPT; }
342 - return plainobj;
343 - }
344 -
345 - // Encrypt an object and return a buffer.
346 - function performPartialRecordDecrypt(plainobj) {
347 - if ((typeof plainobj != 'object') || (plainobj._CRYPT == null)) return plainobj;
348 - var enc = performRecordDecrypt(plainobj._CRYPT);
349 - if (enc != null) { for (var i in enc) { plainobj[i] = enc[i]; } }
350 - delete plainobj._CRYPT;
351 - return plainobj;
352 - }
353 -
354 - // Encrypt an object and return a base64.
355 - function performRecordEncrypt(plainobj) {
356 - if (obj.dbRecordsEncryptKey == null) return null;
357 - const iv = parent.crypto.randomBytes(12);
358 - const aes = parent.crypto.createCipheriv('aes-256-gcm', obj.dbRecordsEncryptKey, iv);
359 - var ciphertext = aes.update(JSON.stringify(plainobj));
360 - var cipherfinal = aes.final();
361 - ciphertext = Buffer.concat([iv, aes.getAuthTag(), ciphertext, cipherfinal]);
362 - return ciphertext.toString('base64');
363 - }
364 -
365 - // Takes a base64 and return an object.
366 - function performRecordDecrypt(ciphertext) {
367 - if (obj.dbRecordsDecryptKey == null) return null;
368 - const ciphertextBytes = Buffer.from(ciphertext, 'base64');
369 - const iv = ciphertextBytes.slice(0, 12);
370 - const data = ciphertextBytes.slice(28);
371 - const aes = parent.crypto.createDecipheriv('aes-256-gcm', obj.dbRecordsDecryptKey, iv);
372 - aes.setAuthTag(ciphertextBytes.slice(12, 28));
373 - var plaintextBytes, r;
374 - try {
375 - plaintextBytes = Buffer.from(aes.update(data));
376 - plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
377 - r = JSON.parse(plaintextBytes.toString());
378 - } catch (e) { throw "Incorrect DbRecordsDecryptKey/DbRecordsEncryptKey or invalid database _CRYPT data: " + e; }
379 - return r;
380 - }
381 -
382 - // Clone an object (TODO: Make this more efficient)
383 - function Clone(v) { return JSON.parse(JSON.stringify(v)); }
384 -
385 - // Read expiration time from configuration file
386 - if (typeof parent.args.dbexpire == 'object') {
387 - if (typeof parent.args.dbexpire.events == 'number') { expireEventsSeconds = parent.args.dbexpire.events; }
388 - if (typeof parent.args.dbexpire.powerevents == 'number') { expirePowerEventsSeconds = parent.args.dbexpire.powerevents; }
389 - if (typeof parent.args.dbexpire.statsevents == 'number') { expireServerStatsSeconds = parent.args.dbexpire.statsevents; }
390 - }
391 -
392 - // If a DB record encryption key is provided, perform database record encryption
393 - if ((typeof parent.args.dbrecordsencryptkey == 'string') && (parent.args.dbrecordsencryptkey.length != 0)) {
394 - // Hash the database password into a AES256 key and setup encryption and decryption.
395 - obj.dbRecordsEncryptKey = obj.dbRecordsDecryptKey = parent.crypto.createHash('sha384').update(parent.args.dbrecordsencryptkey).digest('raw').slice(0, 32);
396 - }
397 -
398 - // If a DB record decryption key is provided, perform database record decryption
399 - if ((typeof parent.args.dbrecordsdecryptkey == 'string') && (parent.args.dbrecordsdecryptkey.length != 0)) {
400 - // Hash the database password into a AES256 key and setup encryption and decryption.
401 - obj.dbRecordsDecryptKey = parent.crypto.createHash('sha384').update(parent.args.dbrecordsdecryptkey).digest('raw').slice(0, 32);
402 - }
403 -
404 - if (parent.args.mariadb || parent.args.mysql) {
405 - if (parent.args.mariadb) {
406 - // Use MariaDB
407 - obj.databaseType = 4;
408 - Datastore = require('mariadb').createPool(parent.args.mariadb);
409 - } else if (parent.args.mysql) {
410 - // Use MySQL
411 - Datastore = require('mysql').createConnection(parent.args.mysql);
412 - obj.databaseType = 5;
413 - }
414 - //sqlDbQuery('DROP DATABASE MeshCentral', null, function (err, docs) { console.log('DROP'); }); return;
415 - sqlDbQuery('USE meshcentral', null, function (err, docs) {
416 - if (err != null) { parent.debug('db', 'ERROR: USE meshcentral: ' + err); }
417 - if (err == null) { setupFunctions(func); } else {
418 - parent.debug('db', 'Creating database...');
419 - sqlDbBatchExec([
420 - 'CREATE DATABASE meshcentral',
421 - // Main table
422 - 'CREATE TABLE meshcentral.main (id VARCHAR(256) NOT NULL, type CHAR(32), domain CHAR(64), extra CHAR(255), extraex CHAR(255), doc JSON, PRIMARY KEY(id), CHECK (json_valid(doc)))',
423 - 'CREATE INDEX ndxtypedomainextra ON meshcentral.main (type, domain, extra)',
424 - 'CREATE INDEX ndxextra ON meshcentral.main (extra)',
425 - 'CREATE INDEX ndxextraex ON meshcentral.main (extraex)',
426 - // Events table
427 - 'CREATE TABLE meshcentral.events(id INT NOT NULL AUTO_INCREMENT, time DATETIME, domain CHAR(64), action CHAR(255), nodeid CHAR(255), userid CHAR(255), doc JSON, PRIMARY KEY(id), CHECK(json_valid(doc)))',
428 - 'CREATE INDEX ndxeventstime ON meshcentral.events(time)',
429 - 'CREATE INDEX ndxeventsusername ON meshcentral.events(domain, userid, time)',
430 - 'CREATE INDEX ndxeventsdomainnodeidtime ON meshcentral.events(domain, nodeid, time)',
431 - // Events ID table
432 - 'CREATE TABLE meshcentral.eventids(fkid INT NOT NULL, target CHAR(255), CONSTRAINT fk_eventid FOREIGN KEY (fkid) REFERENCES events (id) ON DELETE CASCADE ON UPDATE RESTRICT)',
433 - 'CREATE INDEX ndxeventids ON meshcentral.eventids(target)',
434 - // Server stats table
435 - 'CREATE TABLE meshcentral.serverstats (time DATETIME, expire DATETIME, doc JSON, PRIMARY KEY(time), CHECK (json_valid(doc)))',
436 - 'CREATE INDEX ndxserverstattime ON meshcentral.serverstats (time)',
437 - 'CREATE INDEX ndxserverstatexpire ON meshcentral.serverstats (expire)',
438 - // Power events table
439 - 'CREATE TABLE meshcentral.power (id INT NOT NULL AUTO_INCREMENT, time DATETIME, nodeid CHAR(255), doc JSON, PRIMARY KEY(id), CHECK (json_valid(doc)))',
440 - 'CREATE INDEX ndxpowernodeidtime ON meshcentral.power (nodeid, time)',
441 - // SMBIOS table
442 - 'CREATE TABLE meshcentral.smbios (id CHAR(255), time DATETIME, expire DATETIME, doc JSON, PRIMARY KEY(id), CHECK (json_valid(doc)))',
443 - 'CREATE INDEX ndxsmbiostime ON meshcentral.smbios (time)',
444 - 'CREATE INDEX ndxsmbiosexpire ON meshcentral.smbios (expire)',
445 - // Plugins table
446 - 'CREATE TABLE meshcentral.plugin (id INT NOT NULL AUTO_INCREMENT, doc JSON, PRIMARY KEY(id), CHECK (json_valid(doc)))'
447 - ], function (err) {
448 - if (err != null) { parent.debug('db', 'BatchSetupDb: ' + err); }
449 - setupFunctions(func);
450 - });
451 - }
452 - });
453 - } else if (parent.args.mongodb) {
454 - // Use MongoDB
455 - obj.databaseType = 3;
456 - require('mongodb').MongoClient.connect(parent.args.mongodb, { useNewUrlParser: true, useUnifiedTopology: true }, function (err, client) {
457 - if (err != null) { console.log("Unable to connect to database: " + err); process.exit(); return; }
458 - Datastore = client;
459 - parent.debug('db', 'Connected to MongoDB database...');
460 -
461 - // Get the database name and setup the database client
462 - var dbname = 'meshcentral';
463 - if (parent.args.mongodbname) { dbname = parent.args.mongodbname; }
464 - const dbcollectionname = (parent.args.mongodbcol) ? (parent.args.mongodbcol) : 'meshcentral';
465 - const db = client.db(dbname);
466 -
467 - // Check the database version
468 - db.admin().serverInfo(function (err, info) {
469 - if ((err != null) || (info == null) || (info.versionArray == null) || (Array.isArray(info.versionArray) == false) || (info.versionArray.length < 2) || (typeof info.versionArray[0] != 'number') || (typeof info.versionArray[1] != 'number')) {
470 - console.log('WARNING: Unable to check MongoDB version.');
471 - } else {
472 - if ((info.versionArray[0] < 3) || ((info.versionArray[0] == 3) && (info.versionArray[1] < 6))) {
473 - // We are running with mongoDB older than 3.6, this is not good.
474 - parent.addServerWarning("Current version of MongoDB (" + info.version + ") is too old, please upgrade to MongoDB 3.6 or better.");
475 - }
476 - }
477 - });
478 -
479 - // Setup MongoDB main collection and indexes
480 - obj.file = db.collection(dbcollectionname);
481 - obj.file.indexes(function (err, indexes) {
482 - // Check if we need to reset indexes
483 - var indexesByName = {}, indexCount = 0;
484 - for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
485 - if ((indexCount != 4) || (indexesByName['TypeDomainMesh1'] == null) || (indexesByName['Email1'] == null) || (indexesByName['Mesh1'] == null)) {
486 - console.log('Resetting main indexes...');
487 - obj.file.dropIndexes(function (err) {
488 - obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1, name: 'TypeDomainMesh1' }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered()
489 - obj.file.createIndex({ email: 1 }, { sparse: 1, name: 'Email1' }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail()
490 - obj.file.createIndex({ meshid: 1 }, { sparse: 1, name: 'Mesh1' }); // Speeds up RemoveMesh()
491 - });
492 - }
493 - });
494 -
495 - // Setup the changeStream on the MongoDB main collection if possible
496 - if (parent.args.mongodbchangestream == true) {
497 - if (typeof obj.file.watch != 'function') {
498 - console.log('WARNING: watch() is not a function, MongoDB ChangeStream not supported.');
499 - } else {
500 - obj.fileChangeStream = obj.file.watch([{ $match: { $or: [{ 'fullDocument.type': { $in: ['node', 'mesh', 'user', 'ugrp'] } }, { 'operationType': 'delete' }] } }], { fullDocument: 'updateLookup' });
501 - obj.fileChangeStream.on('change', function (change) {
502 - if ((change.operationType == 'update') || (change.operationType == 'replace')) {
503 - switch (change.fullDocument.type) {
504 - case 'node': { dbNodeChange(change, false); break; } // A node has changed
505 - case 'mesh': { dbMeshChange(change, false); break; } // A device group has changed
506 - case 'user': { dbUserChange(change, false); break; } // A user account has changed
507 - case 'ugrp': { dbUGrpChange(change, false); break; } // A user account has changed
508 - }
509 - } else if (change.operationType == 'insert') {
510 - switch (change.fullDocument.type) {
511 - case 'node': { dbNodeChange(change, true); break; } // A node has added
512 - case 'mesh': { dbMeshChange(change, true); break; } // A device group has created
513 - case 'user': { dbUserChange(change, true); break; } // A user account has created
514 - case 'ugrp': { dbUGrpChange(change, true); break; } // A user account has created
515 - }
516 - } else if (change.operationType == 'delete') {
517 - if ((change.documentKey == null) || (change.documentKey._id == null)) return;
518 - var splitId = change.documentKey._id.split('/');
519 - switch (splitId[0]) {
520 - case 'node': {
521 - //Not Good: Problem here is that we don't know what meshid the node belonged to before the delete.
522 - //parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', action: 'removenode', nodeid: change.documentKey._id, domain: splitId[1] });
523 - break;
524 - }
525 - case 'mesh': {
526 - parent.DispatchEvent(['*', change.documentKey._id], obj, { etype: 'mesh', action: 'deletemesh', meshid: change.documentKey._id, domain: splitId[1] });
527 - break;
528 - }
529 - case 'user': {
530 - //Not Good: This is not a perfect user removal because we don't know what groups the user was in.
531 - //parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', action: 'accountremove', userid: change.documentKey._id, domain: splitId[1], username: splitId[2] });
532 - break;
533 - }
534 - case 'ugrp': {
535 - parent.DispatchEvent(['*', change.documentKey._id], obj, { etype: 'ugrp', action: 'deleteusergroup', ugrpid: change.documentKey._id, domain: splitId[1] });
536 - break;
537 - }
538 - }
539 - }
540 - });
541 - obj.changeStream = true;
542 - }
543 - }
544 -
545 - // Setup MongoDB events collection and indexes
546 - obj.eventsfile = db.collection('events'); // Collection containing all events
547 - obj.eventsfile.indexes(function (err, indexes) {
548 - // Check if we need to reset indexes
549 - var indexesByName = {}, indexCount = 0;
550 - for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
551 - if ((indexCount != 5) || (indexesByName['Username1'] == null) || (indexesByName['DomainNodeTime1'] == null) || (indexesByName['IdsAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
552 - // Reset all indexes
553 - console.log("Resetting events indexes...");
554 - obj.eventsfile.dropIndexes(function (err) {
555 - obj.eventsfile.createIndex({ username: 1 }, { sparse: 1, name: 'Username1' });
556 - obj.eventsfile.createIndex({ domain: 1, nodeid: 1, time: -1 }, { sparse: 1, name: 'DomainNodeTime1' });
557 - obj.eventsfile.createIndex({ ids: 1, time: -1 }, { sparse: 1, name: 'IdsAndTime1' });
558 - obj.eventsfile.createIndex({ time: 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
559 - });
560 - } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireEventsSeconds) {
561 - // Reset the timeout index
562 - console.log("Resetting events expire index...");
563 - obj.eventsfile.dropIndex('ExpireTime1', function (err) {
564 - obj.eventsfile.createIndex({ time: 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
565 - });
566 - }
567 - });
568 -
569 - // Setup MongoDB power events collection and indexes
570 - obj.powerfile = db.collection('power'); // Collection containing all power events
571 - obj.powerfile.indexes(function (err, indexes) {
572 - // Check if we need to reset indexes
573 - var indexesByName = {}, indexCount = 0;
574 - for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
575 - if ((indexCount != 3) || (indexesByName['NodeIdAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
576 - // Reset all indexes
577 - console.log("Resetting power events indexes...");
578 - obj.powerfile.dropIndexes(function (err) {
579 - // Create all indexes
580 - obj.powerfile.createIndex({ nodeid: 1, time: 1 }, { sparse: 1, name: 'NodeIdAndTime1' });
581 - obj.powerfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
582 - });
583 - } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expirePowerEventsSeconds) {
584 - // Reset the timeout index
585 - console.log("Resetting power events expire index...");
586 - obj.powerfile.dropIndex('ExpireTime1', function (err) {
587 - // Reset the expire power events index
588 - obj.powerfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
589 - });
590 - }
591 - });
592 -
593 - // Setup MongoDB smbios collection, no indexes needed
594 - obj.smbiosfile = db.collection('smbios'); // Collection containing all smbios information
595 -
596 - // Setup MongoDB server stats collection
597 - obj.serverstatsfile = db.collection('serverstats'); // Collection of server stats
598 - obj.serverstatsfile.indexes(function (err, indexes) {
599 - // Check if we need to reset indexes
600 - var indexesByName = {}, indexCount = 0;
601 - for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
602 - if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) {
603 - // Reset all indexes
604 - console.log("Resetting server stats indexes...");
605 - obj.serverstatsfile.dropIndexes(function (err) {
606 - // Create all indexes
607 - obj.serverstatsfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
608 - obj.serverstatsfile.createIndex({ 'expire': 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events
609 - });
610 - } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) {
611 - // Reset the timeout index
612 - console.log("Resetting server stats expire index...");
613 - obj.serverstatsfile.dropIndex('ExpireTime1', function (err) {
614 - // Reset the expire server stats index
615 - obj.serverstatsfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
616 - });
617 - }
618 - });
619 -
620 - // Setup plugin info collection
621 - if (obj.pluginsActive) { obj.pluginsfile = db.collection('plugins'); }
622 -
623 - setupFunctions(func); // Completed setup of MongoDB
624 - });
625 - } else if (parent.args.xmongodb) {
626 - // Use MongoJS, this is the old system.
627 - obj.databaseType = 2;
628 - Datastore = require('mongojs');
629 - var db = Datastore(parent.args.xmongodb);
630 - var dbcollection = 'meshcentral';
631 - if (parent.args.mongodbcol) { dbcollection = parent.args.mongodbcol; }
632 -
633 - // Setup MongoDB main collection and indexes
634 - obj.file = db.collection(dbcollection);
635 - obj.file.getIndexes(function (err, indexes) {
636 - // Check if we need to reset indexes
637 - var indexesByName = {}, indexCount = 0;
638 - for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
639 - if ((indexCount != 4) || (indexesByName['TypeDomainMesh1'] == null) || (indexesByName['Email1'] == null) || (indexesByName['Mesh1'] == null)) {
640 - console.log("Resetting main indexes...");
641 - obj.file.dropIndexes(function (err) {
642 - obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1, name: 'TypeDomainMesh1' }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered()
643 - obj.file.createIndex({ email: 1 }, { sparse: 1, name: 'Email1' }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail()
644 - obj.file.createIndex({ meshid: 1 }, { sparse: 1, name: 'Mesh1' }); // Speeds up RemoveMesh()
645 - });
646 - }
647 - });
648 -
649 - // Setup MongoDB events collection and indexes
650 - obj.eventsfile = db.collection('events'); // Collection containing all events
651 - obj.eventsfile.getIndexes(function (err, indexes) {
652 - // Check if we need to reset indexes
653 - var indexesByName = {}, indexCount = 0;
654 - for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
655 - if ((indexCount != 5) || (indexesByName['Username1'] == null) || (indexesByName['DomainNodeTime1'] == null) || (indexesByName['IdsAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
656 - // Reset all indexes
657 - console.log("Resetting events indexes...");
658 - obj.eventsfile.dropIndexes(function (err) {
659 - obj.eventsfile.createIndex({ username: 1 }, { sparse: 1, name: 'Username1' });
660 - obj.eventsfile.createIndex({ domain: 1, nodeid: 1, time: -1 }, { sparse: 1, name: 'DomainNodeTime1' });
661 - obj.eventsfile.createIndex({ ids: 1, time: -1 }, { sparse: 1, name: 'IdsAndTime1' });
662 - obj.eventsfile.createIndex({ time: 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
663 - });
664 - } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireEventsSeconds) {
665 - // Reset the timeout index
666 - console.log("Resetting events expire index...");
667 - obj.eventsfile.dropIndex('ExpireTime1', function (err) {
668 - obj.eventsfile.createIndex({ time: 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
669 - });
670 - }
671 - });
672 -
673 - // Setup MongoDB power events collection and indexes
674 - obj.powerfile = db.collection('power'); // Collection containing all power events
675 - obj.powerfile.getIndexes(function (err, indexes) {
676 - // Check if we need to reset indexes
677 - var indexesByName = {}, indexCount = 0;
678 - for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
679 - if ((indexCount != 3) || (indexesByName['NodeIdAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
680 - // Reset all indexes
681 - console.log("Resetting power events indexes...");
682 - obj.powerfile.dropIndexes(function (err) {
683 - // Create all indexes
684 - obj.powerfile.createIndex({ nodeid: 1, time: 1 }, { sparse: 1, name: 'NodeIdAndTime1' });
685 - obj.powerfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
686 - });
687 - } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expirePowerEventsSeconds) {
688 - // Reset the timeout index
689 - console.log("Resetting power events expire index...");
690 - obj.powerfile.dropIndex('ExpireTime1', function (err) {
691 - // Reset the expire power events index
692 - obj.powerfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
693 - });
694 - }
695 - });
696 -
697 - // Setup MongoDB smbios collection, no indexes needed
698 - obj.smbiosfile = db.collection('smbios'); // Collection containing all smbios information
699 -
700 - // Setup MongoDB server stats collection
701 - obj.serverstatsfile = db.collection('serverstats'); // Collection of server stats
702 - obj.serverstatsfile.getIndexes(function (err, indexes) {
703 - // Check if we need to reset indexes
704 - var indexesByName = {}, indexCount = 0;
705 - for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
706 - if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) {
707 - // Reset all indexes
708 - console.log("Resetting server stats indexes...");
709 - obj.serverstatsfile.dropIndexes(function (err) {
710 - // Create all indexes
711 - obj.serverstatsfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
712 - obj.serverstatsfile.createIndex({ 'expire': 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events
713 - });
714 - } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) {
715 - // Reset the timeout index
716 - console.log("Resetting server stats expire index...");
717 - obj.serverstatsfile.dropIndex('ExpireTime1', function (err) {
718 - // Reset the expire server stats index
719 - obj.serverstatsfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
720 - });
721 - }
722 - });
723 -
724 - // Setup plugin info collection
725 - if (obj.pluginsActive) { obj.pluginsfile = db.collection('plugins'); }
726 -
727 - setupFunctions(func); // Completed setup of MongoJS
728 - } else {
729 - // Use NeDB (The default)
730 - obj.databaseType = 1;
731 - Datastore = require('nedb');
732 - var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
733 -
734 - // If a DB encryption key is provided, perform database encryption
735 - if ((typeof parent.args.dbencryptkey == 'string') && (parent.args.dbencryptkey.length != 0)) {
736 - // Hash the database password into a AES256 key and setup encryption and decryption.
737 - obj.dbKey = parent.crypto.createHash('sha384').update(parent.args.dbencryptkey).digest('raw').slice(0, 32);
738 - datastoreOptions.afterSerialization = function (plaintext) {
739 - const iv = parent.crypto.randomBytes(16);
740 - const aes = parent.crypto.createCipheriv('aes-256-cbc', obj.dbKey, iv);
741 - var ciphertext = aes.update(plaintext);
742 - ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
743 - return ciphertext.toString('base64');
744 - }
745 - datastoreOptions.beforeDeserialization = function (ciphertext) {
746 - const ciphertextBytes = Buffer.from(ciphertext, 'base64');
747 - const iv = ciphertextBytes.slice(0, 16);
748 - const data = ciphertextBytes.slice(16);
749 - const aes = parent.crypto.createDecipheriv('aes-256-cbc', obj.dbKey, iv);
750 - var plaintextBytes = Buffer.from(aes.update(data));
751 - plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
752 - return plaintextBytes.toString();
753 - }
754 - }
755 -
756 - // Start NeDB main collection and setup indexes
757 - obj.file = new Datastore(datastoreOptions);
758 - obj.file.persistence.setAutocompactionInterval(86400000); // Compact once a day
759 - obj.file.ensureIndex({ fieldName: 'type' });
760 - obj.file.ensureIndex({ fieldName: 'domain' });
761 - obj.file.ensureIndex({ fieldName: 'meshid', sparse: true });
762 - obj.file.ensureIndex({ fieldName: 'nodeid', sparse: true });
763 - obj.file.ensureIndex({ fieldName: 'email', sparse: true });
764 -
765 - // Setup the events collection and setup indexes
766 - obj.eventsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-events.db'), autoload: true, corruptAlertThreshold: 1 });
767 - obj.eventsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
768 - obj.eventsfile.ensureIndex({ fieldName: 'ids' }); // TODO: Not sure if this is a good index, this is a array field.
769 - obj.eventsfile.ensureIndex({ fieldName: 'nodeid', sparse: true });
770 - obj.eventsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expireEventsSeconds });
771 - obj.eventsfile.remove({ time: { '$lt': new Date(Date.now() - (expireEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
772 -
773 - // Setup the power collection and setup indexes
774 - obj.powerfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-power.db'), autoload: true, corruptAlertThreshold: 1 });
775 - obj.powerfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
776 - obj.powerfile.ensureIndex({ fieldName: 'nodeid' });
777 - obj.powerfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expirePowerEventsSeconds });
778 - obj.powerfile.remove({ time: { '$lt': new Date(Date.now() - (expirePowerEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
779 -
780 - // Setup the SMBIOS collection, for NeDB we don't setup SMBIOS since NeDB will corrupt the database. Remove any existing ones.
781 - //obj.smbiosfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-smbios.db'), autoload: true, corruptAlertThreshold: 1 });
782 - parent.fs.unlink(parent.getConfigFilePath('meshcentral-smbios.db'), function () { });
783 -
784 - // Setup the server stats collection and setup indexes
785 - obj.serverstatsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-stats.db'), autoload: true, corruptAlertThreshold: 1 });
786 - obj.serverstatsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
787 - obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expireServerStatsSeconds });
788 - obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
789 - obj.serverstatsfile.remove({ time: { '$lt': new Date(Date.now() - (expireServerStatsSeconds * 1000)) } }, { multi: true }); // Force delete older events
790 -
791 - // Setup plugin info collection
792 - if (obj.pluginsActive) {
793 - obj.pluginsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-plugins.db'), autoload: true });
794 - obj.pluginsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
795 - }
796 -
797 - setupFunctions(func); // Completed setup of NeDB
798 - }
799 -
800 - // Check the object names for a "."
801 - function checkObjectNames(r, tag) {
802 - if (typeof r != 'object') return;
803 - for (var i in r) {
804 - if (i.indexOf('.') >= 0) { throw ('BadDbName (' + tag + '): ' + JSON.stringify(r)); }
805 - checkObjectNames(r[i], tag);
806 - }
807 - }
808 -
809 - // Query the database
810 - function sqlDbQuery(query, args, func) {
811 - if (obj.databaseType == 4) { // MariaDB
812 - Datastore.getConnection()
813 - .then(function (conn) {
814 - conn.query(query, args)
815 - .then(function (rows) {
816 - conn.release();
817 - const docs = [];
818 - for (var i in rows) { if (rows[i].doc) { docs.push(performTypedRecordDecrypt((typeof rows[i].doc == 'object')? rows[i].doc : JSON.parse(rows[i].doc))); } }
819 - if (func) try { func(null, docs); } catch (ex) { console.log('SQLERR1', ex); }
820 - })
821 - .catch(function (err) { conn.release(); if (func) try { func(err); } catch (ex) { console.log('SQLERR2', ex); } });
822 - }).catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log('SQLERR3', ex); } } });
823 - } else if (obj.databaseType == 5) { // MySQL
824 - Datastore.query(query, args, function (error, results, fields) {
825 - if (error != null) {
826 - if (func) try { func(error); } catch (ex) { console.log('SQLERR4', ex); }
827 - } else {
828 - var docs = [];
829 - for (var i in results) { if (results[i].doc) { docs.push(JSON.parse(results[i].doc)); } }
830 - //console.log(docs);
831 - if (func) { try { func(null, docs); } catch (ex) { console.log('SQLERR5', ex); } }
832 - }
833 - });
834 - }
835 - }
836 -
837 - // Exec on the database
838 - function sqlDbExec(query, args, func) {
839 - if (obj.databaseType == 4) { // MariaDB
840 - Datastore.getConnection()
841 - .then(function (conn) {
842 - conn.query(query, args)
843 - .then(function (rows) {
844 - conn.release();
845 - if (func) try { func(null, rows[0]); } catch (ex) { console.log(ex); }
846 - })
847 - .catch(function (err) { conn.release(); if (func) try { func(err); } catch (ex) { console.log(ex); } });
848 - }).catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
849 - } else if (obj.databaseType == 5) { // MySQL
850 - Datastore.query(query, args, function (error, results, fields) {
851 - if (func) try { func(error, results[0]); } catch (ex) { console.log(ex); }
852 - });
853 - }
854 - }
855 -
856 - // Execute a batch of commands on the database
857 - function sqlDbBatchExec(queries, func) {
858 - if (obj.databaseType == 4) { // MariaDB
859 - Datastore.getConnection()
860 - .then(function (conn) {
861 - var Promises = [];
862 - for (var i in queries) { if (typeof queries[i] == 'string') { Promises.push(conn.query(queries[i])); } else { Promises.push(conn.query(queries[i][0], queries[i][1])); } }
863 - Promise.all(Promises)
864 - .then(function (rows) { conn.release(); if (func) { try { func(null); } catch (ex) { console.log(ex); } } })
865 - .catch(function (err) { conn.release(); if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
866 - })
867 - .catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
868 - } else if (obj.databaseType == 5) { // MySQL
869 - var Promises = [];
870 - for (var i in queries) { if (typeof queries[i] == 'string') { Promises.push(Datastore.query(queries[i])); } else { Promises.push(Datastore.query(queries[i][0], queries[i][1])); } }
871 - Promise.all(Promises)
872 - .then(function (error, results, fields) { if (func) { try { func(error, results); } catch (ex) { console.log(ex); } } })
873 - .catch(function (error, results, fields) { if (func) { try { func(error); } catch (ex) { console.log(ex); } } });
874 - }
875 - }
876 -
877 - function setupFunctions(func) {
878 - if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
879 - // Database actions on the main collection (MariaDB or MySQL)
880 - obj.Set = function (value, func) {
881 - var extra = null, extraex = null;
882 - value = common.escapeLinksFieldNameEx(value);
883 - if (value.meshid) { extra = value.meshid; } else if (value.email) { extra = 'email/' + value.email; } else if (value.nodeid) { extra = value.nodeid; }
884 - if ((value.type == 'node') && (value.intelamt != null) && (value.intelamt.uuid != null)) { extraex = 'uuid/' + value.intelamt.uuid; }
885 - if (value._id == null) { value._id = require('crypto').randomBytes(16).toString('hex'); }
886 - sqlDbQuery('REPLACE INTO meshcentral.main VALUE (?, ?, ?, ?, ?, ?)', [value._id, (value.type ? value.type : null), ((value.domain != null) ? value.domain : null), extra, extraex, JSON.stringify(performTypedRecordEncrypt(value))], func);
887 - }
888 - obj.Get = function (_id, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ?', [_id], function (err, docs) { if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); } func(err, docs); }); }
889 - obj.GetAll = function (func) { sqlDbQuery('SELECT domain, doc FROM meshcentral.main', null, func); }
890 - obj.GetHash = function (id, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ?', [id], func); }
891 - obj.GetAllTypeNoTypeField = function (type, domain, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ?', [type, domain], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); }); };
892 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
893 - if (id && (id != '')) {
894 - sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ? AND type = ? AND domain = ? AND extra IN (?)', [id, type, domain, meshes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
895 - } else {
896 - if (extrasids == null) {
897 - sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ? AND extra IN (?)', [type, domain, meshes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
898 - } else {
899 - sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ? AND (extra IN (?) OR id IN (?))', [type, domain, meshes, extrasids], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
900 - }
901 - }
902 - };
903 - obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
904 - if (id && (id != '')) {
905 - sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ? AND type = ? AND domain = ? AND extra IN (?)', [id, type, domain, nodes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
906 - } else {
907 - sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ? AND extra IN (?)', [type, domain, nodes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
908 - }
909 - };
910 - obj.GetAllType = function (type, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ?', [type], func); }
911 - obj.GetAllIdsOfType = function (ids, domain, type, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id IN (?) AND domain = ? AND type = ?', [ids, domain, type], func); }
912 - obj.GetUserWithEmail = function (domain, email, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE domain = ? AND extra = ?', [domain, 'email/' + email], func); }
913 - obj.GetUserWithVerifiedEmail = function (domain, email, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE domain = ? AND extra = ?', [domain, 'email/' + email], func); }
914 - obj.Remove = function (id, func) { sqlDbQuery('DELETE FROM meshcentral.main WHERE id = ?', [id], func); };
915 - obj.RemoveAll = function (func) { sqlDbQuery('DELETE FROM meshcentral.main', null, func); };
916 - obj.RemoveAllOfType = function (type, func) { sqlDbQuery('DELETE FROM meshcentral.main WHERE type = ?', [type], func); };
917 - obj.InsertMany = function (data, func) { var pendingOps = 0; for (var i in data) { pendingOps++; obj.Set(data[i], function () { if (--pendingOps == 0) { func(); } }); } };
918 - obj.RemoveMeshDocuments = function (id) { sqlDbQuery('DELETE FROM meshcentral.main WHERE extra = ?', [id], function () { sqlDbQuery('DELETE FROM meshcentral.main WHERE id = ?', ['nt' + id], func); } ); };
919 - obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if ((err == null) && (docs.length == 1)) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
920 - obj.DeleteDomain = function (domain, func) { sqlDbQuery('DELETE FROM meshcentral.main WHERE domain = ?', [domain], func); };
921 - obj.SetUser = function (user) { if (user.subscriptions != null) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); } else { obj.Set(user); } };
922 - obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
923 - obj.getLocalAmtNodes = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE (type = "node") AND (extraex IS NOT NULL)', null, function (err, docs) { var r = []; if (err == null) { for (var i in docs) { if (docs[i].host != null) { r.push(docs[i]); } } } func(err, r); }); };
924 - obj.getAmtUuidMeshNode = function (meshid, uuid, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE meshid = ? AND extraex = ?', [meshid, 'uuid/' + uuid], func); };
925 - obj.getAmtUuidNode = function (uuid, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = "node" AND extraex = ?', ['uuid/' + uuid], func); };
926 - obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { sqlDbExec('SELECT COUNT(id) FROM meshcentral.main WHERE domain = ? AND type = ?', [domainid, type], function (err, response) { func((response['COUNT(id)'] == null) || (response['COUNT(id)'] > max), response['COUNT(id)']) }); } }
927 -
928 - // Database actions on the events collection
929 - obj.GetAllEvents = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.events', null, func); };
930 - obj.StoreEvent = function (event, func) {
931 - var batchQuery = [['INSERT INTO meshcentral.events VALUE (?, ?, ?, ?, ?, ?, ?)', [null, event.time, ((typeof event.domain == 'string') ? event.domain : null), event.action, event.nodeid ? event.nodeid : null, event.userid ? event.userid : null, JSON.stringify(event)]]];
932 - for (var i in event.ids) { if (event.ids[i] != '*') { batchQuery.push(['INSERT INTO meshcentral.eventids VALUE (LAST_INSERT_ID(), ?)', [event.ids[i]]]); } }
933 - sqlDbBatchExec(batchQuery, function (err, docs) { if (func != null) { func(err, docs); } });
934 - };
935 - obj.GetEvents = function (ids, domain, func) {
936 - if (ids.indexOf('*') >= 0) {
937 - sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (domain = ?) ORDER BY time DESC', [domain], func);
938 - } else {
939 - sqlDbQuery('SELECT doc FROM meshcentral.events JOIN meshcentral.eventids ON id = fkid WHERE (domain = ? AND target IN (?)) GROUP BY id ORDER BY time DESC', [domain, ids], func);
940 - }
941 - };
942 - obj.GetEventsWithLimit = function (ids, domain, limit, func) {
943 - if (ids.indexOf('*') >= 0) {
944 - sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (domain = ?) ORDER BY time DESC LIMIT ?', [domain, limit], func);
945 - } else {
946 - sqlDbQuery('SELECT doc FROM meshcentral.events JOIN meshcentral.eventids ON id = fkid WHERE (domain = ? AND target IN (?)) GROUP BY id ORDER BY time DESC LIMIT ?', [domain, ids, limit], func);
947 - }
948 - };
949 - obj.GetUserEvents = function (ids, domain, username, func) {
950 - const userid = 'user/' + domain + '/' + username.toLowerCase();
951 - if (ids.indexOf('*') >= 0) {
952 - sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (domain = ? AND userid = ?) ORDER BY time DESC', [domain, userid], func);
953 - } else {
954 - sqlDbQuery('SELECT doc FROM meshcentral.events JOIN meshcentral.eventids ON id = fkid WHERE (domain = ? AND userid = ? AND target IN (?)) GROUP BY id ORDER BY time DESC', [domain, userid, ids, limit], func);
955 - }
956 - };
957 - obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
958 - const userid = 'user/' + domain + '/' + username.toLowerCase();
959 - if (ids.indexOf('*') >= 0) {
960 - sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (domain = ? AND userid = ?) ORDER BY time DESC LIMIT ?', [domain, userid, limit], func);
961 - } else {
962 - sqlDbQuery('SELECT doc FROM meshcentral.events JOIN meshcentral.eventids ON id = fkid WHERE (domain = ? AND userid = ? AND target IN (?)) GROUP BY id ORDER BY time DESC LIMIT ?', [domain, userid, ids, limit], func);
963 - }
964 - };
965 - obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (nodeid = ?) AND (domain = ?) ORDER BY time DESC LIMIT ?', [nodeid, domain, limit], func); };
966 - obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (nodeid = ?) AND (domain = ?) AND ((userid = ?) OR (userid IS NULL)) ORDER BY time DESC LIMIT ?', [nodeid, domain, userid, limit], func); };
967 - obj.RemoveAllEvents = function (domain) { sqlDbQuery('DELETE FROM meshcentral.events', null, function (err, docs) { }); };
968 - obj.RemoveAllNodeEvents = function (domain, nodeid) { sqlDbQuery('DELETE FROM meshcentral.events WHERE domain = ? AND nodeid = ?', [domain, nodeid], function (err, docs) { }); };
969 - obj.RemoveAllUserEvents = function (domain, userid) { sqlDbQuery('DELETE FROM meshcentral.events WHERE domain = ? AND userid = ?', [domain, userid], function (err, docs) { }); };
970 - obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) { sqlDbExec('SELECT COUNT(id) FROM meshcentral.events WHERE action = "authfail" AND domain = ? AND userid = ? AND time > ?', [domainid, 'user/' + domainid + '/' + username.toLowerCase(), lastlogin], function (err, response) { func(err == null ? response['COUNT(id)'] : 0); }); }
971 -
972 - // Database actions on the power collection
973 - obj.getAllPower = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.power', null, func); };
974 - obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } sqlDbQuery('INSERT INTO meshcentral.power VALUE (?, ?, ?, ?)', [null, event.time, event.nodeid ? event.nodeid : null, JSON.stringify(event)], func); };
975 - obj.getPowerTimeline = function (nodeid, func) { sqlDbQuery('SELECT doc FROM meshcentral.power WHERE ((nodeid = ?) OR (nodeid = "*")) ORDER BY time DESC', [nodeid], func); };
976 - obj.removeAllPowerEvents = function () { sqlDbQuery('DELETE FROM meshcentral.power', null, function (err, docs) { }); };
977 - obj.removeAllPowerEventsForNode = function (nodeid) { sqlDbQuery('DELETE FROM meshcentral.power WHERE nodeid = ?', [nodeid], function (err, docs) { }); };
978 -
979 - // Database actions on the SMBIOS collection
980 - obj.GetAllSMBIOS = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.smbios', null, func); };
981 - obj.SetSMBIOS = function (smbios, func) { var expire = new Date(smbios.time); expire.setMonth(expire.getMonth() + 6); sqlDbQuery('REPLACE INTO meshcentral.smbios VALUE (?, ?, ?, ?)', [smbios._id, smbios.time, expire, JSON.stringify(smbios)], func); };
982 - obj.RemoveSMBIOS = function (id) { sqlDbQuery('DELETE FROM meshcentral.smbios WHERE id = ?', [id], function (err, docs) { }); };
983 - obj.GetSMBIOS = function (id, func) { sqlDbQuery('SELECT doc FROM meshcentral.smbios WHERE id = ?', [id], func); };
984 -
985 - // Database actions on the Server Stats collection
986 - obj.SetServerStats = function (data, func) { sqlDbQuery('REPLACE INTO meshcentral.serverstats VALUE (?, ?, ?)', [data.time, data.expire, JSON.stringify(data)], func); };
987 - obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); sqlDbQuery('SELECT doc FROM meshcentral.main WHERE time < ?', [t], func); }; // TODO: Expire old entries
988 -
989 - // Read a configuration file from the database
990 - obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
991 -
992 - // Write a configuration file to the database
993 - obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
994 -
995 - // List all configuration files
996 - obj.listConfigFiles = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = "cfile" ORDER BY id', func); }
997 -
998 - // Get all configuration files
999 - obj.getAllConfigFiles = function (password, func) {
1000 - obj.file.find({ type: 'cfile' }).toArray(function (err, docs) {
1001 - if (err != null) { func(null); return; }
1002 - var r = null;
1003 - for (var i = 0; i < docs.length; i++) {
1004 - var name = docs[i]._id.split('/')[1];
1005 - var data = obj.decryptData(password, docs[i].data);
1006 - if (data != null) { if (r == null) { r = {}; } r[name] = data; }
1007 - }
1008 - func(r);
1009 - });
1010 - }
1011 -
1012 - // Get database information (TODO: Complete this)
1013 - obj.getDbStats = function (func) {
1014 - obj.stats = { c: 4 };
1015 - sqlDbExec('SELECT COUNT(id) FROM meshcentral.main', null, function (err, response) { obj.stats.meshcentral = response['COUNT(id)']; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1016 - sqlDbExec('SELECT COUNT(time) FROM meshcentral.serverstats', null, function (err, response) { obj.stats.serverstats = response['COUNT(time)']; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1017 - sqlDbExec('SELECT COUNT(id) FROM meshcentral.power', null, function (err, response) { obj.stats.power = response['COUNT(id)']; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1018 - sqlDbExec('SELECT COUNT(id) FROM meshcentral.smbios', null, function (err, response) { obj.stats.smbios = response['COUNT(id)']; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1019 - }
1020 -
1021 - // Plugin operations
1022 - if (obj.pluginsActive) {
1023 - obj.addPlugin = function (plugin, func) { sqlDbQuery('INSERT INTO meshcentral.plugin VALUE (?, ?)', [null, JSON.stringify(value)], func); }; // Add a plugin
1024 - obj.getPlugins = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.plugin', null, func); }; // Get all plugins
1025 - obj.getPlugin = function (id, func) { sqlDbQuery('SELECT doc FROM meshcentral.plugin WHERE id = ?', [id], func); }; // Get plugin
1026 - obj.deletePlugin = function (id, func) { sqlDbQuery('DELETE FROM meshcentral.plugin WHERE id = ?', [id], func); }; // Delete plugin
1027 - obj.setPluginStatus = function (id, status, func) { obj.getPlugin(id, function (err, docs) { if ((err == null) && (docs.length == 1)) { docs[0].status = status; obj.updatePlugin(id, docs[0], func); } }); };
1028 - obj.updatePlugin = function (id, args, func) { delete args._id; sqlDbQuery('REPLACE INTO meshcentral.plugin VALUE (?, ?)', [id, JSON.stringify(args)], func); };
1029 - }
1030 - } else if (obj.databaseType == 3) {
1031 - // Database actions on the main collection (MongoDB)
1032 - obj.Set = function (data, func) { data = common.escapeLinksFieldNameEx(data); obj.file.replaceOne({ _id: data._id }, performTypedRecordEncrypt(data), { upsert: true }, func); };
1033 - obj.Get = function (id, func) {
1034 - if (arguments.length > 2) {
1035 - var parms = [func];
1036 - for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
1037 - var func2 = function _func2(arg1, arg2) {
1038 - var userCallback = _func2.userArgs.shift();
1039 - _func2.userArgs.unshift(arg2);
1040 - _func2.userArgs.unshift(arg1);
1041 - userCallback.apply(obj, _func2.userArgs);
1042 - };
1043 - func2.userArgs = parms;
1044 - obj.file.find({ _id: id }).toArray(function (err, docs) {
1045 - if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1046 - func2(err, performTypedRecordDecrypt(docs));
1047 - });
1048 - } else {
1049 - obj.file.find({ _id: id }).toArray(function (err, docs) {
1050 - if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1051 - func(err, performTypedRecordDecrypt(docs));
1052 - });
1053 - }
1054 - };
1055 - obj.GetAll = function (func) { obj.file.find({}).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1056 - obj.GetHash = function (id, func) { obj.file.find({ _id: id }).project({ _id: 0, hash: 1 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1057 - obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }).project({ type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1058 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
1059 - if (extrasids == null) {
1060 - var x = { type: type, domain: domain, meshid: { $in: meshes } };
1061 - if (id) { x._id = id; }
1062 - obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1063 - } else {
1064 - var x = { type: type, domain: domain, $or: [ { meshid: { $in: meshes } }, { _id: { $in: extrasids } } ] };
1065 - if (id) { x._id = id; }
1066 - obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1067 - }
1068 - };
1069 - obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
1070 - var x = { type: type, domain: domain, nodeid: { $in: nodes } };
1071 - if (id) { x._id = id; }
1072 - obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1073 - };
1074 - obj.GetAllType = function (type, func) { obj.file.find({ type: type }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1075 - 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)); }); };
1076 - obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1077 - obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1078 - obj.Remove = function (id, func) { obj.file.deleteOne({ _id: id }, func); };
1079 - obj.RemoveAll = function (func) { obj.file.deleteMany({}, { multi: true }, func); };
1080 - obj.RemoveAllOfType = function (type, func) { obj.file.deleteMany({ type: type }, { multi: true }, func); };
1081 - obj.InsertMany = function (data, func) { obj.file.insertMany(data, func); };
1082 - obj.RemoveMeshDocuments = function (id) { obj.file.deleteMany({ meshid: id }, { multi: true }); obj.file.deleteOne({ _id: 'nt' + id }); };
1083 - obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if ((err == null) && (docs.length == 1)) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
1084 - obj.DeleteDomain = function (domain, func) { obj.file.deleteMany({ domain: domain }, { multi: true }, func); };
1085 - obj.SetUser = function (user) { if (user.subscriptions != null) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); } else { obj.Set(user); } };
1086 - obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
1087 - obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }).toArray(func); };
1088 - obj.getAmtUuidMeshNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }).toArray(func); };
1089 - obj.getAmtUuidNode = function (uuid, func) { obj.file.find({ type: 'node', 'intelamt.uuid': uuid }).toArray(func); };
1090 -
1091 - // TODO: Starting in MongoDB 4.0.3, you should use countDocuments() instead of count() that is deprecated. We should detect MongoDB version and switch.
1092 - // https://docs.mongodb.com/manual/reference/method/db.collection.countDocuments/
1093 - //obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
1094 - obj.isMaxType = function (max, type, domainid, func) {
1095 - if (obj.file.countDocuments) {
1096 - if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); }
1097 - } else {
1098 - if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); }
1099 - }
1100 - }
1101 -
1102 - // Database actions on the events collection
1103 - obj.GetAllEvents = function (func) { obj.eventsfile.find({}).toArray(func); };
1104 - obj.StoreEvent = function (event, func) { obj.eventsfile.insertOne(event, func); };
1105 - obj.GetEvents = function (ids, domain, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); };
1106 - obj.GetEventsWithLimit = function (ids, domain, limit, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
1107 - obj.GetUserEvents = function (ids, domain, username, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); };
1108 - obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
1109 - obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
1110 - obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
1111 - obj.RemoveAllEvents = function (domain) { obj.eventsfile.deleteMany({ domain: domain }, { multi: true }); };
1112 - obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.deleteMany({ domain: domain, nodeid: nodeid }, { multi: true }); };
1113 - obj.RemoveAllUserEvents = function (domain, userid) { obj.eventsfile.deleteMany({ domain: domain, userid: userid }, { multi: true }); };
1114 - obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) {
1115 - if (obj.eventsfile.countDocuments) {
1116 - obj.eventsfile.countDocuments({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); });
1117 - } else {
1118 - obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); });
1119 - }
1120 - }
1121 -
1122 - // Database actions on the power collection
1123 - obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); };
1124 - obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insertOne(event, func); };
1125 - obj.getPowerTimeline = function (nodeid, func) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }).project({ _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).toArray(func); };
1126 - obj.removeAllPowerEvents = function () { obj.powerfile.deleteMany({}, { multi: true }); };
1127 - obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.deleteMany({ nodeid: nodeid }, { multi: true }); };
1128 -
1129 - // Database actions on the SMBIOS collection
1130 - obj.GetAllSMBIOS = function (func) { obj.smbiosfile.find({}).toArray(func); };
1131 - obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.updateOne({ _id: smbios._id }, { $set: smbios }, { upsert: true }, func); };
1132 - obj.RemoveSMBIOS = function (id) { obj.smbiosfile.deleteOne({ _id: id }); };
1133 - obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }).toArray(func); };
1134 -
1135 - // Database actions on the Server Stats collection
1136 - obj.SetServerStats = function (data, func) { obj.serverstatsfile.insertOne(data, func); };
1137 - obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }).toArray(func); };
1138 -
1139 - // Read a configuration file from the database
1140 - obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
1141 -
1142 - // Write a configuration file to the database
1143 - obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
1144 -
1145 - // List all configuration files
1146 - obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).toArray(func); }
1147 -
1148 - // Get all configuration files
1149 - obj.getAllConfigFiles = function (password, func) {
1150 - obj.file.find({ type: 'cfile' }).toArray(function (err, docs) {
1151 - if (err != null) { func(null); return; }
1152 - var r = null;
1153 - for (var i = 0; i < docs.length; i++) {
1154 - var name = docs[i]._id.split('/')[1];
1155 - var data = obj.decryptData(password, docs[i].data);
1156 - if (data != null) { if (r == null) { r = {}; } r[name] = data; }
1157 - }
1158 - func(r);
1159 - });
1160 - }
1161 -
1162 - // Get database information
1163 - obj.getDbStats = function (func) {
1164 - obj.stats = { c: 6 };
1165 - obj.getStats(function (r) { obj.stats.recordTypes = r; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } })
1166 - obj.file.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1167 - obj.eventsfile.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1168 - obj.powerfile.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1169 - obj.smbiosfile.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1170 - obj.serverstatsfile.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1171 - }
1172 -
1173 - // Plugin operations
1174 - if (obj.pluginsActive) {
1175 - obj.addPlugin = function (plugin, func) { plugin.type = 'plugin'; obj.pluginsfile.insertOne(plugin, func); }; // Add a plugin
1176 - obj.getPlugins = function (func) { obj.pluginsfile.find({ type: 'plugin' }).project({ type: 0 }).sort({ name: 1 }).toArray(func); }; // Get all plugins
1177 - obj.getPlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); }; // Get plugin
1178 - obj.deletePlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.deleteOne({ _id: id }, func); }; // Delete plugin
1179 - obj.setPluginStatus = function (id, status, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: { status: status } }, func); };
1180 - obj.updatePlugin = function (id, args, func) { delete args._id; id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: args }, func); };
1181 - }
1182 -
1183 - } else {
1184 - // Database actions on the main collection (NeDB and MongoJS)
1185 - obj.Set = function (data, func) { data = common.escapeLinksFieldNameEx(data); var xdata = performTypedRecordEncrypt(data); obj.file.update({ _id: xdata._id }, xdata, { upsert: true }, func); };
1186 - obj.Get = function (id, func) {
1187 - if (arguments.length > 2) {
1188 - var parms = [func];
1189 - for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
1190 - var func2 = function _func2(arg1, arg2) {
1191 - var userCallback = _func2.userArgs.shift();
1192 - _func2.userArgs.unshift(arg2);
1193 - _func2.userArgs.unshift(arg1);
1194 - userCallback.apply(obj, _func2.userArgs);
1195 - };
1196 - func2.userArgs = parms;
1197 - obj.file.find({ _id: id }, function (err, docs) {
1198 - if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1199 - func2(err, performTypedRecordDecrypt(docs));
1200 - });
1201 - } else {
1202 - obj.file.find({ _id: id }, function (err, docs) {
1203 - if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1204 - func(err, performTypedRecordDecrypt(docs));
1205 - });
1206 - }
1207 - };
1208 - obj.GetAll = function (func) { obj.file.find({}, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1209 - obj.GetHash = function (id, func) { obj.file.find({ _id: id }, { _id: 0, hash: 1 }, func); };
1210 - obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1211 - //obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) {
1212 - //var x = { type: type, domain: domain, meshid: { $in: meshes } };
1213 - //if (id) { x._id = id; }
1214 - //obj.file.find(x, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1215 - //};
1216 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
1217 - if (extrasids == null) {
1218 - var x = { type: type, domain: domain, meshid: { $in: meshes } };
1219 - if (id) { x._id = id; }
1220 - obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1221 - } else {
1222 - var x = { type: type, domain: domain, $or: [{ meshid: { $in: meshes } }, { _id: { $in: extrasids } }] };
1223 - if (id) { x._id = id; }
1224 - obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1225 - }
1226 - };
1227 - obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
1228 - var x = { type: type, domain: domain, nodeid: { $in: nodes } };
1229 - if (id) { x._id = id; }
1230 - obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1231 - };
1232 - obj.GetAllType = function (type, func) { obj.file.find({ type: type }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1233 - obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1234 - obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1235 - obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1236 - obj.Remove = function (id, func) { obj.file.remove({ _id: id }, func); };
1237 - obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
1238 - obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
1239 - obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
1240 - obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
1241 - obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if ((err == null) && (docs.length == 1)) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
1242 - obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
1243 - obj.SetUser = function (user) { if (user.subscriptions != null) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); } else { obj.Set(user); } };
1244 - obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
1245 - obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
1246 - obj.getAmtUuidMeshNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
1247 - obj.getAmtUuidNode = function (uuid, func) { obj.file.find({ type: 'node', 'intelamt.uuid': uuid }, func); };
1248 - obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); } }
1249 -
1250 - // Database actions on the events collection
1251 - obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); };
1252 - obj.StoreEvent = function (event, func) { obj.eventsfile.insert(event, func); };
1253 - obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func); } };
1254 - obj.GetEventsWithLimit = function (ids, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func); } };
1255 - obj.GetUserEvents = function (ids, domain, username, func) {
1256 - if (obj.databaseType == 1) {
1257 - obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func);
1258 - } else {
1259 - obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func);
1260 - }
1261 - };
1262 - obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
1263 - if (obj.databaseType == 1) {
1264 - obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func);
1265 - } else {
1266 - obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func);
1267 - }
1268 - };
1269 - obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
1270 - obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
1271 - obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
1272 - obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
1273 - obj.RemoveAllUserEvents = function (domain, userid) { obj.eventsfile.remove({ domain: domain, userid: userid }, { multi: true }); };
1274 - obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) { obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); }); }
1275 -
1276 - // Database actions on the power collection
1277 - obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
1278 - obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
1279 - obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } };
1280 - obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
1281 - obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
1282 -
1283 - // Database actions on the SMBIOS collection
1284 - if (obj.smbiosfile != null) {
1285 - obj.GetAllSMBIOS = function (func) { obj.smbiosfile.find({}, func); };
1286 - obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
1287 - obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
1288 - obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); };
1289 - }
1290 -
1291 - // Database actions on the Server Stats collection
1292 - obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
1293 - obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }, func); };
1294 -
1295 - // Read a configuration file from the database
1296 - obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
1297 -
1298 - // Write a configuration file to the database
1299 - obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
1300 -
1301 - // List all configuration files
1302 - obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
1303 -
1304 - // Get all configuration files
1305 - obj.getAllConfigFiles = function (password, func) {
1306 - obj.file.find({ type: 'cfile' }, function (err, docs) {
1307 - if (err != null) { func(null); return; }
1308 - var r = null;
1309 - for (var i = 0; i < docs.length; i++) {
1310 - var name = docs[i]._id.split('/')[1];
1311 - var data = obj.decryptData(password, docs[i].data);
1312 - if (data != null) { if (r == null) { r = {}; } r[name] = data; }
1313 - }
1314 - func(r);
1315 - });
1316 - }
1317 -
1318 - // Get database information
1319 - obj.getDbStats = function (func) {
1320 - obj.stats = { c: 5 };
1321 - obj.getStats(function (r) { obj.stats.recordTypes = r; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } })
1322 - obj.file.count({}, function (err, count) { obj.stats.meshcentral = { count: count }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1323 - obj.eventsfile.count({}, function (err, count) { obj.stats.events = { count: count }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1324 - obj.powerfile.count({}, function (err, count) { obj.stats.power = { count: count }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1325 - obj.serverstatsfile.count({}, function (err, count) { obj.stats.serverstats = { count: count }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1326 - }
1327 -
1328 - // Plugin operations
1329 - if (obj.pluginsActive) {
1330 - obj.addPlugin = function (plugin, func) { plugin.type = 'plugin'; obj.pluginsfile.insert(plugin, func); }; // Add a plugin
1331 - obj.getPlugins = function (func) { obj.pluginsfile.find({ 'type': 'plugin' }, { 'type': 0 }).sort({ name: 1 }).exec(func); }; // Get all plugins
1332 - obj.getPlugin = function (id, func) { obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).exec(func); }; // Get plugin
1333 - obj.deletePlugin = function (id, func) { obj.pluginsfile.remove({ _id: id }, func); }; // Delete plugin
1334 - obj.setPluginStatus = function (id, status, func) { obj.pluginsfile.update({ _id: id }, { $set: { status: status } }, func); };
1335 - obj.updatePlugin = function (id, args, func) { delete args._id; obj.pluginsfile.update({ _id: id }, { $set: args }, func); };
1336 - }
1337 -
1338 - }
1339 -
1340 - func(obj); // Completed function setup
1341 - }
1342 -
1343 - // Return a human readable string with current backup configuration
1344 - obj.getBackupConfig = function () {
1345 - var r = '', backupPath = parent.backuppath;
1346 - if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
1347 - const dbname = (parent.args.mongodbname) ? (parent.args.mongodbname) : 'meshcentral';
1348 - const currentDate = new Date();
1349 - const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
1350 - const newAutoBackupFile = 'meshcentral-autobackup-' + fileSuffix;
1351 - const newAutoBackupPath = parent.path.join(backupPath, newAutoBackupFile);
1352 -
1353 - r += 'DB Name: ' + dbname + '\r\n';
1354 - r += 'DB Type: ' + ['None', 'NeDB', 'MongoJS', 'MongoDB'][obj.databaseType] + '\r\n';
1355 - r += 'BackupPath: ' + backupPath + '\r\n';
1356 - r += 'newAutoBackupFile: ' + newAutoBackupFile + '\r\n';
1357 - r += 'newAutoBackupPath: ' + newAutoBackupPath + '\r\n';
1358 -
1359 - if (parent.config.settings.autobackup == null) {
1360 - r += 'No Settings/AutoBackup\r\n';
1361 - } else {
1362 - if (parent.config.settings.autobackup.backupintervalhours != null) {
1363 - if (typeof parent.config.settings.autobackup.backupintervalhours != 'number') { r += 'Bad backupintervalhours type\r\n'; }
1364 - else { r += 'Backup Interval (Hours): ' + parent.config.settings.autobackup.backupintervalhours + '\r\n'; }
1365 - }
1366 - if (parent.config.settings.autobackup.keeplastdaysbackup != null) {
1367 - if (typeof parent.config.settings.autobackup.keeplastdaysbackup != 'number') { r += 'Bad keeplastdaysbackup type\r\n'; }
1368 - else { r += 'Keep Last Backups (Days): ' + parent.config.settings.autobackup.keeplastdaysbackup + '\r\n'; }
1369 - }
1370 - if (parent.config.settings.autobackup.zippassword != null) {
1371 - if (typeof parent.config.settings.autobackup.zippassword != 'string') { r += 'Bad zippassword type\r\n'; }
1372 - else { r += 'ZIP Password Set\r\n'; }
1373 - }
1374 - if (parent.config.settings.autobackup.mongodumppath != null) {
1375 - if (typeof parent.config.settings.autobackup.mongodumppath != 'string') { r += 'Bad mongodumppath type\r\n'; }
1376 - else { r += 'MongoDump Path: ' + parent.config.settings.autobackup.mongodumppath + '\r\n'; }
1377 - }
1378 - }
1379 -
1380 - return r;
1381 - }
1382 -
1383 - // Check that the server is capable of performing a backup
1384 - obj.checkBackupCapability = function (func) {
1385 - if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { func(); }
1386 - if ((obj.databaseType == 2) || (obj.databaseType == 3)) {
1387 - // Check that we have access to MongoDump
1388 - var backupPath = parent.backuppath;
1389 - if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
1390 - var mongoDumpPath = 'mongodump';
1391 - if (parent.config.settings.autobackup && parent.config.settings.autobackup.mongodumppath) { mongoDumpPath = parent.config.settings.autobackup.mongodumppath; }
1392 - const child_process = require('child_process');
1393 - child_process.exec('"' + mongoDumpPath + '"', { cwd: backupPath }, function (error, stdout, stderr) {
1394 - try {
1395 - if ((error != null) && (error != '')) {
1396 - func(1, "Unable to find mongodump.exe, MongoDB database auto-backup will not be performed.");
1397 - } else {
1398 - func();
1399 - }
1400 - } catch (ex) { console.log(ex); }
1401 - });
1402 - } else {
1403 - func();
1404 - }
1405 - }
1406 -
1407 - // Perform a server backup
1408 - obj.performingBackup = false;
1409 - obj.performBackup = function (func) {
1410 - try {
1411 - if (obj.performingBackup) return 1;
1412 - obj.performingBackup = true;
1413 - //console.log('Performing backup...');
1414 -
1415 - var backupPath = parent.backuppath;
1416 - if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
1417 - try { parent.fs.mkdirSync(backupPath); } catch (e) { }
1418 - const dbname = (parent.args.mongodbname) ? (parent.args.mongodbname) : 'meshcentral';
1419 - const dburl = parent.args.mongodb;
1420 - const currentDate = new Date();
1421 - const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
1422 - const newAutoBackupFile = 'meshcentral-autobackup-' + fileSuffix;
1423 - const newAutoBackupPath = parent.path.join(backupPath, newAutoBackupFile);
1424 -
1425 - if ((obj.databaseType == 2) || (obj.databaseType == 3)) {
1426 - // Perform a MongoDump backup
1427 - const newBackupFile = 'mongodump-' + fileSuffix;
1428 - var newBackupPath = parent.path.join(backupPath, newBackupFile);
1429 - var mongoDumpPath = 'mongodump';
1430 - if (parent.config.settings.autobackup && parent.config.settings.autobackup.mongodumppath) { mongoDumpPath = parent.config.settings.autobackup.mongodumppath; }
1431 - const child_process = require('child_process');
1432 - var cmd = '\"' + mongoDumpPath + '\" --db=\"' + dbname + '\" --archive=\"' + newBackupPath + '.archive\"';
1433 - if (dburl) { cmd = '\"' + mongoDumpPath + '\" --uri=\"' + dburl.replace('?', '/?') + '\" --archive=\"' + newBackupPath + '.archive\"'; }
1434 - var backupProcess = child_process.exec(cmd, { cwd: backupPath }, function (error, stdout, stderr) {
1435 - try {
1436 - var mongoDumpSuccess = true;
1437 - backupProcess = null;
1438 - if ((error != null) && (error != '')) { mongoDumpSuccess = false; console.log('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); }
1439 -
1440 - // Perform archive compression
1441 - var archiver = require('archiver');
1442 - var output = parent.fs.createWriteStream(newAutoBackupPath + '.zip');
1443 - var archive = null;
1444 - if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.zippassword == 'string')) {
1445 - try { archiver.registerFormat('zip-encrypted', require("archiver-zip-encrypted")); } catch (ex) { }
1446 - archive = archiver.create('zip-encrypted', { zlib: { level: 9 }, encryptionMethod: 'aes256', password: parent.config.settings.autobackup.zippassword });
1447 - } else {
1448 - archive = archiver('zip', { zlib: { level: 9 } });
1449 - }
1450 - output.on('close', function () {
1451 - obj.performingBackup = false;
1452 - if (func) { if (mongoDumpSuccess) { func('Auto-backup completed.'); } else { func('Auto-backup completed without mongodb database: ' + error); } }
1453 - obj.performCloudBackup(newAutoBackupPath + '.zip', func);
1454 - setTimeout(function () { try { parent.fs.unlink(newBackupPath + '.archive', function () { }); } catch (ex) { console.log(ex); } }, 5000);
1455 - });
1456 - output.on('end', function () { });
1457 - archive.on('warning', function (err) { console.log('Backup warning: ' + err); if (func) { func('Backup warning: ' + err); } });
1458 - archive.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
1459 - archive.pipe(output);
1460 - if (mongoDumpSuccess == true) { archive.file(newBackupPath + '.archive', { name: newBackupFile + '.archive' }); }
1461 - archive.directory(parent.datapath, 'meshcentral-data');
1462 - archive.finalize();
1463 - } catch (ex) { console.log(ex); }
1464 - });
1465 - } else {
1466 - // Perform a NeDB backup
1467 - var archiver = require('archiver');
1468 - var output = parent.fs.createWriteStream(newAutoBackupPath + '.zip');
1469 - var archive = null;
1470 - if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.zippassword == 'string')) {
1471 - try { archiver.registerFormat('zip-encrypted', require("archiver-zip-encrypted")); } catch (ex) { }
1472 - archive = archiver.create('zip-encrypted', { zlib: { level: 9 }, encryptionMethod: 'aes256', password: parent.config.settings.autobackup.zippassword });
1473 - } else {
1474 - archive = archiver('zip', { zlib: { level: 9 } });
1475 - }
1476 - output.on('close', function () { obj.performingBackup = false; if (func) { func('Auto-backup completed.'); } obj.performCloudBackup(newAutoBackupPath + '.zip', func); });
1477 - output.on('end', function () { });
1478 - archive.on('warning', function (err) { console.log('Backup warning: ' + err); if (func) { func('Backup warning: ' + err); } });
1479 - archive.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
1480 - archive.pipe(output);
1481 - archive.directory(parent.datapath, 'meshcentral-data');
1482 - archive.finalize();
1483 - }
1484 -
1485 - // Remove old backups
1486 - if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.keeplastdaysbackup == 'number')) {
1487 - var cutoffDate = new Date();
1488 - cutoffDate.setDate(cutoffDate.getDate() - parent.config.settings.autobackup.keeplastdaysbackup);
1489 - parent.fs.readdir(parent.backuppath, function (err, dir) {
1490 - try {
1491 - if ((err == null) && (dir.length > 0)) {
1492 - for (var i in dir) {
1493 - var name = dir[i];
1494 - if (name.startsWith('meshcentral-autobackup-') && name.endsWith('.zip')) {
1495 - var timex = name.substring(23, name.length - 4).split('-');
1496 - if (timex.length == 5) {
1497 - var fileDate = new Date(parseInt(timex[0]), parseInt(timex[1]) - 1, parseInt(timex[2]), parseInt(timex[3]), parseInt(timex[4]));
1498 - if (fileDate && (cutoffDate > fileDate)) { try { parent.fs.unlink(parent.path.join(parent.backuppath, name), function () { }); } catch (ex) { } }
1499 - }
1500 - }
1501 - }
1502 - }
1503 - } catch (ex) { console.log(ex); }
1504 - });
1505 - }
1506 - } catch (ex) { console.log(ex); }
1507 - return 0;
1508 - }
1509 -
1510 - // Perform cloud backup
1511 - obj.performCloudBackup = function (filename, func) {
1512 -
1513 - // WebDAV Backup
1514 - if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) {
1515 - const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; }
1516 -
1517 - // Fetch the folder name
1518 - var webdavfolderName = 'MeshCentral-Backups';
1519 - if (typeof parent.config.settings.autobackup.webdav.foldername == 'string') { webdavfolderName = parent.config.settings.autobackup.webdav.foldername; }
1520 -
1521 - // Clean up our WebDAV folder
1522 - function performWebDavCleanup(client) {
1523 - if ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') && (parent.config.settings.autobackup.webdav.maxfiles > 1)) {
1524 - var directoryItems = client.getDirectoryContents(webdavfolderName);
1525 - directoryItems.then(
1526 - function (files) {
1527 - for (var i in files) { files[i].xdate = new Date(files[i].lastmod); }
1528 - files.sort(xdateTimeSort);
1529 - while (files.length >= parent.config.settings.autobackup.webdav.maxfiles) {
1530 - client.deleteFile(files.shift().filename).then(function (state) {
1531 - if (func) { func('WebDAV file deleted.'); }
1532 - }).catch(function (err) {
1533 - if (func) { func('WebDAV (deleteFile) error: ' + err); }
1534 - });
1535 - }
1536 - }
1537 - ).catch(function (err) {
1538 - if (func) { func('WebDAV (getDirectoryContents) error: ' + err); }
1539 - });
1540 - }
1541 - }
1542 -
1543 - // Upload to the WebDAV folder
1544 - function performWebDavUpload(client, filepath) {
1545 - var fileStream = require('fs').createReadStream(filepath);
1546 - fileStream.on('close', function () { if (func) { func('WebDAV upload completed'); } })
1547 - fileStream.on('error', function (err) { if (func) { func('WebDAV (fileUpload) error: ' + err); } })
1548 - fileStream.pipe(client.createWriteStream('/' + webdavfolderName + '/' + require('path').basename(filepath)));
1549 - if (func) { func('Uploading using WebDAV...'); }
1550 - }
1551 -
1552 - if (func) { func('Attempting WebDAV upload...'); }
1553 - const { createClient } = require('webdav');
1554 - const client = createClient(parent.config.settings.autobackup.webdav.url, { username: parent.config.settings.autobackup.webdav.username, password: parent.config.settings.autobackup.webdav.password });
1555 - var directoryItems = client.getDirectoryContents('/');
1556 - directoryItems.then(
1557 - function (files) {
1558 - var folderFound = false;
1559 - for (var i in files) { if ((files[i].basename == webdavfolderName) && (files[i].type == 'directory')) { folderFound = true; } }
1560 - if (folderFound == false) {
1561 - client.createDirectory(webdavfolderName).then(function (a) {
1562 - if (a.statusText == 'Created') {
1563 - if (func) { func('WebDAV folder created'); }
1564 - performWebDavUpload(client, filename);
1565 - } else {
1566 - if (func) { func('WebDAV (createDirectory) status: ' + a.statusText); }
1567 - }
1568 - }).catch(function (err) {
1569 - if (func) { func('WebDAV (createDirectory) error: ' + err); }
1570 - });
1571 - } else {
1572 - performWebDavCleanup(client);
1573 - performWebDavUpload(client, filename);
1574 - }
1575 - }
1576 - ).catch(function (err) {
1577 - if (func) { func('WebDAV (getDirectoryContents) error: ' + err); }
1578 - });
1579 - }
1580 -
1581 - // Google Drive Backup
1582 - if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.googledrive == 'object')) {
1583 - obj.Get('GoogleDriveBackup', function (err, docs) {
1584 - if ((err != null) || (docs.length != 1) || (docs[0].state != 3)) return;
1585 - if (func) { func('Attempting Google Drive upload...'); }
1586 - const {google} = require('googleapis');
1587 - const oAuth2Client = new google.auth.OAuth2(docs[0].clientid, docs[0].clientsecret, "urn:ietf:wg:oauth:2.0:oob");
1588 - oAuth2Client.on('tokens', function (tokens) { if (tokens.refresh_token) { docs[0].token = tokens.refresh_token; parent.db.Set(docs[0]); } }); // Update the token in the database
1589 - oAuth2Client.setCredentials(docs[0].token);
1590 - const drive = google.drive({ version: 'v3', auth: oAuth2Client });
1591 - const createdTimeSort = function (a, b) { if (a.createdTime > b.createdTime) return 1; if (a.createdTime < b.createdTime) return -1; return 0; }
1592 -
1593 - // Called once we know our folder id, clean up and upload a backup.
1594 - var useGoogleDrive = function (folderid) {
1595 - // List files to see if we need to delete older ones
1596 - if (typeof parent.config.settings.autobackup.googledrive.maxfiles == 'number') {
1597 - drive.files.list({
1598 - q: 'trashed = false and \'' + folderid + '\' in parents',
1599 - fields: 'nextPageToken, files(id, name, size, createdTime)',
1600 - }, function (err, res) {
1601 - if (err) {
1602 - console.log('GoogleDrive (files.list) error: ' + err);
1603 - if (func) { func('GoogleDrive (files.list) error: ' + err); }
1604 - return;
1605 - }
1606 - // Delete any old files if more than 10 files are present in the backup folder.
1607 - res.data.files.sort(createdTimeSort);
1608 - while (res.data.files.length >= parent.config.settings.autobackup.googledrive.maxfiles) { drive.files.delete({ fileId: res.data.files.shift().id }, function (err, res) { }); }
1609 - });
1610 - }
1611 -
1612 - //console.log('Uploading...');
1613 - if (func) { func('Uploading to Google Drive...'); }
1614 -
1615 - // Upload the backup
1616 - drive.files.create({
1617 - requestBody: { name: require('path').basename(filename), mimeType: 'text/plain', parents: [folderid] },
1618 - media: { mimeType: 'application/zip', body: require('fs').createReadStream(filename) },
1619 - }, function (err, res) {
1620 - if (err) {
1621 - console.log('GoogleDrive (files.create) error: ' + err);
1622 - if (func) { func('GoogleDrive (files.create) error: ' + err); }
1623 - return;
1624 - }
1625 - //console.log('Upload done.');
1626 - if (func) { func('Google Drive upload completed.'); }
1627 - });
1628 - }
1629 -
1630 - // Fetch the folder name
1631 - var folderName = 'MeshCentral-Backups';
1632 - if (typeof parent.config.settings.autobackup.googledrive.foldername == 'string') { folderName = parent.config.settings.autobackup.googledrive.foldername; }
1633 -
1634 - // Find our backup folder, create one if needed.
1635 - drive.files.list({
1636 - q: 'mimeType = \'application/vnd.google-apps.folder\' and name=\'' + folderName + '\' and trashed = false',
1637 - fields: 'nextPageToken, files(id, name)',
1638 - }, function (err, res) {
1639 - if (err) {
1640 - console.log('GoogleDrive error: ' + err);
1641 - if (func) { func('GoogleDrive error: ' + err); }
1642 - return;
1643 - }
1644 - if (res.data.files.length == 0) {
1645 - // Create a folder
1646 - drive.files.create({ resource: { 'name': folderName, 'mimeType': 'application/vnd.google-apps.folder' }, fields: 'id' }, function (err, file) {
1647 - if (err) {
1648 - console.log('GoogleDrive (folder.create) error: ' + err);
1649 - if (func) { func('GoogleDrive (folder.create) error: ' + err); }
1650 - return;
1651 - }
1652 - useGoogleDrive(file.data.id);
1653 - });
1654 - } else { useGoogleDrive(res.data.files[0].id); }
1655 - });
1656 - });
1657 - }
1658 - }
1659 -
1660 - // Transfer NeDB data into the current database
1661 - obj.nedbtodb = function (func) {
1662 - var nedbDatastore = require('nedb');
1663 - var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
1664 -
1665 - // If a DB encryption key is provided, perform database encryption
1666 - if ((typeof parent.args.dbencryptkey == 'string') && (parent.args.dbencryptkey.length != 0)) {
1667 - // Hash the database password into a AES256 key and setup encryption and decryption.
1668 - var nedbKey = parent.crypto.createHash('sha384').update(parent.args.dbencryptkey).digest('raw').slice(0, 32);
1669 - datastoreOptions.afterSerialization = function (plaintext) {
1670 - const iv = parent.crypto.randomBytes(16);
1671 - const aes = parent.crypto.createCipheriv('aes-256-cbc', nedbKey, iv);
1672 - var ciphertext = aes.update(plaintext);
1673 - ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
1674 - return ciphertext.toString('base64');
1675 - }
1676 - datastoreOptions.beforeDeserialization = function (ciphertext) {
1677 - const ciphertextBytes = Buffer.from(ciphertext, 'base64');
1678 - const iv = ciphertextBytes.slice(0, 16);
1679 - const data = ciphertextBytes.slice(16);
1680 - const aes = parent.crypto.createDecipheriv('aes-256-cbc', nedbKey, iv);
1681 - var plaintextBytes = Buffer.from(aes.update(data));
1682 - plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
1683 - return plaintextBytes.toString();
1684 - }
1685 - }
1686 -
1687 - // Setup all NeDB collections
1688 - var nedbfile = new nedbDatastore(datastoreOptions);
1689 - var nedbeventsfile = new nedbDatastore({ filename: parent.getConfigFilePath('meshcentral-events.db'), autoload: true, corruptAlertThreshold: 1 });
1690 - var nedbpowerfile = new nedbDatastore({ filename: parent.getConfigFilePath('meshcentral-power.db'), autoload: true, corruptAlertThreshold: 1 });
1691 - var nedbserverstatsfile = new nedbDatastore({ filename: parent.getConfigFilePath('meshcentral-stats.db'), autoload: true, corruptAlertThreshold: 1 });
1692 -
1693 - // Transfered record counts
1694 - var normalRecordsTransferCount = 0;
1695 - var eventRecordsTransferCount = 0;
1696 - var powerRecordsTransferCount = 0;
1697 - var statsRecordsTransferCount = 0;
1698 - var pendingTransfer = 0;
1699 -
1700 - // Transfer the data from main database
1701 - nedbfile.find({}, function (err, docs) {
1702 - if ((err == null) && (docs.length > 0)) {
1703 - performTypedRecordDecrypt(docs)
1704 - for (var i in docs) {
1705 - pendingTransfer++;
1706 - normalRecordsTransferCount++;
1707 - obj.Set(common.unEscapeLinksFieldName(docs[i]), function () { pendingTransfer--; });
1708 - }
1709 - }
1710 -
1711 - // Transfer events
1712 - nedbeventsfile.find({}, function (err, docs) {
1713 - if ((err == null) && (docs.length > 0)) {
1714 - for (var i in docs) {
1715 - pendingTransfer++;
1716 - eventRecordsTransferCount++;
1717 - obj.StoreEvent(docs[i], function () { pendingTransfer--; });
1718 - }
1719 - }
1720 -
1721 - // Transfer power events
1722 - nedbpowerfile.find({}, function (err, docs) {
1723 - if ((err == null) && (docs.length > 0)) {
1724 - for (var i in docs) {
1725 - pendingTransfer++;
1726 - powerRecordsTransferCount++;
1727 - obj.storePowerEvent(docs[i], null, function () { pendingTransfer--; });
1728 - }
1729 - }
1730 -
1731 - // Transfer server stats
1732 - nedbserverstatsfile.find({}, function (err, docs) {
1733 - if ((err == null) && (docs.length > 0)) {
1734 - for (var i in docs) {
1735 - pendingTransfer++;
1736 - statsRecordsTransferCount++;
1737 - obj.SetServerStats(docs[i], function () { pendingTransfer--; });
1738 - }
1739 - }
1740 -
1741 - // Only exit when all the records are stored.
1742 - setInterval(function () {
1743 - if (pendingTransfer == 0) { func("Done. " + normalRecordsTransferCount + " record(s), " + eventRecordsTransferCount + " event(s), " + powerRecordsTransferCount + " power change(s), " + statsRecordsTransferCount + " stat(s)."); }
1744 - }, 200)
1745 - });
1746 - });
1747 - });
1748 - });
1749 - }
1750 -
1751 - function padNumber(number, digits) { return Array(Math.max(digits - String(number).length + 1, 0)).join(0) + number; }
1752 -
1753 - // Called when a node has changed
1754 - function dbNodeChange(nodeChange, added) {
1755 - common.unEscapeLinksFieldName(nodeChange.fullDocument);
1756 - const node = performTypedRecordDecrypt([nodeChange.fullDocument])[0];
1757 - if (node.intelamt != null) { // Remove the Intel AMT password and MPS password before eventing this.
1758 - if (node.intelamt.pass != null) { node.intelamt.pass = 1; }
1759 - if (node.intelamt.mpspass != null) { node.intelamt.mpspass = 1; }
1760 - }
1761 - parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', action: (added ? 'addnode' : 'changenode'), node: node, nodeid: node._id, domain: node.domain, nolog: 1 });
1762 - }
1763 -
1764 - // Called when a device group has changed
1765 - function dbMeshChange(meshChange, added) {
1766 - if (parent.webserver == null) return;
1767 - common.unEscapeLinksFieldName(meshChange.fullDocument);
1768 - const mesh = performTypedRecordDecrypt([meshChange.fullDocument])[0];
1769 -
1770 - // Update the mesh object in memory
1771 - const mmesh = parent.webserver.meshes[mesh._id];
1772 - for (var i in mesh) { mmesh[i] = mesh[i]; }
1773 - for (var i in mmesh) { if (mesh[i] == null) { delete mmesh[i]; } }
1774 -
1775 - // Send the mesh update
1776 - if (mesh.deleted) { mesh.action = 'deletemesh'; } else { mesh.action = (added ? 'createmesh' : 'meshchange'); }
1777 - mesh.meshid = mesh._id;
1778 - mesh.nolog = 1;
1779 - delete mesh.type;
1780 - delete mesh._id;
1781 - if ((mesh.amt != null) && (mesh.amt.password != null)) {
1782 - mesh.amt = Object.assign({}, mesh.amt); // Shallow clone
1783 - if (mesh.amt.password != null) { mesh.amt.password = 1; } // Remove the Intel AMT password if present
1784 - }
1785 - parent.DispatchEvent(['*', mesh.meshid], obj, mesh);
1786 - }
1787 -
1788 - // Called when a user account has changed
1789 - function dbUserChange(userChange, added) {
1790 - if (parent.webserver == null) return;
1791 - common.unEscapeLinksFieldName(userChange.fullDocument);
1792 - const user = performTypedRecordDecrypt([userChange.fullDocument])[0];
1793 -
1794 - // Update the user object in memory
1795 - const muser = parent.webserver.users[user._id];
1796 - for (var i in user) { muser[i] = user[i]; }
1797 - for (var i in muser) { if (user[i] == null) { delete muser[i]; } }
1798 -
1799 - // Send the user update
1800 - var targets = ['*', 'server-users', user._id];
1801 - if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1802 - parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.webserver.CloneSafeUser(user), action: (added ? 'accountcreate' : 'accountchange'), domain: user.domain, nolog: 1 });
1803 - }
1804 -
1805 - // Called when a user group has changed
1806 - function dbUGrpChange(ugrpChange, added) {
1807 - if (parent.webserver == null) return;
1808 - common.unEscapeLinksFieldName(ugrpChange.fullDocument);
1809 - const usergroup = ugrpChange.fullDocument;
1810 -
1811 - // Update the user group object in memory
1812 - const uusergroup = parent.webserver.userGroups[usergroup._id];
1813 - for (var i in usergroup) { uusergroup[i] = usergroup[i]; }
1814 - for (var i in uusergroup) { if (usergroup[i] == null) { delete uusergroup[i]; } }
1815 -
1816 - // Send the user group update
1817 - usergroup.action = (added ? 'createusergroup' : 'usergroupchange');
1818 - usergroup.ugrpid = usergroup._id;
1819 - usergroup.nolog = 1;
1820 - delete usergroup.type;
1821 - delete usergroup._id;
1822 - parent.DispatchEvent(['*', usergroup.ugrpid], obj, usergroup);
1823 - }
1824 -
1825 - return obj;
1826 -};
db.js
+11 -3
@@ -1511,7 +1511,12 @@ module.exports.CreateDB = function (parent, func) {
1511 for (var i in docs) {
1512 if (docs[i].links != null) { docs[i] = common.unEscapeLinksFieldName(docs[i]); }
1513 const id = docs[i]._id;
1514 - if (obj.filePendingGets[id] != null) { for (var j in obj.filePendingGets[id]) { obj.filePendingGets[id][j](err, performTypedRecordDecrypt([docs[i]])); } delete obj.filePendingGets[id]; }
1514 + if (obj.filePendingGets[id] != null) {
1515 + for (var j in obj.filePendingGets[id]) {
1516 + if (typeof obj.filePendingGets[id][j] == 'function') { obj.filePendingGets[id][j](err, performTypedRecordDecrypt([docs[i]])); }
1517 + }
1518 + delete obj.filePendingGets[id];
1519 + }
1520 }
1521 }
1522
@@ -1533,7 +1538,7 @@ module.exports.CreateDB = function (parent, func) {
1538 // Send out callbacks
1539 for (var i in obj.filePendingRemoves) {
1540 for (var j in obj.filePendingRemoves[i]) {
1536 - obj.filePendingRemoves[i][j](err);
1541 + if (typeof obj.filePendingRemoves[i][j] == 'function') { obj.filePendingRemoves[i][j](err); }
1542 }
1543 }
1544
@@ -1550,7 +1555,10 @@ module.exports.CreateDB = function (parent, func) {
1555 // MongoDB pending bulk write operation, perform fast bulk document replacement.
1556 function fileBulkWriteCompleted() {
1557 // Callbacks
1553 - if (obj.filePendingCbs != null) { for (var i in obj.filePendingCbs) { obj.filePendingCbs[i](); } obj.filePendingCbs = null; }
1558 + if (obj.filePendingCbs != null) {
1559 + for (var i in obj.filePendingCbs) { if (typeof obj.filePendingCbs[i] == 'function') { obj.filePendingCbs[i](); } }
1560 + obj.filePendingCbs = null;
1561 + }
1562 if (obj.filePendingSets != null) {
1563 // Perform pending operations
1564 var ops = [];