Fixed terminal support of recoverycore
Bryan Roe committed
Apr 23, 2020 at 02:15 UTC
d1ff8e5b62dc62819fea7b2671a64b356fb116e5
2 files changed
+136
-28
agents/recoverycore.js
+128
-24
@@ -116,8 +116,80 @@ function onTunnelUpgrade(response, s, head) {
116
}
117
}
118
119
+// Called when receiving control data on websocket
120
+function onTunnelControlData(data, ws)
121
+{
122
+ var obj;
123
+ if (ws == null) { ws = this; }
124
+ if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } }
125
+ else if (typeof data == 'object') { obj = data; } else { return; }
126
+ //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
127
+ //console.log('onTunnelControlData: ' + JSON.stringify(data));
128
+
129
+ if (obj.action)
130
+ {
131
+ switch (obj.action)
132
+ {
133
+ case 'lock': {
134
+ // Lock the current user out of the desktop
135
+ try
136
+ {
137
+ if (process.platform == 'win32')
138
+ {
139
+ MeshServerLog("Locking remote user out of desktop", ws.httprequest);
140
+ var child = require('child_process');
141
+ child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
142
+ }
143
+ } catch (e) { }
144
+ break;
145
+ }
146
+ default:
147
+ // Unknown action, ignore it.
148
+ break;
149
+ }
150
+ return;
151
+ }
152
+
153
+ switch (obj.type)
154
+ {
155
+ case 'options': {
156
+ // These are additional connection options passed in the control channel.
157
+ //sendConsoleText('options: ' + JSON.stringify(obj));
158
+ delete obj.type;
159
+ ws.httprequest.xoptions = obj;
160
+
161
+ // Set additional user consent options if present
162
+ if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }
163
+
164
+ break;
165
+ }
166
+ case 'close': {
167
+ // We received the close on the websocket
168
+ //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
169
+ try { ws.close(); } catch (e) { }
170
+ break;
171
+ }
172
+ case 'termsize': {
173
+ // Indicates a change in terminal size
174
+ if (process.platform == 'win32')
175
+ {
176
+ if (ws.httprequest._dispatcher == null) return;
177
+ if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
178
+ }
179
+ else
180
+ {
181
+ if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
182
+ if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
183
+ }
184
+ break;
185
+ }
186
+ }
187
+}
188
+
189
+
190
require('MeshAgent').AddCommandHandler(function (data) {
120
- if (typeof data == 'object') {
191
+ if (typeof data == 'object')
192
+ {
193
// If this is a console command, parse it and call the console handler
194
switch (data.action) {
195
case 'msg':
@@ -171,20 +243,60 @@ require('MeshAgent').AddCommandHandler(function (data) {
243
if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid); }
244
} else {
245
// Handle tunnel data
174
- if (this.httprequest.protocol == 0) {
246
+ if (this.httprequest.protocol == 0)
247
+ {
248
+ if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
249
// Take a look at the protocol
250
this.httprequest.protocol = parseInt(data);
251
if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
252
if (this.httprequest.protocol == 1) {
253
// Remote terminal using native pipes
180
- if (process.platform == "win32") {
181
- this.httprequest._term = require('win-terminal').Start(80, 25);
182
- this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
183
- this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
184
- this.prependListener('end', function () { this.httprequest._term.end(function () { sendConsoleText('Terminal was closed'); }); });
254
+ if (process.platform == "win32")
255
+ {
256
+ var cols = 80, rows = 25;
257
+ if (this.httprequest.xoptions)
258
+ {
259
+ if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
260
+ if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
261
+ }
262
+
263
+ // Admin Terminal
264
+ if (require('win-virtual-terminal').supported)
265
+ {
266
+ // ConPTY PseudoTerminal
267
+ // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
268
+
269
+ // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
270
+ this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: 'Start', args: [cols, rows] } });
271
+ this.httprequest._dispatcher.ws = this;
272
+ this.httprequest._dispatcher.on('connection', function (c)
273
+ {
274
+ this.ws._term = c;
275
+ c.pipe(this.ws, { dataTypeSkip: 1 });
276
+ this.ws.pipe(c, { dataTypeSkip: 1 });
277
+ });
278
+ }
279
+ else
280
+ {
281
+ // Legacy Terminal
282
+ this.httprequest._term = require('win-terminal').Start(80, 25);
283
+ this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
284
+ this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
285
+ this.prependListener('end', function () { this.httprequest._term.end(function () { sendConsoleText('Terminal was closed'); }); });
286
+ }
287
}
186
- else {
187
- this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
288
+ else
289
+ {
290
+ var env = { HISTCONTROL: 'ignoreboth' };
291
+ if (this.httprequest.xoptions)
292
+ {
293
+ if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
294
+ if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
295
+ }
296
+ var options = { type: childProcess.SpawnTypes.TERM, env: env };
297
+ this.httprequest.process = childProcess.execFile('/bin/bash', ['bash'], options); // Start bash
298
+ // Spaces at the beginning of lines are needed to hide commands from the command history
299
+ if (process.platform == 'linux') { this.httprequest.process.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
300
this.httprequest.process.tunnel = this;
301
this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
302
this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
@@ -192,18 +304,6 @@ require('MeshAgent').AddCommandHandler(function (data) {
304
this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
305
this.prependListener('end', function () { this.httprequest.process.kill(); });
306
}
195
-
196
- this.on('end', function () {
197
- if (process.platform == "win32") {
198
- // Unpipe the web socket
199
- this.unpipe(this.httprequest._term);
200
- this.httprequest._term.unpipe(this);
201
-
202
- // Clean up
203
- this.httprequest._term.end();
204
- this.httprequest._term = null;
205
- }
206
- });
307
}
308
}
309
else if (this.httprequest.protocol == 5) {
@@ -318,11 +418,15 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
418
break;
419
case 'osinfo': { // Return the operating system information
420
var i = 1;
321
- if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = "Calling " + i + " times."; }
322
- for (var j = 0; j < i; j++) {
421
+ if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
422
+ for (var j = 0; j < i; j++)
423
+ {
424
var pr = require('os').name();
425
pr.sessionid = sessionid;
325
- pr.then(function (v) { sendConsoleText("OS: " + v, this.sessionid); });
426
+ pr.then(function (v)
427
+ {
428
+ sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid);
429
+ });
430
}
431
break;
432
}
meshcentral.js
+8
-4
@@ -1901,15 +1901,19 @@ function CreateMeshCentralServer(config, args) {
1901
}
1902
1903
// Merge this module to recovery modules if needed
1904
- if (modulesAdd['windows-recovery'] != null) {
1905
- if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal')) {
1904
+ if (modulesAdd['windows-recovery'] != null)
1905
+ {
1906
+ if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal') || (moduleName == 'win-virtual-terminal'))
1907
+ {
1908
modulesAdd['windows-recovery'].push(...moduleData);
1909
}
1910
}
1911
1912
// Merge this module to agent recovery modules if needed
1911
- if (modulesAdd['windows-agentrecovery'] != null) {
1912
- if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal')) {
1913
+ if (modulesAdd['windows-agentrecovery'] != null)
1914
+ {
1915
+ if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal') || (moduleName == 'win-virtual-terminal'))
1916
+ {
1917
modulesAdd['windows-agentrecovery'].push(...moduleData);
1918
}
1919
}