2024-05-26 23:19:55 -04:00
|
|
|
import * as http from 'http';
|
2024-08-22 04:20:26 -04:00
|
|
|
import { NetworkServer } from '../NetworkServer.js';
|
2024-05-26 23:19:55 -04:00
|
|
|
import EventEmitter from 'events';
|
|
|
|
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
|
|
|
import internal from 'stream';
|
2024-08-22 04:26:17 -04:00
|
|
|
import IConfig from '../../IConfig.js';
|
2024-05-26 23:19:55 -04:00
|
|
|
import { isIP } from 'net';
|
2024-08-22 04:26:17 -04:00
|
|
|
import { IPDataManager } from '../../IPData.js';
|
2024-05-26 23:19:55 -04:00
|
|
|
import WSClient from './WSClient.js';
|
2024-08-22 04:26:17 -04:00
|
|
|
import { User } from '../../User.js';
|
2025-11-20 02:41:30 -05:00
|
|
|
import { pino, type Logger } from 'pino';
|
2024-08-22 04:26:17 -04:00
|
|
|
import { BanManager } from '../../BanManager.js';
|
2025-11-20 02:41:30 -05:00
|
|
|
import { v4 as uuid4 } from 'uuid';
|
2024-05-26 23:19:55 -04:00
|
|
|
|
2025-03-21 20:29:16 -04:00
|
|
|
const kAllowedProtocols = [
|
|
|
|
|
"guacamole" // Regular ol' collabvm1 protocol
|
|
|
|
|
]
|
|
|
|
|
|
2024-05-26 23:19:55 -04:00
|
|
|
export default class WSServer extends EventEmitter implements NetworkServer {
|
2024-06-22 21:26:49 -04:00
|
|
|
private httpServer: http.Server;
|
|
|
|
|
private wsServer: WebSocketServer;
|
|
|
|
|
private clients: WSClient[];
|
|
|
|
|
private Config: IConfig;
|
2025-11-20 02:41:30 -05:00
|
|
|
private logger: Logger;
|
2024-07-31 16:34:42 -04:00
|
|
|
private banmgr: BanManager;
|
2025-11-20 02:41:30 -05:00
|
|
|
private uuid: string;
|
2024-06-22 21:26:49 -04:00
|
|
|
|
2024-07-31 16:34:42 -04:00
|
|
|
constructor(config: IConfig, banmgr: BanManager) {
|
2024-06-22 21:26:49 -04:00
|
|
|
super();
|
|
|
|
|
this.Config = config;
|
|
|
|
|
this.clients = [];
|
2025-11-20 02:41:30 -05:00
|
|
|
this.uuid = uuid4();
|
|
|
|
|
this.logger = pino().child({
|
|
|
|
|
stream: 'CVMTS.WSServer',
|
|
|
|
|
"uuid/websocket/server": this.uuid,
|
|
|
|
|
node: config.collabvm.node,
|
|
|
|
|
});
|
2024-06-22 21:26:49 -04:00
|
|
|
this.httpServer = http.createServer();
|
2024-09-05 04:15:19 -04:00
|
|
|
this.wsServer = new WebSocketServer({ noServer: true, perMessageDeflate: false, clientTracking: false });
|
2024-05-26 23:19:55 -04:00
|
|
|
this.httpServer.on('upgrade', (req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) => this.httpOnUpgrade(req, socket, head));
|
|
|
|
|
this.httpServer.on('request', (req, res) => {
|
2025-11-20 02:41:30 -05:00
|
|
|
this.logger.debug({ event: "request", path: req.url });
|
2024-05-26 23:19:55 -04:00
|
|
|
res.writeHead(426);
|
|
|
|
|
res.write('This server only accepts WebSocket connections.');
|
|
|
|
|
res.end();
|
|
|
|
|
});
|
2024-07-31 16:34:42 -04:00
|
|
|
this.banmgr = banmgr;
|
2024-06-22 21:26:49 -04:00
|
|
|
}
|
2024-05-26 23:19:55 -04:00
|
|
|
|
2024-06-22 21:26:49 -04:00
|
|
|
start(): void {
|
2025-11-20 02:41:30 -05:00
|
|
|
this.logger.info({
|
|
|
|
|
event: "websocket server starting",
|
|
|
|
|
host: this.Config.http.host,
|
|
|
|
|
port: this.Config.http.port,
|
|
|
|
|
});
|
2024-06-22 21:26:49 -04:00
|
|
|
this.httpServer.listen(this.Config.http.port, this.Config.http.host, () => {
|
2025-11-20 02:41:30 -05:00
|
|
|
this.logger.info({
|
|
|
|
|
event: "websocket server started",
|
|
|
|
|
host: this.Config.http.host,
|
|
|
|
|
port: this.Config.http.port,
|
|
|
|
|
});
|
2024-06-22 21:26:49 -04:00
|
|
|
});
|
|
|
|
|
}
|
2024-05-26 23:19:55 -04:00
|
|
|
|
2024-06-22 21:26:49 -04:00
|
|
|
stop(): void {
|
2025-11-20 02:41:30 -05:00
|
|
|
this.logger.info({
|
|
|
|
|
event: "websocket server stopping",
|
|
|
|
|
host: this.Config.http.host,
|
|
|
|
|
port: this.Config.http.port,
|
|
|
|
|
});
|
|
|
|
|
this.httpServer.close(() => {
|
|
|
|
|
this.logger.info({
|
|
|
|
|
event: "websocket server stopped",
|
|
|
|
|
host: this.Config.http.host,
|
|
|
|
|
port: this.Config.http.port,
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-06-22 21:26:49 -04:00
|
|
|
}
|
2024-05-26 23:19:55 -04:00
|
|
|
|
2024-07-31 16:34:42 -04:00
|
|
|
private async httpOnUpgrade(req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) {
|
2024-05-26 23:19:55 -04:00
|
|
|
var killConnection = () => {
|
|
|
|
|
socket.write('HTTP/1.1 400 Bad Request\n\n400 Bad Request');
|
|
|
|
|
socket.destroy();
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-21 20:29:16 -04:00
|
|
|
let protocol = req.headers['sec-websocket-protocol'];
|
|
|
|
|
|
|
|
|
|
if (!protocol || kAllowedProtocols.indexOf(protocol) === -1) {
|
2024-05-26 23:19:55 -04:00
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.Config.http.origin) {
|
|
|
|
|
// If the client is not sending an Origin header, kill the connection.
|
|
|
|
|
if (!req.headers.origin) {
|
|
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to parse the Origin header sent by the client, if it fails, kill the connection.
|
|
|
|
|
var _uri;
|
|
|
|
|
var _host;
|
|
|
|
|
try {
|
|
|
|
|
_uri = new URL(req.headers.origin.toLowerCase());
|
|
|
|
|
_host = _uri.host;
|
|
|
|
|
} catch {
|
|
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// detect fake origin headers
|
|
|
|
|
if (_uri.pathname !== '/' || _uri.search !== '') {
|
|
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the domain name is not in the list of allowed origins, kill the connection.
|
|
|
|
|
if (!this.Config.http.originAllowedDomains.includes(_host)) {
|
|
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ip: string;
|
|
|
|
|
if (this.Config.http.proxying) {
|
|
|
|
|
// If the requesting IP isn't allowed to proxy, kill it
|
|
|
|
|
if (this.Config.http.proxyAllowedIps.indexOf(req.socket.remoteAddress!) === -1) {
|
|
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Make sure x-forwarded-for is set
|
|
|
|
|
if (req.headers['x-forwarded-for'] === undefined) {
|
|
|
|
|
killConnection();
|
2024-07-16 08:29:52 -04:00
|
|
|
this.logger.error('X-Forwarded-For header not set. This is most likely a misconfiguration of your reverse proxy.');
|
2024-05-26 23:19:55 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
// Get the first IP from the X-Forwarded-For variable
|
|
|
|
|
ip = req.headers['x-forwarded-for']?.toString().replace(/\ /g, '').split(',')[0];
|
|
|
|
|
} catch {
|
|
|
|
|
// If we can't get the IP, kill the connection
|
2024-07-16 08:29:52 -04:00
|
|
|
this.logger.error('Invalid X-Forwarded-For header. This is most likely a misconfiguration of your reverse proxy.');
|
2024-05-26 23:19:55 -04:00
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// If for some reason the IP isn't defined, kill it
|
|
|
|
|
if (!ip) {
|
|
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Make sure the IP is valid. If not, kill the connection.
|
|
|
|
|
if (!isIP(ip)) {
|
|
|
|
|
killConnection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!req.socket.remoteAddress) return;
|
|
|
|
|
ip = req.socket.remoteAddress;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-31 16:34:42 -04:00
|
|
|
if (await this.banmgr.isIPBanned(ip)) {
|
2024-08-04 15:50:00 -04:00
|
|
|
socket.write('HTTP/1.1 403 Forbidden\n\nYou have been banned.');
|
2024-07-31 16:34:42 -04:00
|
|
|
socket.destroy();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-26 23:19:55 -04:00
|
|
|
this.wsServer.handleUpgrade(req, socket, head, (ws: WebSocket) => {
|
|
|
|
|
this.wsServer.emit('connection', ws, req);
|
2025-03-21 20:29:16 -04:00
|
|
|
this.onConnection(ws, req, ip, protocol);
|
2024-05-26 23:19:55 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-21 20:29:16 -04:00
|
|
|
private onConnection(ws: WebSocket, req: http.IncomingMessage, ip: string, protocol: string) {
|
2025-11-20 02:41:30 -05:00
|
|
|
const uuid = uuid4();
|
|
|
|
|
const connectionId = {
|
|
|
|
|
"uuid/websocket/client": uuid,
|
|
|
|
|
src_ip: ip
|
|
|
|
|
};
|
|
|
|
|
this.logger.info({ ...connectionId, event: "websocket client connecting" });
|
|
|
|
|
|
|
|
|
|
let client = new WSClient(ws, ip, uuid);
|
2024-06-22 21:26:49 -04:00
|
|
|
this.clients.push(client);
|
2025-11-20 02:41:30 -05:00
|
|
|
|
2025-03-21 20:29:16 -04:00
|
|
|
let user = new User(client, protocol, IPDataManager.GetIPData(ip), this.Config);
|
2025-11-20 02:41:30 -05:00
|
|
|
this.logger.info({
|
|
|
|
|
...connectionId,
|
|
|
|
|
event: "websocket client connection bound to user",
|
|
|
|
|
"uuid/user": user.uuid
|
|
|
|
|
});
|
2024-05-26 23:19:55 -04:00
|
|
|
|
2024-06-22 21:26:49 -04:00
|
|
|
this.emit('connect', user);
|
2024-05-26 23:19:55 -04:00
|
|
|
|
|
|
|
|
ws.on('error', (e) => {
|
2025-11-20 02:41:30 -05:00
|
|
|
this.logger.error({ ...connectionId, event: "websocket connection error" });
|
2024-05-26 23:19:55 -04:00
|
|
|
ws.close();
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-20 02:41:30 -05:00
|
|
|
ws.on('close', () => {
|
|
|
|
|
this.logger.error({ ...connectionId, event: "websocket connection closed" });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.logger.info({ ...connectionId, event: "websocket client connected" });
|
2024-05-26 23:19:55 -04:00
|
|
|
}
|
2024-06-19 17:56:55 -04:00
|
|
|
}
|