diff --git a/modules/nickserv/src/set.rs b/modules/nickserv/src/set.rs index 937593b..e4fd689 100644 --- a/modules/nickserv/src/set.rs +++ b/modules/nickserv/src/set.rs @@ -29,6 +29,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: Some("EMAIL") => { let email = args.get(2).map(|s| s.to_string()); let cleared = email.is_none(); + if let Some(addr) = &email { + if db.is_forbidden("EMAIL", addr).is_some() { + ctx.notice(me, from.uid, "That email address is forbidden by network policy. Use a different one."); + return; + } + } match db.set_email(account, email) { Ok(()) if cleared => ctx.notice(me, from.uid, format!("Email for \x02{account}\x02 cleared.")), Ok(()) => ctx.notice(me, from.uid, format!("Email for \x02{account}\x02 updated.")), diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 6f2deed..76897cb 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -1268,6 +1268,8 @@ enum RegOutcome { VerifyRequired, /// The name is on the OperServ FORBID list. Forbidden, + /// The supplied email matches an OperServ FORBID EMAIL pattern. + ForbiddenEmail, Exists, RateLimited, Frozen, @@ -1284,6 +1286,7 @@ fn reg_reply(reply: &RegReply, outcome: RegOutcome, account: &str) -> Vec ("success", "*", "Account registered."), RegOutcome::VerifyRequired => ("verification_required", "VERIFICATION_REQUIRED", "Registered — check your email for a code, then VERIFY."), RegOutcome::Forbidden => ("error", "BAD_ACCOUNT_NAME", "That account name is forbidden by network policy."), + RegOutcome::ForbiddenEmail => ("error", "BAD_EMAIL", "That email address is forbidden by network policy."), RegOutcome::Exists => ("error", "ACCOUNT_EXISTS", "That account name is already registered."), RegOutcome::RateLimited => ("error", "TEMPORARILY_UNAVAILABLE", "Too many registrations, please wait a moment."), RegOutcome::Frozen => ("error", "TEMPORARILY_UNAVAILABLE", "Registrations are temporarily frozen by network staff."), @@ -1311,6 +1314,7 @@ fn reg_reply(reply: &RegReply, outcome: RegOutcome, account: &str) -> Vec vec![notice("That nickname is forbidden and can't be registered.".to_string())], + RegOutcome::ForbiddenEmail => vec![notice("That email address is forbidden by network policy. Use a different one.".to_string())], RegOutcome::Exists => vec![notice(format!("\x02{nick}\x02 is already registered. If it's yours, use \x02IDENTIFY \x02."))], RegOutcome::RateLimited => vec![notice("Registrations are busy right now. Please try again in a moment.".to_string())], RegOutcome::Frozen => vec![notice("Registrations are temporarily frozen by network staff. Please try again later.".to_string())], diff --git a/src/engine/register.rs b/src/engine/register.rs index 7f8a4bf..fc07802 100644 --- a/src/engine/register.rs +++ b/src/engine/register.rs @@ -220,6 +220,12 @@ impl Engine { let Some(creds) = creds else { return reg_reply(&reply, RegOutcome::Internal, account); }; + // A forbidden email pattern (OperServ FORBID EMAIL) blocks registration. + if let Some(addr) = &email { + if self.db.is_forbidden("EMAIL", addr).is_some() { + return reg_reply(&reply, RegOutcome::ForbiddenEmail, account); + } + } let addr = email.clone(); let outcome = match self.db.register_prepared(account, creds, email) { Ok(()) if self.db.is_verified(account) => RegOutcome::Ok, diff --git a/src/engine/tests.rs b/src/engine/tests.rs index f69c9df..fe7d4b5 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -1224,6 +1224,18 @@ assert!(e.pre_register_check("cleanname", &reply).is_none(), "clean nick allowed"); assert!(e.db.is_forbidden("CHAN", "#warez").is_some(), "channel is forbidden"); + // EMAIL forbids block registration and SET EMAIL. + assert!(notice(&os(&mut e, "FORBID ADD EMAIL *@spam.tld disposable"), "Forbade"), "email forbid added"); + let ereply = || crate::proto::RegReply::NickServ { agent: "42SAAAAAA".into(), uid: "000AAAAAE".into(), nick: "newbie".into() }; + let out = e.complete_register("newbie", Db::derive_credentials("pw", 4096), Some("evil@spam.tld".into()), ereply()); + assert!(notice(&out, "email address is forbidden"), "forbidden email blocks registration: {out:?}"); + assert!(!e.db.exists("newbie"), "no account created with a forbidden email"); + e.complete_register("newbie", Db::derive_credentials("pw", 4096), Some("ok@good.tld".into()), ereply()); + assert!(e.db.exists("newbie"), "a clean email registers fine"); + // boss (identified) can't SET EMAIL to a forbidden address either. + let out = e.handle(NetEvent::Privmsg { from: "000AAAAAB".into(), to: "42SAAAAAA".into(), text: "SET EMAIL nope@spam.tld".into() }); + assert!(notice(&out, "email address is forbidden"), "SET EMAIL rejects a forbidden address: {out:?}"); + assert!(notice(&os(&mut e, "FORBID LIST"), "evil*"), "list shows the nick forbid"); assert!(notice(&os(&mut e, "FORBID DEL NICK evil*"), "Removed"), "nick forbid removed"); assert!(e.pre_register_check("evilbob", &reply).is_none(), "no longer forbidden");