OperServ: enforce FORBID EMAIL at registration and SET EMAIL
FORBID accepted and stored EMAIL patterns, but nothing ever checked them: a forbidden address still registered and could be set via NickServ SET EMAIL. Both paths now reject a matching address (new ForbiddenEmail outcome), matching how NICK and CHAN forbids already gate registration.
This commit is contained in:
parent
01ea0fa95e
commit
b749cd2969
4 changed files with 28 additions and 0 deletions
|
|
@ -29,6 +29,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
||||||
Some("EMAIL") => {
|
Some("EMAIL") => {
|
||||||
let email = args.get(2).map(|s| s.to_string());
|
let email = args.get(2).map(|s| s.to_string());
|
||||||
let cleared = email.is_none();
|
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) {
|
match db.set_email(account, email) {
|
||||||
Ok(()) if cleared => ctx.notice(me, from.uid, format!("Email for \x02{account}\x02 cleared.")),
|
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.")),
|
Ok(()) => ctx.notice(me, from.uid, format!("Email for \x02{account}\x02 updated.")),
|
||||||
|
|
|
||||||
|
|
@ -1268,6 +1268,8 @@ enum RegOutcome {
|
||||||
VerifyRequired,
|
VerifyRequired,
|
||||||
/// The name is on the OperServ FORBID list.
|
/// The name is on the OperServ FORBID list.
|
||||||
Forbidden,
|
Forbidden,
|
||||||
|
/// The supplied email matches an OperServ FORBID EMAIL pattern.
|
||||||
|
ForbiddenEmail,
|
||||||
Exists,
|
Exists,
|
||||||
RateLimited,
|
RateLimited,
|
||||||
Frozen,
|
Frozen,
|
||||||
|
|
@ -1284,6 +1286,7 @@ fn reg_reply(reply: &RegReply, outcome: RegOutcome, account: &str) -> Vec<NetAct
|
||||||
RegOutcome::Ok => ("success", "*", "Account registered."),
|
RegOutcome::Ok => ("success", "*", "Account registered."),
|
||||||
RegOutcome::VerifyRequired => ("verification_required", "VERIFICATION_REQUIRED", "Registered — check your email for a code, then VERIFY."),
|
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::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::Exists => ("error", "ACCOUNT_EXISTS", "That account name is already registered."),
|
||||||
RegOutcome::RateLimited => ("error", "TEMPORARILY_UNAVAILABLE", "Too many registrations, please wait a moment."),
|
RegOutcome::RateLimited => ("error", "TEMPORARILY_UNAVAILABLE", "Too many registrations, please wait a moment."),
|
||||||
RegOutcome::Frozen => ("error", "TEMPORARILY_UNAVAILABLE", "Registrations are temporarily frozen by network staff."),
|
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<NetAct
|
||||||
notice(format!("Your nick \x02{nick}\x02 is now registered and you're logged in. Welcome!")),
|
notice(format!("Your nick \x02{nick}\x02 is now registered and you're logged in. Welcome!")),
|
||||||
],
|
],
|
||||||
RegOutcome::Forbidden => vec![notice("That nickname is forbidden and can't be registered.".to_string())],
|
RegOutcome::Forbidden => 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 <password>\x02."))],
|
RegOutcome::Exists => vec![notice(format!("\x02{nick}\x02 is already registered. If it's yours, use \x02IDENTIFY <password>\x02."))],
|
||||||
RegOutcome::RateLimited => vec![notice("Registrations are busy right now. Please try again in a moment.".to_string())],
|
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())],
|
RegOutcome::Frozen => vec![notice("Registrations are temporarily frozen by network staff. Please try again later.".to_string())],
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,12 @@ impl Engine {
|
||||||
let Some(creds) = creds else {
|
let Some(creds) = creds else {
|
||||||
return reg_reply(&reply, RegOutcome::Internal, account);
|
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 addr = email.clone();
|
||||||
let outcome = match self.db.register_prepared(account, creds, email) {
|
let outcome = match self.db.register_prepared(account, creds, email) {
|
||||||
Ok(()) if self.db.is_verified(account) => RegOutcome::Ok,
|
Ok(()) if self.db.is_verified(account) => RegOutcome::Ok,
|
||||||
|
|
|
||||||
|
|
@ -1224,6 +1224,18 @@
|
||||||
assert!(e.pre_register_check("cleanname", &reply).is_none(), "clean nick allowed");
|
assert!(e.pre_register_check("cleanname", &reply).is_none(), "clean nick allowed");
|
||||||
assert!(e.db.is_forbidden("CHAN", "#warez").is_some(), "channel is forbidden");
|
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 LIST"), "evil*"), "list shows the nick forbid");
|
||||||
assert!(notice(&os(&mut e, "FORBID DEL NICK evil*"), "Removed"), "nick forbid removed");
|
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");
|
assert!(e.pre_register_check("evilbob", &reply).is_none(), "no longer forbidden");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue