add some protection to server commands

This commit is contained in:
zefie
2025-06-16 19:42:21 -04:00
parent e53fc8118b
commit bfbe98054c

View File

@@ -12,16 +12,16 @@ class WTVIRC {
* @constructor * @constructor
* @class WTVIRC * @class WTVIRC
* WTVIRC - A small IRC server implementation for WebTV * WTVIRC - A small IRC server implementation for WebTV
* Tested with WebTV and KvIRC * Tested with WebT, KvIRC and mIRC.
* This is a basic implementation and does not cover all IRC features.
* Supports unencrypted and encrypted (SSL) connections on the same port. * 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. * 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. * Basic IRCOp functionality is included.
* Channel modes are supported, including invite-only, topic protection, password protection, and user modes (op/voice). * 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) * 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: k-line? other "lines"?
* TODO: Test for crashes with arbitrary data, or malformed commands (especially SSL handshake). * 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 {Object} minisrv_config - The configuration object for minisrv.
* @param {string} [host='localhost'] - The host to bind the IRC server to. * @param {string} [host='localhost'] - The host to bind the IRC server to.
@@ -176,6 +176,7 @@ class WTVIRC {
secureSocket.nickname = ''; secureSocket.nickname = '';
secureSocket.username = ''; secureSocket.username = '';
secureSocket.isserver = false; secureSocket.isserver = false;
secureSocket.is_srv_authorized = false;
secureSocket.realhost = socket.remoteAddress secureSocket.realhost = socket.remoteAddress
secureSocket.host = this.filterHostname(secureSocket, socket.remoteAddress); secureSocket.host = this.filterHostname(secureSocket, socket.remoteAddress);
this.getHostname(secureSocket, (hostname) => { this.getHostname(secureSocket, (hostname) => {
@@ -219,6 +220,7 @@ class WTVIRC {
socket.nickname = ''; socket.nickname = '';
socket.username = ''; socket.username = '';
socket.isserver = false; socket.isserver = false;
socket.is_srv_authorized = false;
socket.realhost = socket.remoteAddress; socket.realhost = socket.remoteAddress;
socket.host = this.filterHostname(socket, socket.remoteAddress); socket.host = this.filterHostname(socket, socket.remoteAddress);
this.getHostname(socket, (hostname) => { this.getHostname(socket, (hostname) => {
@@ -281,6 +283,7 @@ class WTVIRC {
console.log(`Server ${serverObj.name || key} matched with provided password`); console.log(`Server ${serverObj.name || key} matched with provided password`);
} }
socket.write(`PASS ${serverObj.password}\r\n`); socket.write(`PASS ${serverObj.password}\r\n`);
socket.is_srv_authorized = true;
return; return;
} }
}); });
@@ -291,6 +294,10 @@ class WTVIRC {
socket.serverinfo = matchedServer socket.serverinfo = matchedServer
break; break;
case 'CAPAB': case 'CAPAB':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
// Handle CAPAB command from server // Handle CAPAB command from server
if (parts.length < 2) { if (parts.length < 2) {
console.warn('Invalid CAPAB command from server'); console.warn('Invalid CAPAB command from server');
@@ -303,6 +310,10 @@ class WTVIRC {
socket.write(`CAP * ACK :${capabilities.join(' ')}\r\n`); socket.write(`CAP * ACK :${capabilities.join(' ')}\r\n`);
break; break;
case 'SERVER': case 'SERVER':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
// Handle SERVER command from server // Handle SERVER command from server
if (parts.length < 6) { if (parts.length < 6) {
console.warn('Invalid SERVER command from server'); console.warn('Invalid SERVER command from server');
@@ -365,6 +376,10 @@ class WTVIRC {
// Ignore PONG from server // Ignore PONG from server
break; break;
case 'RESV': case 'RESV':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
// Handle RESV command from server // Handle RESV command from server
if (parts.length < 2) { if (parts.length < 2) {
console.warn('Invalid RESV command from server'); console.warn('Invalid RESV command from server');
@@ -388,6 +403,10 @@ class WTVIRC {
} }
break; break;
case 'UID': case 'UID':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
// Handle UID command from server // Handle UID command from server
if (parts.length < 10) { if (parts.length < 10) {
console.warn('Invalid UID command from server'); console.warn('Invalid UID command from server');
@@ -423,6 +442,10 @@ class WTVIRC {
} }
break; break;
case 'SVSHOST': case 'SVSHOST':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
// Handle SVSHOST command from server // Handle SVSHOST command from server
if (parts.length < 4) { if (parts.length < 4) {
console.warn('Invalid SVSHOST command from server'); 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`); targetSocket.write(`:${this.servername} 396 ${targetSocket.nickname} ${targetSocket.host} :is now your displayed host\r\n`);
break; break;
case 'SVSNICK': case 'SVSNICK':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
// Handle SVSNICK command from server // Handle SVSNICK command from server
if (parts.length < 5) { if (parts.length < 5) {
console.warn('Invalid SVSNICK command from server'); console.warn('Invalid SVSNICK command from server');
@@ -453,6 +480,10 @@ class WTVIRC {
this.broadcastToAllServers(line, socket); this.broadcastToAllServers(line, socket);
break; break;
case 'SJOIN': case 'SJOIN':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
var channel = parts[2]; var channel = parts[2];
var modes = parts[3]; var modes = parts[3];
var uniqueId = parts[4].slice(1); var uniqueId = parts[4].slice(1);
@@ -475,6 +506,10 @@ class WTVIRC {
this.servers.delete(socket); this.servers.delete(socket);
break; break;
case (command.match(/^\d{3}$/) || {}).input: 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 reply from server
// Numeric replies are usually in the format: <numeric> <nickname> :<message> // Numeric replies are usually in the format: <numeric> <nickname> :<message>
var senderID = parts[1] var senderID = parts[1]
@@ -570,7 +605,6 @@ class WTVIRC {
} }
const numericCode = parts[0]; const numericCode = parts[0];
const targetID = parts[1]; const targetID = parts[1];
const senderName = parts[2]; // Remove server ID prefix
var numericMessage = parts.slice(3).join(' '); var numericMessage = parts.slice(3).join(' ');
if (numericMessage.startsWith(':')) { if (numericMessage.startsWith(':')) {
numericMessage = numericMessage.slice(1); // Remove leading ':' numericMessage = numericMessage.slice(1); // Remove leading ':'
@@ -611,6 +645,10 @@ class WTVIRC {
this.uniqueids.delete(nick_name); this.uniqueids.delete(nick_name);
break; break;
case 'JOIN': case 'JOIN':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
var channel = parts[3]; var channel = parts[3];
if (!this.channels.has(channel)) { if (!this.channels.has(channel)) {
this.createChannel(channel); this.createChannel(channel);
@@ -628,6 +666,10 @@ class WTVIRC {
this.broadcastChannel(channel, `:${nickname}!${username}@${userSocket.host} JOIN ${channel}\r\n`, userSocket); this.broadcastChannel(channel, `:${nickname}!${username}@${userSocket.host} JOIN ${channel}\r\n`, userSocket);
break; break;
case 'PART': case 'PART':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
var channel = parts[2]; var channel = parts[2];
var nickname = this.findUserByUniqueId(sourceUniqueId); var nickname = this.findUserByUniqueId(sourceUniqueId);
var username = this.usernames.get(nickname) || nickname; var username = this.usernames.get(nickname) || nickname;
@@ -638,10 +680,18 @@ class WTVIRC {
} }
break; break;
case 'GLOBOPS': case 'GLOBOPS':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
var message = parts.slice(3).join(' '); var message = parts.slice(3).join(' ');
this.broadcastToAllServers(`:${sourceUniqueId} GLOBOPS :${message}`, socket); this.broadcastToAllServers(`:${sourceUniqueId} GLOBOPS :${message}`, socket);
break; break;
case 'TBURST': case 'TBURST':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
// Handle TBURST command from server // Handle TBURST command from server
if (parts.length < 6) { if (parts.length < 6) {
console.warn(`Invalid TBURST command from server: ${line}`); console.warn(`Invalid TBURST command from server: ${line}`);
@@ -660,6 +710,10 @@ class WTVIRC {
this.broadcastChannel(channel, `:${nickname} TOPIC ${channel} :${topic}\r\n`); this.broadcastChannel(channel, `:${nickname} TOPIC ${channel} :${topic}\r\n`);
break; break;
case 'KILL': case 'KILL':
if (!socket.is_srv_authorized) {
socket.write(`:${socket.servername} 481 :Permission denied\r\n`);
return;
}
// Handle KILL command from server // Handle KILL command from server
if (parts.length < 3) { if (parts.length < 3) {
console.warn(`Invalid KILL command from server: ${line}`); console.warn(`Invalid KILL command from server: ${line}`);
@@ -676,6 +730,10 @@ class WTVIRC {
this.terminateSession(targetSocket, true); this.terminateSession(targetSocket, true);
break; break;
case 'MODE': case 'MODE':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
var targetUniqueId = parts[2]; var targetUniqueId = parts[2];
if (this.channelprefixes.some(prefix => targetUniqueId.startsWith(prefix))) { if (this.channelprefixes.some(prefix => targetUniqueId.startsWith(prefix))) {
// It's a channel, broadcast to all users in the channel // It's a channel, broadcast to all users in the channel
@@ -867,6 +925,10 @@ class WTVIRC {
break; break;
case 'PRIVMSG': case 'PRIVMSG':
case 'NOTICE': case 'NOTICE':
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
var targetUniqueId = parts[2]; var targetUniqueId = parts[2];
var message = parts.slice(3).join(' '); var message = parts.slice(3).join(' ');
@@ -904,6 +966,10 @@ class WTVIRC {
this.broadcastToAllServers(`:${sourceUniqueId} ${srvCommand} ${targetUniqueId} :${message}\r\n`, socket); this.broadcastToAllServers(`:${sourceUniqueId} ${srvCommand} ${targetUniqueId} :${message}\r\n`, socket);
break; break;
case "SVSJOIN": case "SVSJOIN":
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
if (parts.length < 3) { if (parts.length < 3) {
console.warn('Invalid SVSJOIN command from server'); console.warn('Invalid SVSJOIN command from server');
break; break;
@@ -944,6 +1010,10 @@ class WTVIRC {
this.broadcastToAllServers(`:${this.serverId} SJOIN ${this.getDate()} ${channelName} +${modeString}${modeParams.length ? ' ' + modeParams.join(' ') : ''} ${targetUniqueId}\r\n`); this.broadcastToAllServers(`:${this.serverId} SJOIN ${this.getDate()} ${channelName} +${modeString}${modeParams.length ? ' ' + modeParams.join(' ') : ''} ${targetUniqueId}\r\n`);
break; break;
case "SVSMODE": case "SVSMODE":
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
}
if (parts.length < 4) { if (parts.length < 4) {
console.warn('Invalid SVSMODE command from server'); console.warn('Invalid SVSMODE command from server');
break; break;
@@ -975,6 +1045,9 @@ class WTVIRC {
this.broadcastToAllServers(`:${sourceUniqueId} SVSMODE ${targetUniqueId} ${modes.join('')}\r\n`, socket); this.broadcastToAllServers(`:${sourceUniqueId} SVSMODE ${targetUniqueId} ${modes.join('')}\r\n`, socket);
break; break;
default: default:
if (!socket.is_srv_authorized) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
}
if (this.debug) { if (this.debug) {
console.log(`Unhandled server command from ${sourceUniqueId} to ${targetUniqueId}: ${srvCommand} ${message}`); console.log(`Unhandled server command from ${sourceUniqueId} to ${targetUniqueId}: ${srvCommand} ${message}`);
} }