throttle some stuff for kvirc tls, fix hostnames

This commit is contained in:
zefie
2025-06-18 14:49:51 -04:00
parent f901dfbb50
commit d43f7dc213

View File

@@ -100,7 +100,7 @@ class WTVIRC {
this.supported_user_modes = "BRZciorswxz"; this.supported_user_modes = "BRZciorswxz";
this.supported_prefixes = ["ohv", "@%+"]; this.supported_prefixes = ["ohv", "@%+"];
this.supported_client_caps = ['chghost', 'away-notify', 'echo-message', 'invite-notify', 'multi-prefix', 'userhost-in-names', 'account-notify', 'extended-join']; this.supported_client_caps = ['chghost', 'away-notify', 'echo-message', 'invite-notify', 'multi-prefix', 'userhost-in-names', 'account-notify', 'extended-join'];
this.supported_capabilities = ['TBURST', 'EOB', 'IE', 'EX']; this.supported_server_caps = ['TBURST', 'EOB', 'IE', 'EX'];
this.caps = [ this.caps = [
`AWAYLEN=${this.awaylen} CASEMAPPING=rfc1459 BOT=B CHANMODES=${this.supported_channel_modes} CHANNELLEN=${this.channellen} CHANTYPES=${this.channelprefixes.join('')} PREFIX=(${this.supported_prefixes[0]})${this.supported_prefixes[1]} USERMODES=${this.supported_user_modes} MAXLIST=b:${this.maxbans},e:${this.maxexcept},i:${this.maxinvite},k:${this.maxkeylen},l:${this.maxlimit}`, `AWAYLEN=${this.awaylen} CASEMAPPING=rfc1459 BOT=B CHANMODES=${this.supported_channel_modes} CHANNELLEN=${this.channellen} CHANTYPES=${this.channelprefixes.join('')} PREFIX=(${this.supported_prefixes[0]})${this.supported_prefixes[1]} USERMODES=${this.supported_user_modes} MAXLIST=b:${this.maxbans},e:${this.maxexcept},i:${this.maxinvite},k:${this.maxkeylen},l:${this.maxlimit}`,
`CHARSET=ascii MODES=3 EXCEPTS=e INVEX=I NETWORK=${this.network} CHANLIMIT=${this.channelprefixes.join('')}:${this.channellimit} NICKLEN=${this.nicklen} TOPICLEN=${this.topiclen} KICKLEN=${this.kicklen}` `CHARSET=ascii MODES=3 EXCEPTS=e INVEX=I NETWORK=${this.network} CHANLIMIT=${this.channelprefixes.join('')}:${this.channellimit} NICKLEN=${this.nicklen} TOPICLEN=${this.topiclen} KICKLEN=${this.kicklen}`
@@ -123,7 +123,7 @@ class WTVIRC {
} }
} }
this.server_start_time = this.getDate(); this.server_start_time = this.getDate();
this.server = net.createServer(async (socket) => { this.server = net.createServer(async socket => {
// Detect SSL handshake and wrap socket if needed // Detect SSL handshake and wrap socket if needed
socket.once('data', async firstChunk => { socket.once('data', async firstChunk => {
this.totalConnections++; this.totalConnections++;
@@ -158,6 +158,9 @@ class WTVIRC {
socket.push(firstChunk); socket.push(firstChunk);
secureSocket.on('error', (err) => { secureSocket.on('error', (err) => {
if (this.debug) {
console.error('Secure socket error:', err);
}
this.terminateSession(secureSocket, true); this.terminateSession(secureSocket, true);
}); });
@@ -227,7 +230,7 @@ class WTVIRC {
console.log('<', ...log_args); console.log('<', ...log_args);
return originalWrite.apply(socket, args); return originalWrite.apply(socket, args);
}; };
} }
socket.registered = false; socket.registered = false;
socket.nickname = ''; socket.nickname = '';
socket.username = ''; socket.username = '';
@@ -239,7 +242,7 @@ class WTVIRC {
socket.upgrading_to_tls = false; socket.upgrading_to_tls = false;
socket.client_version = ''; socket.client_version = '';
socket.client_caps = []; socket.client_caps = [];
socket.host = this.filterHostname(socket, socket.remoteAddress); this.filterHostname(socket, socket.remoteAddress);
socket.timestamp = this.getDate(); socket.timestamp = this.getDate();
socket.secure = false; socket.secure = false;
socket.uniqueId = `${this.serverId}${this.generateUniqueId(socket)}`; socket.uniqueId = `${this.serverId}${this.generateUniqueId(socket)}`;
@@ -328,7 +331,7 @@ class WTVIRC {
} }
var output_reply = []; var output_reply = [];
for (const cap of capabilities) { for (const cap of capabilities) {
if (this.supported_capabilities.includes(cap)) { if (this.supported_server_caps.includes(cap)) {
output_reply.push(cap); output_reply.push(cap);
} else { } else {
if (this.debug) { if (this.debug) {
@@ -566,8 +569,8 @@ class WTVIRC {
} }
var oldNick = this.findUserByUniqueId(parts[1]); var oldNick = this.findUserByUniqueId(parts[1]);
var newNick = parts[3]; var newNick = parts[3];
var targetSocket = this.findSocketByUniqueId(parts[1]); var targetSocket = this.findSocketByUniqueId(parts[1]);
this.broadcastUser(oldNick, `:${targetSocket.nickname}!${targetSocket.username}@${targetSocket.host} NICK :${newNick}\r\n`); this.broadcastUser(oldNick, `:${oldNick}!${this.usernames.get(oldNick)}@${targetSocket.host} NICK :${newNick}\r\n`);
this.processNickChange(targetSocket, newNick); this.processNickChange(targetSocket, newNick);
this.broadcastToAllServers(line, socket); this.broadcastToAllServers(line, socket);
break; break;
@@ -1108,7 +1111,7 @@ class WTVIRC {
for (const user of users) { for (const user of users) {
const userSocket = Array.from(this.nicknames.keys()).find(s => this.nicknames.get(s) === user); const userSocket = Array.from(this.nicknames.keys()).find(s => this.nicknames.get(s) === user);
if (userSocket && userSocket.uniqueId !== sourceUniqueId) { if (userSocket && userSocket.uniqueId !== sourceUniqueId) {
await this.sendThrottled(userSocket, [`:${nickname}!${sourceUsername}@${sourceSocket.host} ${srvCommand} ${targetUniqueId} :${message}`], 30); await this.sendThrottled(userSocket, [`:${nickname}!${sourceUsername}@${sourceSocket.host} ${srvCommand} ${targetUniqueId} :${message}\r\n`], 30);
this.broadcastToAllServers(`:${sourceUniqueId} ${srvCommand} ${targetUniqueId} :${message}\r\n`, socket); this.broadcastToAllServers(`:${sourceUniqueId} ${srvCommand} ${targetUniqueId} :${message}\r\n`, socket);
} }
} }
@@ -1129,7 +1132,7 @@ class WTVIRC {
if (this.clientIsWebTV(targetSocket)) { if (this.clientIsWebTV(targetSocket)) {
srvCommand = 'PRIVMSG'; srvCommand = 'PRIVMSG';
} }
await this.sendThrottled(targetSocket, [`:${nickname}!${sourceUsername}@${sourceSocket.host} ${srvCommand} ${targetNickname} :${message}`], 30); await this.sendThrottled(targetSocket, [`:${nickname}!${sourceUsername}@${sourceSocket.host} ${srvCommand} ${targetNickname} :${message}\r\n`], 30);
this.broadcastToAllServers(`:${sourceUniqueId} ${srvCommand} ${targetUniqueId} :${message}\r\n`, socket); this.broadcastToAllServers(`:${sourceUniqueId} ${srvCommand} ${targetUniqueId} :${message}\r\n`, socket);
break; break;
case "WHOIS": case "WHOIS":
@@ -1157,9 +1160,10 @@ class WTVIRC {
whoisNick = whoisSocket.nickname; whoisNick = whoisSocket.nickname;
const whois_username = this.usernames.get(whoisNick); const whois_username = this.usernames.get(whoisNick);
var userinfo = this.userinfo.get(whoisNick) || whoisSocket.userinfo || ''; var userinfo = this.userinfo.get(whoisNick) || whoisSocket.userinfo || '';
socket.write(`:${this.serverId} 311 ${targetUniqueId} ${whoisNick} ${whois_username} ${whoisSocket.host} * :${userinfo}\r\n`); var output_lines = [];
output_lines.push(`:${this.serverId} 311 ${targetUniqueId} ${whoisNick} ${whois_username} ${whoisSocket.host} * :${userinfo}\r\n`);
if (this.awaymsgs.has(whoisNick)) { if (this.awaymsgs.has(whoisNick)) {
socket.write(`:${this.serverId} 301 ${targetUniqueId} ${whoisNick} :${this.awaymsgs.get(whoisNick)}\r\n`); output_lines.push(`:${this.serverId} 301 ${targetUniqueId} ${whoisNick} :${this.awaymsgs.get(whoisNick)}\r\n`);
} }
const userChannels = []; const userChannels = [];
for (const [ch, users] of this.channels.entries()) { for (const [ch, users] of this.channels.entries()) {
@@ -1179,26 +1183,27 @@ class WTVIRC {
userChannels.push(prefix + ch); userChannels.push(prefix + ch);
} }
} }
socket.write(`:${this.serverId} 312 ${targetUniqueId} ${whoisNick} :${this.servername} :minisrv-${this.minisrv_config.version}\r\n`); output_lines.push(`:${this.serverId} 312 ${targetUniqueId} ${whoisNick} :${this.servername} :minisrv-${this.minisrv_config.version}\r\n`);
if (this.isIRCOp(whoisNick)) { if (this.isIRCOp(whoisNick)) {
socket.write(`:${this.serverId} 313 ${targetUniqueId} ${whoisNick} :is an IRC operator\r\n`); output_lines.push(`:${this.serverId} 313 ${targetUniqueId} ${whoisNick} :is an IRC operator\r\n`);
} }
if (usermodes && this.getUserModes(whoisNick).includes('s')) { if (usermodes && this.getUserModes(whoisNick).includes('s')) {
socket.write(`:${this.serverId} 671 ${targetUniqueId} ${whoisNick} :is using a secure connection\r\n`); output_lines.push(`:${this.serverId} 671 ${targetUniqueId} ${whoisNick} :is using a secure connection\r\n`);
} }
if (usermodes && this.getUserModes(whoisNick).includes('r')) { if (usermodes && this.getUserModes(whoisNick).includes('r')) {
socket.write(`:${this.serverId} 307 ${targetUniqueId} ${whoisNick} :is a registered nick\r\n`); output_lines.push(`:${this.serverId} 307 ${targetUniqueId} ${whoisNick} :is a registered nick\r\n`);
} }
var now = this.getDate(); var now = this.getDate();
var userTimestamp = this.usertimestamps.get(whoisNick) || now; var userTimestamp = this.usertimestamps.get(whoisNick) || now;
var idleTime = now - userTimestamp; var idleTime = now - userTimestamp;
socket.write(`:${this.serverId} 317 ${targetUniqueId} ${whoisNick} ${idleTime} ${this.usersignontimestamps.get(whoisNick) || 0} :seconds idle, signon time\r\n`); output_lines.push(`:${this.serverId} 317 ${targetUniqueId} ${whoisNick} ${idleTime} ${this.usersignontimestamps.get(whoisNick) || 0} :seconds idle, signon time\r\n`);
if (userChannels.length > 0) { if (userChannels.length > 0) {
socket.write(`:${this.serverId} 319 ${targetUniqueId} ${whoisNick} :${userChannels.join(' ')}\r\n`); output_lines.push(`:${this.serverId} 319 ${targetUniqueId} ${whoisNick} :${userChannels.join(' ')}\r\n`);
} }
socket.write(`:${this.serverId} 318 ${targetUniqueId} ${whoisNick} :End of /WHOIS list\r\n`); output_lines.push(`:${this.serverId} 318 ${targetUniqueId} ${whoisNick} :End of /WHOIS list\r\n`);
} await this.sendThrottled(socket, output_lines);
break; }
break;
case "SVSJOIN": case "SVSJOIN":
if (parts.length < 3) { if (parts.length < 3) {
if (this.debug) { if (this.debug) {
@@ -1343,7 +1348,6 @@ class WTVIRC {
}; };
} }
socket.removeAllListeners('error'); socket.removeAllListeners('error');
secureSocket.setEncoding('ascii');
secureSocket.registered = socket.registered; secureSocket.registered = socket.registered;
secureSocket.nickname = socket.nickname secureSocket.nickname = socket.nickname
secureSocket.username = socket.username secureSocket.username = socket.username
@@ -1382,7 +1386,7 @@ class WTVIRC {
// Ensure data is a string // Ensure data is a string
if (typeof data !== 'string') { if (typeof data !== 'string') {
if (Buffer.isBuffer(data)) { if (Buffer.isBuffer(data)) {
data = data.toString('utf8'); data = data.toString('ascii');
} else if (data && typeof data.toString === 'function') { } else if (data && typeof data.toString === 'function') {
data = data.toString(); data = data.toString();
} else { } else {
@@ -2171,11 +2175,13 @@ class WTVIRC {
} }
} }
var users = this.getUsersInChannel(ch); var users = this.getUsersInChannel(ch);
var output_lines = [];
var prefixRegex = new RegExp(`^[${this.supported_prefixes[1]}]`);
if (users.length > 0) { if (users.length > 0) {
users.sort((a, b) => { users.sort((a, b) => {
// Remove any prefixes for comparison // Remove any prefixes for comparison
const cleanA = a.replace(/^[@%+]/, ''); const cleanA = a.replace(prefixRegex, '');
const cleanB = b.replace(/^[@%+]/, ''); const cleanB = b.replace(prefixRegex, '');
// Get privilege for each user // Get privilege for each user
const ops = this.channelops.get(ch) || new Set(); const ops = this.channelops.get(ch) || new Set();
const halfops = this.channelhalfops.get(ch) || new Set(); const halfops = this.channelhalfops.get(ch) || new Set();
@@ -2194,17 +2200,18 @@ class WTVIRC {
}); });
if (socket.client_caps.includes('userhost-in-names')) { if (socket.client_caps.includes('userhost-in-names')) {
const userHosts = users.map(user => { const userHosts = users.map(user => {
var nick = this.findUser(user.replace(/^[@%+]/, '')); var nick = this.findUser(user.replace(prefixRegex, ''));
var username = this.usernames.get(nick) || 'unknown'; var username = this.usernames.get(nick) || 'unknown';
var host = this.hostnames.get(nick) || 'unknown'; var host = this.hostnames.get(nick) || 'unknown';
return `${user}!${username}@${host}`; return `${user}!${username}@${host}`;
}); });
socket.write(`:${this.servername} 353 ${socket.nickname} = ${ch} :${userHosts.join(' ')}\r\n`); output_lines.push(`:${this.servername} 353 ${socket.nickname} = ${ch} :${userHosts.join(' ')}\r\n`);
} else { } else {
socket.write(`:${this.servername} 353 ${socket.nickname} = ${ch} :${users.join(' ')}\r\n`); output_lines.push(`:${this.servername} 353 ${socket.nickname} = ${ch} :${users.join(' ')}\r\n`);
} }
} }
socket.write(`:${this.servername} 366 ${socket.nickname} ${ch} :End of /NAMES list\r\n`); output_lines.push(`:${this.servername} 366 ${socket.nickname} ${ch} :End of /NAMES list\r\n`);
this.sendThrottled(socket, output_lines);
if (this.isReservedChannel(ch)) { if (this.isReservedChannel(ch)) {
if (this.checkIfReservedChannelOp(socket, ch)) { if (this.checkIfReservedChannelOp(socket, ch)) {
if (!this.channelops.has(ch) || this.channelops.get(ch) === true) { if (!this.channelops.has(ch) || this.channelops.get(ch) === true) {
@@ -2235,20 +2242,23 @@ class WTVIRC {
break; break;
} }
var users = this.getUsersInChannel(channel); var users = this.getUsersInChannel(channel);
var output_lines = [];
if (users.length > 0) { if (users.length > 0) {
if (socket.client_caps.includes('userhost-in-names')) { if (socket.client_caps.includes('userhost-in-names')) {
const userHosts = users.map(user => { const userHosts = users.map(user => {
var nick = this.findUser(user.replace(/^[@%+]/, '')); var prefixRegex = new RegExp(`^[${this.supported_prefixes[1]}]`);
var nick = this.findUser(user.replace(prefixRegex, ''));
var username = this.usernames.get(nick) || 'unknown'; var username = this.usernames.get(nick) || 'unknown';
var host = this.hostnames.get(nick) || 'unknown'; var host = this.hostnames.get(nick) || 'unknown';
return `${user}!${username}@${host}`; return `${user}!${username}@${host}`;
}); });
socket.write(`:${this.servername} 353 ${socket.nickname} = ${ch} :${userHosts.join(' ')}\r\n`); output_lines.push(`:${this.servername} 353 ${socket.nickname} = ${ch} :${userHosts.join(' ')}\r\n`);
} else { } else {
socket.write(`:${this.servername} 353 ${socket.nickname} = ${ch} :${users.join(' ')}\r\n`); output_lines.push(`:${this.servername} 353 ${socket.nickname} = ${ch} :${users.join(' ')}\r\n`);
} }
} }
socket.write(`:${this.servername} 366 ${socket.nickname} ${channel} :End of /NAMES list\r\n`); output_lines.push(`:${this.servername} 366 ${socket.nickname} ${channel} :End of /NAMES list\r\n`);
this.sendThrottled(socket, output_lines);
break; break;
case 'PART': case 'PART':
if (!socket.registered) { if (!socket.registered) {
@@ -2457,6 +2467,7 @@ class WTVIRC {
socket.write(`:${this.servername} 315 ${socket.nickname} ${target} :End of /WHO list\r\n`); socket.write(`:${this.servername} 315 ${socket.nickname} ${target} :End of /WHO list\r\n`);
} else { } else {
// WHO for nickname // WHO for nickname
var output_lines = [];
if (target.includes('*') || target.includes('?')) { if (target.includes('*') || target.includes('?')) {
// Wildcard mask search for nicknames // Wildcard mask search for nicknames
const maskRegex = new RegExp('^' + target.replace(/\*/g, '.*').replace(/\?/g, '.') + '$', 'i'); const maskRegex = new RegExp('^' + target.replace(/\*/g, '.*').replace(/\?/g, '.') + '$', 'i');
@@ -2467,13 +2478,14 @@ class WTVIRC {
continue; continue;
} }
found = true; found = true;
socket.write(`:${this.servername} 352 ${socket.nickname} * ${nick} ${sock.host} ${this.servername} ${nick} ${(this.awaymsgs.has(nick)) ? 'G' : 'H'}${(sock.secure) ? 'z' : ''} :0 ${sock.userinfo}\r\n`); output_lines.push(`:${this.servername} 352 ${socket.nickname} * ${nick} ${sock.host} ${this.servername} ${nick} ${(this.awaymsgs.has(nick)) ? 'G' : 'H'}${(sock.secure) ? 'z' : ''} :0 ${sock.userinfo}\r\n`);
} }
} }
if (!found) { if (!found) {
socket.write(`:${this.servername} 401 ${socket.nickname} ${target} :No such nick/channel\r\n`); output_lines.push(`:${this.servername} 401 ${socket.nickname} ${target} :No such nick/channel\r\n`);
} }
socket.write(`:${this.servername} 315 ${socket.nickname} ${target} :End of /WHO list\r\n`); output_lines.push(`:${this.servername} 315 ${socket.nickname} ${target} :End of /WHO list\r\n`);
this.sendThrottled(socket, output_lines);
break; break;
} else { } else {
var whoisSocket = Array.from(this.nicknames.keys()).find( var whoisSocket = Array.from(this.nicknames.keys()).find(
@@ -2482,14 +2494,15 @@ class WTVIRC {
if (whoisSocket) { if (whoisSocket) {
if (this.getUserModes(whoisSocket.nickname).includes('s')) { if (this.getUserModes(whoisSocket.nickname).includes('s')) {
// Skip invisible users // Skip invisible users
socket.write(`:${this.servername} 315 ${socket.nickname} ${target} :End of /WHO list\r\n`); output_lines.push(`:${this.servername} 315 ${socket.nickname} ${target} :End of /WHO list\r\n`);
break; break;
} }
socket.write(`:${this.servername} 352 ${socket.nickname} * ${whoisSocket.nickname} ${whoisSocket.host} ${this.servername} ${whoisSocket.nickname} ${(this.awaymsgs.has(target)) ? 'G' : 'H'}${(whoisSocket.secure) ? 'z' : ''} :0 ${whoisSocket.userinfo}\r\n`); output_lines.push(`:${this.servername} 352 ${socket.nickname} * ${whoisSocket.nickname} ${whoisSocket.host} ${this.servername} ${whoisSocket.nickname} ${(this.awaymsgs.has(target)) ? 'G' : 'H'}${(whoisSocket.secure) ? 'z' : ''} :0 ${whoisSocket.userinfo}\r\n`);
} else { } else {
socket.write(`:${this.servername} 401 ${socket.nickname} ${target} :No such nick/channel\r\n`); output_lines.push(`:${this.servername} 401 ${socket.nickname} ${target} :No such nick/channel\r\n`);
} }
socket.write(`:${this.servername} 315 ${socket.nickname} ${target} :End of /WHO list\r\n`); output_lines.push(`:${this.servername} 315 ${socket.nickname} ${target} :End of /WHO list\r\n`);
this.sendThrottled(socket, output_lines);
break; break;
} }
} }
@@ -3031,7 +3044,7 @@ class WTVIRC {
socket.write(`:${this.servername} 396 ${socket.nickname} :Your VHost has been changed to ${socket.host}\r\n`); socket.write(`:${this.servername} 396 ${socket.nickname} :Your VHost has been changed to ${socket.host}\r\n`);
}); });
break; break;
case 'STARTTLS': case 'STARTTLS':
socket.upgrading_to_tls = true; socket.upgrading_to_tls = true;
socket.write(`:${this.servername} 670 ${socket.uniqueId} :STARTTLS successful, go ahead with TLS handshake\r\n`); socket.write(`:${this.servername} 670 ${socket.uniqueId} :STARTTLS successful, go ahead with TLS handshake\r\n`);
break; break;
@@ -3350,50 +3363,6 @@ class WTVIRC {
return matches; return matches;
} }
async getHostname(socket) {
// Resolve the hostname for a socket, using reverse DNS lookup
// Requires a callback
if (socket && socket.remoteAddress) {
try {
var hostname = socket.remoteAddress
hostname = await new Promise((resolve) => {
// dns.reverse does not support a native timeout option, so we implement our own timeout
let timeoutHandle;
const timeoutPromise = new Promise((resolveTimeout) => {
timeoutHandle = setTimeout(() => {
if (!socket.hostname_resolved) {
socket.hostname_resolved = true;
socket.write(`:${this.servername} NOTICE AUTH :*** Could not resolve your hostname: Timeout; using your IP address (${socket.remoteAddress}) instead.\r\n`);
}
resolveTimeout(socket.remoteAddress);
}, 5000); // 5 seconds timeout
});
dns.reverse(socket.remoteAddress, (err, hostnames) => {
if (!err && hostnames && hostnames.length > 0) {
socket.hostname_resolved = true;
socket.write(`:${this.servername} NOTICE AUTH :*** Hostname found: ${hostnames[0]}\r\n`);
resolve(hostnames[0]);
} else {
if (!err) {
err = 'Domain name not found';
}
socket.hostname_resolved = true;
socket.write(`:${this.servername} NOTICE AUTH :*** Could not resolve your hostname: ${err}; using your IP address (${socket.remoteAddress}) instead.\r\n`);
resolve(socket.remoteAddress);
}
});
});
} catch (e) {
if (this.debug) {
console.error(`Error resolving hostname for ${socket.remoteAddress}:`, e);
}
socket.hostname_resolved = true;
socket.write(`:${this.servername} NOTICE AUTH :*** Could not resolve your hostname: ${e}; using your IP address (${socket.remoteAddress}) instead.\r\n`);
hostname = socket.remoteAddress;
}
}
}
filterHostname(socket, hostname) { filterHostname(socket, hostname) {
// Masks the user's hostname or IP for privacy // Masks the user's hostname or IP for privacy
const username = this.nicknames.get(socket); const username = this.nicknames.get(socket);
@@ -4275,9 +4244,9 @@ class WTVIRC {
return id; return id;
} }
async sendThrottled(socket, lines, delayMs = 30) { async sendThrottled(socket, lines, delayMs = 10) {
for (const line of lines) { for (const line of lines) {
socket.write(line + '\r\n'); socket.write(line);
await new Promise(res => setTimeout(res, delayMs)); await new Promise(res => setTimeout(res, delayMs));
} }
} }
@@ -4401,6 +4370,38 @@ class WTVIRC {
this.usermodes.set(nickname, modes); this.usermodes.set(nickname, modes);
} }
async getHostname(socket) {
// Resolve the hostname for a socket, using reverse DNS lookup
if (socket && socket.remoteAddress) {
try {
var hostname = socket.remoteAddress
hostname = await new Promise((resolve) => {
dns.reverse(socket.remoteAddress, (err, hostnames) => {
if (!err && hostnames && hostnames.length > 0) {
socket.hostname_resolved = true;
socket.write(`:${this.servername} NOTICE AUTH :*** Hostname found: ${hostnames[0]}\r\n`);
resolve(hostnames[0]);
} else {
if (!err) {
err = 'Domain name not found';
}
socket.hostname_resolved = true;
socket.write(`:${this.servername} NOTICE AUTH :*** Could not resolve your hostname: ${err}; using your IP address (${socket.remoteAddress}) instead.\r\n`);
resolve(socket.remoteAddress);
}
});
});
} catch (e) {
if (this.debug) {
console.error(`Error resolving hostname for ${socket.remoteAddress}:`, e);
}
socket.hostname_resolved = true;
socket.write(`:${this.servername} NOTICE AUTH :*** Could not resolve your hostname: ${e}; using your IP address (${socket.remoteAddress}) instead.\r\n`);
}
return hostname;
}
}
async doInitialHandshake(socket) { async doInitialHandshake(socket) {
// Perform the initial handshake with the client // Perform the initial handshake with the client
// We pause the socket to prevent sending data before the hostname is resolved // We pause the socket to prevent sending data before the hostname is resolved
@@ -4427,11 +4428,11 @@ class WTVIRC {
this.addUserUniqueId(nickname, socket.uniqueId); this.addUserUniqueId(nickname, socket.uniqueId);
this.hostnames.set(nickname, socket.host); this.hostnames.set(nickname, socket.host);
this.realhosts.set(nickname, socket.realhost); this.realhosts.set(nickname, socket.realhost);
var output_lines = []
socket.write(`:${this.servername} NOTICE AUTH :Welcome to \x02${this.network}\x0F\r\n`); output_lines.push(`:${this.servername} NOTICE AUTH :Welcome to \x02${this.network}\x0F\r\n`);
socket.write(`:${this.servername} 001 ${nickname} :Welcome to the IRC server, ${nickname}\r\n`); output_lines.push(`:${this.servername} 001 ${nickname} :Welcome to the IRC server, ${nickname}\r\n`);
socket.write(`:${this.servername} 002 ${nickname} :Your host is ${this.servername}, running version zefIRCd v${this.version}\r\n`); output_lines.push(`:${this.servername} 002 ${nickname} :Your host is ${this.servername}, running version zefIRCd v${this.version}\r\n`);
socket.write(`:${this.servername} 003 ${nickname} :This server is ready to accept commands\r\n`); output_lines.push(`:${this.servername} 003 ${nickname} :This server is ready to accept commands\r\n`);
// Sort supported_channel_modes and supported_user_modes alphabetically with capitals first // Sort supported_channel_modes and supported_user_modes alphabetically with capitals first
function sortModesAlphaCapsFirst(modes) { function sortModesAlphaCapsFirst(modes) {
// Remove commas, split to chars, sort, then re-insert commas if needed // Remove commas, split to chars, sort, then re-insert commas if needed
@@ -4469,11 +4470,11 @@ class WTVIRC {
var channelModes = this.supported_channel_modes.split(',').join('') + this.supported_prefixes[0]; var channelModes = this.supported_channel_modes.split(',').join('') + this.supported_prefixes[0];
var sortedChannelModes = sortModesAlphaCapsFirst(channelModes).replace(/,/g, ''); var sortedChannelModes = sortModesAlphaCapsFirst(channelModes).replace(/,/g, '');
var sortedUserModes = sortModesAlphaCapsFirst(this.supported_user_modes); var sortedUserModes = sortModesAlphaCapsFirst(this.supported_user_modes);
socket.write(`:${this.servername} 004 ${nickname} ${this.servername} zefIRCd-${this.version} ${sortedUserModes} ${sortedChannelModes} ${sortedModesWithParams}\r\n`); output_lines.push(`:${this.servername} 004 ${nickname} ${this.servername} zefIRCd-${this.version} ${sortedUserModes} ${sortedChannelModes} ${sortedModesWithParams}\r\n`);
for (const caps of this.caps) { for (const caps of this.caps) {
socket.write(`:${this.servername} 005 ${caps}\r\n`); output_lines.push(`:${this.servername} 005 ${caps}\r\n`);
} }
socket.write(`:${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`);
this.doMOTD(nickname, socket); this.doMOTD(nickname, socket);
@@ -4490,22 +4491,22 @@ class WTVIRC {
return modes.includes('o'); return modes.includes('o');
}); });
const serverCount = this.servers.size + 1; // Include this server const serverCount = this.servers.size + 1; // Include this server
socket.write(`:${this.servername} 251 ${nickname} :There are ${visibleClients.length} visible users and ${invisibleClients.length} invisible users on this server\r\n`); output_lines.push(`:${this.servername} 251 ${nickname} :There are ${visibleClients.length} visible users and ${invisibleClients.length} invisible users on this server\r\n`);
if (operClients.length > 0) { if (operClients.length > 0) {
socket.write(`:${this.servername} 252 ${nickname} ${operClients.length} :operator(s) online\r\n`); output_lines.push(`:${this.servername} 252 ${nickname} ${operClients.length} :operator(s) online\r\n`);
} }
if (this.channels.size > 0) { if (this.channels.size > 0) {
socket.write(`:${this.servername} 253 ${nickname} ${this.channels.size} :channels formed\r\n`); output_lines.push(`:${this.servername} 253 ${nickname} ${this.channels.size} :channels formed\r\n`);
} }
socket.write(`:${this.servername} 255 ${nickname} :I have ${this.clients.length} clients and ${serverCount} servers\r\n`); output_lines.push(`:${this.servername} 255 ${nickname} :I have ${this.clients.length} clients and ${serverCount} servers\r\n`);
socket.write(`:${this.servername} 265 ${nickname} :Current Local Users: ${this.clients.length} Max: ${this.clientpeak}\r\n`); output_lines.push(`:${this.servername} 265 ${nickname} :Current Local Users: ${this.clients.length} Max: ${this.clientpeak}\r\n`);
const globalUsers = this.countGlobalUsers(); const globalUsers = this.countGlobalUsers();
this.globalpeak = Math.max(this.globalpeak, this.countGlobalUsers()); this.globalpeak = Math.max(this.globalpeak, this.countGlobalUsers());
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);
socket.write(`:${this.servername} 266 ${nickname} :Current Global Users: ${globalUsers} Max: ${this.globalpeak}\r\n`); output_lines.push(`:${this.servername} 266 ${nickname} :Current Global Users: ${globalUsers} Max: ${this.globalpeak}\r\n`);
socket.write(`:${this.servername} 250 ${nickname} :Highest connection count: ${this.socketpeak} (${this.clientpeak} clients) (${this.totalConnections} connections received)\r\n`); output_lines.push(`:${this.servername} 250 ${nickname} :Highest connection count: ${this.socketpeak} (${this.clientpeak} clients) (${this.totalConnections} connections received)\r\n`);
var usermodes = this.usermodes.get(nickname); var usermodes = this.usermodes.get(nickname);
if (!usermodes || usermodes === true) { if (!usermodes || usermodes === true) {
usermodes = []; usermodes = [];
@@ -4521,8 +4522,16 @@ class WTVIRC {
if (usermodes) { if (usermodes) {
this.usermodes.set(nickname, usermodes); this.usermodes.set(nickname, usermodes);
} }
socket.write(`:${this.servername} 221 ${nickname} :+${this.usermodes.get(nickname).join('')}\r\n`); if (usermodes.includes('x')) {
socket.write(`:${this.servername} PRIVMSG ${socket.nickname} :\x01VERSION\x01\r\n`); socket.host = this.filterHostname(socket, socket.realhost);
if (socket.client_caps && socket.client_caps.includes('CHGHOST')) {
output_lines.push(`:${socket.nickname}!${socket.username}@${socket.host} CHGHOST ${socket.username} ${socket.host}\r\n`);
}
output_lines.push(`:${this.servername} 396 ${socket.nickname} ${socket.host} :is now your visible host\r\n`);
}
output_lines.push(`:${this.servername} 221 ${nickname} :+${this.usermodes.get(nickname).join('')}\r\n`);
output_lines.push(`:${this.servername} PRIVMSG ${socket.nickname} :\x01VERSION\x01\r\n`);
await this.sendThrottled(socket, output_lines);
this.broadcastConnection(socket); this.broadcastConnection(socket);
} }
} }