diff --git a/config.example.toml b/config.example.toml index ab7d5a6..0ba3885 100644 --- a/config.example.toml +++ b/config.example.toml @@ -10,8 +10,6 @@ proxyAllowedIps = ["127.0.0.1"] origin = false # Origins to accept connections from. originAllowedDomains = ["computernewb.com"] -# Maximum amount of active connections allowed from the same IP. -maxConnections = 3 [auth] enabled = false @@ -39,6 +37,8 @@ qmpSockDir = "/tmp/" node = "acoolvm" displayname = "A Really Cool CollabVM Instance" motd = "welcome!" +# Maximum amount of active connections allowed from the same IP. +maxConnections = 3 # Command used to ban an IP. # Use $IP to specify an ip and (optionally) use $NAME to specify a username bancmd = "iptables -A INPUT -s $IP -j REJECT" diff --git a/cvmts/src/CollabVMServer.ts b/cvmts/src/CollabVMServer.ts index d3dab83..b75287d 100644 --- a/cvmts/src/CollabVMServer.ts +++ b/cvmts/src/CollabVMServer.ts @@ -119,6 +119,12 @@ export default class CollabVMServer { } public addUser(user: User) { + let sameip = this.clients.filter(c => c.IP.address === user.IP.address); + if (sameip.length >= this.Config.collabvm.maxConnections) { + // Kick the oldest client + // I think this is a better solution than just rejecting the connection + sameip[0].kick(); + } this.clients.push(user); user.socket.on('msg', (msg: string) => this.onMessage(user, msg)); user.socket.on('disconnect', () => this.connectionClosed(user)); diff --git a/cvmts/src/IConfig.ts b/cvmts/src/IConfig.ts index 5d0f98a..d438e56 100644 --- a/cvmts/src/IConfig.ts +++ b/cvmts/src/IConfig.ts @@ -6,8 +6,12 @@ export default interface IConfig { proxyAllowedIps: string[]; origin: boolean; originAllowedDomains: string[]; - maxConnections: number; }; + tcp: { + enabled: boolean; + host: string; + port: number; + } auth: { enabled: boolean; apiEndpoint: string; @@ -31,6 +35,7 @@ export default interface IConfig { node: string; displayname: string; motd: string; + maxConnections: number; bancmd: string | string[]; moderatorEnabled: boolean; usernameblacklist: string[]; diff --git a/cvmts/src/TCP/TCPClient.ts b/cvmts/src/TCP/TCPClient.ts new file mode 100644 index 0000000..806e4e1 --- /dev/null +++ b/cvmts/src/TCP/TCPClient.ts @@ -0,0 +1,55 @@ +import EventEmitter from "events"; +import NetworkClient from "../NetworkClient.js"; +import { Socket } from "net"; + +export default class TCPClient extends EventEmitter implements NetworkClient { + private socket: Socket; + private cache: string; + + constructor(socket: Socket) { + super(); + this.socket = socket; + this.cache = ''; + this.socket.on('end', () => { + this.emit('disconnect'); + }) + this.socket.on('data', (data) => { + var msg = data.toString('utf-8'); + if (msg[msg.length - 1] === '\n') msg = msg.slice(0, -1); + this.cache += msg; + this.readCache(); + }); + } + + private readCache() { + for (var index = this.cache.indexOf(';'); index !== -1; index = this.cache.indexOf(';')) { + this.emit('msg', this.cache.slice(0, index + 1)); + this.cache = this.cache.slice(index + 1); + } + } + + getIP(): string { + return this.socket.remoteAddress!; + } + + send(msg: string): Promise { + return new Promise((res, rej) => { + this.socket.write(msg, (err) => { + if (err) { + rej(err); + return; + } + res(); + }); + }); + } + + close(): void { + this.emit('disconnect'); + this.socket.end(); + } + + isOpen(): boolean { + return this.socket.writable; + } +} \ No newline at end of file diff --git a/cvmts/src/TCP/TCPServer.ts b/cvmts/src/TCP/TCPServer.ts new file mode 100644 index 0000000..61d4250 --- /dev/null +++ b/cvmts/src/TCP/TCPServer.ts @@ -0,0 +1,39 @@ +import EventEmitter from "events"; +import NetworkServer from "../NetworkServer.js"; +import { Server, Socket } from "net"; +import IConfig from "../IConfig.js"; +import { Logger } from "@cvmts/shared"; +import TCPClient from "./TCPClient.js"; +import { IPDataManager } from "../IPData.js"; +import { User } from "../User.js"; + +export default class TCPServer extends EventEmitter implements NetworkServer { + listener: Server; + Config: IConfig; + logger: Logger; + clients: TCPClient[]; + + constructor(config: IConfig) { + super(); + this.logger = new Logger("CVMTS.TCPServer"); + this.Config = config; + this.listener = new Server(); + this.clients = []; + this.listener.on('connection', socket => this.onConnection(socket)); + } + + private onConnection(socket: Socket) { + var client = new TCPClient(socket); + this.clients.push(client); + this.emit('connect', new User(client, IPDataManager.GetIPData(client.getIP()), this.Config)); + } + + start(): void { + this.listener.listen(this.Config.tcp.port, this.Config.tcp.host, () => { + this.logger.Info(`TCP server listening on ${this.Config.tcp.host}:${this.Config.tcp.port}`); + }) + } + stop(): void { + this.listener.close(); + } +} \ No newline at end of file diff --git a/cvmts/src/WebSocket/WSClient.ts b/cvmts/src/WebSocket/WSClient.ts index 96b9a36..69b6468 100644 --- a/cvmts/src/WebSocket/WSClient.ts +++ b/cvmts/src/WebSocket/WSClient.ts @@ -6,13 +6,11 @@ import { Logger } from "@cvmts/shared"; export default class WSClient extends EventEmitter implements NetworkClient { socket: WebSocket; ip: string; - logger: Logger; constructor(ws: WebSocket, ip: string) { super(); this.socket = ws; this.ip = ip; - this.logger = new Logger("CVMTS.WSClient"); this.socket.on('message', (buf: Buffer, isBinary: boolean) => { // Close the user's connection if they send a non-string message if (isBinary) { @@ -25,7 +23,6 @@ export default class WSClient extends EventEmitter implements NetworkClient { this.socket.on('close', () => { this.emit('disconnect'); - }); } diff --git a/cvmts/src/index.ts b/cvmts/src/index.ts index 980c984..37f7ee6 100644 --- a/cvmts/src/index.ts +++ b/cvmts/src/index.ts @@ -9,6 +9,7 @@ import * as Shared from '@cvmts/shared'; import AuthManager from './AuthManager.js'; import WSServer from './WebSocket/WSServer.js'; import { User } from './User.js'; +import TCPServer from './TCP/TCPServer.js'; let logger = new Shared.Logger('CVMTS.Init'); @@ -58,5 +59,11 @@ async function start() { var WS = new WSServer(Config); WS.on('connect', (client: User) => CVM.addUser(client)); WS.start(); + + if (Config.tcp.enabled) { + var TCP = new TCPServer(Config); + TCP.on('connect', (client: User) => CVM.addUser(client)); + TCP.start(); + } } start();