From bfbe98054c9de6b771d331222e17498cc7952823 Mon Sep 17 00:00:00 2001 From: zefie Date: Mon, 16 Jun 2025 19:42:21 -0400 Subject: [PATCH] add some protection to server commands --- zefie_wtvp_minisrv/includes/classes/WTVIRC.js | 87 +++++++++++++++++-- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/zefie_wtvp_minisrv/includes/classes/WTVIRC.js b/zefie_wtvp_minisrv/includes/classes/WTVIRC.js index 4f6bf736..babed6e5 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVIRC.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVIRC.js @@ -12,16 +12,16 @@ class WTVIRC { * @constructor * @class WTVIRC * WTVIRC - A small IRC server implementation for WebTV - * Tested with WebTV and KvIRC - * This is a basic implementation and does not cover all IRC features. + * Tested with WebT, KvIRC and mIRC. * Supports unencrypted and encrypted (SSL) connections on the same port. * It supports basic commands like NICK, USER, JOIN, PART, PRIVMSG, NOTICE, TOPIC, AWAY, MODE, KICK, and PING. - * Basic IRCOp functionality is included, you can basically be an channel operator in every channel, or /kill users. - * Channel modes are supported, including invite-only, topic protection, password protection, and user modes (op/voice). + * Basic IRCOp functionality is included. + * hybridircd compatible server link protocol (tested with Anope IRC Services, and partially with hybridircd itself). + * Channel modes are supported, including invite-only, topic protection, password protection, and user modes (op/halfop/voice), and more. * SSL only channel mode +z is supported. As is usermode +Z (no DMs from non-SSL users) * - * TODO: k-line? probably not, but maybe in a different format. - * TODO: Test for crashes with arbitrary data, or malformed commands (especially SSL handshake). + * TODO: k-line? other "lines"? + * TODO: Test for crashes with arbitrary data, or malformed commands (especially SSL handshake, or server interface). * * @param {Object} minisrv_config - The configuration object for minisrv. * @param {string} [host='localhost'] - The host to bind the IRC server to. @@ -176,6 +176,7 @@ class WTVIRC { secureSocket.nickname = ''; secureSocket.username = ''; secureSocket.isserver = false; + secureSocket.is_srv_authorized = false; secureSocket.realhost = socket.remoteAddress secureSocket.host = this.filterHostname(secureSocket, socket.remoteAddress); this.getHostname(secureSocket, (hostname) => { @@ -219,6 +220,7 @@ class WTVIRC { socket.nickname = ''; socket.username = ''; socket.isserver = false; + socket.is_srv_authorized = false; socket.realhost = socket.remoteAddress; socket.host = this.filterHostname(socket, socket.remoteAddress); this.getHostname(socket, (hostname) => { @@ -281,6 +283,7 @@ class WTVIRC { console.log(`Server ${serverObj.name || key} matched with provided password`); } socket.write(`PASS ${serverObj.password}\r\n`); + socket.is_srv_authorized = true; return; } }); @@ -291,6 +294,10 @@ class WTVIRC { socket.serverinfo = matchedServer break; case 'CAPAB': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } // Handle CAPAB command from server if (parts.length < 2) { console.warn('Invalid CAPAB command from server'); @@ -303,6 +310,10 @@ class WTVIRC { socket.write(`CAP * ACK :${capabilities.join(' ')}\r\n`); break; case 'SERVER': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } // Handle SERVER command from server if (parts.length < 6) { console.warn('Invalid SERVER command from server'); @@ -365,6 +376,10 @@ class WTVIRC { // Ignore PONG from server break; case 'RESV': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } // Handle RESV command from server if (parts.length < 2) { console.warn('Invalid RESV command from server'); @@ -388,6 +403,10 @@ class WTVIRC { } break; case 'UID': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } // Handle UID command from server if (parts.length < 10) { console.warn('Invalid UID command from server'); @@ -423,6 +442,10 @@ class WTVIRC { } break; case 'SVSHOST': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } // Handle SVSHOST command from server if (parts.length < 4) { console.warn('Invalid SVSHOST command from server'); @@ -440,6 +463,10 @@ class WTVIRC { targetSocket.write(`:${this.servername} 396 ${targetSocket.nickname} ${targetSocket.host} :is now your displayed host\r\n`); break; case 'SVSNICK': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } // Handle SVSNICK command from server if (parts.length < 5) { console.warn('Invalid SVSNICK command from server'); @@ -453,6 +480,10 @@ class WTVIRC { this.broadcastToAllServers(line, socket); break; case 'SJOIN': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } var channel = parts[2]; var modes = parts[3]; var uniqueId = parts[4].slice(1); @@ -475,6 +506,10 @@ class WTVIRC { this.servers.delete(socket); break; case (command.match(/^\d{3}$/) || {}).input: + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } // Numeric reply from server // Numeric replies are usually in the format: : var senderID = parts[1] @@ -570,7 +605,6 @@ class WTVIRC { } const numericCode = parts[0]; const targetID = parts[1]; - const senderName = parts[2]; // Remove server ID prefix var numericMessage = parts.slice(3).join(' '); if (numericMessage.startsWith(':')) { numericMessage = numericMessage.slice(1); // Remove leading ':' @@ -611,6 +645,10 @@ class WTVIRC { this.uniqueids.delete(nick_name); break; case 'JOIN': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } var channel = parts[3]; if (!this.channels.has(channel)) { this.createChannel(channel); @@ -628,6 +666,10 @@ class WTVIRC { this.broadcastChannel(channel, `:${nickname}!${username}@${userSocket.host} JOIN ${channel}\r\n`, userSocket); break; case 'PART': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } var channel = parts[2]; var nickname = this.findUserByUniqueId(sourceUniqueId); var username = this.usernames.get(nickname) || nickname; @@ -638,10 +680,18 @@ class WTVIRC { } break; case 'GLOBOPS': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } var message = parts.slice(3).join(' '); this.broadcastToAllServers(`:${sourceUniqueId} GLOBOPS :${message}`, socket); break; case 'TBURST': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } // Handle TBURST command from server if (parts.length < 6) { console.warn(`Invalid TBURST command from server: ${line}`); @@ -660,6 +710,10 @@ class WTVIRC { this.broadcastChannel(channel, `:${nickname} TOPIC ${channel} :${topic}\r\n`); break; case 'KILL': + if (!socket.is_srv_authorized) { + socket.write(`:${socket.servername} 481 :Permission denied\r\n`); + return; + } // Handle KILL command from server if (parts.length < 3) { console.warn(`Invalid KILL command from server: ${line}`); @@ -676,6 +730,10 @@ class WTVIRC { this.terminateSession(targetSocket, true); break; case 'MODE': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } var targetUniqueId = parts[2]; if (this.channelprefixes.some(prefix => targetUniqueId.startsWith(prefix))) { // It's a channel, broadcast to all users in the channel @@ -867,6 +925,10 @@ class WTVIRC { break; case 'PRIVMSG': case 'NOTICE': + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } var targetUniqueId = parts[2]; var message = parts.slice(3).join(' '); @@ -904,6 +966,10 @@ class WTVIRC { this.broadcastToAllServers(`:${sourceUniqueId} ${srvCommand} ${targetUniqueId} :${message}\r\n`, socket); break; case "SVSJOIN": + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } if (parts.length < 3) { console.warn('Invalid SVSJOIN command from server'); break; @@ -944,6 +1010,10 @@ class WTVIRC { this.broadcastToAllServers(`:${this.serverId} SJOIN ${this.getDate()} ${channelName} +${modeString}${modeParams.length ? ' ' + modeParams.join(' ') : ''} ${targetUniqueId}\r\n`); break; case "SVSMODE": + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + return; + } if (parts.length < 4) { console.warn('Invalid SVSMODE command from server'); break; @@ -975,6 +1045,9 @@ class WTVIRC { this.broadcastToAllServers(`:${sourceUniqueId} SVSMODE ${targetUniqueId} ${modes.join('')}\r\n`, socket); break; default: + if (!socket.is_srv_authorized) { + socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); + } if (this.debug) { console.log(`Unhandled server command from ${sourceUniqueId} to ${targetUniqueId}: ${srvCommand} ${message}`); }