Improved remote desktop command decoding.
Ylian Saint-Hilaire committed
May 16, 2020 at 14:07 UTC
7668b282bbdd498bc5c5b7052f04566e6fb636d1
3 files changed
+45
-136
public/scripts/agent-desktop-0.0.2.js
+23
-80
@@ -103,13 +103,13 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
103
104
// KVM Control.
105
// Routines for processing incoming packets from the AJAX server, and handling individual messages.
106
- obj.ProcessPictureMsg = function (str, X, Y) {
106
+ obj.ProcessPictureMsg = function (data, X, Y) {
107
//if (obj.targetnode != null) obj.Debug("ProcessPictureMsg " + X + "," + Y + " - " + obj.targetnode.substring(0, 8));
108
var tile = new Image();
109
tile.xcount = obj.tilesReceived++;
110
//console.log('Tile #' + tile.xcount);
111
var r = obj.tilesReceived;
112
- tile.src = "data:image/jpeg;base64," + btoa(str.substring(4, str.length));
112
+ tile.src = "data:image/jpeg;base64," + btoa(String.fromCharCode.apply(null, data.slice(4)));
113
tile.onload = function () {
114
//console.log('DecodeTile #' + this.xcount);
115
if (obj.Canvas != null && obj.KillDraw < r && obj.State != 0) {
@@ -185,72 +185,16 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
185
if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight, obj.CanvasId); }
186
}
187
188
- obj.ProcessData = function (str) {
189
- var ptr = 0;
190
- while (ptr < str.length) {
191
- var r = obj.ProcessDataEx(str.substring(ptr));
192
- if ((r == null) || (r == 0)) break;
193
- ptr += r;
194
- }
195
- }
196
-
197
- obj.ProcessDataEx = function (str) {
198
- if (obj.accumulator != null) {
199
- str = obj.accumulator + str;
200
- //console.log('KVM using accumulated data, total size is now ' + str.length + ' bytes.');
201
- obj.accumulator = null;
202
- }
203
- if (obj.debugmode > 1) { console.log("KRecv(" + str.length + "): " + rstr2hex(str.substring(0, Math.min(str.length, 40)))); }
204
- if (str.length < 4) return;
205
- var cmdmsg = null, X = 0, Y = 0, command = ReadShort(str, 0), cmdsize = ReadShort(str, 2), jumboAdd = 0;
206
- if (obj.recordedData != null) { obj.recordedData.push(recordingEntry(2, 1, str.length)); obj.recordedData.push(str); }
207
- if ((command == 27) && (cmdsize == 8)) {
208
- // Jumbo packet
209
- if (str.length < 12) return;
210
- command = ReadShort(str, 8)
211
- cmdsize = ReadInt(str, 4);
212
- //console.log('JUMBO cmd=' + command + ', cmdsize=' + cmdsize + ', data received=' + str.length);
213
- if ((cmdsize + 8) > str.length) {
214
- //console.log('KVM accumulator set to ' + str.length + ' bytes, need ' + cmdsize + ' bytes.');
215
- obj.accumulator = str;
216
- return;
217
- }
218
- str = str.substring(8);
219
- jumboAdd = 8;
220
- }
221
- if ((cmdsize != str.length) && (obj.debugmode > 0)) { console.log(cmdsize, str.length, cmdsize == str.length); }
222
- if ((command >= 18) && (command != 65) && (command != 88)) {
223
- console.error("Invalid KVM command " + command + " of size " + cmdsize);
224
- console.log("Invalid KVM data", str.length, rstr2hex(str.substring(0, 40)) + '...');
225
- if (obj.parent && obj.parent.setConsoleMessage) { obj.parent.setConsoleMessage("Received invalid network data", 5); }
226
- return;
227
- }
228
- if (cmdsize > str.length) {
229
- //console.log('KVM accumulator set to ' + str.length + ' bytes, need ' + cmdsize + ' bytes.');
230
- obj.accumulator = str;
231
- return;
232
- }
233
- //console.log("KVM Command: " + command + " Len:" + cmdsize);
234
-
235
- if (command == 3 || command == 4 || command == 7) {
236
- cmdmsg = str.substring(4, cmdsize);
237
- X = ((cmdmsg.charCodeAt(0) & 0xFF) << 8) + (cmdmsg.charCodeAt(1) & 0xFF);
238
- Y = ((cmdmsg.charCodeAt(2) & 0xFF) << 8) + (cmdmsg.charCodeAt(3) & 0xFF);
239
- if (obj.debugmode > 0) { console.log("CMD" + command + " at X=" + X + " Y=" + Y); }
240
- }
188
+ obj.ProcessBinaryCommand = function (cmd, cmdsize, view) {
189
+ var X, Y;
190
+ if ((cmd == 3) || (cmd == 4) || (cmd == 7)) { X = (view[4] << 8) + view[5]; Y = (view[6] << 8) + view[7]; }
191
+ //console.log('CMD', cmd, cmdsize, X, Y);
192
242
- switch (command) {
193
+ switch (cmd) {
194
case 3: // Tile
195
if (obj.FirstDraw) obj.onResize();
245
- obj.ProcessPictureMsg(cmdmsg, X, Y);
246
- break;
247
- case 4: // Tile Copy
248
- if (obj.FirstDraw) obj.onResize();
249
- if (obj.TilesDrawn == obj.tilesReceived) {
250
- obj.ProcessCopyRectMsg(cmdmsg);
251
- } else {
252
- obj.PendingOperations.push([ ++tilesReceived, 1, cmdmsg ]);
253
- }
196
+ //console.log('TILE', X, Y);
197
+ obj.ProcessPictureMsg(view.slice(4), X, Y);
198
break;
199
case 7: // Screen size
200
obj.ProcessScreenMsg(X, Y);
@@ -262,13 +206,13 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
206
obj.SendKeyMsgKC(obj.KeyAction.UP, 16); // Shift
207
obj.send(String.fromCharCode(0x00, 0x0E, 0x00, 0x04));
208
break;
265
- case 11: // GetDisplays
266
- var selectedDisplay = 0, displays = { }, dcount = ((str.charCodeAt(4) & 0xFF) << 8) + (str.charCodeAt(5) & 0xFF);
209
+ case 11: // GetDisplays (TODO)
210
+ var selectedDisplay = 0, displays = {}, dcount = (view[4] << 8) + view[5];
211
if (dcount > 0) {
212
// Many displays present
269
- selectedDisplay = ((str.charCodeAt(6 + (dcount * 2)) & 0xFF) << 8) + (str.charCodeAt(7 + (dcount * 2)) & 0xFF);
213
+ selectedDisplay = (view[6 + (dcount * 2)] << 8) + view[7 + (dcount * 2)];
214
for (var i = 0; i < dcount; i++) {
271
- var disp = ((str.charCodeAt(6 + (i * 2)) & 0xFF) << 8) + (str.charCodeAt(7 + (i * 2)) & 0xFF);
215
+ var disp = (view[6 + (i * 2)] << 8) + view[7 + (i * 2)];
216
if (disp == 65535) { displays[disp] = 'All Displays'; } else { displays[disp] = 'Display ' + disp; }
217
}
218
}
@@ -287,17 +231,13 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
231
case 15: // KVM_TOUCH
232
obj.TouchArray = {};
233
break;
290
- case 16: // MNG_KVM_CONNECTCOUNT
291
- obj.connectioncount = ReadInt(str, 4);
292
- //obj.Debug("Got KVM Connect Count: " + obj.connectioncount);
293
- if (obj.onConnectCountChanged != null) obj.onConnectCountChanged(obj.connectioncount, obj);
294
- break;
234
case 17: // MNG_KVM_MESSAGE
296
- //obj.Debug("Got KVM Message: " + str.substring(4, cmdsize));
297
- if (obj.onMessage != null) obj.onMessage(str.substring(4, cmdsize), obj);
235
+ var str = String.fromCharCode.apply(null, data.slice(4));
236
+ obj.Debug("Got KVM Message: " + str);
237
+ if (obj.onMessage != null) obj.onMessage(str, obj);
238
break;
239
case 65: // Alert
300
- str = str.substring(4);
240
+ var str = String.fromCharCode.apply(null, data.slice(4));
241
if (str[0] != '.') {
242
console.log(str); //alert('KVM: ' + str);
243
if (obj.parent && obj.parent.setConsoleMessage) { obj.parent.setConsoleMessage(str); }
@@ -307,15 +247,18 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
247
break;
248
case 88: // MNG_KVM_MOUSE_CURSOR
249
if (cmdsize != 5) break;
310
- var cursorNum = str.charCodeAt(4);
250
+ var cursorNum = view[4];
251
if (cursorNum > mouseCursors.length) { cursorNum = 0; }
252
xMouseCursorCurrent = mouseCursors[cursorNum];
253
if (xMouseCursorActive) { obj.CanvasId.style.cursor = xMouseCursorCurrent; }
254
break;
255
+ default:
256
+ console.log('Unknown command', cmd, cmdsize);
257
+ break;
258
}
316
- return cmdsize + jumboAdd;
317
- }
259
260
+ }
261
+
262
// Keyboard and Mouse I/O.
263
obj.MouseButton = { "NONE": 0x00, "LEFT": 0x02, "RIGHT": 0x08, "MIDDLE": 0x20 };
264
obj.KeyAction = { "NONE": 0, "DOWN": 1, "UP": 2, "SCROLL": 3, "EXUP": 4, "EXDOWN": 5, "DBLCLICK": 6 };
public/scripts/agent-redir-ws-0.1.1.js
+15
-53
@@ -50,6 +50,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
50
obj.nodeid = nodeid;
51
obj.connectstate = 0;
52
obj.socket = new WebSocket(url);
53
+ obj.socket.binaryType = 'arraybuffer';
54
obj.socket.onopen = obj.xxOnSocketConnected;
55
obj.socket.onmessage = obj.xxOnMessage;
56
//obj.socket.onmessage = function (e) { console.log('Websocket data', e.data); obj.xxOnMessage(e); }
@@ -136,6 +137,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
137
else if (typeof webkitRTCPeerConnection !== 'undefined') { obj.webrtc = new webkitRTCPeerConnection(configuration); }
138
if ((obj.webrtc != null) && (obj.webrtc.createDataChannel)) {
139
obj.webchannel = obj.webrtc.createDataChannel('DataChannel', {}); // { ordered: false, maxRetransmits: 2 }
140
+ obj.webchannel.binaryType = 'arraybuffer';
141
obj.webchannel.onmessage = obj.xxOnMessage;
142
//obj.webchannel.onmessage = function (e) { console.log('WebRTC data', e.data); obj.xxOnMessage(e); }
143
obj.webchannel.onopen = function () { obj.webRtcActive = true; performWebRtcSwitch(); };
@@ -165,66 +167,26 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
167
}
168
}
169
170
+ // Control messages, most likely WebRTC setup
171
if (typeof e.data == 'string') {
169
- // Control messages, most likely WebRTC setup
172
obj.xxOnControlCommand(e.data);
171
- return;
172
- }
173
-
174
- if (typeof e.data == 'object') {
175
- if (fileReaderInuse == true) { fileReaderAcc.push(e.data); return; }
176
- if (fileReader.readAsBinaryString && (obj.m.ProcessBinaryData == null)) {
177
- // Chrome & Firefox (Draft)
178
- fileReaderInuse = true;
179
- fileReader.readAsBinaryString(new Blob([e.data]));
180
- } else if (fileReader.readAsArrayBuffer) {
181
- // Chrome & Firefox (Spec)
182
- fileReaderInuse = true;
183
- fileReader.readAsArrayBuffer(e.data);
173
+ } else {
174
+ // Send the data to the module
175
+ if (obj.m.ProcessBinaryCommand) {
176
+ // Send as Binary Command
177
+ var view = new Uint8Array(e.data), cmd = (view[0] << 8) + view[1], cmdsize = (view[2] << 8) + view[3];
178
+ if ((cmd == 27) && (cmdsize == 8)) { cmd = (view[8] << 8) + view[9]; cmdsize = (view[5] << 16) + (view[6] << 8) + view[7]; view = view.slice(8); }
179
+ if (cmdsize != view.byteLength) { console.log('REDIR-ERROR', cmd, cmdsize, view.byteLength); } else { obj.m.ProcessBinaryCommand(cmd, cmdsize, view); }
180
+ } else if (obj.m.ProcessBinaryData) {
181
+ // Send as Binary
182
+ obj.m.ProcessBinaryData(new Uint8Array(e.data));
183
} else {
185
- // IE10, readAsBinaryString does not exist, use an alternative.
186
- var binary = '', bytes = new Uint8Array(e.data), length = bytes.byteLength;
187
- for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
188
- obj.xxOnSocketData(binary);
184
+ // Send as Text
185
+ obj.m.ProcessData(String.fromCharCode.apply(null, new Uint8Array(e.data)));
186
}
190
- } else {
191
- // If we get a string object, it maybe the WebRTC confirm. Ignore it.
192
- obj.xxOnSocketData(e.data);
193
- }
194
-
195
- // Request RTT mesure, don't use this if WebRTC is active
196
- if (obj.webRtcActive != true) {
197
- var ticks = new Date().getTime();
198
- if ((obj.latency.lastSend == null) || ((ticks - obj.latency.lastSend) > 5000)) { obj.latency.lastSend = ticks; obj.sendCtrlMsg('{"ctrlChannel":"102938","type":"rtt","time":' + ticks + '}'); }
187
}
188
};
189
202
- // Setup the file reader
203
- var fileReader = new FileReader();
204
- var fileReaderInuse = false, fileReaderAcc = [];
205
- if (fileReader.readAsBinaryString && (obj.m.ProcessBinaryData == null)) {
206
- // Chrome & Firefox (Draft)
207
- fileReader.onload = function (e) { obj.xxOnSocketData(e.target.result); if (fileReaderAcc.length == 0) { fileReaderInuse = false; } else { fileReader.readAsBinaryString(new Blob([fileReaderAcc.shift()])); } }
208
- } else if (fileReader.readAsArrayBuffer) {
209
- // Chrome & Firefox (Spec)
210
- fileReader.onloadend = function (e) { obj.xxOnSocketData(e.target.result); if (fileReaderAcc.length == 0) { fileReaderInuse = false; } else { fileReader.readAsArrayBuffer(fileReaderAcc.shift()); } }
211
- }
212
-
213
- obj.xxOnSocketData = function (data) {
214
- if (!data || obj.connectstate == -1) return;
215
- if (typeof data === 'object') {
216
- if (obj.m.ProcessBinaryData) { return obj.m.ProcessBinaryData(data); }
217
- // This is an ArrayBuffer, convert it to a string array (used in IE)
218
- var binary = '', bytes = new Uint8Array(data), length = bytes.byteLength;
219
- for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
220
- data = binary;
221
- }
222
- else if (typeof data !== 'string') return;
223
- //console.log('xxOnSocketData', rstr2hex(data));
224
- if ((typeof args != 'undefined') && args.redirtrace) { console.log('RedirRecv', typeof data, data.length, (data[0] == '{')?data:rstr2hex(data).substring(0, 64)); }
225
- return obj.m.ProcessData(data);
226
- }
227
-
190
obj.sendText = function (x) {
191
if (typeof x != 'string') { x = JSON.stringify(x); } // Turn into a string if needed
192
obj.send(encode_utf8(x)); // Encode UTF8 correctly
webserver.js
+7
-3
@@ -4357,7 +4357,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4357
// Authenticates a session and forwards
4358
function PerformWSSessionAuth(ws, req, noAuthOk, func) {
4359
// Check if this is a banned ip address
4360
- if (obj.checkAllowLogin(req) == false) { try { ws.send(JSON.stringify({ action: 'close', cause: 'banned', msg: 'banned-1' })); ws.close(); } catch (e) { } return; }
4360
+ if (obj.checkAllowLogin(req) == false) { parent.debug('web', 'WSERROR: Banned connection.'); try { ws.send(JSON.stringify({ action: 'close', cause: 'banned', msg: 'banned-1' })); ws.close(); } catch (e) { } return; }
4361
try {
4362
// Hold this websocket until we are ready.
4363
ws._socket.pause();
@@ -4366,11 +4366,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4366
var domain = null;
4367
if (noAuthOk == true) {
4368
domain = getDomain(req);
4369
- if (domain == null) { try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-1' })); ws.close(); return; } catch (e) { } return; }
4369
+ if (domain == null) { parent.debug('web', 'WSERROR: Got no domain, no auth ok.'); try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-1' })); ws.close(); return; } catch (e) { } return; }
4370
} else {
4371
// If authentication is required, enforce IP address filtering.
4372
domain = checkUserIpAddress(ws, req);
4373
- if (domain == null) { return; }
4373
+ if (domain == null) { parent.debug('web', 'WSERROR: Got no domain, user auth required.'); return; }
4374
}
4375
4376
var emailcheck = ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true) && (domain.auth != 'sspi') && (domain.auth != 'ldap'))
@@ -4405,17 +4405,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4405
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', sms2fa: sms2fa, sms2fasent: true })); ws.close(); } catch (e) { }
4406
} else {
4407
// Ask for a login token
4408
+ parent.debug('web', 'Asking for login token');
4409
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa })); ws.close(); } catch (e) { }
4410
}
4411
} else {
4412
checkUserOneTimePassword(req, domain, user, req.query.token, null, function (result) {
4413
if (result == false) {
4414
// Failed, ask for a login token again
4415
+ parent.debug('web', 'Invalid login token, asking again');
4416
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa })); ws.close(); } catch (e) { }
4417
} else {
4418
// We are authenticated with 2nd factor.
4419
// Check email verification
4420
if (emailcheck && (user.email != null) && (user.emailVerified !== true)) {
4421
+ parent.debug('web', 'Invalid login, asking for email validation');
4422
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, email2fasent: true })); ws.close(); } catch (e) { }
4423
} else {
4424
func(ws, req, domain, user);
@@ -4426,6 +4429,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4429
} else {
4430
// Check email verification
4431
if (emailcheck && (user.email != null) && (user.emailVerified !== true)) {
4432
+ parent.debug('web', 'Invalid login, asking for email validation');
4433
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, email2fasent: true })); ws.close(); } catch (e) { }
4434
} else {
4435
// We are authenticated