add some protection to server commands
This commit is contained in:
@@ -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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user