numerous fixes due to tests

This commit is contained in:
zefie
2025-06-20 05:08:07 -04:00
parent bd43b5248b
commit 40db8dee96
3 changed files with 255 additions and 137 deletions

View File

@@ -240,12 +240,10 @@ class WTVIRC {
socket.signedoff = oldSocket.signedoff; socket.signedoff = oldSocket.signedoff;
socket.hostname_resolved = oldSocket.hostname_resolved; socket.hostname_resolved = oldSocket.hostname_resolved;
socket.realhost = oldSocket.realhost; socket.realhost = oldSocket.realhost;
socket.upgrading_to_tls = false;
socket.client_version = oldSocket.client_version; socket.client_version = oldSocket.client_version;
socket.client_caps = oldSocket.client_caps || []; socket.client_caps = oldSocket.client_caps || [];
socket.host = oldSocket.host; socket.host = oldSocket.host;
socket.timestamp = oldSocket.timestamp; socket.timestamp = oldSocket.timestamp;
socket.secure = secure;
socket.uniqueId = oldSocket.uniqueId; socket.uniqueId = oldSocket.uniqueId;
} else { } else {
socket.registered = false; socket.registered = false;
@@ -256,14 +254,15 @@ class WTVIRC {
socket.signedoff = false; socket.signedoff = false;
socket.hostname_resolved = false; socket.hostname_resolved = false;
socket.realhost = socket.remoteAddress; socket.realhost = socket.remoteAddress;
socket.upgrading_to_tls = false;
socket.client_version = ''; socket.client_version = '';
socket.client_caps = []; socket.client_caps = [];
socket.host = this.filterHostname(socket, socket.remoteAddress); socket.host = this.filterHostname(socket, socket.remoteAddress);
socket.timestamp = this.getDate(); socket.timestamp = this.getDate();
socket.secure = secure;
socket.uniqueId = `${this.serverId}${this.generateUniqueId(socket)}`; socket.uniqueId = `${this.serverId}${this.generateUniqueId(socket)}`;
} }
socket.secure = secure;
socket.upgrading_to_tls = false;
socket.error_count = 0;
await this.doInitialHandshake(socket); await this.doInitialHandshake(socket);
socket.on('data', async data => { socket.on('data', async data => {
@@ -320,14 +319,23 @@ class WTVIRC {
}); });
if (!matchedServer) { if (!matchedServer) {
socket.write(`:${this.servername} :ERROR :Invalid server password\r\n`); socket.write(`:${this.servername} :ERROR :Invalid server password\r\n`);
this.terminateSession(socket); socket.error_count++;
setTimeout((socket) => {
if (socket) {
socket.error_count--;
}
}, 60000);
if (socket.error_count >= 5) {
socket.write(`:${this.servername} :ERROR :Too many errors, disconnecting\r\n`);
this.terminateSession(socket, true);
}
return;
} }
socket.serverinfo = matchedServer socket.serverinfo = matchedServer
return; return;
case 'CAPAB': case 'CAPAB':
if (!socket.is_srv_authorized) { if (!this.checkRegistered(socket, true)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
return;
} }
// Handle CAPAB command from server // Handle CAPAB command from server
if (parts.length < 2) { if (parts.length < 2) {
@@ -354,9 +362,8 @@ class WTVIRC {
socket.write(`CAPAB :${output_reply.join(' ')}\r\n`); socket.write(`CAPAB :${output_reply.join(' ')}\r\n`);
break; break;
case 'SERVER': case 'SERVER':
if (!socket.is_srv_authorized) { if (!this.checkRegistered(socket, true)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
return;
} }
// Handle SERVER command from server // Handle SERVER command from server
if (parts.length < 6) { if (parts.length < 6) {
@@ -373,6 +380,7 @@ class WTVIRC {
socket.isserver = true; socket.isserver = true;
this.clients = this.clients.filter(c => c !== socket); this.clients = this.clients.filter(c => c !== socket);
this.clientpeak = this.clientpeak - 1; this.clientpeak = this.clientpeak - 1;
socket.registered = true;
socket.servername = serverName; socket.servername = serverName;
socket.uniqueId = serverId; socket.uniqueId = serverId;
socket.serverIdent = line; socket.serverIdent = line;
@@ -409,6 +417,9 @@ class WTVIRC {
socket.write(`:${this.serverId} EOB \r\n`); socket.write(`:${this.serverId} EOB \r\n`);
break; break;
case 'SVINFO': case 'SVINFO':
if (!this.checkRegistered(socket, true)) {
break;
}
// Handle SVINFO command from server // Handle SVINFO command from server
if (parts.length < 4) { if (parts.length < 4) {
if (this.debug) { if (this.debug) {
@@ -431,9 +442,8 @@ class WTVIRC {
// Ignore PONG from server // Ignore PONG from server
break; break;
case 'RESV': case 'RESV':
if (!socket.is_srv_authorized) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
return;
} }
// Handle RESV command from server // Handle RESV command from server
if (parts.length < 2) { if (parts.length < 2) {
@@ -461,9 +471,8 @@ class WTVIRC {
this.broadcastToAllServers(`:${socket.servername} RESV ${targetMask} ${expiry} ${reservedNick} :${reason}\r\n`, socket); this.broadcastToAllServers(`:${socket.servername} RESV ${targetMask} ${expiry} ${reservedNick} :${reason}\r\n`, socket);
break; break;
case 'UID': case 'UID':
if (!socket.is_srv_authorized) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
return;
} }
// Handle UID command from server // Handle UID command from server
if (parts.length < 10) { if (parts.length < 10) {
@@ -496,9 +505,8 @@ class WTVIRC {
this.broadcastToAllServers(`:${socket.servername} UID ${nickname} ${server_Id} ${timestamp} +${userModes.join('')} ${username} ${hostname} ${ipaddress} ${ipaddress2} ${userUniqueId} * :${userinfo}\r\n`, socket); this.broadcastToAllServers(`:${socket.servername} UID ${nickname} ${server_Id} ${timestamp} +${userModes.join('')} ${username} ${hostname} ${ipaddress} ${ipaddress2} ${userUniqueId} * :${userinfo}\r\n`, socket);
break; break;
case 'SVSHOST': case 'SVSHOST':
if (!socket.is_srv_authorized) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
return;
} }
// Handle SVSHOST command from server // Handle SVSHOST command from server
if (parts.length < 4) { if (parts.length < 4) {
@@ -525,10 +533,8 @@ class WTVIRC {
this.broadcastToAllServers(`:${socket.servername} SVSHOST ${uniqueId} ${hostname}\r\n`, socket); this.broadcastToAllServers(`:${socket.servername} SVSHOST ${uniqueId} ${hostname}\r\n`, socket);
break; break;
case 'SVSACCOUNT': case 'SVSACCOUNT':
//:00B SVSACCOUNT 00A4KP23X 1750191263 zefie if (!this.checkRegistered(socket)) {
if (!socket.is_srv_authorized) { break;
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`);
return;
} }
if (parts.length < 4) { if (parts.length < 4) {
if (this.debug) { if (this.debug) {
@@ -563,9 +569,8 @@ class WTVIRC {
} }
break; break;
case 'SVSNICK': case 'SVSNICK':
if (!socket.is_srv_authorized) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
return;
} }
// Handle SVSNICK command from server // Handle SVSNICK command from server
if (parts.length < 5) { if (parts.length < 5) {
@@ -582,9 +587,8 @@ class WTVIRC {
this.broadcastToAllServers(line, socket); this.broadcastToAllServers(line, socket);
break; break;
case 'SJOIN': case 'SJOIN':
if (!socket.is_srv_authorized) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
return;
} }
var channel = parts[2]; var channel = parts[2];
var modes = parts[3]; var modes = parts[3];
@@ -619,9 +623,8 @@ 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) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
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>
@@ -744,9 +747,8 @@ class WTVIRC {
targetSocket.write(`:${socket.serverinfo.name} ${numericCode} ${targetID} :${numericMessage}\r\n`); targetSocket.write(`:${socket.serverinfo.name} ${numericCode} ${targetID} :${numericMessage}\r\n`);
break; break;
default: default:
if (!socket.is_srv_authorized) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
return;
} }
if (command.startsWith(':')) { if (command.startsWith(':')) {
// part out the line to "sourceUniqueId command targetUniqueId :message" // part out the line to "sourceUniqueId command targetUniqueId :message"
@@ -1122,8 +1124,8 @@ 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) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} :ERROR :Permission denied\r\n`); break;
} }
if (this.debug) { if (this.debug) {
console.warn(`Unhandled server command from ${sourceUniqueId} to ${targetUniqueId}: ${srvCommand} ${message}`); console.warn(`Unhandled server command from ${sourceUniqueId} to ${targetUniqueId}: ${srvCommand} ${message}`);
@@ -1255,8 +1257,7 @@ class WTVIRC {
const [command, ...params] = line.trim().split(' '); const [command, ...params] = line.trim().split(' ');
switch (command.toUpperCase()) { switch (command.toUpperCase()) {
case 'OPER': case 'OPER':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (!this.oper_enabled) { if (!this.oper_enabled) {
@@ -1282,8 +1283,7 @@ class WTVIRC {
this.broadcastToAllServers(`:${socket.uniqueId} MODE ${socket.uniqueId} +o\r\n`); this.broadcastToAllServers(`:${socket.uniqueId} MODE ${socket.uniqueId} +o\r\n`);
break; break;
case 'UPTIME': case 'UPTIME':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
const uptime = this.getDate() - this.server_start_time; const uptime = this.getDate() - this.server_start_time;
@@ -1294,8 +1294,7 @@ class WTVIRC {
socket.write(`:${this.servername} 242 ${socket.nickname} :Server uptime is ${days} days, ${hours} hours, ${minutes} minutes, ${seconds} seconds\r\n`); socket.write(`:${this.servername} 242 ${socket.nickname} :Server uptime is ${days} days, ${hours} hours, ${minutes} minutes, ${seconds} seconds\r\n`);
break; break;
case 'KICK': case 'KICK':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
var channel = this.findChannel(params[0]); var channel = this.findChannel(params[0]);
@@ -1357,8 +1356,7 @@ class WTVIRC {
} }
break; break;
case 'TOPIC': case 'TOPIC':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (params.length < 1) { if (params.length < 1) {
@@ -1399,8 +1397,7 @@ class WTVIRC {
} }
break; break;
case 'AWAY': case 'AWAY':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
this.usertimestamps.set(socket.nickname, this.getDate()); this.usertimestamps.set(socket.nickname, this.getDate());
@@ -1452,8 +1449,7 @@ class WTVIRC {
} }
break; break;
case 'MODE': case 'MODE':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (params.length < 1) { if (params.length < 1) {
@@ -1653,8 +1649,7 @@ class WTVIRC {
} }
if (!mode) { if (!mode) {
// List channel modes // List channel modes
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
let validPrefix = this.channelprefixes.some(prefix => channel.startsWith(prefix)); let validPrefix = this.channelprefixes.some(prefix => channel.startsWith(prefix));
@@ -1764,14 +1759,15 @@ class WTVIRC {
socket.nickname = new_nickname; socket.nickname = new_nickname;
} }
this.nicknames.set(socket, socket.nickname); this.nicknames.set(socket, socket.nickname);
if (socket.nickname && socket.nickname !== new_nickname) { if (socket.nickname && socket.newickname != new_nickname) {
this.processNickChange(socket, new_nickname);
if (socket.registered) {
socket.write(`:${socket.nickname}!${socket.username}@${socket.host} NICK :${new_nickname}\r\n`); socket.write(`:${socket.nickname}!${socket.username}@${socket.host} NICK :${new_nickname}\r\n`);
this.broadcastUser(socket.nickname, `:${socket.nickname}!${socket.username}@${socket.host} NICK :${new_nickname}\r\n`, socket); this.broadcastUser(socket.nickname, `:${socket.nickname}!${socket.username}@${socket.host} NICK :${new_nickname}\r\n`, socket);
this.processNickChange(socket, new_nickname);
this.broadcastToAllServers(`:${socket.uniqueId} NICK ${new_nickname} :${this.getDate()}\r\n`); this.broadcastToAllServers(`:${socket.uniqueId} NICK ${new_nickname} :${this.getDate()}\r\n`);
} }
}
if (!socket.registered && socket.nickname && socket.username) { if (!socket.registered && socket.nickname && socket.username) {
socket.registered = true;
var totalSockets = this.clients.length + this.servers.size; var totalSockets = this.clients.length + this.servers.size;
var totalSockets = this.clients.length + this.servers.size; var totalSockets = this.clients.length + this.servers.size;
this.socketpeak = Math.max(this.socketpeak, totalSockets); this.socketpeak = Math.max(this.socketpeak, totalSockets);
@@ -1782,14 +1778,14 @@ class WTVIRC {
} }
break; break;
case 'USER': case 'USER':
socket.username = params[0];
if (params.length < 4) { if (params.length < 4) {
socket.write(`:${this.servername} 461 ${socket.nickname} USER :Not enough parameters\r\n`); socket.write(`:${this.servername} 461 ${socket.nickname} USER :Not enough parameters\r\n`);
this.addSocketError(socket);
break; break;
} }
socket.username = params[0];
socket.userinfo = params.slice(3).join(' ').replace(/^:/, ''); socket.userinfo = params.slice(3).join(' ').replace(/^:/, '');
if (!socket.registered && socket.nickname && socket.username) { if (!socket.registered && socket.nickname && socket.username) {
socket.registered = true;
var totalSockets = this.clients.length + this.servers.size; var totalSockets = this.clients.length + this.servers.size;
this.socketpeak = Math.max(this.socketpeak, totalSockets); this.socketpeak = Math.max(this.socketpeak, totalSockets);
this.usernames.set(socket.nickname, socket.username); this.usernames.set(socket.nickname, socket.username);
@@ -1800,8 +1796,7 @@ class WTVIRC {
break; break;
case 'JOIN': case 'JOIN':
var key = null; var key = null;
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
channel = params[0]; channel = params[0];
@@ -2059,8 +2054,7 @@ class WTVIRC {
} }
break; break;
case 'NAMES': case 'NAMES':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (params.length < 1) { if (params.length < 1) {
@@ -2092,8 +2086,7 @@ class WTVIRC {
this.sendThrottled(socket, output_lines); this.sendThrottled(socket, output_lines);
break; break;
case 'PART': case 'PART':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
channel = this.findChannel(params[0]); channel = this.findChannel(params[0]);
@@ -2131,8 +2124,7 @@ class WTVIRC {
this.broadcastToAllServers(`:${socket.uniqueId} PART ${channel}\r\n`); this.broadcastToAllServers(`:${socket.uniqueId} PART ${channel}\r\n`);
break; break;
case 'INVITE': case 'INVITE':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (params.length < 2) { if (params.length < 2) {
@@ -2184,11 +2176,9 @@ class WTVIRC {
break; break;
} }
case 'LIST': case 'LIST':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} } let channelsToList;
let channelsToList;
if (params.length > 0 && params[0]) { if (params.length > 0 && params[0]) {
channelsToList = params[0].split(',').filter(ch => ch.length > 0); channelsToList = params[0].split(',').filter(ch => ch.length > 0);
} else { } else {
@@ -2219,8 +2209,7 @@ class WTVIRC {
socket.write(`:${this.servername} 323 ${socket.nickname} :End of /LIST\r\n`); socket.write(`:${this.servername} 323 ${socket.nickname} :End of /LIST\r\n`);
break; break;
case 'WHO': case 'WHO':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.nickname} ${command} :You have not registered\r\n`);
break; break;
} }
if (!params[0]) { if (!params[0]) {
@@ -2342,8 +2331,7 @@ class WTVIRC {
} }
break; break;
case 'PRIVMSG': case 'PRIVMSG':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
this.usertimestamps.set(socket.nickname, this.getDate()); this.usertimestamps.set(socket.nickname, this.getDate());
@@ -2467,8 +2455,7 @@ class WTVIRC {
} }
break; break;
case 'NOTICE': case 'NOTICE':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
this.usertimestamps.set(socket.nickname, this.getDate()); this.usertimestamps.set(socket.nickname, this.getDate());
@@ -2603,15 +2590,13 @@ class WTVIRC {
} }
break; break;
case 'PING': case 'PING':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
socket.write(`PONG ${params.join(' ')}\r\n`); socket.write(`PONG ${params.join(' ')}\r\n`);
break; break;
case 'KLINE': case 'KLINE':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (!this.isIRCOp(socket.nickname)) { if (!this.isIRCOp(socket.nickname)) {
@@ -2650,8 +2635,7 @@ class WTVIRC {
await this.scanUsersForKLines(); await this.scanUsersForKLines();
break; break;
case 'UNKLINE': case 'UNKLINE':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (!this.isIRCOp(socket.nickname)) { if (!this.isIRCOp(socket.nickname)) {
@@ -2674,8 +2658,7 @@ class WTVIRC {
socket.write(`:${this.servername} 381 ${socket.nickname} :KLINE removed for ${targetMask}\r\n`); socket.write(`:${this.servername} 381 ${socket.nickname} :KLINE removed for ${targetMask}\r\n`);
break; break;
case 'WHOIS': case 'WHOIS':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (params.length < 1) { if (params.length < 1) {
@@ -2778,8 +2761,7 @@ class WTVIRC {
break; break;
case 'EVAL': case 'EVAL':
// VERY DANGEROUS // VERY DANGEROUS
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (!this.isIRCOp(socket.nickname)) { if (!this.isIRCOp(socket.nickname)) {
@@ -2798,8 +2780,7 @@ class WTVIRC {
} }
break; break;
case 'KILL': case 'KILL':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (!this.isIRCOp(socket.nickname)) { if (!this.isIRCOp(socket.nickname)) {
@@ -2832,9 +2813,9 @@ class WTVIRC {
this.terminateSession(targetSocket, true); this.terminateSession(targetSocket, true);
break; break;
case 'QUIT': case 'QUIT':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`); break;
} else { }
for (const [ch, users] of this.channels.entries()) { for (const [ch, users] of this.channels.entries()) {
if (users.has(socket.nickname)) { if (users.has(socket.nickname)) {
if (this.channelops.has(ch) && this.channelops.get(ch) instanceof Set) { if (this.channelops.has(ch) && this.channelops.get(ch) instanceof Set) {
@@ -2865,26 +2846,22 @@ class WTVIRC {
this.broadcastConnection(socket, 'Quit: Client disconnected'); this.broadcastConnection(socket, 'Quit: Client disconnected');
socket.signedoff = true; socket.signedoff = true;
} }
}
this.terminateSession(socket, true); this.terminateSession(socket, true);
break; break;
case 'MOTD': case 'MOTD':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
await this.doMOTD(socket.nickname, socket); await this.doMOTD(socket.nickname, socket);
break; break;
case 'VERSION': case 'VERSION':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
socket.write(`:${this.servername} 351 ${socket.nickname} ${this.servername} zefIRCd ${this.version} :zefIRCd IRC server\r\n`); socket.write(`:${this.servername} 351 ${socket.nickname} ${this.servername} zefIRCd ${this.version} :zefIRCd IRC server\r\n`);
break; break;
case 'WALLOPS': case 'WALLOPS':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (!this.isIRCOp(socket.nickname)) { if (!this.isIRCOp(socket.nickname)) {
@@ -2901,8 +2878,7 @@ class WTVIRC {
} }
this.broadcastWallops(`:${socket.nickname}!${socket.username}@${socket.host} WALLOPS :${wallopsMessage}\r\n`); this.broadcastWallops(`:${socket.nickname}!${socket.username}@${socket.host} WALLOPS :${wallopsMessage}\r\n`);
case 'VHOST': case 'VHOST':
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
break; break;
} }
if (!this.isIRCOp(socket.nickname) && !this.allow_public_vhosts) { if (!this.isIRCOp(socket.nickname) && !this.allow_public_vhosts) {
@@ -3560,9 +3536,11 @@ class WTVIRC {
async sendThrottled(socket, lines, delayMs = 1) { async sendThrottled(socket, lines, delayMs = 1) {
for (const line of lines) { for (const line of lines) {
await new Promise(res => setTimeout(res, delayMs)); await new Promise(res => setTimeout(res, delayMs));
if (socket.writable) {
socket.write(line); socket.write(line);
} }
} }
}
getDate() { getDate() {
// Returns the current timestamp in seconds // Returns the current timestamp in seconds
@@ -4037,8 +4015,7 @@ class WTVIRC {
let sourceUniqueId = this.uniqueids.get(nickname); let sourceUniqueId = this.uniqueids.get(nickname);
serverModeMsg = `:${sourceUniqueId} MODE ${channel} `; serverModeMsg = `:${sourceUniqueId} MODE ${channel} `;
} else { } else {
if (!socket.registered) { if (!this.checkRegistered(socket)) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} ${command} :You have not registered\r\n`);
return; return;
} }
serverModeMsg = `:${socket.uniqueId} MODE ${channel} `; serverModeMsg = `:${socket.uniqueId} MODE ${channel} `;
@@ -4194,6 +4171,45 @@ class WTVIRC {
} }
} }
addSocketError(socket) {
socket.error_count++;
if (socket.error_count >= 5) {
if (socket.writable) {
socket.write(`:${this.servername} :ERROR :Too many errors, disconnecting\r\n`);
}
this.terminateSession(socket, true);
return;
}
setTimeout((socket) => {
if (socket) {
socket.error_count--;
}
}, 60000);
}
checkRegistered(socket, allowUnregistered = false) {
var retval = false
if (socket.isserver) {
if (!socket.is_srv_authorized && (!socket.registered && !allowUnregistered)) {
if (socket.writable) {
socket.write(`:${this.servername} ERROR :Unauthorized\r\n`);
}
this.addSocketError(socket);
} else {
retval = true; // Server is authorized
}
}
if (!socket.registered && (!socket.registered && !allowUnregistered)) {
if (socket.writable) {
socket.write(`:${this.servername} 451 ${socket.uniqueId} :You have not registered\r\n`);
}
this.addSocketError(socket);
} else {
retval = true; // User is registered
}
return retval;
}
async doLogin(nickname, socket) { async doLogin(nickname, socket) {
if (await this.scanSocketForKLine(socket)) { if (await this.scanSocketForKLine(socket)) {
return; // If the socket is K-lined, exit early return; // If the socket is K-lined, exit early
@@ -4265,6 +4281,7 @@ class WTVIRC {
for (const caps of this.caps) { for (const caps of this.caps) {
output_lines.push(`:${this.servername} 005 ${caps}\r\n`); output_lines.push(`:${this.servername} 005 ${caps}\r\n`);
} }
socket.registered = true;
output_lines.push(`:${this.servername} 042 ${nickname} ${socket.uniqueId} :your unique ID\r\n`); output_lines.push(`:${this.servername} 042 ${nickname} ${socket.uniqueId} :your unique ID\r\n`);
output_lines.push(...(await this.doMOTD(nickname))); output_lines.push(...(await this.doMOTD(nickname)));

View File

@@ -0,0 +1,101 @@
'use strict';
const path = require('path');
var classPath = path.resolve(__dirname + path.sep + "includes" + path.sep + "classes" + path.sep) + path.sep;
require(classPath + "Prototypes.js");
const WTVIRC = require(classPath + "WTVIRC.js");
const { WTVShared, clientShowAlert } = require(classPath + "WTVShared.js");
const wtvshared = new WTVShared(); // creates minisrv_config
const net = require('net');
var minisrv_config = wtvshared.readMiniSrvConfig(true, false, true);
if (!minisrv_config.config.irc || !minisrv_config.config.irc.enabled) {
console.error('IRC is not enabled in the configuration.');
process.exit(1);
}
const HOST = 'localhost';
const PORT = minisrv_config.config.irc.port; // Example IRC port
const client = new net.Socket();
var lastLine = '';
client.connect(PORT, HOST, () => {
console.log(`Connected to ${HOST}:${PORT}`);
// You can send data here, e.g.:
// client.write('NICK testuser\r\n');
// client.write('USER testuser 0 * :Test User\r\n');
});
client.on('data', (data) => {
console.log('Received:', data.toString());
lastLine = data.toString();
});
client.on('close', () => {
console.log('Connection closed');
});
client.on('error', (err) => {
console.error('Connection error:', err);
});
testCase4();
function testCase1() {
// Try to auth as a server when we are not allowed to
client.write('SERVER testserver 0 * :Test Server\r\n');
client.write('PASS \b\b\b\b\b\b\b\b\b\b\r\n');
client.write('SERVER testserver 0 * :Test Server\r\n');
client.write(`SVINFO 6 6 0 :-1\r\n`);
client.write('PASS \b\b\b\b\b\b\b\b\b\b\r\n');
// we should be disconnected here
}
function testCase2() {
// Malformed user authentication
client.write('NICK invaliduser\r\n');
client.write('USER invaliduser\r\n');
client.write('NICK invaliduser2\r\n');
client.write('NICK invaliduser\r\n');
client.write('USER invaliduser\r\n');
client.write('USER invaliduser\r\n');
client.write('USER invaliduser\r\n');
client.write('USER invaliduser\r\n');
// we should be disconnected here
}
async function waitFor(expectedResponse) {
while (!lastLine.includes(expectedResponse)) {
await new Promise(resolve => setTimeout(resolve, 10)); // wait for 10ms
}
}
async function testCase3() {
// join, msg, quit
client.write('NICK testuser\r\n');
client.write('USER testuser 0 * :Test User\r\n');
await waitFor("005");
client.write('JOIN #testchannel\r\n');
await new Promise(resolve => setTimeout(resolve, 10)); // wait for 10ms
client.write('PRIVMSG #testchannel :Hello, world!\r\n');
await new Promise(resolve => setTimeout(resolve, 10)); // wait for 10ms
client.write('PART #testchannel\r\n');
await new Promise(resolve => setTimeout(resolve, 10)); // wait for 10ms
client.write('QUIT :Goodbye\r\n');
// we should be disconnected here
}
function testCase4() {
// Arbitrary commands
client.write('NICK testuser\r\n');
client.write('MODE testuser +i\r\n');
client.write('TOPIC #testchannel :New topic\r\n');
client.write('KICK #testchannel testuser :You have been kicked\r\n');
client.write('NOTICE testuser :This is a notice\r\n');
client.write('INVITE testuser #testchannel\r\n');
client.write('WHO #testchannel\r\n');
client.write('LIST\r\n');
}