/** * IRC dial policy for ctx.bareOsTlsConnect / /bin/irc. * BARE_OS_IRC=0 disables. BARE_OS_IRC_DENYLIST always wins. * Unset allowlist defaults to irc.libera.chat and irc.*.libera.chat. */ import { parseHostPatternList } from '../security/bare-os-http-policy.js' export const BARE_OS_IRC_DEFAULT_ALLOW = [ 'irc.libera.chat', 'irc.*.libera.chat' ] /** * @param {string} pattern * @param {string} host */ export function bareOsIrcHostMatchesGlob(pattern, host) { const p = String(pattern || '').toLowerCase() const h = String(host || '').toLowerCase() if (p === '*' || p === '*:*') return true const esc = p .replace(/[.+^${}()|[\]\\]/g, '\\$&') .replace(/\*/g, '.*') .replace(/\?/g, '.') try { return new RegExp('^' + esc + '$').test(h) } catch { return h === p } } /** * @param {string} host */ export function bareOsIrcIsLiberaHost(host) { const h = String(host || '') .trim() .toLowerCase() return h === 'libera.chat' || h.endsWith('.libera.chat') } /** * @param {string} host * @param {Record | null | undefined} env * @returns {{ ok: boolean, reason?: string }} */ export function bareOsIrcHostAllowed(host, env) { const e = env && typeof env === 'object' ? env : {} const v = e.BARE_OS_IRC if (v === '0' || v === 'false') return { ok: false, reason: 'disabled' } const h = String(host || '') .trim() .toLowerCase() if (!h) return { ok: false, reason: 'missing host' } const deny = parseHostPatternList(e.BARE_OS_IRC_DENYLIST) for (const pat of deny) { if (bareOsIrcHostMatchesGlob(pat, h)) return { ok: false, reason: 'denylist' } } const rawAllow = e.BARE_OS_IRC_ALLOWLIST const allow = rawAllow == null || String(rawAllow).trim() === '' ? BARE_OS_IRC_DEFAULT_ALLOW : parseHostPatternList(rawAllow) if (!allow.length) return { ok: true } for (const pat of allow) { if (bareOsIrcHostMatchesGlob(pat, h)) return { ok: true } } return { ok: false, reason: 'not in allowlist' } } /** * Libera must not be dialed in plaintext unless the operator forces it twice. * @param {string} host * @param {{ tls?: boolean, insecurePlain?: boolean }} opts */ export function bareOsIrcPlaintextAllowed(host, opts) { const o = opts || {} if (o.tls !== false) return { ok: true } if (bareOsIrcIsLiberaHost(host) && o.insecurePlain !== true) { return { ok: false, reason: 'libera requires TLS (use --insecure-plain to override)' } } return { ok: true } }