UNPKG

twiedenbein-thelounge

Version:

The self-hosted Web IRC client

140 lines (139 loc) 6.27 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const net_1 = __importDefault(require("net")); const chalk_1 = __importDefault(require("chalk")); const helper_1 = __importDefault(require("./helper")); const config_1 = __importDefault(require("./config")); const log_1 = __importDefault(require("./log")); class Identification { connectionId; connections; oidentdFile; constructor(startedCallback) { this.connectionId = 0; this.connections = new Map(); if (typeof config_1.default.values.oidentd === "string") { this.oidentdFile = helper_1.default.expandHome(config_1.default.values.oidentd); log_1.default.info(`Oidentd file: ${chalk_1.default.green(this.oidentdFile)}`); this.refresh(); } if (config_1.default.values.identd.enable) { if (this.oidentdFile) { log_1.default.warn("Using both identd and oidentd at the same time, this is most likely not intended."); } const server = net_1.default.createServer(this.serverConnection.bind(this)); server.on("error", (err) => { startedCallback(this, err); }); server.listen({ port: config_1.default.values.identd.port || 113, host: config_1.default.values.bind, }, () => { const address = server.address(); if (typeof address === "string") { log_1.default.info(`Identd server available on ${chalk_1.default.green(address)}`); } else if (address?.address) { log_1.default.info(`Identd server available on ${chalk_1.default.green(address.address + ":" + address.port.toString())}`); } startedCallback(this); }); } else { startedCallback(this); } } serverConnection(socket) { socket.on("error", (err) => log_1.default.error(`Identd socket error: ${err}`)); socket.setTimeout(5000, () => { log_1.default.warn(`identd: no data received, closing connection to ${socket.remoteAddress || "undefined"}`); socket.destroy(); }); socket.once("data", (data) => { this.respondToIdent(socket, data); socket.end(); }); } respondToIdent(socket, buffer) { if (!socket.remoteAddress) { log_1.default.warn("identd: no remote address"); return; } const data = buffer.toString().split(","); const lport = parseInt(data[0], 10) || 0; const fport = parseInt(data[1], 10) || 0; if (lport < 1 || fport < 1 || lport > 65535 || fport > 65535) { log_1.default.warn(`identd: bogus request from ${socket.remoteAddress}`); return; } log_1.default.debug(`identd: remote ${socket.remoteAddress} query ${lport}, ${fport}`); for (const connection of this.connections.values()) { // we only want to respond if all the ip,port tuples match, to avoid user enumeration if (connection.socket.remotePort === fport && connection.socket.localPort === lport && socket.remoteAddress === connection.socket.remoteAddress && socket.localAddress === connection.socket.localAddress) { const reply = `${lport}, ${fport} : USERID : TheLounge : ${connection.user}\r\n`; log_1.default.debug(`identd: reply is ${reply.trimEnd()}`); socket.write(reply); return; } } const reply = `${lport}, ${fport} : ERROR : NO-USER\r\n`; log_1.default.debug(`identd: reply is ${reply.trimEnd()}`); socket.write(reply); } addSocket(socket, user) { const id = ++this.connectionId; this.connections.set(id, { socket, user }); if (this.oidentdFile) { this.refresh(); } return id; } removeSocket(id) { this.connections.delete(id); if (this.oidentdFile) { this.refresh(); } } refresh() { let file = "# Warning: file generated by The Lounge: changes will be overwritten!\n"; this.connections.forEach((connection, id) => { if (!connection.socket.remotePort || !connection.socket.localPort) { // Race condition: this can happen when more than one socket gets disconnected at // once, since we `refresh()` for each one being added/removed. This results // in there possibly being one or more disconnected sockets remaining when we get here. log_1.default.warn(`oidentd: socket has no remote or local port (id=${id}). See https://github.com/thelounge/thelounge/pull/4695.`); return; } if (!connection.socket.remoteAddress) { log_1.default.warn(`oidentd: socket has no remote address, will not respond to queries`); return; } if (!connection.socket.localAddress) { log_1.default.warn(`oidentd: socket has no local address, will not respond to queries`); return; } // we only want to respond if all the ip,port tuples match, to avoid user enumeration file += `to ${connection.socket.remoteAddress}` + ` fport ${connection.socket.remotePort}` + ` from ${connection.socket.localAddress}` + ` lport ${connection.socket.localPort}` + ` { reply "${connection.user}" }\n`; }); if (this.oidentdFile) { fs_1.default.writeFile(this.oidentdFile, file, { flag: "w+" }, function (err) { if (err) { log_1.default.error("Failed to update oidentd file!", err.message); } }); } } } exports.default = Identification;