Added self-update capability
Bryan Roe committed
Jan 14, 2021 at 17:08 UTC
522836eacce1a5f7733644972cefc321b49a1f58
1 file changed
+246
-21
agents/recoverycore.js
+246
-21
@@ -6,11 +6,210 @@ var nextTunnelIndex = 1;
6
var tunnels = {};
7
var fs = require('fs');
8
9
+if (require('MeshAgent').ARCHID == null)
10
+{
11
+ var id = null;
12
+ switch (process.platform)
13
+ {
14
+ case 'win32':
15
+ id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4;
16
+ break;
17
+ case 'freebsd':
18
+ id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30;
19
+ break;
20
+ case 'darwin':
21
+ id = require('os').arch() == 'x64' ? 16 : 29;
22
+ break;
23
+ }
24
+ if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); }
25
+}
26
+
27
//attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); });
28
11
-function sendConsoleText(msg) {
12
- require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg });
29
+function sendConsoleText(msg, sessionid)
30
+{
31
+ if (sessionid != null)
32
+ {
33
+ require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg, sessionid: sessionid });
34
+ }
35
+ else
36
+ {
37
+ require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg });
38
+ }
39
+}
40
+
41
+function sendAgentMessage(msg, icon)
42
+{
43
+ if (sendAgentMessage.messages == null)
44
+ {
45
+ sendAgentMessage.messages = {};
46
+ sendAgentMessage.nextid = 1;
47
+ }
48
+ sendAgentMessage.messages[sendAgentMessage.nextid++] = { msg: msg, icon: icon };
49
+ require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: sendAgentMessage.messages });
50
}
51
+
52
+function agentUpdate_Start(updateurl, updateoptions)
53
+{
54
+ var sessionid = updateoptions != null ? updateoptions.session : null;
55
+
56
+ if (this._selfupdate != null)
57
+ {
58
+ if (sessionid != null) { sendConsoleText('Self update already in progress...', sessionid); }
59
+ }
60
+ else
61
+ {
62
+ if (require('MeshAgent').ARCHID == null && updateurl == null)
63
+ {
64
+ if (sessionid != null) { sendConsoleText('Unable to initiate update, agent ARCHID is not defined', sessionid); }
65
+ }
66
+ else
67
+ {
68
+ var agentfilename = process.execPath.split(process.platform == 'win32' ? '\\' : '/').pop();
69
+ var name = require('MeshAgent').serviceName;
70
+ if (name == null) { name = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; }
71
+ try
72
+ {
73
+ var s = require('service-manager').manager.getService(name);
74
+ if (!s.isMe())
75
+ {
76
+ if (process.platform == 'win32') { s.close(); }
77
+ if (sessionid != null) { sendConsoleText('Service check FAILED', sessionid); }
78
+ return;
79
+ }
80
+ if (process.platform == 'win32') { s.close(); }
81
+ }
82
+ catch (zz)
83
+ {
84
+ if (sessionid != null) { sendConsoleText('Service check FAILED', sessionid); }
85
+ else
86
+ {
87
+ sendAgentMessage('Self Update Failed, because this agent is not running as a service', 3);
88
+ }
89
+ return;
90
+ }
91
+
92
+ if (sessionid != null) { sendConsoleText('Downloading update...', sessionid); }
93
+ var options = require('http').parseUri(updateurl != null ? updateurl : require('MeshAgent').ServerUrl);
94
+ options.protocol = 'https:';
95
+ if (updateurl == null) { options.path = ('/meshagents?id=' + require('MeshAgent').ARCHID); }
96
+ options.rejectUnauthorized = false;
97
+ options.checkServerIdentity = function checkServerIdentity(certs)
98
+ {
99
+ // If the tunnel certificate matches the control channel certificate, accept the connection
100
+ try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
101
+ try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
102
+
103
+ // Check that the certificate is the one expected by the server, fail if not.
104
+ if (checkServerIdentity.servertlshash == null)
105
+ {
106
+ if(sessionid!=null)
107
+ {
108
+ sendConsoleText('Self Update failed, because the url cannot be verified', sessionid);
109
+ }
110
+ else
111
+ {
112
+ sendAgentMessage('Self Update failed, because the url cannot be verified', 3);
113
+ }
114
+ throw new Error('BadCert');
115
+ }
116
+ if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase()))
117
+ {
118
+ if (sessionid != null)
119
+ {
120
+ sendConsoleText('Self Update failed, because the supplied certificate does not match', sessionid);
121
+ }
122
+ else
123
+ {
124
+ sendAgentMessage('Self Update failed, because the supplied certificate does not match', 3);
125
+ }
126
+ throw new Error('BadCert')
127
+ }
128
+ }
129
+ options.checkServerIdentity.servertlshash = (updateoptions != null ? updateoptions.tlshash : null);
130
+ this._selfupdate = require('https').get(options);
131
+ this._selfupdate.on('error', function (e)
132
+ {
133
+ if (sessionid != null) { sendConsoleText('Error fetching update', sessionid); }
134
+ else
135
+ {
136
+ sendAgentMessage('Self Update failed, because there was a problem trying to download the update', 3);
137
+ }
138
+ });
139
+ this._selfupdate.on('response', function (img)
140
+ {
141
+ this._file = require('fs').createWriteStream(agentfilename + '.update', { flags: 'wb' });
142
+ this._filehash = require('SHA384Stream').create();
143
+ this._filehash.on('hash', function (h)
144
+ {
145
+ if (updateoptions != null && updateoptions.hash != null)
146
+ {
147
+ if (updateoptions.hash.toLowerCase() == h.toString('hex').toLowerCase())
148
+ {
149
+ if (sessionid != null) { sendConsoleText('Download complete. HASH verified.', sessionid); }
150
+ }
151
+ else
152
+ {
153
+ if (sessionid != null) { sendConsoleText('Download complete. HASH FAILED.', sessionid); }
154
+ else
155
+ {
156
+ sendAgentMessage('Self Update FAILED because the downloaded agent FAILED hash check', 3);
157
+ }
158
+ return;
159
+ }
160
+ }
161
+ else
162
+ {
163
+ if (sessionid != null) { sendConsoleText('Download complete. HASH=' + h.toString('hex'), sessionid); }
164
+ }
165
+
166
+ if (sessionid != null) { sendConsoleText('Updating and restarting agent...', sessionid); }
167
+ if (process.platform == 'win32')
168
+ {
169
+ this.child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe',
170
+ ['/C wmic service "' + name + '" call stopservice && copy "' + process.cwd() + agentfilename + '.update" "' + process.execPath + '" && wmic service "' + name + '" call startservice && erase "' + process.cwd() + agentfilename + '.update"'], { type: 4 | 0x8000 });
171
+ }
172
+ else
173
+ {
174
+ // remove binary
175
+ require('fs').unlinkSync(process.execPath);
176
+
177
+ // copy update
178
+ require('fs').copyFileSync(process.cwd() + agentfilename + '.update', process.execPath);
179
+
180
+ // erase update
181
+ require('fs').unlinkSync(process.cwd() + agentfilename + '.update');
182
+
183
+ // add execute permissions
184
+ var m = require('fs').statSync(process.execPath).mode;
185
+ m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH);
186
+ require('fs').chmodSync(process.execPath, m);
187
+
188
+ if (sessionid != null) { sendConsoleText('Restarting service...', sessionid); }
189
+ try
190
+ {
191
+ // restart service
192
+ var s = require('service-manager').manager.getService(name);
193
+ s.restart();
194
+ }
195
+ catch (zz)
196
+ {
197
+ if (sessionid != null) { sendConsoleText('Error restarting service', sessionid); }
198
+ else
199
+ {
200
+ sendAgentMessage('Self Update encountered an error trying to restart service', 3);
201
+ }
202
+ }
203
+ }
204
+ });
205
+ img.pipe(this._file);
206
+ img.pipe(this._filehash);
207
+ });
208
+ }
209
+ }
210
+}
211
+
212
+
213
// Return p number of spaces
214
function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
215
@@ -187,16 +386,23 @@ function onTunnelControlData(data, ws)
386
}
387
388
190
-require('MeshAgent').AddCommandHandler(function (data) {
389
+require('MeshAgent').AddCommandHandler(function (data)
390
+{
391
if (typeof data == 'object')
392
{
393
// If this is a console command, parse it and call the console handler
194
- switch (data.action) {
394
+ switch (data.action)
395
+ {
396
+ case 'agentupdate':
397
+ agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash });
398
+ break;
399
case 'msg':
400
{
197
- switch (data.type) {
401
+ switch (data.type)
402
+ {
403
case 'console': { // Process a console command
199
- if (data.value && data.sessionid) {
404
+ if (data.value && data.sessionid)
405
+ {
406
var args = splitArgs(data.value);
407
processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
408
}
@@ -204,19 +410,23 @@ require('MeshAgent').AddCommandHandler(function (data) {
410
}
411
case 'tunnel':
412
{
207
- if (data.value != null) { // Process a new tunnel connection request
413
+ if (data.value != null)
414
+ { // Process a new tunnel connection request
415
// Create a new tunnel object
416
var xurl = getServerTargetUrlEx(data.value);
210
- if (xurl != null) {
417
+ if (xurl != null)
418
+ {
419
var woptions = http.parseUri(xurl);
420
woptions.rejectUnauthorized = 0;
421
//sendConsoleText(JSON.stringify(woptions));
422
var tunnel = http.request(woptions);
215
- tunnel.on('upgrade', function (response, s, head) {
423
+ tunnel.on('upgrade', function (response, s, head)
424
+ {
425
this.s = s;
426
s.httprequest = this;
427
s.tunnel = this;
219
- s.on('end', function () {
428
+ s.on('end', function ()
429
+ {
430
if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.
431
432
// If there is a upload or download active on this connection, close the file
@@ -230,18 +440,22 @@ require('MeshAgent').AddCommandHandler(function (data) {
440
// Clean up WebSocket
441
this.removeAllListeners('data');
442
});
233
- s.on('data', function (data) {
443
+ s.on('data', function (data)
444
+ {
445
// If this is upload data, save it to file
235
- if (this.httprequest.uploadFile) {
446
+ if (this.httprequest.uploadFile)
447
+ {
448
try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
449
this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data
450
return;
451
}
452
241
- if (this.httprequest.state == 0) {
453
+ if (this.httprequest.state == 0)
454
+ {
455
// Check if this is a relay connection
456
if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid); }
244
- } else {
457
+ } else
458
+ {
459
// Handle tunnel data
460
if (this.httprequest.protocol == 0)
461
{
@@ -296,7 +510,7 @@ require('MeshAgent').AddCommandHandler(function (data) {
510
}
511
var options = { type: childProcess.SpawnTypes.TERM, env: env };
512
299
- if(require('fs').existsSync('/bin/bash'))
513
+ if (require('fs').existsSync('/bin/bash'))
514
{
515
this.httprequest.process = childProcess.execFile('/bin/bash', ['bash'], options); // Start bash
516
}
@@ -316,7 +530,8 @@ require('MeshAgent').AddCommandHandler(function (data) {
530
}
531
}
532
}
319
- else if (this.httprequest.protocol == 5) {
533
+ else if (this.httprequest.protocol == 5)
534
+ {
535
// Process files commands
536
var cmd = null;
537
try { cmd = JSON.parse(data); } catch (e) { };
@@ -329,7 +544,8 @@ require('MeshAgent').AddCommandHandler(function (data) {
544
545
if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
546
//console.log(objToString(cmd, 0, ' '));
332
- switch (cmd.action) {
547
+ switch (cmd.action)
548
+ {
549
case 'ls':
550
// Send the folder content to the browser
551
var response = getDirectoryInfo(cmd.path);
@@ -343,7 +559,8 @@ require('MeshAgent').AddCommandHandler(function (data) {
559
}
560
case 'rm': {
561
// Delete, possibly recursive delete
346
- for (var i in cmd.delfiles) {
562
+ for (var i in cmd.delfiles)
563
+ {
564
try { deleteFolderRecursive(path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { }
565
}
566
break;
@@ -367,7 +584,8 @@ require('MeshAgent').AddCommandHandler(function (data) {
584
}
585
case 'copy': {
586
// Copy a bunch of files from scpath to dspath
370
- for (var i in cmd.names) {
587
+ for (var i in cmd.names)
588
+ {
589
var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
590
if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
591
}
@@ -375,7 +593,8 @@ require('MeshAgent').AddCommandHandler(function (data) {
593
}
594
case 'move': {
595
// Move a bunch of files from scpath to dspath
378
- for (var i in cmd.names) {
596
+ for (var i in cmd.names)
597
+ {
598
var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
599
if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
600
}
@@ -424,7 +643,13 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
643
var response = null;
644
switch (cmd) {
645
case 'help':
427
- response = "Available commands are: osinfo, dbkeys, dbget, dbset, dbcompact, netinfo.";
646
+ response = "Available commands are: osinfo, dbkeys, dbget, dbset, dbcompact, netinfo, versions.";
647
+ break;
648
+ case 'versions':
649
+ response = JSON.stringify(process.versions, null, ' ');
650
+ break;
651
+ case 'agentupdate':
652
+ agentUpdate_Start(null, { session: sessionid });
653
break;
654
case 'osinfo': { // Return the operating system information
655
var i = 1;