mirror of
https://github.com/thelounge/thelounge
synced 2024-11-10 06:34:21 +00:00
184 lines
4.9 KiB
TypeScript
184 lines
4.9 KiB
TypeScript
import fs from "fs";
|
|
import net, {Socket} from "net";
|
|
import colors from "chalk";
|
|
import Helper from "./helper";
|
|
import Config from "./config";
|
|
import log from "./log";
|
|
|
|
type Connection = {
|
|
socket: Socket;
|
|
user: string;
|
|
};
|
|
class Identification {
|
|
private connectionId: number;
|
|
private connections: Map<number, Connection>;
|
|
private oidentdFile?: string;
|
|
|
|
constructor(startedCallback: (identHandler: Identification, err?: Error) => void) {
|
|
this.connectionId = 0;
|
|
this.connections = new Map();
|
|
|
|
if (typeof Config.values.oidentd === "string") {
|
|
this.oidentdFile = Helper.expandHome(Config.values.oidentd);
|
|
log.info(`Oidentd file: ${colors.green(this.oidentdFile)}`);
|
|
|
|
this.refresh();
|
|
}
|
|
|
|
if (Config.values.identd.enable) {
|
|
if (this.oidentdFile) {
|
|
log.warn(
|
|
"Using both identd and oidentd at the same time, this is most likely not intended."
|
|
);
|
|
}
|
|
|
|
const server = net.createServer(this.serverConnection.bind(this));
|
|
|
|
server.on("error", (err) => {
|
|
startedCallback(this, err);
|
|
});
|
|
|
|
server.listen(
|
|
{
|
|
port: Config.values.identd.port || 113,
|
|
host: Config.values.bind,
|
|
},
|
|
() => {
|
|
const address = server.address();
|
|
|
|
if (typeof address === "string") {
|
|
log.info(`Identd server available on ${colors.green(address)}`);
|
|
} else if (address?.address) {
|
|
log.info(
|
|
`Identd server available on ${colors.green(
|
|
address.address + ":" + address.port.toString()
|
|
)}`
|
|
);
|
|
}
|
|
|
|
startedCallback(this);
|
|
}
|
|
);
|
|
} else {
|
|
startedCallback(this);
|
|
}
|
|
}
|
|
|
|
serverConnection(socket: Socket) {
|
|
socket.on("error", (err: string) => log.error(`Identd socket error: ${err}`));
|
|
socket.setTimeout(5000, () => {
|
|
log.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: Socket, buffer: Buffer) {
|
|
if (!socket.remoteAddress) {
|
|
log.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.warn(`identd: bogus request from ${socket.remoteAddress}`);
|
|
return;
|
|
}
|
|
|
|
log.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.debug(`identd: reply is ${reply.trimEnd()}`);
|
|
socket.write(reply);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const reply = `${lport}, ${fport} : ERROR : NO-USER\r\n`;
|
|
log.debug(`identd: reply is ${reply.trimEnd()}`);
|
|
socket.write(reply);
|
|
}
|
|
|
|
addSocket(socket: Socket, user: string) {
|
|
const id = ++this.connectionId;
|
|
|
|
this.connections.set(id, {socket, user});
|
|
|
|
if (this.oidentdFile) {
|
|
this.refresh();
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
removeSocket(id: number) {
|
|
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.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.warn(`oidentd: socket has no remote address, will not respond to queries`);
|
|
return;
|
|
}
|
|
|
|
if (!connection.socket.localAddress) {
|
|
log.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.writeFile(this.oidentdFile, file, {flag: "w+"}, function (err) {
|
|
if (err) {
|
|
log.error("Failed to update oidentd file!", err.message);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Identification;
|