89 lines
2.5 KiB
JavaScript
89 lines
2.5 KiB
JavaScript
/**
|
|
* 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<string, string | undefined> | 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 }
|
|
}
|