Started work on using the official MongoDB module.
Ylian Saint-Hilaire committed
May 8, 2019 at 18:14 UTC
f01b4f7ee0bf75d4a8e2d0362a2723b713a04716
3 files changed
+1204
-488
db-test.js
new
+487
@@ -0,0 +1,487 @@
1
+/**
2
+* @description MeshCentral database module
3
+* @author Ylian Saint-Hilaire
4
+* @copyright Intel Corporation 2018-2019
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. (Seconds * Minutes * Hours * Days)
32
+ var expirePowerEventsSeconds = (60 * 60 * 24 * 10); // By default, expire power events after 10 days. (Seconds * Minutes * Hours * Days)
33
+ var expireServerStatsSeconds = (60 * 60 * 24 * 30); // By default, expire power events after 30 days. (Seconds * Minutes * Hours * Days)
34
+ obj.path = require('path');
35
+ obj.parent = parent;
36
+ obj.identifier = null;
37
+ obj.dbKey = null;
38
+
39
+ // Read expiration time from configuration file
40
+ if (typeof obj.parent.args.dbexpire == 'object') {
41
+ if (typeof obj.parent.args.dbexpire.events == 'number') { expireEventsSeconds = obj.parent.args.dbexpire.events; }
42
+ if (typeof obj.parent.args.dbexpire.powerevents == 'number') { expirePowerEventsSeconds = obj.parent.args.dbexpire.powerevents; }
43
+ if (typeof obj.parent.args.dbexpire.statsevents == 'number') { expireServerStatsSeconds = obj.parent.args.dbexpire.statsevents; }
44
+ }
45
+
46
+ if (obj.parent.args.mongodb) {
47
+ // Use MongoDB
48
+ obj.databaseType = 2;
49
+ Datastore = require('mongodb').MongoClient;
50
+ Datastore.connect(obj.parent.args.mongodb, function (err, client) {
51
+ if (err != null) { console.log("Unable to connect to database: " + err); process.exit(); return; }
52
+ const db = client.db('meshcentral');
53
+
54
+ var dbcollection = 'meshcentral';
55
+ if (obj.parent.args.mongodbcol) { dbcollection = obj.parent.args.mongodbcol; }
56
+
57
+ // Setup MongoDB main collection and indexes
58
+ obj.file = db.collection(dbcollection);
59
+
60
+ obj.file.find({ type: 'mesh' }, function (err, cursor) {
61
+ cursor.each(function (err, item) {
62
+ console.log(err, item);
63
+ });
64
+ });
65
+
66
+
67
+ /*
68
+ obj.file.getIndexes(function (err, indexes) {
69
+ // Check if we need to reset indexes
70
+ var indexesByName = {}, indexCount = 0;
71
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
72
+ if ((indexCount != 4) || (indexesByName['TypeDomainMesh1'] == null) || (indexesByName['Email1'] == null) || (indexesByName['Mesh1'] == null)) {
73
+ console.log('Resetting main indexes...');
74
+ obj.file.dropIndexes(function (err) {
75
+ obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1, name: 'TypeDomainMesh1' }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered()
76
+ obj.file.createIndex({ email: 1 }, { sparse: 1, name: 'Email1' }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail()
77
+ obj.file.createIndex({ meshid: 1 }, { sparse: 1, name: 'Mesh1' }); // Speeds up RemoveMesh()
78
+ });
79
+ }
80
+ });
81
+ */
82
+
83
+ /*
84
+ // Setup the changeStream on the MongoDB main collection
85
+ obj.fileChangeStream = obj.file.watch();
86
+ obj.fileChangeStream.on('change', function (next) {
87
+ // Process next document
88
+ console.log('change', next);
89
+ });
90
+ */
91
+
92
+ // Setup MongoDB events collection and indexes
93
+ obj.eventsfile = db.collection('events'); // Collection containing all events
94
+ /*
95
+ obj.eventsfile.getIndexes(function (err, indexes) {
96
+ // Check if we need to reset indexes
97
+ var indexesByName = {}, indexCount = 0;
98
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
99
+ if ((indexCount != 5) || (indexesByName['Username1'] == null) || (indexesByName['DomainNodeTime1'] == null) || (indexesByName['IdsAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
100
+ // Reset all indexes
101
+ console.log('Resetting events indexes...');
102
+ obj.eventsfile.dropIndexes(function (err) {
103
+ obj.eventsfile.createIndex({ username: 1 }, { sparse: 1, name: 'Username1' });
104
+ obj.eventsfile.createIndex({ domain: 1, nodeid: 1, time: -1 }, { sparse: 1, name: 'DomainNodeTime1' });
105
+ obj.eventsfile.createIndex({ ids: 1, time: -1 }, { sparse: 1, name: 'IdsAndTime1' });
106
+ obj.eventsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
107
+ });
108
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireEventsSeconds) {
109
+ // Reset the timeout index
110
+ console.log('Resetting events expire index...');
111
+ obj.eventsfile.dropIndex("ExpireTime1", function (err) {
112
+ obj.eventsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
113
+ });
114
+ }
115
+ });
116
+ */
117
+
118
+ // Setup MongoDB power events collection and indexes
119
+ obj.powerfile = db.collection('power'); // Collection containing all power events
120
+ /*
121
+ obj.powerfile.getIndexes(function (err, indexes) {
122
+ // Check if we need to reset indexes
123
+ var indexesByName = {}, indexCount = 0;
124
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
125
+ if ((indexCount != 3) || (indexesByName['NodeIdAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
126
+ // Reset all indexes
127
+ console.log('Resetting power events indexes...');
128
+ obj.powerfile.dropIndexes(function (err) {
129
+ // Create all indexes
130
+ obj.powerfile.createIndex({ nodeid: 1, time: 1 }, { sparse: 1, name: 'NodeIdAndTime1' });
131
+ obj.powerfile.createIndex({ "time": 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
132
+ });
133
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expirePowerEventsSeconds) {
134
+ // Reset the timeout index
135
+ console.log('Resetting power events expire index...');
136
+ obj.powerfile.dropIndex("ExpireTime1", function (err) {
137
+ // Reset the expire power events index
138
+ obj.powerfile.createIndex({ "time": 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
139
+ });
140
+ }
141
+ });
142
+ */
143
+
144
+ // Setup MongoDB smbios collection, no indexes needed
145
+ obj.smbiosfile = db.collection('smbios'); // Collection containing all smbios information
146
+
147
+ // Setup MongoDB server stats collection
148
+ obj.serverstatsfile = db.collection('serverstats'); // Collection of server stats
149
+ /*
150
+ obj.serverstatsfile.getIndexes(function (err, indexes) {
151
+ // Check if we need to reset indexes
152
+ var indexesByName = {}, indexCount = 0;
153
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
154
+ if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) {
155
+ // Reset all indexes
156
+ console.log('Resetting server stats indexes...');
157
+ obj.serverstatsfile.dropIndexes(function (err) {
158
+ // Create all indexes
159
+ obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
160
+ obj.serverstatsfile.createIndex({ "expire": 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events
161
+ });
162
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) {
163
+ // Reset the timeout index
164
+ console.log('Resetting server stats expire index...');
165
+ obj.serverstatsfile.dropIndex("ExpireTime1", function (err) {
166
+ // Reset the expire server stats index
167
+ obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
168
+ });
169
+ }
170
+ });
171
+ */
172
+ func(); // Completed MongoDB setup
173
+ });
174
+ } else {
175
+ // Use NeDB (The default)
176
+ obj.databaseType = 1;
177
+ Datastore = require('nedb');
178
+ var datastoreOptions = { filename: obj.parent.getConfigFilePath('meshcentral.db'), autoload: true };
179
+
180
+ // If a DB encryption key is provided, perform database encryption
181
+ if ((typeof obj.parent.args.dbencryptkey == 'string') && (obj.parent.args.dbencryptkey.length != 0)) {
182
+ // Hash the database password into a AES256 key and setup encryption and decryption.
183
+ obj.dbKey = obj.parent.crypto.createHash('sha384').update(obj.parent.args.dbencryptkey).digest("raw").slice(0, 32);
184
+ datastoreOptions.afterSerialization = function (plaintext) {
185
+ const iv = obj.parent.crypto.randomBytes(16);
186
+ const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', obj.dbKey, iv);
187
+ var ciphertext = aes.update(plaintext);
188
+ ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
189
+ return ciphertext.toString('base64');
190
+ }
191
+ datastoreOptions.beforeDeserialization = function (ciphertext) {
192
+ const ciphertextBytes = Buffer.from(ciphertext, 'base64');
193
+ const iv = ciphertextBytes.slice(0, 16);
194
+ const data = ciphertextBytes.slice(16);
195
+ const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', obj.dbKey, iv);
196
+ var plaintextBytes = Buffer.from(aes.update(data));
197
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
198
+ return plaintextBytes.toString();
199
+ }
200
+ }
201
+
202
+ // Start NeDB main collection and setup indexes
203
+ obj.file = new Datastore(datastoreOptions);
204
+ obj.file.persistence.setAutocompactionInterval(36000);
205
+ obj.file.ensureIndex({ fieldName: 'type' });
206
+ obj.file.ensureIndex({ fieldName: 'domain' });
207
+ obj.file.ensureIndex({ fieldName: 'meshid', sparse: true });
208
+ obj.file.ensureIndex({ fieldName: 'nodeid', sparse: true });
209
+ obj.file.ensureIndex({ fieldName: 'email', sparse: true });
210
+
211
+ // Setup the events collection and setup indexes
212
+ obj.eventsfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-events.db'), autoload: true });
213
+ obj.eventsfile.persistence.setAutocompactionInterval(36000);
214
+ obj.eventsfile.ensureIndex({ fieldName: 'ids' }); // TODO: Not sure if this is a good index, this is a array field.
215
+ obj.eventsfile.ensureIndex({ fieldName: 'nodeid', sparse: true });
216
+ obj.eventsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 20 }); // Limit the power event log to 20 days (Seconds * Minutes * Hours * Days)
217
+
218
+ // Setup the power collection and setup indexes
219
+ obj.powerfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-power.db'), autoload: true });
220
+ obj.powerfile.persistence.setAutocompactionInterval(36000);
221
+ obj.powerfile.ensureIndex({ fieldName: 'nodeid' });
222
+ obj.powerfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 10 }); // Limit the power event log to 10 days (Seconds * Minutes * Hours * Days)
223
+
224
+ // Setup the SMBIOS collection
225
+ obj.smbiosfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-smbios.db'), autoload: true });
226
+
227
+ // Setup the server stats collection and setup indexes
228
+ obj.serverstatsfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-stats.db'), autoload: true });
229
+ obj.serverstatsfile.persistence.setAutocompactionInterval(36000);
230
+ obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 30 }); // Limit the server stats log to 30 days (Seconds * Minutes * Hours * Days)
231
+ obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
232
+
233
+ func(); // Completed NeDB setup
234
+ }
235
+
236
+ obj.SetupDatabase = function (func) {
237
+ // Check if the database unique identifier is present
238
+ // This is used to check that in server peering mode, everyone is using the same database.
239
+ obj.Get('DatabaseIdentifier', function (err, docs) {
240
+ if ((docs.length == 1) && (docs[0].value != null)) {
241
+ obj.identifier = docs[0].value;
242
+ } else {
243
+ obj.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
244
+ obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
245
+ }
246
+ });
247
+
248
+ // Load database schema version and check if we need to update
249
+ obj.Get('SchemaVersion', function (err, docs) {
250
+ var ver = 0;
251
+ if (docs && docs.length == 1) { ver = docs[0].value; }
252
+ if (ver == 1) { console.log('This is an unsupported beta 1 database, delete it to create a new one.'); process.exit(0); }
253
+
254
+ // TODO: Any schema upgrades here...
255
+ obj.Set({ _id: 'SchemaVersion', value: 2 });
256
+
257
+ func(ver);
258
+ });
259
+ };
260
+
261
+ obj.cleanup = function (func) {
262
+ // TODO: Remove all mesh links to invalid users
263
+ // TODO: Remove all meshes that dont have any links
264
+
265
+ // Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
266
+ obj.file.remove({ type: 'event' }, { multi: true });
267
+ obj.file.remove({ type: 'power' }, { multi: true });
268
+ obj.file.remove({ type: 'smbios' }, { multi: true });
269
+
270
+ // Remove all objects that have a "meshid" that no longer points to a valid mesh.
271
+ obj.GetAllType('mesh', function (err, docs) {
272
+ var meshlist = [];
273
+ if ((err == null) && (docs.length > 0)) { for (var i in docs) { meshlist.push(docs[i]._id); } }
274
+ obj.file.remove({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
275
+
276
+ // Fix all of the creating & login to ticks by seconds, not milliseconds.
277
+ obj.GetAllType('user', function (err, docs) {
278
+ if (err == null && docs.length > 0) {
279
+ for (var i in docs) {
280
+ var fixed = false;
281
+
282
+ // Fix account creation
283
+ if (docs[i].creation) {
284
+ if (docs[i].creation > 1300000000000) { docs[i].creation = Math.floor(docs[i].creation / 1000); fixed = true; }
285
+ if ((docs[i].creation % 1) != 0) { docs[i].creation = Math.floor(docs[i].creation); fixed = true; }
286
+ }
287
+
288
+ // Fix last account login
289
+ if (docs[i].login) {
290
+ if (docs[i].login > 1300000000000) { docs[i].login = Math.floor(docs[i].login / 1000); fixed = true; }
291
+ if ((docs[i].login % 1) != 0) { docs[i].login = Math.floor(docs[i].login); fixed = true; }
292
+ }
293
+
294
+ // Fix last password change
295
+ if (docs[i].passchange) {
296
+ if (docs[i].passchange > 1300000000000) { docs[i].passchange = Math.floor(docs[i].passchange / 1000); fixed = true; }
297
+ if ((docs[i].passchange % 1) != 0) { docs[i].passchange = Math.floor(docs[i].passchange); fixed = true; }
298
+ }
299
+
300
+ // Fix subscriptions
301
+ if (docs[i].subscriptions != null) { delete docs[i].subscriptions; fixed = true; }
302
+
303
+ // Save the user if needed
304
+ if (fixed) { obj.Set(docs[i]); }
305
+
306
+ // We are done
307
+ if (func) { func(); }
308
+ }
309
+ }
310
+ });
311
+ });
312
+ };
313
+
314
+ // Database actions on the main collection
315
+ obj.Set = function (data, func) { obj.file.update({ _id: data._id }, data, { upsert: true }, func); };
316
+ obj.Get = function (id, func)
317
+ {
318
+ if (arguments.length > 2)
319
+ {
320
+ var parms = [func];
321
+ for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
322
+ var func2 = function _func2(arg1, arg2)
323
+ {
324
+ var userCallback = _func2.userArgs.shift();
325
+ _func2.userArgs.unshift(arg2);
326
+ _func2.userArgs.unshift(arg1);
327
+ userCallback.apply(obj, _func2.userArgs);
328
+ };
329
+ func2.userArgs = parms;
330
+ obj.file.find({ _id: id }, func2);
331
+ }
332
+ else
333
+ {
334
+ obj.file.find({ _id: id }, func);
335
+ }
336
+ };
337
+ obj.GetAll = function (func) { obj.file.find({}, func); };
338
+ obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, func); };
339
+ obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) { var x = { type: type, domain: domain, meshid: { $in: meshes } }; if (id) { x._id = id; } obj.file.find(x, { type: 0 }, func); };
340
+ //obj.GetAllType = function (type, func) { obj.file.find({ type: type }, func); };
341
+
342
+ obj.GetAllType = function (type, func) { obj.file.find({ type: type }, function (err, cursor) { if (err) { func(err); } else { var r = []; cursor.each(function (err, item) { if (err) { func(err); } else { if (item) { r.push(item); } else { func(null, r); } } }); } }); };
343
+
344
+
345
+
346
+ obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, func); };
347
+ obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, func); };
348
+ obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, { type: 0 }, func); };
349
+ obj.Remove = function (id) { obj.file.remove({ _id: id }); };
350
+ obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
351
+ obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
352
+ obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
353
+ obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
354
+ obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
355
+ obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
356
+ obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); };
357
+ obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
358
+ obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
359
+ obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
360
+ 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)); }); } }
361
+
362
+ // Database actions on the events collection
363
+ obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); };
364
+ obj.StoreEvent = function (event) { obj.eventsfile.insert(event); };
365
+ 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); } };
366
+ 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); } };
367
+ obj.GetUserEvents = function (ids, domain, username, func) {
368
+ if (obj.databaseType == 1) {
369
+ 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);
370
+ } else {
371
+ 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);
372
+ }
373
+ };
374
+ obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
375
+ if (obj.databaseType == 1) {
376
+ 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);
377
+ } else {
378
+ 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);
379
+ }
380
+ };
381
+ 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); } };
382
+ obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
383
+ obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
384
+
385
+ // Database actions on the power collection
386
+ obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
387
+ obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
388
+ 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); } };
389
+ obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
390
+ obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
391
+
392
+ // Database actions on the SMBIOS collection
393
+ obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
394
+ obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
395
+ obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); };
396
+
397
+ // Database actions on the Server Stats collection
398
+ obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
399
+ 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); };
400
+
401
+ // Read a configuration file from the database
402
+ obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
403
+
404
+ // Write a configuration file to the database
405
+ obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
406
+
407
+ // List all configuration files
408
+ obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
409
+
410
+ // Get all configuration files
411
+ obj.getAllConfigFiles = function (password, func) {
412
+ obj.file.find({ type: 'cfile' }, function (err, docs) {
413
+ if (err != null) { func(null); return; }
414
+ var r = null;
415
+ for (var i = 0; i < docs.length; i++) {
416
+ var name = docs[i]._id.split('/')[1];
417
+ var data = obj.decryptData(password, docs[i].data);
418
+ if (data != null) { if (r == null) { r = {}; } r[name] = data; }
419
+ }
420
+ func(r);
421
+ });
422
+ }
423
+
424
+ // Get encryption key
425
+ obj.getEncryptDataKey = function (password) {
426
+ if (typeof password != 'string') return null;
427
+ return obj.parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
428
+ }
429
+
430
+ // Encrypt data
431
+ obj.encryptData = function (password, plaintext) {
432
+ var key = obj.getEncryptDataKey(password);
433
+ if (key == null) return null;
434
+ const iv = obj.parent.crypto.randomBytes(16);
435
+ const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', key, iv);
436
+ var ciphertext = aes.update(plaintext);
437
+ ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
438
+ return ciphertext.toString('base64');
439
+ }
440
+
441
+ // Decrypt data
442
+ obj.decryptData = function (password, ciphertext) {
443
+ try {
444
+ var key = obj.getEncryptDataKey(password);
445
+ if (key == null) return null;
446
+ const ciphertextBytes = Buffer.from(ciphertext, 'base64');
447
+ const iv = ciphertextBytes.slice(0, 16);
448
+ const data = ciphertextBytes.slice(16);
449
+ const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
450
+ var plaintextBytes = Buffer.from(aes.update(data));
451
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
452
+ return plaintextBytes;
453
+ } catch (ex) { return null; }
454
+ }
455
+
456
+ // Get the number of records in the database for various types, this is the slow NeDB way.
457
+ // 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.
458
+ obj.getStats = function (func) {
459
+ if (obj.databaseType == 2) {
460
+ // MongoDB version
461
+ obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
462
+ var counters = {}, totalCount = 0;
463
+ for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } }
464
+ func({ nodes: counters['node'], meshes: counters['mesh'], users: counters['user'], total: totalCount });
465
+ })
466
+ } else {
467
+ // NeDB version
468
+ obj.file.count({ type: 'node' }, function (err, nodeCount) {
469
+ obj.file.count({ type: 'mesh' }, function (err, meshCount) {
470
+ obj.file.count({ type: 'user' }, function (err, userCount) {
471
+ obj.file.count({}, function (err, totalCount) {
472
+ func({ nodes: nodeCount, meshes: meshCount, users: userCount, total: totalCount });
473
+ });
474
+ });
475
+ });
476
+ });
477
+ }
478
+ }
479
+
480
+ // 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.
481
+ obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if (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 }); }); };
482
+ obj.escapeBase64 = function escapeBase64(val) { return (val.replace(/\+/g, '@').replace(/\//g, '$')); }
483
+
484
+ function Clone(v) { return JSON.parse(JSON.stringify(v)); }
485
+
486
+ return obj;
487
+};
\ No newline at end of file
db.js
+470
-246
@@ -25,7 +25,7 @@
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) {
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. (Seconds * Minutes * Hours * Days)
@@ -36,6 +36,347 @@ module.exports.CreateDB = function (parent) {
36
obj.identifier = null;
37
obj.dbKey = null;
38
39
+ obj.SetupDatabase = function (func) {
40
+ // Check if the database unique identifier is present
41
+ // This is used to check that in server peering mode, everyone is using the same database.
42
+ obj.Get('DatabaseIdentifier', function (err, docs) {
43
+ if ((docs.length == 1) && (docs[0].value != null)) {
44
+ obj.identifier = docs[0].value;
45
+ } else {
46
+ obj.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
47
+ obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
48
+ }
49
+ });
50
+
51
+ // Load database schema version and check if we need to update
52
+ obj.Get('SchemaVersion', function (err, docs) {
53
+ var ver = 0;
54
+ if (docs && docs.length == 1) { ver = docs[0].value; }
55
+ if (ver == 1) { console.log('This is an unsupported beta 1 database, delete it to create a new one.'); process.exit(0); }
56
+
57
+ // TODO: Any schema upgrades here...
58
+ obj.Set({ _id: 'SchemaVersion', value: 2 });
59
+
60
+ func(ver);
61
+ });
62
+ };
63
+
64
+ obj.cleanup = function (func) {
65
+ // TODO: Remove all mesh links to invalid users
66
+ // TODO: Remove all meshes that dont have any links
67
+
68
+ // Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
69
+ obj.file.remove({ type: 'event' }, { multi: true });
70
+ obj.file.remove({ type: 'power' }, { multi: true });
71
+ obj.file.remove({ type: 'smbios' }, { multi: true });
72
+
73
+ // Remove all objects that have a "meshid" that no longer points to a valid mesh.
74
+ obj.GetAllType('mesh', function (err, docs) {
75
+ var meshlist = [];
76
+ if ((err == null) && (docs.length > 0)) { for (var i in docs) { meshlist.push(docs[i]._id); } }
77
+ obj.file.remove({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
78
+
79
+ // Fix all of the creating & login to ticks by seconds, not milliseconds.
80
+ obj.GetAllType('user', function (err, docs) {
81
+ if (err == null && docs.length > 0) {
82
+ for (var i in docs) {
83
+ var fixed = false;
84
+
85
+ // Fix account creation
86
+ if (docs[i].creation) {
87
+ if (docs[i].creation > 1300000000000) { docs[i].creation = Math.floor(docs[i].creation / 1000); fixed = true; }
88
+ if ((docs[i].creation % 1) != 0) { docs[i].creation = Math.floor(docs[i].creation); fixed = true; }
89
+ }
90
+
91
+ // Fix last account login
92
+ if (docs[i].login) {
93
+ if (docs[i].login > 1300000000000) { docs[i].login = Math.floor(docs[i].login / 1000); fixed = true; }
94
+ if ((docs[i].login % 1) != 0) { docs[i].login = Math.floor(docs[i].login); fixed = true; }
95
+ }
96
+
97
+ // Fix last password change
98
+ if (docs[i].passchange) {
99
+ if (docs[i].passchange > 1300000000000) { docs[i].passchange = Math.floor(docs[i].passchange / 1000); fixed = true; }
100
+ if ((docs[i].passchange % 1) != 0) { docs[i].passchange = Math.floor(docs[i].passchange); fixed = true; }
101
+ }
102
+
103
+ // Fix subscriptions
104
+ if (docs[i].subscriptions != null) { delete docs[i].subscriptions; fixed = true; }
105
+
106
+ // Save the user if needed
107
+ if (fixed) { obj.Set(docs[i]); }
108
+
109
+ // We are done
110
+ if (func) { func(); }
111
+ }
112
+ }
113
+ });
114
+ });
115
+ };
116
+
117
+ if (obj.databaseType == 3) {
118
+ // Database actions on the main collection (MongoDB)
119
+ function xfind(collection, query, projection, func) {
120
+ if (projection) {
121
+ collection.find(query, projection, function (err, cursor) { if (err) { func(err); } else { var r = []; cursor.each(function (err, item) { if (err) { func(err); } else { if (item) { r.push(item); } else { func(null, r); } } }); } });
122
+ } else {
123
+ collection.find(query, function (err, cursor) { if (err) { func(err); } else { var r = []; cursor.each(function (err, item) { if (err) { func(err); } else { if (item) { r.push(item); } else { func(null, r); } } }); } });
124
+ }
125
+ };
126
+
127
+ obj.Set = function (data, func) { obj.file.update({ _id: data._id }, data, { upsert: true }, func); };
128
+ obj.Get = function (id, func) {
129
+ if (arguments.length > 2) {
130
+ var parms = [func];
131
+ for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
132
+ var func2 = function _func2(arg1, arg2) {
133
+ var userCallback = _func2.userArgs.shift();
134
+ _func2.userArgs.unshift(arg2);
135
+ _func2.userArgs.unshift(arg1);
136
+ userCallback.apply(obj, _func2.userArgs);
137
+ };
138
+ func2.userArgs = parms;
139
+ xfind(obj.file, { _id: id }, null, func2);
140
+ } else {
141
+ xfind(obj.file, { _id: id }, null, func);
142
+ }
143
+ };
144
+ obj.GetAll = function (func) { xfind(obj.file, {}, null, func); };
145
+ obj.GetAllTypeNoTypeField = function (type, domain, func) { xfind(obj.file, { type: type, domain: domain }, { type: 0 }, func); };
146
+ obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) { var x = { type: type, domain: domain, meshid: { $in: meshes } }; if (id) { x._id = id; } xfind(obj.file, x, { type: 0 }, func); };
147
+ obj.GetAllType = function (type, func) { xfind(obj.file, { type: type }, null, func); };
148
+ obj.GetAllIdsOfType = function (ids, domain, type, func) { xfind(obj.file, { type: type, domain: domain, _id: { $in: ids } }, func); };
149
+ obj.GetUserWithEmail = function (domain, email, func) { xfind(obj.file, { type: 'user', domain: domain, email: email }, { type: 0 }, func); };
150
+ obj.GetUserWithVerifiedEmail = function (domain, email, func) { xfind(obj.file, { type: 'user', domain: domain, email: email, emailVerified: true }, { type: 0 }, func); };
151
+ obj.Remove = function (id) { obj.file.remove({ _id: id }); };
152
+ obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
153
+ obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
154
+ obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
155
+ obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
156
+ obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
157
+ obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
158
+ obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); };
159
+ obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
160
+ obj.getLocalAmtNodes = function (func) { xfind(obj.file, { type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
161
+ obj.getAmtUuidNode = function (meshid, uuid, func) { xfind(obj.file, { type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
162
+ 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)); }); } }
163
+
164
+ // Database actions on the events collection
165
+ obj.GetAllEvents = function (func) { xfind(obj.eventsfile, {}, func); };
166
+ obj.StoreEvent = function (event) { obj.eventsfile.insert(event); };
167
+ obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { xfind(obj.eventsfile, { 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); } };
168
+ 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); } };
169
+ obj.GetUserEvents = function (ids, domain, username, func) { xfind(obj.eventsfile, { domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); };
170
+ obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) { xfind(obj.eventsfile, { 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); };
171
+ obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { xfind(obj.eventsfile, { 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); };
172
+ obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
173
+ obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
174
+
175
+ // Database actions on the power collection
176
+ obj.getAllPower = function (func) { xfind(obj.powerfile, {}, func); };
177
+ obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
178
+ obj.getPowerTimeline = function (nodeid, func) { xfind(obj.powerfile, { nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); };
179
+ obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
180
+ obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
181
+
182
+ // Database actions on the SMBIOS collection
183
+ obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
184
+ obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
185
+ obj.GetSMBIOS = function (id, func) { xfind(obj.smbiosfile, { _id: id }, func); };
186
+
187
+ // Database actions on the Server Stats collection
188
+ obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
189
+ obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); xfind(obj.serverstatsfile, { time: { $gt: t } }, { _id: 0, cpu: 0 }, func); };
190
+
191
+ // Read a configuration file from the database
192
+ obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
193
+
194
+ // Write a configuration file to the database
195
+ obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
196
+
197
+ // List all configuration files
198
+ obj.listConfigFiles = function (func) { xfind(obj.file, { type: 'cfile' }).sort({ _id: 1 }).exec(func); }
199
+
200
+ // Get all configuration files
201
+ obj.getAllConfigFiles = function (password, func) {
202
+ xfind(obj.file, { type: 'cfile' }, function (err, docs) {
203
+ if (err != null) { func(null); return; }
204
+ var r = null;
205
+ for (var i = 0; i < docs.length; i++) {
206
+ var name = docs[i]._id.split('/')[1];
207
+ var data = obj.decryptData(password, docs[i].data);
208
+ if (data != null) { if (r == null) { r = {}; } r[name] = data; }
209
+ }
210
+ func(r);
211
+ });
212
+ }
213
+ } else {
214
+ // Database actions on the main collection (NeDB and MongoJS)
215
+ obj.Set = function (data, func) { obj.file.update({ _id: data._id }, data, { upsert: true }, func); };
216
+ obj.Get = function (id, func) {
217
+ if (arguments.length > 2) {
218
+ var parms = [func];
219
+ for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
220
+ var func2 = function _func2(arg1, arg2) {
221
+ var userCallback = _func2.userArgs.shift();
222
+ _func2.userArgs.unshift(arg2);
223
+ _func2.userArgs.unshift(arg1);
224
+ userCallback.apply(obj, _func2.userArgs);
225
+ };
226
+ func2.userArgs = parms;
227
+ obj.file.find({ _id: id }, func2);
228
+ }
229
+ else {
230
+ obj.file.find({ _id: id }, func);
231
+ }
232
+ };
233
+ obj.GetAll = function (func) { obj.file.find({}, func); };
234
+ obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, func); };
235
+ obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) { var x = { type: type, domain: domain, meshid: { $in: meshes } }; if (id) { x._id = id; } obj.file.find(x, { type: 0 }, func); };
236
+ obj.GetAllType = function (type, func) { obj.file.find({ type: type }, func); };
237
+ obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, func); };
238
+ obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, func); };
239
+ obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, { type: 0 }, func); };
240
+ obj.Remove = function (id) { obj.file.remove({ _id: id }); };
241
+ obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
242
+ obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
243
+ obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
244
+ obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
245
+ obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
246
+ obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
247
+ obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); };
248
+ obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
249
+ obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
250
+ obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
251
+ 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)); }); } }
252
+
253
+ // Database actions on the events collection
254
+ obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); };
255
+ obj.StoreEvent = function (event) { obj.eventsfile.insert(event); };
256
+ 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); } };
257
+ 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); } };
258
+ obj.GetUserEvents = function (ids, domain, username, func) {
259
+ if (obj.databaseType == 1) {
260
+ 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);
261
+ } else {
262
+ 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);
263
+ }
264
+ };
265
+ obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
266
+ if (obj.databaseType == 1) {
267
+ 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);
268
+ } else {
269
+ 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);
270
+ }
271
+ };
272
+ 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); } };
273
+ obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
274
+ obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
275
+
276
+ // Database actions on the power collection
277
+ obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
278
+ obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
279
+ 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); } };
280
+ obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
281
+ obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
282
+
283
+ // Database actions on the SMBIOS collection
284
+ obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
285
+ obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
286
+ obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); };
287
+
288
+ // Database actions on the Server Stats collection
289
+ obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
290
+ 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); };
291
+
292
+ // Read a configuration file from the database
293
+ obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
294
+
295
+ // Write a configuration file to the database
296
+ obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
297
+
298
+ // List all configuration files
299
+ obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
300
+
301
+ // Get all configuration files
302
+ obj.getAllConfigFiles = function (password, func) {
303
+ obj.file.find({ type: 'cfile' }, function (err, docs) {
304
+ if (err != null) { func(null); return; }
305
+ var r = null;
306
+ for (var i = 0; i < docs.length; i++) {
307
+ var name = docs[i]._id.split('/')[1];
308
+ var data = obj.decryptData(password, docs[i].data);
309
+ if (data != null) { if (r == null) { r = {}; } r[name] = data; }
310
+ }
311
+ func(r);
312
+ });
313
+ }
314
+ }
315
+
316
+
317
+ // Get encryption key
318
+ obj.getEncryptDataKey = function (password) {
319
+ if (typeof password != 'string') return null;
320
+ return obj.parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
321
+ }
322
+
323
+ // Encrypt data
324
+ obj.encryptData = function (password, plaintext) {
325
+ var key = obj.getEncryptDataKey(password);
326
+ if (key == null) return null;
327
+ const iv = obj.parent.crypto.randomBytes(16);
328
+ const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', key, iv);
329
+ var ciphertext = aes.update(plaintext);
330
+ ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
331
+ return ciphertext.toString('base64');
332
+ }
333
+
334
+ // Decrypt data
335
+ obj.decryptData = function (password, ciphertext) {
336
+ try {
337
+ var key = obj.getEncryptDataKey(password);
338
+ if (key == null) return null;
339
+ const ciphertextBytes = Buffer.from(ciphertext, 'base64');
340
+ const iv = ciphertextBytes.slice(0, 16);
341
+ const data = ciphertextBytes.slice(16);
342
+ const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
343
+ var plaintextBytes = Buffer.from(aes.update(data));
344
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
345
+ return plaintextBytes;
346
+ } catch (ex) { return null; }
347
+ }
348
+
349
+ // Get the number of records in the database for various types, this is the slow NeDB way.
350
+ // 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.
351
+ obj.getStats = function (func) {
352
+ if (obj.databaseType == 2) {
353
+ // MongoDB version
354
+ obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
355
+ var counters = {}, totalCount = 0;
356
+ for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } }
357
+ func({ nodes: counters['node'], meshes: counters['mesh'], users: counters['user'], total: totalCount });
358
+ })
359
+ } else {
360
+ // NeDB version
361
+ obj.file.count({ type: 'node' }, function (err, nodeCount) {
362
+ obj.file.count({ type: 'mesh' }, function (err, meshCount) {
363
+ obj.file.count({ type: 'user' }, function (err, userCount) {
364
+ obj.file.count({}, function (err, totalCount) {
365
+ func({ nodes: nodeCount, meshes: meshCount, users: userCount, total: totalCount });
366
+ });
367
+ });
368
+ });
369
+ });
370
+ }
371
+ }
372
+
373
+ // 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.
374
+ obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if (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 }); }); };
375
+ obj.escapeBase64 = function escapeBase64(val) { return (val.replace(/\+/g, '@').replace(/\//g, '$')); }
376
+
377
+ function Clone(v) { return JSON.parse(JSON.stringify(v)); }
378
+
379
+
380
// Read expiration time from configuration file
381
if (typeof obj.parent.args.dbexpire == 'object') {
382
if (typeof obj.parent.args.dbexpire.events == 'number') { expireEventsSeconds = obj.parent.args.dbexpire.events; }
@@ -43,8 +384,132 @@ module.exports.CreateDB = function (parent) {
384
if (typeof obj.parent.args.dbexpire.statsevents == 'number') { expireServerStatsSeconds = obj.parent.args.dbexpire.statsevents; }
385
}
386
46
- if (obj.parent.args.mongodb) {
387
+ if (obj.parent.args.mongo) {
388
// Use MongoDB
389
+ obj.databaseType = 3;
390
+ Datastore = require('mongodb').MongoClient;
391
+ Datastore.connect(obj.parent.args.mongo, function (err, client) {
392
+ if (err != null) { console.log("Unable to connect to database: " + err); process.exit(); return; }
393
+
394
+ var dbname = 'meshcentral';
395
+ if (obj.parent.args.mongodbname) { dbname = obj.parent.args.mongodbname; }
396
+ const db = client.db(dbname);
397
+
398
+ var dbcollection = 'meshcentral';
399
+ if (obj.parent.args.mongodbcol) { dbcollection = obj.parent.args.mongodbcol; }
400
+
401
+ // Setup MongoDB main collection and indexes
402
+ obj.file = db.collection(dbcollection);
403
+ /*
404
+ obj.file.getIndexes(function (err, indexes) {
405
+ // Check if we need to reset indexes
406
+ var indexesByName = {}, indexCount = 0;
407
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
408
+ if ((indexCount != 4) || (indexesByName['TypeDomainMesh1'] == null) || (indexesByName['Email1'] == null) || (indexesByName['Mesh1'] == null)) {
409
+ console.log('Resetting main indexes...');
410
+ obj.file.dropIndexes(function (err) {
411
+ obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1, name: 'TypeDomainMesh1' }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered()
412
+ obj.file.createIndex({ email: 1 }, { sparse: 1, name: 'Email1' }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail()
413
+ obj.file.createIndex({ meshid: 1 }, { sparse: 1, name: 'Mesh1' }); // Speeds up RemoveMesh()
414
+ });
415
+ }
416
+ });
417
+ */
418
+
419
+ /*
420
+ // Setup the changeStream on the MongoDB main collection
421
+ obj.fileChangeStream = obj.file.watch();
422
+ obj.fileChangeStream.on('change', function (next) {
423
+ // Process next document
424
+ console.log('change', next);
425
+ });
426
+ */
427
+
428
+ // Setup MongoDB events collection and indexes
429
+ obj.eventsfile = db.collection('events'); // Collection containing all events
430
+ /*
431
+ obj.eventsfile.getIndexes(function (err, indexes) {
432
+ // Check if we need to reset indexes
433
+ var indexesByName = {}, indexCount = 0;
434
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
435
+ if ((indexCount != 5) || (indexesByName['Username1'] == null) || (indexesByName['DomainNodeTime1'] == null) || (indexesByName['IdsAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
436
+ // Reset all indexes
437
+ console.log('Resetting events indexes...');
438
+ obj.eventsfile.dropIndexes(function (err) {
439
+ obj.eventsfile.createIndex({ username: 1 }, { sparse: 1, name: 'Username1' });
440
+ obj.eventsfile.createIndex({ domain: 1, nodeid: 1, time: -1 }, { sparse: 1, name: 'DomainNodeTime1' });
441
+ obj.eventsfile.createIndex({ ids: 1, time: -1 }, { sparse: 1, name: 'IdsAndTime1' });
442
+ obj.eventsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
443
+ });
444
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireEventsSeconds) {
445
+ // Reset the timeout index
446
+ console.log('Resetting events expire index...');
447
+ obj.eventsfile.dropIndex("ExpireTime1", function (err) {
448
+ obj.eventsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
449
+ });
450
+ }
451
+ });
452
+ */
453
+
454
+ // Setup MongoDB power events collection and indexes
455
+ obj.powerfile = db.collection('power'); // Collection containing all power events
456
+ /*
457
+ obj.powerfile.getIndexes(function (err, indexes) {
458
+ // Check if we need to reset indexes
459
+ var indexesByName = {}, indexCount = 0;
460
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
461
+ if ((indexCount != 3) || (indexesByName['NodeIdAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
462
+ // Reset all indexes
463
+ console.log('Resetting power events indexes...');
464
+ obj.powerfile.dropIndexes(function (err) {
465
+ // Create all indexes
466
+ obj.powerfile.createIndex({ nodeid: 1, time: 1 }, { sparse: 1, name: 'NodeIdAndTime1' });
467
+ obj.powerfile.createIndex({ "time": 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
468
+ });
469
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expirePowerEventsSeconds) {
470
+ // Reset the timeout index
471
+ console.log('Resetting power events expire index...');
472
+ obj.powerfile.dropIndex("ExpireTime1", function (err) {
473
+ // Reset the expire power events index
474
+ obj.powerfile.createIndex({ "time": 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
475
+ });
476
+ }
477
+ });
478
+ */
479
+
480
+ // Setup MongoDB smbios collection, no indexes needed
481
+ obj.smbiosfile = db.collection('smbios'); // Collection containing all smbios information
482
+
483
+ // Setup MongoDB server stats collection
484
+ obj.serverstatsfile = db.collection('serverstats'); // Collection of server stats
485
+ /*
486
+ obj.serverstatsfile.getIndexes(function (err, indexes) {
487
+ // Check if we need to reset indexes
488
+ var indexesByName = {}, indexCount = 0;
489
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
490
+ if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) {
491
+ // Reset all indexes
492
+ console.log('Resetting server stats indexes...');
493
+ obj.serverstatsfile.dropIndexes(function (err) {
494
+ // Create all indexes
495
+ obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
496
+ obj.serverstatsfile.createIndex({ "expire": 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events
497
+ });
498
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) {
499
+ // Reset the timeout index
500
+ console.log('Resetting server stats expire index...');
501
+ obj.serverstatsfile.dropIndex("ExpireTime1", function (err) {
502
+ // Reset the expire server stats index
503
+ obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
504
+ });
505
+ }
506
+ });
507
+ */
508
+
509
+ func(obj); // Completed setup of MongoDB
510
+ });
511
+ } else if (obj.parent.args.mongodb) {
512
+ // Use MongoJS
513
obj.databaseType = 2;
514
Datastore = require('mongojs');
515
var db = Datastore(obj.parent.args.mongodb);
@@ -141,6 +606,8 @@ module.exports.CreateDB = function (parent) {
606
});
607
}
608
});
609
+
610
+ func(obj); // Completed setup of MongoJS
611
} else {
612
// Use NeDB (The default)
613
obj.databaseType = 1;
@@ -199,252 +666,9 @@ module.exports.CreateDB = function (parent) {
666
obj.serverstatsfile.persistence.setAutocompactionInterval(36000);
667
obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 30 }); // Limit the server stats log to 30 days (Seconds * Minutes * Hours * Days)
668
obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
202
- }
203
-
204
- obj.SetupDatabase = function (func) {
205
- // Check if the database unique identifier is present
206
- // This is used to check that in server peering mode, everyone is using the same database.
207
- obj.Get('DatabaseIdentifier', function (err, docs) {
208
- if ((docs.length == 1) && (docs[0].value != null)) {
209
- obj.identifier = docs[0].value;
210
- } else {
211
- obj.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
212
- obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
213
- }
214
- });
215
-
216
- // Load database schema version and check if we need to update
217
- obj.Get('SchemaVersion', function (err, docs) {
218
- var ver = 0;
219
- if (docs && docs.length == 1) { ver = docs[0].value; }
220
- if (ver == 1) { console.log('This is an unsupported beta 1 database, delete it to create a new one.'); process.exit(0); }
221
-
222
- // TODO: Any schema upgrades here...
223
- obj.Set({ _id: 'SchemaVersion', value: 2 });
224
-
225
- func(ver);
226
- });
227
- };
228
-
229
- obj.cleanup = function (func) {
230
- // TODO: Remove all mesh links to invalid users
231
- // TODO: Remove all meshes that dont have any links
232
-
233
- // Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
234
- obj.file.remove({ type: 'event' }, { multi: true });
235
- obj.file.remove({ type: 'power' }, { multi: true });
236
- obj.file.remove({ type: 'smbios' }, { multi: true });
237
-
238
- // Remove all objects that have a "meshid" that no longer points to a valid mesh.
239
- obj.GetAllType('mesh', function (err, docs) {
240
- var meshlist = [];
241
- if ((err == null) && (docs.length > 0)) { for (var i in docs) { meshlist.push(docs[i]._id); } }
242
- obj.file.remove({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
243
-
244
- // Fix all of the creating & login to ticks by seconds, not milliseconds.
245
- obj.GetAllType('user', function (err, docs) {
246
- if (err == null && docs.length > 0) {
247
- for (var i in docs) {
248
- var fixed = false;
249
-
250
- // Fix account creation
251
- if (docs[i].creation) {
252
- if (docs[i].creation > 1300000000000) { docs[i].creation = Math.floor(docs[i].creation / 1000); fixed = true; }
253
- if ((docs[i].creation % 1) != 0) { docs[i].creation = Math.floor(docs[i].creation); fixed = true; }
254
- }
255
-
256
- // Fix last account login
257
- if (docs[i].login) {
258
- if (docs[i].login > 1300000000000) { docs[i].login = Math.floor(docs[i].login / 1000); fixed = true; }
259
- if ((docs[i].login % 1) != 0) { docs[i].login = Math.floor(docs[i].login); fixed = true; }
260
- }
261
-
262
- // Fix last password change
263
- if (docs[i].passchange) {
264
- if (docs[i].passchange > 1300000000000) { docs[i].passchange = Math.floor(docs[i].passchange / 1000); fixed = true; }
265
- if ((docs[i].passchange % 1) != 0) { docs[i].passchange = Math.floor(docs[i].passchange); fixed = true; }
266
- }
267
-
268
- // Fix subscriptions
269
- if (docs[i].subscriptions != null) { delete docs[i].subscriptions; fixed = true; }
270
-
271
- // Save the user if needed
272
- if (fixed) { obj.Set(docs[i]); }
273
-
274
- // We are done
275
- if (func) { func(); }
276
- }
277
- }
278
- });
279
- });
280
- };
281
-
282
- // Database actions on the main collection
283
- obj.Set = function (data, func) { obj.file.update({ _id: data._id }, data, { upsert: true }, func); };
284
- obj.Get = function (id, func)
285
- {
286
- if (arguments.length > 2)
287
- {
288
- var parms = [func];
289
- for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
290
- var func2 = function _func2(arg1, arg2)
291
- {
292
- var userCallback = _func2.userArgs.shift();
293
- _func2.userArgs.unshift(arg2);
294
- _func2.userArgs.unshift(arg1);
295
- userCallback.apply(obj, _func2.userArgs);
296
- };
297
- func2.userArgs = parms;
298
- obj.file.find({ _id: id }, func2);
299
- }
300
- else
301
- {
302
- obj.file.find({ _id: id }, func);
303
- }
304
- };
305
- obj.GetAll = function (func) { obj.file.find({}, func); };
306
- obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, func); };
307
- obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) { var x = { type: type, domain: domain, meshid: { $in: meshes } }; if (id) { x._id = id; } obj.file.find(x, { type: 0 }, func); };
308
- obj.GetAllType = function (type, func) { obj.file.find({ type: type }, func); };
309
- obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, func); };
310
- obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, func); };
311
- obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, { type: 0 }, func); };
312
- obj.Remove = function (id) { obj.file.remove({ _id: id }); };
313
- obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
314
- obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
315
- obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
316
- obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
317
- obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
318
- obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
319
- obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); };
320
- obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
321
- obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
322
- obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
323
- 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)); }); } }
324
-
325
- // Database actions on the events collection
326
- obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); };
327
- obj.StoreEvent = function (event) { obj.eventsfile.insert(event); };
328
- 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); } };
329
- 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); } };
330
- obj.GetUserEvents = function (ids, domain, username, func) {
331
- if (obj.databaseType == 1) {
332
- 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);
333
- } else {
334
- 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);
335
- }
336
- };
337
- obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
338
- if (obj.databaseType == 1) {
339
- 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);
340
- } else {
341
- 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);
342
- }
343
- };
344
- 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); } };
345
- obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
346
- obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
347
-
348
- // Database actions on the power collection
349
- obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
350
- obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
351
- 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); } };
352
- obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
353
- obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
354
-
355
- // Database actions on the SMBIOS collection
356
- obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
357
- obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
358
- obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); };
359
-
360
- // Database actions on the Server Stats collection
361
- obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
362
- 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); };
363
-
364
- // Read a configuration file from the database
365
- obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
366
-
367
- // Write a configuration file to the database
368
- obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
369
-
370
- // List all configuration files
371
- obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
372
-
373
- // Get all configuration files
374
- obj.getAllConfigFiles = function (password, func) {
375
- obj.file.find({ type: 'cfile' }, function (err, docs) {
376
- if (err != null) { func(null); return; }
377
- var r = null;
378
- for (var i = 0; i < docs.length; i++) {
379
- var name = docs[i]._id.split('/')[1];
380
- var data = obj.decryptData(password, docs[i].data);
381
- if (data != null) { if (r == null) { r = {}; } r[name] = data; }
382
- }
383
- func(r);
384
- });
385
- }
669
387
- // Get encryption key
388
- obj.getEncryptDataKey = function (password) {
389
- if (typeof password != 'string') return null;
390
- return obj.parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
670
+ func(obj); // Completed setup of NeDB
671
}
672
393
- // Encrypt data
394
- obj.encryptData = function (password, plaintext) {
395
- var key = obj.getEncryptDataKey(password);
396
- if (key == null) return null;
397
- const iv = obj.parent.crypto.randomBytes(16);
398
- const aes = obj.parent.crypto.createCipheriv('aes-256-cbc', key, iv);
399
- var ciphertext = aes.update(plaintext);
400
- ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
401
- return ciphertext.toString('base64');
402
- }
403
-
404
- // Decrypt data
405
- obj.decryptData = function (password, ciphertext) {
406
- try {
407
- var key = obj.getEncryptDataKey(password);
408
- if (key == null) return null;
409
- const ciphertextBytes = Buffer.from(ciphertext, 'base64');
410
- const iv = ciphertextBytes.slice(0, 16);
411
- const data = ciphertextBytes.slice(16);
412
- const aes = obj.parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
413
- var plaintextBytes = Buffer.from(aes.update(data));
414
- plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
415
- return plaintextBytes;
416
- } catch (ex) { return null; }
417
- }
418
-
419
- // Get the number of records in the database for various types, this is the slow NeDB way.
420
- // 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.
421
- obj.getStats = function (func) {
422
- if (obj.databaseType == 2) {
423
- // MongoDB version
424
- obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
425
- var counters = {}, totalCount = 0;
426
- for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } }
427
- func({ nodes: counters['node'], meshes: counters['mesh'], users: counters['user'], total: totalCount });
428
- })
429
- } else {
430
- // NeDB version
431
- obj.file.count({ type: 'node' }, function (err, nodeCount) {
432
- obj.file.count({ type: 'mesh' }, function (err, meshCount) {
433
- obj.file.count({ type: 'user' }, function (err, userCount) {
434
- obj.file.count({}, function (err, totalCount) {
435
- func({ nodes: nodeCount, meshes: meshCount, users: userCount, total: totalCount });
436
- });
437
- });
438
- });
439
- });
440
- }
441
- }
442
-
443
- // 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.
444
- obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if (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 }); }); };
445
- obj.escapeBase64 = function escapeBase64(val) { return (val.replace(/\+/g, '@').replace(/\//g, '$')); }
446
-
447
- function Clone(v) { return JSON.parse(JSON.stringify(v)); }
448
-
673
return obj;
674
};
\ No newline at end of file
meshcentral.js
+247
-242
@@ -238,268 +238,272 @@ function CreateMeshCentralServer(config, args) {
238
if (typeof obj.args.swarmallowedip == 'string') { if (obj.args.swarmallowedip == '') { obj.args.swarmallowedip = null; } else { obj.args.swarmallowedip = obj.args.swarmallowedip.split(','); } }
239
if (typeof obj.args.debug == 'number') obj.debugLevel = obj.args.debug;
240
if (obj.args.debug == true) obj.debugLevel = 1;
241
- obj.db = require('./db.js').CreateDB(obj);
242
- obj.db.SetupDatabase(function (dbversion) {
243
- // See if any database operations needs to be completed
244
- if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; }
245
- if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; }
246
- if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; }
247
- if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; }
248
- if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; }
249
- if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; }
250
- if (obj.args.showevents) { obj.db.GetAllEvents(function (err, docs) { console.log(docs); process.exit(); }); return; }
251
- if (obj.args.showpower) { obj.db.getAllPower(function (err, docs) { console.log(docs); process.exit(); }); return; }
252
- if (obj.args.clearpower) { obj.db.removeAllPowerEvents(function () { process.exit(); }); return; }
253
- if (obj.args.showiplocations) { obj.db.GetAllType('iploc', function (err, docs) { console.log(docs); process.exit(); }); return; }
254
- if (obj.args.logintoken) { obj.getLoginToken(obj.args.logintoken, function (r) { console.log(r); process.exit(); }); return; }
255
- if (obj.args.logintokenkey) { obj.showLoginTokenKey(function (r) { console.log(r); process.exit(); }); return; }
256
-
257
- // Show a list of all configuration files in the database
258
- if (obj.args.dblistconfigfiles) {
259
- obj.db.GetAllType('cfile', function (err, docs) { if (err == null) { if (docs.length == 0) { console.log('No files found.'); } else { for (var i in docs) { console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' bytes.'); } } } else { console.log('Unable to read from database.'); } process.exit(); }); return;
260
- }
241
+ require('./db.js').CreateDB(obj,
242
+ function (db) {
243
+ obj.db = db;
244
+ obj.db.SetupDatabase(function (dbversion) {
245
+ // See if any database operations needs to be completed
246
+ if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; }
247
+ if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; }
248
+ if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; }
249
+ if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; }
250
+ if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; }
251
+ if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; }
252
+ if (obj.args.showevents) { obj.db.GetAllEvents(function (err, docs) { console.log(docs); process.exit(); }); return; }
253
+ if (obj.args.showpower) { obj.db.getAllPower(function (err, docs) { console.log(docs); process.exit(); }); return; }
254
+ if (obj.args.clearpower) { obj.db.removeAllPowerEvents(function () { process.exit(); }); return; }
255
+ if (obj.args.showiplocations) { obj.db.GetAllType('iploc', function (err, docs) { console.log(docs); process.exit(); }); return; }
256
+ if (obj.args.logintoken) { obj.getLoginToken(obj.args.logintoken, function (r) { console.log(r); process.exit(); }); return; }
257
+ if (obj.args.logintokenkey) { obj.showLoginTokenKey(function (r) { console.log(r); process.exit(); }); return; }
258
+
259
+ // Show a list of all configuration files in the database
260
+ if (obj.args.dblistconfigfiles) {
261
+ obj.db.GetAllType('cfile', function (err, docs) { if (err == null) { if (docs.length == 0) { console.log('No files found.'); } else { for (var i in docs) { console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' bytes.'); } } } else { console.log('Unable to read from database.'); } process.exit(); }); return;
262
+ }
263
262
- // Display the content of a configuration file in the database
263
- if (obj.args.dbshowconfigfile) {
264
- if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
265
- obj.db.getConfigFile(obj.args.dbshowconfigfile, function (err, docs) {
266
- if (err == null) {
267
- if (docs.length == 0) { console.log('File not found.'); } else {
268
- var data = obj.db.decryptData(obj.args.configkey, docs[0].data);
269
- if (data == null) { console.log('Invalid config key.'); } else { console.log(data); }
270
- }
271
- } else { console.log('Unable to read from database.'); }
272
- process.exit();
273
- }); return;
274
- }
264
+ // Display the content of a configuration file in the database
265
+ if (obj.args.dbshowconfigfile) {
266
+ if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
267
+ obj.db.getConfigFile(obj.args.dbshowconfigfile, function (err, docs) {
268
+ if (err == null) {
269
+ if (docs.length == 0) { console.log('File not found.'); } else {
270
+ var data = obj.db.decryptData(obj.args.configkey, docs[0].data);
271
+ if (data == null) { console.log('Invalid config key.'); } else { console.log(data); }
272
+ }
273
+ } else { console.log('Unable to read from database.'); }
274
+ process.exit();
275
+ }); return;
276
+ }
277
276
- // Delete all configuration files from database
277
- if (obj.args.dbdeleteconfigfiles) {
278
- console.log('Deleting all configuration files from the database...'); obj.db.RemoveAllOfType('cfile', function () { console.log('Done.'); process.exit(); });
279
- }
278
+ // Delete all configuration files from database
279
+ if (obj.args.dbdeleteconfigfiles) {
280
+ console.log('Deleting all configuration files from the database...'); obj.db.RemoveAllOfType('cfile', function () { console.log('Done.'); process.exit(); });
281
+ }
282
281
- // Push all relevent files from meshcentral-data into the database
282
- if (obj.args.dbpushconfigfiles) {
283
- if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
284
- if ((obj.args.dbpushconfigfiles !== true) && (typeof obj.args.dbpushconfigfiles != 'string')) {
285
- console.log('Usage: --dbpulldatafiles (path) This will import files from folder into the database');
286
- console.log(' --dbpulldatafiles This will import files from meshcentral-data into the db.');
287
- process.exit();
288
- } else {
289
- if ((obj.args.dbpushconfigfiles == '*') || (obj.args.dbpushconfigfiles === true)) { obj.args.dbpushconfigfiles = obj.datapath; }
290
- obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
291
- if (err != null) { console.log('ERROR: Unable to read from folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
292
- var configFound = false;
293
- for (var i in files) { if (files[i] == 'config.json') { configFound = true; } }
294
- if (configFound == false) { console.log('ERROR: No config.json in folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
295
- obj.db.RemoveAllOfType('cfile', function () {
283
+ // Push all relevent files from meshcentral-data into the database
284
+ if (obj.args.dbpushconfigfiles) {
285
+ if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
286
+ if ((obj.args.dbpushconfigfiles !== true) && (typeof obj.args.dbpushconfigfiles != 'string')) {
287
+ console.log('Usage: --dbpulldatafiles (path) This will import files from folder into the database');
288
+ console.log(' --dbpulldatafiles This will import files from meshcentral-data into the db.');
289
+ process.exit();
290
+ } else {
291
+ if ((obj.args.dbpushconfigfiles == '*') || (obj.args.dbpushconfigfiles === true)) { obj.args.dbpushconfigfiles = obj.datapath; }
292
obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
297
- var lockCount = 1
298
- for (var i in files) {
299
- const file = files[i];
300
- if ((file == 'config.json') || file.endsWith('.key') || file.endsWith('.crt') || (file == 'terms.txt') || file.endsWith('.jpg') || file.endsWith('.png')) {
301
- const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
302
- console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
303
- lockCount++;
304
- obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
305
- }
306
- }
307
- if (--lockCount == 0) { process.exit(); }
293
+ if (err != null) { console.log('ERROR: Unable to read from folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
294
+ var configFound = false;
295
+ for (var i in files) { if (files[i] == 'config.json') { configFound = true; } }
296
+ if (configFound == false) { console.log('ERROR: No config.json in folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
297
+ obj.db.RemoveAllOfType('cfile', function () {
298
+ obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
299
+ var lockCount = 1
300
+ for (var i in files) {
301
+ const file = files[i];
302
+ if ((file == 'config.json') || file.endsWith('.key') || file.endsWith('.crt') || (file == 'terms.txt') || file.endsWith('.jpg') || file.endsWith('.png')) {
303
+ const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
304
+ console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
305
+ lockCount++;
306
+ obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
307
+ }
308
+ }
309
+ if (--lockCount == 0) { process.exit(); }
310
+ });
311
+ });
312
});
309
- });
310
- });
311
- }
312
- return;
313
- }
313
+ }
314
+ return;
315
+ }
316
315
- // Pull all database files into meshcentral-data
316
- if (obj.args.dbpullconfigfiles) {
317
- if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
318
- if (typeof obj.args.dbpullconfigfiles != 'string') {
319
- console.log('Usage: --dbpulldatafiles (path)');
320
- process.exit();
321
- } else {
322
- obj.db.GetAllType('cfile', function (err, docs) {
323
- if (err == null) {
324
- if (docs.length == 0) {
325
- console.log('File not found.');
326
- } else {
327
- for (var i in docs) {
328
- const file = docs[i]._id.split('/')[1], binary = obj.db.decryptData(obj.args.configkey, docs[i].data);
329
- if (binary == null) {
330
- console.log('Invalid config key.');
317
+ // Pull all database files into meshcentral-data
318
+ if (obj.args.dbpullconfigfiles) {
319
+ if (typeof obj.args.configkey != 'string') { console.log('Error, --configkey is required.'); process.exit(); return; }
320
+ if (typeof obj.args.dbpullconfigfiles != 'string') {
321
+ console.log('Usage: --dbpulldatafiles (path)');
322
+ process.exit();
323
+ } else {
324
+ obj.db.GetAllType('cfile', function (err, docs) {
325
+ if (err == null) {
326
+ if (docs.length == 0) {
327
+ console.log('File not found.');
328
} else {
332
- var fullFileName = obj.path.join(obj.args.dbpullconfigfiles, file);
333
- try { obj.fs.writeFileSync(fullFileName, binary); } catch (ex) { console.log('Unable to write to ' + fullFileName); process.exit(); return; }
334
- console.log('Pulling ' + file + ', ' + binary.length + ' bytes.');
329
+ for (var i in docs) {
330
+ const file = docs[i]._id.split('/')[1], binary = obj.db.decryptData(obj.args.configkey, docs[i].data);
331
+ if (binary == null) {
332
+ console.log('Invalid config key.');
333
+ } else {
334
+ var fullFileName = obj.path.join(obj.args.dbpullconfigfiles, file);
335
+ try { obj.fs.writeFileSync(fullFileName, binary); } catch (ex) { console.log('Unable to write to ' + fullFileName); process.exit(); return; }
336
+ console.log('Pulling ' + file + ', ' + binary.length + ' bytes.');
337
+ }
338
+ }
339
}
340
+ } else {
341
+ console.log('Unable to read from database.');
342
}
337
- }
338
- } else {
339
- console.log('Unable to read from database.');
343
+ process.exit();
344
+ });
345
}
341
- process.exit();
342
- });
343
- }
344
- return;
345
- }
346
+ return;
347
+ }
348
347
- if (obj.args.dbexport) {
348
- // Export the entire database to a JSON file
349
- if (obj.args.dbexport == true) { obj.args.dbexport = obj.getConfigFilePath('meshcentral.db.json'); }
350
- obj.db.GetAll(function (err, docs) {
351
- obj.fs.writeFileSync(obj.args.dbexport, JSON.stringify(docs));
352
- console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexport + '.'); process.exit();
353
- });
354
- return;
355
- }
356
- if (obj.args.dbexportmin) {
357
- // Export a minimal database to a JSON file. Export only users, meshes and nodes.
358
- // This is a useful command to look at the database.
359
- if (obj.args.dbexportmin == true) { obj.args.dbexportmin = obj.getConfigFilePath('meshcentral.db.json'); }
360
- obj.db.GetAllType({ $in: ['user', 'node', 'mesh'] }, function (err, docs) {
361
- obj.fs.writeFileSync(obj.args.dbexportmin, JSON.stringify(docs));
362
- console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexportmin + '.'); process.exit();
363
- });
364
- return;
365
- }
366
- if (obj.args.dbimport) {
367
- // Import the entire database from a JSON file
368
- if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
369
- var json = null, json2 = "", badCharCount = 0;
370
- try { json = obj.fs.readFileSync(obj.args.dbimport, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbimport + ': ' + e); process.exit(); }
371
- for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
372
- if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
373
- try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbimport + ': ' + e); process.exit(); }
374
- if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
375
- for (i in json) { if ((json[i].type == "mesh") && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } } } // Escape MongoDB invalid field chars
376
- //for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname
377
- setTimeout(function () { // If the Mongo database is being created for the first time, there is a race condition here. This will get around it.
378
- obj.db.RemoveAll(function () {
379
- obj.db.InsertMany(json, function (err) {
380
- if (err != null) { console.log(err); } else { console.log('Imported ' + json.length + ' objects(s) from ' + obj.args.dbimport + '.'); } process.exit();
349
+ if (obj.args.dbexport) {
350
+ // Export the entire database to a JSON file
351
+ if (obj.args.dbexport == true) { obj.args.dbexport = obj.getConfigFilePath('meshcentral.db.json'); }
352
+ obj.db.GetAll(function (err, docs) {
353
+ obj.fs.writeFileSync(obj.args.dbexport, JSON.stringify(docs));
354
+ console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexport + '.'); process.exit();
355
});
382
- });
383
- }, 100);
384
- return;
385
- }
386
- /*
387
- if (obj.args.dbimport) {
388
- // Import the entire database from a very large JSON file
389
- obj.db.RemoveAll(function () {
390
- if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
391
- var json = null, json2 = "", badCharCount = 0;
392
- const StreamArray = require('stream-json/streamers/StreamArray');
393
- const jsonStream = StreamArray.withParser();
394
- jsonStream.on('data', function (data) { obj.db.Set(data.value); });
395
- jsonStream.on('end', () => { console.log('Done.'); process.exit(); });
396
- obj.fs.createReadStream(obj.args.dbimport).pipe(jsonStream.input);
397
- });
398
- return;
399
- }
400
- */
401
- if (obj.args.dbmerge) {
402
- // Import the entire database from a JSON file
403
- if (obj.args.dbmerge == true) { obj.args.dbmerge = obj.getConfigFilePath('meshcentral.db.json'); }
404
- var json = null, json2 = "", badCharCount = 0;
405
- try { json = obj.fs.readFileSync(obj.args.dbmerge, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbmerge + ': ' + e); process.exit(); }
406
- for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
407
- if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
408
- try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbmerge + ': ' + e); process.exit(); }
409
- if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
410
-
411
- // Get all users from current database
412
- obj.db.GetAllType('user', function (err, docs) {
413
- var users = {}, usersCount = 0;
414
- for (var i in docs) { users[docs[i]._id] = docs[i]; usersCount++; }
415
-
416
- // Fetch all meshes from the database
417
- obj.db.GetAllType('mesh', function (err, docs) {
418
- obj.common.unEscapeAllLinksFieldName(docs);
419
- var meshes = {}, meshesCount = 0;
420
- for (var i in docs) { meshes[docs[i]._id] = docs[i]; meshesCount++; }
421
- console.log('Loaded ' + usersCount + ' users and ' + meshesCount + ' meshes.');
422
- // Look at each object in the import file
423
- var objectToAdd = [];
424
- for (var i in json) {
425
- var newobj = json[i];
426
- if (newobj.type == 'user') {
427
- // Check if the user already exists
428
- var existingUser = users[newobj._id];
429
- if (existingUser) {
430
- // Merge the links
431
- if (typeof newobj.links == 'object') {
432
- for (var j in newobj.links) {
433
- if ((existingUser.links == null) || (existingUser.links[j] == null)) {
434
- if (existingUser.links == null) { existingUser.links = {}; }
435
- existingUser.links[j] = newobj.links[j];
356
+ return;
357
+ }
358
+ if (obj.args.dbexportmin) {
359
+ // Export a minimal database to a JSON file. Export only users, meshes and nodes.
360
+ // This is a useful command to look at the database.
361
+ if (obj.args.dbexportmin == true) { obj.args.dbexportmin = obj.getConfigFilePath('meshcentral.db.json'); }
362
+ obj.db.GetAllType({ $in: ['user', 'node', 'mesh'] }, function (err, docs) {
363
+ obj.fs.writeFileSync(obj.args.dbexportmin, JSON.stringify(docs));
364
+ console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexportmin + '.'); process.exit();
365
+ });
366
+ return;
367
+ }
368
+ if (obj.args.dbimport) {
369
+ // Import the entire database from a JSON file
370
+ if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
371
+ var json = null, json2 = "", badCharCount = 0;
372
+ try { json = obj.fs.readFileSync(obj.args.dbimport, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbimport + ': ' + e); process.exit(); }
373
+ for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
374
+ if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
375
+ try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbimport + ': ' + e); process.exit(); }
376
+ if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
377
+ for (i in json) { if ((json[i].type == "mesh") && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } } } // Escape MongoDB invalid field chars
378
+ //for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname
379
+ setTimeout(function () { // If the Mongo database is being created for the first time, there is a race condition here. This will get around it.
380
+ obj.db.RemoveAll(function () {
381
+ obj.db.InsertMany(json, function (err) {
382
+ if (err != null) { console.log(err); } else { console.log('Imported ' + json.length + ' objects(s) from ' + obj.args.dbimport + '.'); } process.exit();
383
+ });
384
+ });
385
+ }, 100);
386
+ return;
387
+ }
388
+ /*
389
+ if (obj.args.dbimport) {
390
+ // Import the entire database from a very large JSON file
391
+ obj.db.RemoveAll(function () {
392
+ if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
393
+ var json = null, json2 = "", badCharCount = 0;
394
+ const StreamArray = require('stream-json/streamers/StreamArray');
395
+ const jsonStream = StreamArray.withParser();
396
+ jsonStream.on('data', function (data) { obj.db.Set(data.value); });
397
+ jsonStream.on('end', () => { console.log('Done.'); process.exit(); });
398
+ obj.fs.createReadStream(obj.args.dbimport).pipe(jsonStream.input);
399
+ });
400
+ return;
401
+ }
402
+ */
403
+ if (obj.args.dbmerge) {
404
+ // Import the entire database from a JSON file
405
+ if (obj.args.dbmerge == true) { obj.args.dbmerge = obj.getConfigFilePath('meshcentral.db.json'); }
406
+ var json = null, json2 = "", badCharCount = 0;
407
+ try { json = obj.fs.readFileSync(obj.args.dbmerge, { encoding: 'utf8' }); } catch (e) { console.log('Invalid JSON file: ' + obj.args.dbmerge + ': ' + e); process.exit(); }
408
+ for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
409
+ if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
410
+ try { json = JSON.parse(json2); } catch (e) { console.log('Invalid JSON format: ' + obj.args.dbmerge + ': ' + e); process.exit(); }
411
+ if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
412
+
413
+ // Get all users from current database
414
+ obj.db.GetAllType('user', function (err, docs) {
415
+ var users = {}, usersCount = 0;
416
+ for (var i in docs) { users[docs[i]._id] = docs[i]; usersCount++; }
417
+
418
+ // Fetch all meshes from the database
419
+ obj.db.GetAllType('mesh', function (err, docs) {
420
+ obj.common.unEscapeAllLinksFieldName(docs);
421
+ var meshes = {}, meshesCount = 0;
422
+ for (var i in docs) { meshes[docs[i]._id] = docs[i]; meshesCount++; }
423
+ console.log('Loaded ' + usersCount + ' users and ' + meshesCount + ' meshes.');
424
+ // Look at each object in the import file
425
+ var objectToAdd = [];
426
+ for (var i in json) {
427
+ var newobj = json[i];
428
+ if (newobj.type == 'user') {
429
+ // Check if the user already exists
430
+ var existingUser = users[newobj._id];
431
+ if (existingUser) {
432
+ // Merge the links
433
+ if (typeof newobj.links == 'object') {
434
+ for (var j in newobj.links) {
435
+ if ((existingUser.links == null) || (existingUser.links[j] == null)) {
436
+ if (existingUser.links == null) { existingUser.links = {}; }
437
+ existingUser.links[j] = newobj.links[j];
438
+ }
439
+ }
440
}
441
+ if (existingUser.name == 'admin') { existingUser.links = {}; }
442
+ objectToAdd.push(existingUser); // Add this user
443
+ } else {
444
+ objectToAdd.push(newobj); // Add this user
445
}
438
- }
439
- if (existingUser.name == 'admin') { existingUser.links = {}; }
440
- objectToAdd.push(existingUser); // Add this user
441
- } else {
442
- objectToAdd.push(newobj); // Add this user
446
+ } else if (newobj.type == 'mesh') {
447
+ // Add this object after escaping
448
+ objectToAdd.push(obj.common.escapeLinksFieldName(newobj));
449
+ } // Don't add nodes.
450
}
444
- } else if (newobj.type == 'mesh') {
445
- // Add this object after escaping
446
- objectToAdd.push(obj.common.escapeLinksFieldName(newobj));
447
- } // Don't add nodes.
448
- }
449
- console.log('Importing ' + objectToAdd.length + ' object(s)...');
450
- var pendingCalls = 1;
451
- for (var i in objectToAdd) {
452
- pendingCalls++;
453
- obj.db.Set(objectToAdd[i], function (err) { if (err != null) { console.log(err); } else { if (--pendingCalls == 0) { process.exit(); } } });
454
- }
455
- if (--pendingCalls == 0) { process.exit(); }
456
- });
457
- });
458
- return;
459
- }
460
-
461
- // Load configuration for database if needed
462
- if (obj.args.loadconfigfromdb) {
463
- var key = null;
464
- if (typeof obj.args.configkey == 'string') { key = obj.args.configkey; }
465
- else if (typeof obj.args.loadconfigfromdb == 'string') { key = obj.args.loadconfigfromdb; }
466
- if (key == null) { console.log('Error, --configkey is required.'); process.exit(); return; }
467
- obj.db.getAllConfigFiles(key, function (configFiles) {
468
- if (configFiles == null) { console.log('Error, no configuration files found or invalid configkey.'); process.exit(); return; }
469
- if (!configFiles['config.json']) { console.log('Error, could not file config.json from database.'); process.exit(); return; }
470
- obj.configurationFiles = configFiles;
471
-
472
- // Parse the new configuration file
473
- var config2 = null;
474
- try { config2 = JSON.parse(configFiles['config.json']); } catch (ex) { console.log('Error, unable to parse config.json from database.'); process.exit(); return; }
475
-
476
- // Set the command line arguments to the config file if they are not present
477
- if (!config2.settings) { config2.settings = {}; }
478
- for (i in args) { config2.settings[i] = args[i]; }
479
-
480
- // Lower case all keys in the config file
481
- try {
482
- require('./common.js').objKeysToLower(config2, ["ldapoptions"]);
483
- } catch (ex) {
484
- console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.');
485
- process.exit();
451
+ console.log('Importing ' + objectToAdd.length + ' object(s)...');
452
+ var pendingCalls = 1;
453
+ for (var i in objectToAdd) {
454
+ pendingCalls++;
455
+ obj.db.Set(objectToAdd[i], function (err) { if (err != null) { console.log(err); } else { if (--pendingCalls == 0) { process.exit(); } } });
456
+ }
457
+ if (--pendingCalls == 0) { process.exit(); }
458
+ });
459
+ });
460
return;
461
}
462
489
- // Grad some of the values from the original config.json file if present.
490
- config2['mongodb'] = config['mongodb'];
491
- config2['mongodbcol'] = config['mongodbcol'];
492
- config2['dbencryptkey'] = config['dbencryptkey'];
463
+ // Load configuration for database if needed
464
+ if (obj.args.loadconfigfromdb) {
465
+ var key = null;
466
+ if (typeof obj.args.configkey == 'string') { key = obj.args.configkey; }
467
+ else if (typeof obj.args.loadconfigfromdb == 'string') { key = obj.args.loadconfigfromdb; }
468
+ if (key == null) { console.log('Error, --configkey is required.'); process.exit(); return; }
469
+ obj.db.getAllConfigFiles(key, function (configFiles) {
470
+ if (configFiles == null) { console.log('Error, no configuration files found or invalid configkey.'); process.exit(); return; }
471
+ if (!configFiles['config.json']) { console.log('Error, could not file config.json from database.'); process.exit(); return; }
472
+ obj.configurationFiles = configFiles;
473
+
474
+ // Parse the new configuration file
475
+ var config2 = null;
476
+ try { config2 = JSON.parse(configFiles['config.json']); } catch (ex) { console.log('Error, unable to parse config.json from database.'); process.exit(); return; }
477
+
478
+ // Set the command line arguments to the config file if they are not present
479
+ if (!config2.settings) { config2.settings = {}; }
480
+ for (i in args) { config2.settings[i] = args[i]; }
481
+
482
+ // Lower case all keys in the config file
483
+ try {
484
+ require('./common.js').objKeysToLower(config2, ["ldapoptions"]);
485
+ } catch (ex) {
486
+ console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.');
487
+ process.exit();
488
+ return;
489
+ }
490
+
491
+ // Grad some of the values from the original config.json file if present.
492
+ config2['mongodb'] = config['mongodb'];
493
+ config2['mongodbcol'] = config['mongodbcol'];
494
+ config2['dbencryptkey'] = config['dbencryptkey'];
495
494
- // We got a new config.json from the database, let's use it.
495
- config = obj.config = config2;
496
- obj.StartEx1b();
496
+ // We got a new config.json from the database, let's use it.
497
+ config = obj.config = config2;
498
+ obj.StartEx1b();
499
+ });
500
+ } else {
501
+ config = obj.config = getConfig(true);
502
+ obj.StartEx1b();
503
+ }
504
});
498
- } else {
499
- config = obj.config = getConfig(true);
500
- obj.StartEx1b();
505
}
502
- });
506
+ );
507
};
508
509
// Time to start the serverf or real.
@@ -1711,7 +1715,8 @@ function mainStart(args) {
1715
if (require('os').platform() == 'win32') { modules.push('node-windows'); if (sspi == true) { modules.push('node-sspi'); } } // Add Windows modules
1716
if (ldap == true) { modules.push('ldapauth-fork'); }
1717
if (config.letsencrypt != null) { modules.push('greenlock'); modules.push('le-store-certbot'); modules.push('le-challenge-fs'); modules.push('le-acme-core'); } // Add Greenlock Modules
1714
- if (config.settings.mongodb != null) { modules.push('mongojs'); } // Add MongoDB
1718
+ if (config.settings.mongodb != null) { modules.push('mongojs'); } // Add MongoJS
1719
+ else if (config.settings.mongo != null) { modules.push('mongodb'); } // Add MongoDB
1720
if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support
1721
1722
// Get the current node version