mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-11-09 13:39:42 -05:00
@@ -21,12 +21,11 @@ import Keyboard from "./input/keyboard.js";
|
||||
import GestureHandler from "./input/gesturehandler.js";
|
||||
import Cursor from "./util/cursor.js";
|
||||
import Websock from "./websock.js";
|
||||
import DES from "./des.js";
|
||||
import KeyTable from "./input/keysym.js";
|
||||
import XtScancode from "./input/xtscancodes.js";
|
||||
import { encodings } from "./encodings.js";
|
||||
import RSAAESAuthenticationState from "./ra2.js";
|
||||
import { MD5 } from "./util/md5.js";
|
||||
import legacyCrypto from "./crypto/crypto.js";
|
||||
|
||||
import RawDecoder from "./decoders/raw.js";
|
||||
import CopyRectDecoder from "./decoders/copyrect.js";
|
||||
@@ -258,10 +257,11 @@ export default class RFB extends EventTargetMixin {
|
||||
Log.Error("Display exception: " + exc);
|
||||
throw exc;
|
||||
}
|
||||
this._display.onflush = this._onFlush.bind(this);
|
||||
|
||||
this._keyboard = new Keyboard(this._canvas);
|
||||
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
|
||||
this._remoteCapsLock = null; // Null indicates unknown or irrelevant
|
||||
this._remoteNumLock = null;
|
||||
|
||||
this._gestures = new GestureHandler();
|
||||
|
||||
@@ -960,7 +960,7 @@ export default class RFB extends EventTargetMixin {
|
||||
}
|
||||
|
||||
_handleMessage() {
|
||||
if (this._sock.rQlen === 0) {
|
||||
if (this._sock.rQwait("message", 1)) {
|
||||
Log.Warn("handleMessage called on an empty receive queue");
|
||||
return;
|
||||
}
|
||||
@@ -977,7 +977,7 @@ export default class RFB extends EventTargetMixin {
|
||||
if (!this._normalMsg()) {
|
||||
break;
|
||||
}
|
||||
if (this._sock.rQlen === 0) {
|
||||
if (this._sock.rQwait("message", 1)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -995,7 +995,35 @@ export default class RFB extends EventTargetMixin {
|
||||
}
|
||||
}
|
||||
|
||||
_handleKeyEvent(keysym, code, down) {
|
||||
_handleKeyEvent(keysym, code, down, numlock, capslock) {
|
||||
// If remote state of capslock is known, and it doesn't match the local led state of
|
||||
// the keyboard, we send a capslock keypress first to bring it into sync.
|
||||
// If we just pressed CapsLock, or we toggled it remotely due to it being out of sync
|
||||
// we clear the remote state so that we don't send duplicate or spurious fixes,
|
||||
// since it may take some time to receive the new remote CapsLock state.
|
||||
if (code == 'CapsLock' && down) {
|
||||
this._remoteCapsLock = null;
|
||||
}
|
||||
if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) {
|
||||
Log.Debug("Fixing remote caps lock");
|
||||
|
||||
this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
// We clear the remote capsLock state when we do this to prevent issues with doing this twice
|
||||
// before we receive an update of the the remote state.
|
||||
this._remoteCapsLock = null;
|
||||
}
|
||||
|
||||
// Logic for numlock is exactly the same.
|
||||
if (code == 'NumLock' && down) {
|
||||
this._remoteNumLock = null;
|
||||
}
|
||||
if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) {
|
||||
Log.Debug("Fixing remote num lock");
|
||||
this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true);
|
||||
this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false);
|
||||
this._remoteNumLock = null;
|
||||
}
|
||||
this.sendKey(keysym, code, down);
|
||||
}
|
||||
|
||||
@@ -1383,7 +1411,8 @@ export default class RFB extends EventTargetMixin {
|
||||
while (repeaterID.length < 250) {
|
||||
repeaterID += "\0";
|
||||
}
|
||||
this._sock.sendString(repeaterID);
|
||||
this._sock.sQpushString(repeaterID);
|
||||
this._sock.flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1393,7 +1422,8 @@ export default class RFB extends EventTargetMixin {
|
||||
|
||||
const cversion = "00" + parseInt(this._rfbVersion, 10) +
|
||||
".00" + ((this._rfbVersion * 10) % 10);
|
||||
this._sock.sendString("RFB " + cversion + "\n");
|
||||
this._sock.sQpushString("RFB " + cversion + "\n");
|
||||
this._sock.flush();
|
||||
Log.Debug('Sent ProtocolVersion: ' + cversion);
|
||||
|
||||
this._rfbInitState = 'Security';
|
||||
@@ -1445,7 +1475,8 @@ export default class RFB extends EventTargetMixin {
|
||||
return this._fail("Unsupported security types (types: " + types + ")");
|
||||
}
|
||||
|
||||
this._sock.send([this._rfbAuthScheme]);
|
||||
this._sock.sQpush8(this._rfbAuthScheme);
|
||||
this._sock.flush();
|
||||
} else {
|
||||
// Server decides
|
||||
if (this._sock.rQwait("security scheme", 4)) { return false; }
|
||||
@@ -1507,12 +1538,15 @@ export default class RFB extends EventTargetMixin {
|
||||
return false;
|
||||
}
|
||||
|
||||
const xvpAuthStr = String.fromCharCode(this._rfbCredentials.username.length) +
|
||||
String.fromCharCode(this._rfbCredentials.target.length) +
|
||||
this._rfbCredentials.username +
|
||||
this._rfbCredentials.target;
|
||||
this._sock.sendString(xvpAuthStr);
|
||||
this._sock.sQpush8(this._rfbCredentials.username.length);
|
||||
this._sock.sQpush8(this._rfbCredentials.target.length);
|
||||
this._sock.sQpushString(this._rfbCredentials.username);
|
||||
this._sock.sQpushString(this._rfbCredentials.target);
|
||||
|
||||
this._sock.flush();
|
||||
|
||||
this._rfbAuthScheme = securityTypeVNCAuth;
|
||||
|
||||
return this._negotiateAuthentication();
|
||||
}
|
||||
|
||||
@@ -1530,7 +1564,9 @@ export default class RFB extends EventTargetMixin {
|
||||
return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
|
||||
}
|
||||
|
||||
this._sock.send([0, 2]);
|
||||
this._sock.sQpush8(0);
|
||||
this._sock.sQpush8(2);
|
||||
this._sock.flush();
|
||||
this._rfbVeNCryptState = 1;
|
||||
}
|
||||
|
||||
@@ -1589,12 +1625,10 @@ export default class RFB extends EventTargetMixin {
|
||||
return this._fail("Unsupported security types (types: " + subtypes + ")");
|
||||
}
|
||||
|
||||
this._sock.send([this._rfbAuthScheme >> 24,
|
||||
this._rfbAuthScheme >> 16,
|
||||
this._rfbAuthScheme >> 8,
|
||||
this._rfbAuthScheme]);
|
||||
this._sock.sQpush32(this._rfbAuthScheme);
|
||||
this._sock.flush();
|
||||
|
||||
this._rfbVeNCryptState == 4;
|
||||
this._rfbVeNCryptState = 4;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1611,20 +1645,11 @@ export default class RFB extends EventTargetMixin {
|
||||
const user = encodeUTF8(this._rfbCredentials.username);
|
||||
const pass = encodeUTF8(this._rfbCredentials.password);
|
||||
|
||||
this._sock.send([
|
||||
(user.length >> 24) & 0xFF,
|
||||
(user.length >> 16) & 0xFF,
|
||||
(user.length >> 8) & 0xFF,
|
||||
user.length & 0xFF
|
||||
]);
|
||||
this._sock.send([
|
||||
(pass.length >> 24) & 0xFF,
|
||||
(pass.length >> 16) & 0xFF,
|
||||
(pass.length >> 8) & 0xFF,
|
||||
pass.length & 0xFF
|
||||
]);
|
||||
this._sock.sendString(user);
|
||||
this._sock.sendString(pass);
|
||||
this._sock.sQpush32(user.length);
|
||||
this._sock.sQpush32(pass.length);
|
||||
this._sock.sQpushString(user);
|
||||
this._sock.sQpushString(pass);
|
||||
this._sock.flush();
|
||||
|
||||
this._rfbInitState = "SecurityResult";
|
||||
return true;
|
||||
@@ -1643,7 +1668,8 @@ export default class RFB extends EventTargetMixin {
|
||||
// TODO(directxman12): make genDES not require an Array
|
||||
const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
|
||||
const response = RFB.genDES(this._rfbCredentials.password, challenge);
|
||||
this._sock.send(response);
|
||||
this._sock.sQpushBytes(response);
|
||||
this._sock.flush();
|
||||
this._rfbInitState = "SecurityResult";
|
||||
return true;
|
||||
}
|
||||
@@ -1661,8 +1687,9 @@ export default class RFB extends EventTargetMixin {
|
||||
if (this._rfbCredentials.ardPublicKey != undefined &&
|
||||
this._rfbCredentials.ardCredentials != undefined) {
|
||||
// if the async web crypto is done return the results
|
||||
this._sock.send(this._rfbCredentials.ardCredentials);
|
||||
this._sock.send(this._rfbCredentials.ardPublicKey);
|
||||
this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);
|
||||
this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);
|
||||
this._sock.flush();
|
||||
this._rfbCredentials.ardCredentials = null;
|
||||
this._rfbCredentials.ardPublicKey = null;
|
||||
this._rfbInitState = "SecurityResult";
|
||||
@@ -1681,77 +1708,35 @@ export default class RFB extends EventTargetMixin {
|
||||
let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
|
||||
let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
|
||||
|
||||
let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength));
|
||||
let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join('');
|
||||
|
||||
this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
|
||||
let clientKey = legacyCrypto.generateKey(
|
||||
{ name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
|
||||
this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_modPow(base, exponent, modulus) {
|
||||
async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {
|
||||
const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);
|
||||
const sharedKey = legacyCrypto.deriveBits(
|
||||
{ name: "DH", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);
|
||||
|
||||
let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
||||
let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
||||
let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
||||
const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
|
||||
const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
|
||||
|
||||
let b = BigInt(baseHex);
|
||||
let e = BigInt(exponentHex);
|
||||
let m = BigInt(modulusHex);
|
||||
let r = 1n;
|
||||
b = b % m;
|
||||
while (e > 0) {
|
||||
if (e % 2n === 1n) {
|
||||
r = (r * b) % m;
|
||||
}
|
||||
e = e / 2n;
|
||||
b = (b * b) % m;
|
||||
const credentials = window.crypto.getRandomValues(new Uint8Array(128));
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
credentials[i] = username.charCodeAt(i);
|
||||
}
|
||||
let hexResult = r.toString(16);
|
||||
|
||||
while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
|
||||
hexResult = "0"+hexResult;
|
||||
credentials[username.length] = 0;
|
||||
for (let i = 0; i < password.length; i++) {
|
||||
credentials[64 + i] = password.charCodeAt(i);
|
||||
}
|
||||
credentials[64 + password.length] = 0;
|
||||
|
||||
let bytesResult = [];
|
||||
for (let c = 0; c < hexResult.length; c += 2) {
|
||||
bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
|
||||
}
|
||||
return bytesResult;
|
||||
}
|
||||
|
||||
async _aesEcbEncrypt(string, key) {
|
||||
// perform AES-ECB blocks
|
||||
let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
|
||||
let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
|
||||
let data = new Uint8Array(string.length);
|
||||
for (let i = 0; i < string.length; ++i) {
|
||||
data[i] = string.charCodeAt(i);
|
||||
}
|
||||
let encrypted = new Uint8Array(data.length);
|
||||
for (let i=0;i<data.length;i+=16) {
|
||||
let block = data.slice(i, i+16);
|
||||
let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
|
||||
aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
);
|
||||
encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
|
||||
}
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
|
||||
// calculate the DH keys
|
||||
let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
|
||||
let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
|
||||
|
||||
let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
|
||||
let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
|
||||
|
||||
let paddedUsername = username + '\0' + padding.substring(0, 63);
|
||||
let paddedPassword = password + '\0' + padding.substring(0, 63);
|
||||
let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
|
||||
|
||||
let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
|
||||
const key = await legacyCrypto.digest("MD5", sharedKey);
|
||||
const cipher = await legacyCrypto.importKey(
|
||||
"raw", key, { name: "AES-ECB" }, false, ["encrypt"]);
|
||||
const encrypted = await legacyCrypto.encrypt({ name: "AES-ECB" }, cipher, credentials);
|
||||
|
||||
this._rfbCredentials.ardCredentials = encrypted;
|
||||
this._rfbCredentials.ardPublicKey = clientPublicKey;
|
||||
@@ -1768,10 +1753,12 @@ export default class RFB extends EventTargetMixin {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._sock.send([0, 0, 0, this._rfbCredentials.username.length]);
|
||||
this._sock.send([0, 0, 0, this._rfbCredentials.password.length]);
|
||||
this._sock.sendString(this._rfbCredentials.username);
|
||||
this._sock.sendString(this._rfbCredentials.password);
|
||||
this._sock.sQpush32(this._rfbCredentials.username.length);
|
||||
this._sock.sQpush32(this._rfbCredentials.password.length);
|
||||
this._sock.sQpushString(this._rfbCredentials.username);
|
||||
this._sock.sQpushString(this._rfbCredentials.password);
|
||||
this._sock.flush();
|
||||
|
||||
this._rfbInitState = "SecurityResult";
|
||||
return true;
|
||||
}
|
||||
@@ -1809,7 +1796,8 @@ export default class RFB extends EventTargetMixin {
|
||||
"vendor or signature");
|
||||
}
|
||||
Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
|
||||
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
|
||||
this._sock.sQpush32(0); // use NOTUNNEL
|
||||
this._sock.flush();
|
||||
return false; // wait until we receive the sub auth count to continue
|
||||
} else {
|
||||
return this._fail("Server wanted tunnels, but doesn't support " +
|
||||
@@ -1859,7 +1847,8 @@ export default class RFB extends EventTargetMixin {
|
||||
|
||||
for (let authType in clientSupportedTypes) {
|
||||
if (serverSupportedTypes.indexOf(authType) != -1) {
|
||||
this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
|
||||
this._sock.sQpush32(clientSupportedTypes[authType]);
|
||||
this._sock.flush();
|
||||
Log.Debug("Selected authentication type: " + authType);
|
||||
|
||||
switch (authType) {
|
||||
@@ -1905,8 +1894,8 @@ export default class RFB extends EventTargetMixin {
|
||||
if (e.message !== "disconnect normally") {
|
||||
this._fail(e.message);
|
||||
}
|
||||
}).then(() => {
|
||||
this.dispatchEvent(new CustomEvent('securityresult'));
|
||||
})
|
||||
.then(() => {
|
||||
this._rfbInitState = "SecurityResult";
|
||||
return true;
|
||||
}).finally(() => {
|
||||
@@ -1934,15 +1923,15 @@ export default class RFB extends EventTargetMixin {
|
||||
const g = this._sock.rQshiftBytes(8);
|
||||
const p = this._sock.rQshiftBytes(8);
|
||||
const A = this._sock.rQshiftBytes(8);
|
||||
const b = window.crypto.getRandomValues(new Uint8Array(8));
|
||||
const B = new Uint8Array(this._modPow(g, b, p));
|
||||
const secret = new Uint8Array(this._modPow(A, b, p));
|
||||
const dhKey = legacyCrypto.generateKey({ name: "DH", g: g, p: p }, true, ["deriveBits"]);
|
||||
const B = legacyCrypto.exportKey("raw", dhKey.publicKey);
|
||||
const secret = legacyCrypto.deriveBits({ name: "DH", public: A }, dhKey.privateKey, 64);
|
||||
|
||||
const des = new DES(secret);
|
||||
const key = legacyCrypto.importKey("raw", secret, { name: "DES-CBC" }, false, ["encrypt"]);
|
||||
const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
|
||||
const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
|
||||
const usernameBytes = new Uint8Array(256);
|
||||
const passwordBytes = new Uint8Array(64);
|
||||
let usernameBytes = new Uint8Array(256);
|
||||
let passwordBytes = new Uint8Array(64);
|
||||
window.crypto.getRandomValues(usernameBytes);
|
||||
window.crypto.getRandomValues(passwordBytes);
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
@@ -1953,25 +1942,12 @@ export default class RFB extends EventTargetMixin {
|
||||
passwordBytes[i] = password.charCodeAt(i);
|
||||
}
|
||||
passwordBytes[password.length] = 0;
|
||||
let x = new Uint8Array(secret);
|
||||
for (let i = 0; i < 32; i++) {
|
||||
for (let j = 0; j < 8; j++) {
|
||||
x[j] ^= usernameBytes[i * 8 + j];
|
||||
}
|
||||
x = des.enc8(x);
|
||||
usernameBytes.set(x, i * 8);
|
||||
}
|
||||
x = new Uint8Array(secret);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
for (let j = 0; j < 8; j++) {
|
||||
x[j] ^= passwordBytes[i * 8 + j];
|
||||
}
|
||||
x = des.enc8(x);
|
||||
passwordBytes.set(x, i * 8);
|
||||
}
|
||||
this._sock.send(B);
|
||||
this._sock.send(usernameBytes);
|
||||
this._sock.send(passwordBytes);
|
||||
usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);
|
||||
passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);
|
||||
this._sock.sQpushBytes(B);
|
||||
this._sock.sQpushBytes(usernameBytes);
|
||||
this._sock.sQpushBytes(passwordBytes);
|
||||
this._sock.flush();
|
||||
this._rfbInitState = "SecurityResult";
|
||||
return true;
|
||||
}
|
||||
@@ -1979,7 +1955,11 @@ export default class RFB extends EventTargetMixin {
|
||||
_negotiateAuthentication() {
|
||||
switch (this._rfbAuthScheme) {
|
||||
case securityTypeNone:
|
||||
this._rfbInitState = 'SecurityResult';
|
||||
if (this._rfbVersion >= 3.8) {
|
||||
this._rfbInitState = 'SecurityResult';
|
||||
} else {
|
||||
this._rfbInitState = 'ClientInitialisation';
|
||||
}
|
||||
return true;
|
||||
|
||||
case securityTypeXVP:
|
||||
@@ -2016,13 +1996,6 @@ export default class RFB extends EventTargetMixin {
|
||||
}
|
||||
|
||||
_handleSecurityResult() {
|
||||
// There is no security choice, and hence no security result
|
||||
// until RFB 3.7
|
||||
if (this._rfbVersion < 3.7) {
|
||||
this._rfbInitState = 'ClientInitialisation';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
|
||||
|
||||
const status = this._sock.rQshift32();
|
||||
@@ -2158,6 +2131,7 @@ export default class RFB extends EventTargetMixin {
|
||||
encs.push(encodings.pseudoEncodingDesktopSize);
|
||||
encs.push(encodings.pseudoEncodingLastRect);
|
||||
encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
|
||||
encs.push(encodings.pseudoEncodingQEMULedEvent);
|
||||
encs.push(encodings.pseudoEncodingExtendedDesktopSize);
|
||||
encs.push(encodings.pseudoEncodingXvp);
|
||||
encs.push(encodings.pseudoEncodingFence);
|
||||
@@ -2199,7 +2173,8 @@ export default class RFB extends EventTargetMixin {
|
||||
return this._handleSecurityReason();
|
||||
|
||||
case 'ClientInitialisation':
|
||||
this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
|
||||
this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation
|
||||
this._sock.flush();
|
||||
this._rfbInitState = 'ServerInitialisation';
|
||||
return true;
|
||||
|
||||
@@ -2381,7 +2356,7 @@ export default class RFB extends EventTargetMixin {
|
||||
textData = textData.slice(0, -1);
|
||||
}
|
||||
|
||||
textData = textData.replace("\r\n", "\n");
|
||||
textData = textData.replaceAll("\r\n", "\n");
|
||||
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"clipboard",
|
||||
@@ -2512,19 +2487,11 @@ export default class RFB extends EventTargetMixin {
|
||||
|
||||
default:
|
||||
this._fail("Unexpected server message (type " + msgType + ")");
|
||||
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
|
||||
Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_onFlush() {
|
||||
this._flushing = false;
|
||||
// Resume processing
|
||||
if (this._sock.rQlen > 0) {
|
||||
this._handleMessage();
|
||||
}
|
||||
}
|
||||
|
||||
_framebufferUpdate() {
|
||||
if (this._FBU.rects === 0) {
|
||||
if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
|
||||
@@ -2535,7 +2502,14 @@ export default class RFB extends EventTargetMixin {
|
||||
// to avoid building up an excessive queue
|
||||
if (this._display.pending()) {
|
||||
this._flushing = true;
|
||||
this._display.flush();
|
||||
this._display.flush()
|
||||
.then(() => {
|
||||
this._flushing = false;
|
||||
// Resume processing
|
||||
if (!this._sock.rQwait("message", 1)) {
|
||||
this._handleMessage();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2545,13 +2519,13 @@ export default class RFB extends EventTargetMixin {
|
||||
if (this._sock.rQwait("rect header", 12)) { return false; }
|
||||
/* New FramebufferUpdate */
|
||||
|
||||
const hdr = this._sock.rQshiftBytes(12);
|
||||
this._FBU.x = (hdr[0] << 8) + hdr[1];
|
||||
this._FBU.y = (hdr[2] << 8) + hdr[3];
|
||||
this._FBU.width = (hdr[4] << 8) + hdr[5];
|
||||
this._FBU.height = (hdr[6] << 8) + hdr[7];
|
||||
this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
|
||||
(hdr[10] << 8) + hdr[11], 10);
|
||||
this._FBU.x = this._sock.rQshift16();
|
||||
this._FBU.y = this._sock.rQshift16();
|
||||
this._FBU.width = this._sock.rQshift16();
|
||||
this._FBU.height = this._sock.rQshift16();
|
||||
this._FBU.encoding = this._sock.rQshift32();
|
||||
/* Encodings are signed */
|
||||
this._FBU.encoding >>= 0;
|
||||
}
|
||||
|
||||
if (!this._handleRect()) {
|
||||
@@ -2593,6 +2567,9 @@ export default class RFB extends EventTargetMixin {
|
||||
case encodings.pseudoEncodingExtendedDesktopSize:
|
||||
return this._handleExtendedDesktopSize();
|
||||
|
||||
case encodings.pseudoEncodingQEMULedEvent:
|
||||
return this._handleLedEvent();
|
||||
|
||||
default:
|
||||
return this._handleDataRect();
|
||||
}
|
||||
@@ -2770,6 +2747,21 @@ export default class RFB extends EventTargetMixin {
|
||||
return true;
|
||||
}
|
||||
|
||||
_handleLedEvent() {
|
||||
if (this._sock.rQwait("LED Status", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let data = this._sock.rQshift8();
|
||||
// ScrollLock state can be retrieved with data & 1. This is currently not needed.
|
||||
let numLock = data & 2 ? true : false;
|
||||
let capsLock = data & 4 ? true : false;
|
||||
this._remoteCapsLock = capsLock;
|
||||
this._remoteNumLock = numLock;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_handleExtendedDesktopSize() {
|
||||
if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
|
||||
return false;
|
||||
@@ -2785,26 +2777,18 @@ export default class RFB extends EventTargetMixin {
|
||||
const firstUpdate = !this._supportsSetDesktopSize;
|
||||
this._supportsSetDesktopSize = true;
|
||||
|
||||
// Normally we only apply the current resize mode after a
|
||||
// window resize event. However there is no such trigger on the
|
||||
// initial connect. And we don't know if the server supports
|
||||
// resizing until we've gotten here.
|
||||
if (firstUpdate) {
|
||||
this._requestRemoteResize();
|
||||
}
|
||||
|
||||
this._sock.rQskipBytes(1); // number-of-screens
|
||||
this._sock.rQskipBytes(3); // padding
|
||||
|
||||
for (let i = 0; i < numberOfScreens; i += 1) {
|
||||
// Save the id and flags of the first screen
|
||||
if (i === 0) {
|
||||
this._screenID = this._sock.rQshiftBytes(4); // id
|
||||
this._sock.rQskipBytes(2); // x-position
|
||||
this._sock.rQskipBytes(2); // y-position
|
||||
this._sock.rQskipBytes(2); // width
|
||||
this._sock.rQskipBytes(2); // height
|
||||
this._screenFlags = this._sock.rQshiftBytes(4); // flags
|
||||
this._screenID = this._sock.rQshift32(); // id
|
||||
this._sock.rQskipBytes(2); // x-position
|
||||
this._sock.rQskipBytes(2); // y-position
|
||||
this._sock.rQskipBytes(2); // width
|
||||
this._sock.rQskipBytes(2); // height
|
||||
this._screenFlags = this._sock.rQshift32(); // flags
|
||||
} else {
|
||||
this._sock.rQskipBytes(16);
|
||||
}
|
||||
@@ -2842,6 +2826,14 @@ export default class RFB extends EventTargetMixin {
|
||||
this._resize(this._FBU.width, this._FBU.height);
|
||||
}
|
||||
|
||||
// Normally we only apply the current resize mode after a
|
||||
// window resize event. However there is no such trigger on the
|
||||
// initial connect. And we don't know if the server supports
|
||||
// resizing until we've gotten here.
|
||||
if (firstUpdate) {
|
||||
this._requestRemoteResize();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2937,28 +2929,22 @@ export default class RFB extends EventTargetMixin {
|
||||
|
||||
static genDES(password, challenge) {
|
||||
const passwordChars = password.split('').map(c => c.charCodeAt(0));
|
||||
return (new DES(passwordChars)).encrypt(challenge);
|
||||
const key = legacyCrypto.importKey(
|
||||
"raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]);
|
||||
return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge);
|
||||
}
|
||||
}
|
||||
|
||||
// Class Methods
|
||||
RFB.messages = {
|
||||
keyEvent(sock, keysym, down) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(4); // msg-type
|
||||
sock.sQpush8(down);
|
||||
|
||||
buff[offset] = 4; // msg-type
|
||||
buff[offset + 1] = down;
|
||||
sock.sQpush16(0);
|
||||
|
||||
buff[offset + 2] = 0;
|
||||
buff[offset + 3] = 0;
|
||||
sock.sQpush32(keysym);
|
||||
|
||||
buff[offset + 4] = (keysym >> 24);
|
||||
buff[offset + 5] = (keysym >> 16);
|
||||
buff[offset + 6] = (keysym >> 8);
|
||||
buff[offset + 7] = keysym;
|
||||
|
||||
sock._sQlen += 8;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
@@ -2972,46 +2958,28 @@ RFB.messages = {
|
||||
return xtScanCode;
|
||||
}
|
||||
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(255); // msg-type
|
||||
sock.sQpush8(0); // sub msg-type
|
||||
|
||||
buff[offset] = 255; // msg-type
|
||||
buff[offset + 1] = 0; // sub msg-type
|
||||
sock.sQpush16(down);
|
||||
|
||||
buff[offset + 2] = (down >> 8);
|
||||
buff[offset + 3] = down;
|
||||
|
||||
buff[offset + 4] = (keysym >> 24);
|
||||
buff[offset + 5] = (keysym >> 16);
|
||||
buff[offset + 6] = (keysym >> 8);
|
||||
buff[offset + 7] = keysym;
|
||||
sock.sQpush32(keysym);
|
||||
|
||||
const RFBkeycode = getRFBkeycode(keycode);
|
||||
|
||||
buff[offset + 8] = (RFBkeycode >> 24);
|
||||
buff[offset + 9] = (RFBkeycode >> 16);
|
||||
buff[offset + 10] = (RFBkeycode >> 8);
|
||||
buff[offset + 11] = RFBkeycode;
|
||||
sock.sQpush32(RFBkeycode);
|
||||
|
||||
sock._sQlen += 12;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
pointerEvent(sock, x, y, mask) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(5); // msg-type
|
||||
|
||||
buff[offset] = 5; // msg-type
|
||||
sock.sQpush8(mask);
|
||||
|
||||
buff[offset + 1] = mask;
|
||||
sock.sQpush16(x);
|
||||
sock.sQpush16(y);
|
||||
|
||||
buff[offset + 2] = x >> 8;
|
||||
buff[offset + 3] = x;
|
||||
|
||||
buff[offset + 4] = y >> 8;
|
||||
buff[offset + 5] = y;
|
||||
|
||||
sock._sQlen += 6;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
@@ -3111,14 +3079,11 @@ RFB.messages = {
|
||||
},
|
||||
|
||||
clientCutText(sock, data, extended = false) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(6); // msg-type
|
||||
|
||||
buff[offset] = 6; // msg-type
|
||||
|
||||
buff[offset + 1] = 0; // padding
|
||||
buff[offset + 2] = 0; // padding
|
||||
buff[offset + 3] = 0; // padding
|
||||
sock.sQpush8(0); // padding
|
||||
sock.sQpush8(0); // padding
|
||||
sock.sQpush8(0); // padding
|
||||
|
||||
let length;
|
||||
if (extended) {
|
||||
@@ -3127,121 +3092,63 @@ RFB.messages = {
|
||||
length = data.length;
|
||||
}
|
||||
|
||||
buff[offset + 4] = length >> 24;
|
||||
buff[offset + 5] = length >> 16;
|
||||
buff[offset + 6] = length >> 8;
|
||||
buff[offset + 7] = length;
|
||||
|
||||
sock._sQlen += 8;
|
||||
|
||||
// We have to keep track of from where in the data we begin creating the
|
||||
// buffer for the flush in the next iteration.
|
||||
let dataOffset = 0;
|
||||
|
||||
let remaining = data.length;
|
||||
while (remaining > 0) {
|
||||
|
||||
let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
|
||||
for (let i = 0; i < flushSize; i++) {
|
||||
buff[sock._sQlen + i] = data[dataOffset + i];
|
||||
}
|
||||
|
||||
sock._sQlen += flushSize;
|
||||
sock.flush();
|
||||
|
||||
remaining -= flushSize;
|
||||
dataOffset += flushSize;
|
||||
}
|
||||
|
||||
sock.sQpush32(length);
|
||||
sock.sQpushBytes(data);
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
setDesktopSize(sock, width, height, id, flags) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(251); // msg-type
|
||||
|
||||
buff[offset] = 251; // msg-type
|
||||
buff[offset + 1] = 0; // padding
|
||||
buff[offset + 2] = width >> 8; // width
|
||||
buff[offset + 3] = width;
|
||||
buff[offset + 4] = height >> 8; // height
|
||||
buff[offset + 5] = height;
|
||||
sock.sQpush8(0); // padding
|
||||
|
||||
buff[offset + 6] = 1; // number-of-screens
|
||||
buff[offset + 7] = 0; // padding
|
||||
sock.sQpush16(width);
|
||||
sock.sQpush16(height);
|
||||
|
||||
sock.sQpush8(1); // number-of-screens
|
||||
|
||||
sock.sQpush8(0); // padding
|
||||
|
||||
// screen array
|
||||
buff[offset + 8] = id >> 24; // id
|
||||
buff[offset + 9] = id >> 16;
|
||||
buff[offset + 10] = id >> 8;
|
||||
buff[offset + 11] = id;
|
||||
buff[offset + 12] = 0; // x-position
|
||||
buff[offset + 13] = 0;
|
||||
buff[offset + 14] = 0; // y-position
|
||||
buff[offset + 15] = 0;
|
||||
buff[offset + 16] = width >> 8; // width
|
||||
buff[offset + 17] = width;
|
||||
buff[offset + 18] = height >> 8; // height
|
||||
buff[offset + 19] = height;
|
||||
buff[offset + 20] = flags >> 24; // flags
|
||||
buff[offset + 21] = flags >> 16;
|
||||
buff[offset + 22] = flags >> 8;
|
||||
buff[offset + 23] = flags;
|
||||
sock.sQpush32(id);
|
||||
sock.sQpush16(0); // x-position
|
||||
sock.sQpush16(0); // y-position
|
||||
sock.sQpush16(width);
|
||||
sock.sQpush16(height);
|
||||
sock.sQpush32(flags);
|
||||
|
||||
sock._sQlen += 24;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
clientFence(sock, flags, payload) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(248); // msg-type
|
||||
|
||||
buff[offset] = 248; // msg-type
|
||||
sock.sQpush8(0); // padding
|
||||
sock.sQpush8(0); // padding
|
||||
sock.sQpush8(0); // padding
|
||||
|
||||
buff[offset + 1] = 0; // padding
|
||||
buff[offset + 2] = 0; // padding
|
||||
buff[offset + 3] = 0; // padding
|
||||
sock.sQpush32(flags);
|
||||
|
||||
buff[offset + 4] = flags >> 24; // flags
|
||||
buff[offset + 5] = flags >> 16;
|
||||
buff[offset + 6] = flags >> 8;
|
||||
buff[offset + 7] = flags;
|
||||
sock.sQpush8(payload.length);
|
||||
sock.sQpushString(payload);
|
||||
|
||||
const n = payload.length;
|
||||
|
||||
buff[offset + 8] = n; // length
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
buff[offset + 9 + i] = payload.charCodeAt(i);
|
||||
}
|
||||
|
||||
sock._sQlen += 9 + n;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
enableContinuousUpdates(sock, enable, x, y, width, height) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(150); // msg-type
|
||||
|
||||
buff[offset] = 150; // msg-type
|
||||
buff[offset + 1] = enable; // enable-flag
|
||||
sock.sQpush8(enable);
|
||||
|
||||
buff[offset + 2] = x >> 8; // x
|
||||
buff[offset + 3] = x;
|
||||
buff[offset + 4] = y >> 8; // y
|
||||
buff[offset + 5] = y;
|
||||
buff[offset + 6] = width >> 8; // width
|
||||
buff[offset + 7] = width;
|
||||
buff[offset + 8] = height >> 8; // height
|
||||
buff[offset + 9] = height;
|
||||
sock.sQpush16(x);
|
||||
sock.sQpush16(y);
|
||||
sock.sQpush16(width);
|
||||
sock.sQpush16(height);
|
||||
|
||||
sock._sQlen += 10;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
pixelFormat(sock, depth, trueColor) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
|
||||
let bpp;
|
||||
|
||||
if (depth > 16) {
|
||||
@@ -3254,100 +3161,69 @@ RFB.messages = {
|
||||
|
||||
const bits = Math.floor(depth/3);
|
||||
|
||||
buff[offset] = 0; // msg-type
|
||||
sock.sQpush8(0); // msg-type
|
||||
|
||||
buff[offset + 1] = 0; // padding
|
||||
buff[offset + 2] = 0; // padding
|
||||
buff[offset + 3] = 0; // padding
|
||||
sock.sQpush8(0); // padding
|
||||
sock.sQpush8(0); // padding
|
||||
sock.sQpush8(0); // padding
|
||||
|
||||
buff[offset + 4] = bpp; // bits-per-pixel
|
||||
buff[offset + 5] = depth; // depth
|
||||
buff[offset + 6] = 0; // little-endian
|
||||
buff[offset + 7] = trueColor ? 1 : 0; // true-color
|
||||
sock.sQpush8(bpp);
|
||||
sock.sQpush8(depth);
|
||||
sock.sQpush8(0); // little-endian
|
||||
sock.sQpush8(trueColor ? 1 : 0);
|
||||
|
||||
buff[offset + 8] = 0; // red-max
|
||||
buff[offset + 9] = (1 << bits) - 1; // red-max
|
||||
sock.sQpush16((1 << bits) - 1); // red-max
|
||||
sock.sQpush16((1 << bits) - 1); // green-max
|
||||
sock.sQpush16((1 << bits) - 1); // blue-max
|
||||
|
||||
buff[offset + 10] = 0; // green-max
|
||||
buff[offset + 11] = (1 << bits) - 1; // green-max
|
||||
sock.sQpush8(bits * 0); // red-shift
|
||||
sock.sQpush8(bits * 1); // green-shift
|
||||
sock.sQpush8(bits * 2); // blue-shift
|
||||
|
||||
buff[offset + 12] = 0; // blue-max
|
||||
buff[offset + 13] = (1 << bits) - 1; // blue-max
|
||||
sock.sQpush8(0); // padding
|
||||
sock.sQpush8(0); // padding
|
||||
sock.sQpush8(0); // padding
|
||||
|
||||
buff[offset + 14] = bits * 0; // red-shift
|
||||
buff[offset + 15] = bits * 1; // green-shift
|
||||
buff[offset + 16] = bits * 2; // blue-shift
|
||||
|
||||
buff[offset + 17] = 0; // padding
|
||||
buff[offset + 18] = 0; // padding
|
||||
buff[offset + 19] = 0; // padding
|
||||
|
||||
sock._sQlen += 20;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
clientEncodings(sock, encodings) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(2); // msg-type
|
||||
|
||||
buff[offset] = 2; // msg-type
|
||||
buff[offset + 1] = 0; // padding
|
||||
sock.sQpush8(0); // padding
|
||||
|
||||
buff[offset + 2] = encodings.length >> 8;
|
||||
buff[offset + 3] = encodings.length;
|
||||
|
||||
let j = offset + 4;
|
||||
sock.sQpush16(encodings.length);
|
||||
for (let i = 0; i < encodings.length; i++) {
|
||||
const enc = encodings[i];
|
||||
buff[j] = enc >> 24;
|
||||
buff[j + 1] = enc >> 16;
|
||||
buff[j + 2] = enc >> 8;
|
||||
buff[j + 3] = enc;
|
||||
|
||||
j += 4;
|
||||
sock.sQpush32(encodings[i]);
|
||||
}
|
||||
|
||||
sock._sQlen += j - offset;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
fbUpdateRequest(sock, incremental, x, y, w, h) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
|
||||
if (typeof(x) === "undefined") { x = 0; }
|
||||
if (typeof(y) === "undefined") { y = 0; }
|
||||
|
||||
buff[offset] = 3; // msg-type
|
||||
buff[offset + 1] = incremental ? 1 : 0;
|
||||
sock.sQpush8(3); // msg-type
|
||||
|
||||
buff[offset + 2] = (x >> 8) & 0xFF;
|
||||
buff[offset + 3] = x & 0xFF;
|
||||
sock.sQpush8(incremental ? 1 : 0);
|
||||
|
||||
buff[offset + 4] = (y >> 8) & 0xFF;
|
||||
buff[offset + 5] = y & 0xFF;
|
||||
sock.sQpush16(x);
|
||||
sock.sQpush16(y);
|
||||
sock.sQpush16(w);
|
||||
sock.sQpush16(h);
|
||||
|
||||
buff[offset + 6] = (w >> 8) & 0xFF;
|
||||
buff[offset + 7] = w & 0xFF;
|
||||
|
||||
buff[offset + 8] = (h >> 8) & 0xFF;
|
||||
buff[offset + 9] = h & 0xFF;
|
||||
|
||||
sock._sQlen += 10;
|
||||
sock.flush();
|
||||
},
|
||||
|
||||
xvpOp(sock, ver, op) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
sock.sQpush8(250); // msg-type
|
||||
|
||||
buff[offset] = 250; // msg-type
|
||||
buff[offset + 1] = 0; // padding
|
||||
sock.sQpush8(0); // padding
|
||||
|
||||
buff[offset + 2] = ver;
|
||||
buff[offset + 3] = op;
|
||||
sock.sQpush8(ver);
|
||||
sock.sQpush8(op);
|
||||
|
||||
sock._sQlen += 4;
|
||||
sock.flush();
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user