Files
collabvm-1.2.ts/cvmts/src/GuacamoleProtocol.ts

283 lines
7.0 KiB
TypeScript
Raw Normal View History

import pino from 'pino';
import { IProtocol, IProtocolHandlers, ListEntry, ProtocolAddUser, ProtocolChatHistory, ScreenRect } from './Protocol';
import { User } from './User';
import * as cvm from '@cvmts/cvm-rs';
// CollabVM protocol implementation for Guacamole.
export class GuacamoleProtocol implements IProtocol {
private handlers: IProtocolHandlers | null = null;
private logger = pino({
name: 'CVMTS.GuacamoleProtocol'
});
private user: User | null = null;
init(u: User): void {
this.user = u;
}
setHandler(handlers: IProtocolHandlers): void {
this.handlers = handlers;
}
private __processMessage_admin(decodedElements: string[]): boolean {
switch (decodedElements[1]) {
case '2':
// Login
if (decodedElements.length !== 3) return false;
this.handlers?.onAdminLogin(this.user!, decodedElements[2]);
break;
case '5':
// QEMU Monitor
if (decodedElements.length !== 4) return false;
// [2] node
// [3] cmd
break;
case '8':
// Restore
break;
case '10':
// Reboot
if (decodedElements.length !== 3) return false;
// [2] - node
break;
case '12':
// Ban
case '13':
// Force Vote
if (decodedElements.length !== 3) return false;
break;
case '14':
// Mute
if (decodedElements.length !== 4) return false;
break;
case '15':
// Kick
case '16':
// End turn
if (decodedElements.length !== 3) return false;
break;
case '17':
// Clear turn queue
if (decodedElements.length !== 3) return false;
// [2] - node
break;
case '18':
// Rename user
if (decodedElements.length !== 4) return false;
// [2] - username
// [3] - new username
break;
case '19':
// Get IP
if (decodedElements.length !== 3) return false;
break;
case '20':
// Steal turn
break;
case '21':
// XSS
if (decodedElements.length !== 3) return false;
// [2] message
break;
case '22':
// Toggle turns
if (decodedElements.length !== 3) return false;
// [2] 0 == disable 1 == enable
break;
case '23':
// Indefinite turn
break;
case '24':
// Hide screen
if (decodedElements.length !== 3) return false;
// 0 - hide
// 1 - unhide
break;
case '25':
if (decodedElements.length !== 3) return false;
// [2]
break;
}
return true;
}
processMessage(buffer: Buffer): boolean {
let decodedElements = cvm.guacDecode(buffer.toString('utf-8'));
if (decodedElements.length < 1) return false;
// The first element is the "opcode".
switch (decodedElements[0]) {
case 'nop':
this.handlers?.onNop(this.user!);
break;
case 'cap':
if (decodedElements.length < 2) return false;
this.handlers?.onCapabilityUpgrade(this.user!, decodedElements.slice(1));
break;
case 'login':
if (decodedElements.length !== 2) return false;
this.handlers?.onLogin(this.user!, decodedElements[1]);
break;
case 'noflag':
this.handlers?.onNoFlag(this.user!);
break;
case 'list':
this.handlers?.onList(this.user!);
break;
case 'connect':
if (decodedElements.length !== 2) return false;
this.handlers?.onConnect(this.user!, decodedElements[1]);
break;
case 'view':
{
if (decodedElements.length !== 3) return false;
let viewMode = parseInt(decodedElements[2]);
if (viewMode == undefined) return false;
this.handlers?.onView(this.user!, decodedElements[1], viewMode);
}
break;
case 'rename':
this.handlers?.onRename(this.user!, decodedElements[1]);
break;
case 'chat':
if (decodedElements.length !== 2) return false;
this.handlers?.onChat(this.user!, decodedElements[1]);
break;
case 'turn':
let forfeit = false;
if (decodedElements.length > 2) return false;
if (decodedElements.length == 1) {
forfeit = false;
} else {
if (decodedElements[1] == '0') forfeit = true;
else if (decodedElements[1] == '1') forfeit = false;
}
this.handlers?.onTurnRequest(this.user!, forfeit);
break;
case 'mouse':
if (decodedElements.length !== 4) return false;
let x = parseInt(decodedElements[1]);
let y = parseInt(decodedElements[2]);
let mask = parseInt(decodedElements[3]);
if (x === undefined || y === undefined || mask === undefined) return false;
this.handlers?.onMouse(this.user!, x, y, mask);
break;
case 'key':
if (decodedElements.length !== 3) return false;
var keysym = parseInt(decodedElements[1]);
var down = parseInt(decodedElements[2]);
if (keysym === undefined || (down !== 0 && down !== 1)) return false;
this.handlers?.onKey(this.user!, keysym, down === 1);
break;
case 'vote':
if (decodedElements.length !== 2) return false;
let choice = parseInt(decodedElements[1]);
if (choice == undefined) return false;
this.handlers?.onVote(this.user!, choice);
break;
case 'admin':
if (decodedElements.length < 2) return false;
return this.__processMessage_admin(decodedElements);
}
return true;
}
// Senders
sendAuth(authServer: string): void {
this.user?.sendMsg(cvm.guacEncode('auth', authServer));
}
sendNop(): void {
this.user?.sendMsg(cvm.guacEncode('nop'));
}
sendSync(now: number): void {
this.user?.sendMsg(cvm.guacEncode('sync', now.toString()));
}
sendConnectFailResponse(): void {
this.user?.sendMsg(cvm.guacEncode('connect', '0'));
}
sendConnectOKResponse(votes: boolean): void {
this.user?.sendMsg(cvm.guacEncode('connect', '1', '1', votes ? '1' : '0', '0'));
}
sendLoginResponse(ok: boolean, message: string | undefined): void {
if (ok) {
this.user?.sendMsg(cvm.guacEncode('login', '1'));
return;
} else {
this.user?.sendMsg(cvm.guacEncode('login', '0', message!));
}
}
sendChatMessage(username: string, message: string): void {
this.user?.sendMsg(cvm.guacEncode('chat', username, message));
}
sendChatHistoryMessage(history: ProtocolChatHistory[]): void {
let arr = ['chat'];
for (let a of history) {
arr.push(a.user);
arr.push(a.msg);
}
this.user?.sendMsg(cvm.guacEncode(...arr));
}
sendAddUser(users: ProtocolAddUser[]): void {
let arr = ['adduser', users.length.toString()];
for (let user of users) {
arr.push(user.username);
arr.push(user.rank.toString());
}
this.user?.sendMsg(cvm.guacEncode(...arr));
}
sendRemUser(users: string[]): void {
let arr = ['remuser', users.length.toString()];
for (let user of users) {
arr.push(user);
}
this.user?.sendMsg(cvm.guacEncode(...arr));
}
sendListResponse(list: ListEntry[]): void {
let arr = ['list'];
for (let node of list) {
arr.push(node.id);
arr.push(node.name);
arr.push(node.thumbnail.toString('base64'));
}
this.user?.sendMsg(cvm.guacEncode(...arr));
}
sendScreenResize(width: number, height: number): void {
this.user?.sendMsg(cvm.guacEncode('size', '0', width.toString(), height.toString()));
}
sendScreenUpdate(rect: ScreenRect): void {
this.user?.sendMsg(cvm.guacEncode('png', '0', '0', rect.x.toString(), rect.y.toString(), rect.data.toString('base64')));
this.sendSync(Date.now());
}
}