diff --git a/src/engine/db/network.rs b/src/engine/db/network.rs index ad4f5ad..b8d2ef4 100644 --- a/src/engine/db/network.rs +++ b/src/engine/db/network.rs @@ -343,6 +343,10 @@ impl Db { self.confusable_check = on; } + pub fn confusable_check_enabled(&self) -> bool { + self.confusable_check + } + /// The network defence level (5 = normal, 1 = full lockdown). pub fn defcon(&self) -> u8 { self.defcon diff --git a/src/engine/register.rs b/src/engine/register.rs index aeeb657..7024d1b 100644 --- a/src/engine/register.rs +++ b/src/engine/register.rs @@ -13,15 +13,42 @@ impl Engine { if self.db.exists(name) { return Err(AuthorityStatus::AlreadyExists); } + // A website write must obey the same namespace guards as an IRC REGISTER, or + // it becomes a hole around them (a look-alike "аdmin" that impersonates, a + // FORBIDden/reserved nick, or a registration created while frozen). + if self.db.registrations_frozen() || self.db.readonly() { + return Err(AuthorityStatus::Invalid); + } + self.authority_name_ok(name)?; if !self.reg_limiter.allow() { return Err(AuthorityStatus::RateLimited); } Ok(()) } + // The namespace guards the IRC REGISTER path enforces (FORBID list + look-alike/ + // confusable check), applied to every authority write so it can't create a name + // IRC would have rejected. + fn authority_name_ok(&self, name: &str) -> Result<(), AuthorityStatus> { + if self.db.is_forbidden(echo_api::ForbidKind::Nick, name).is_some() { + return Err(AuthorityStatus::Invalid); + } + if self.db.confusable_check_enabled() && echo_api::confusable_reason(name).is_some() { + return Err(AuthorityStatus::Invalid); + } + Ok(()) + } + // Provision an account from pre-derived SCRAM verifiers (bulk backfill from // the authority). No email confirmation — the authority already vouches for it. pub fn authority_provision(&mut self, name: &str, scram256: &str, scram512: &str, email: Option) -> AuthorityStatus { + // Backfill still can't mint a forbidden/look-alike name (skip only the + // frozen/rate-limit gates, which don't apply to an authority backfill). + if !self.db.exists(name) { + if let Err(status) = self.authority_name_ok(name) { + return status; + } + } match self.db.provision_account(name, scram256, scram512, email) { Ok(()) => AuthorityStatus::Ok, Err(RegError::Exists) => AuthorityStatus::AlreadyExists,