diff --git a/modules/nickserv/nickserv.rs b/modules/nickserv/nickserv.rs index fb2af92..f9359c6 100644 --- a/modules/nickserv/nickserv.rs +++ b/modules/nickserv/nickserv.rs @@ -39,12 +39,18 @@ impl Service for NickServ { nick: from.nick.to_string(), }); } - Some("IDENTIFY") => { - let Some(password) = args.get(1) else { - ctx.notice(me, from.uid, "Syntax: IDENTIFY "); - return; + Some("IDENTIFY") | Some("ID") => { + // IDENTIFY [account] — the account defaults to the + // current nick, so both the bare-password and account+password forms work. + let (account_name, password) = match (args.get(1), args.get(2)) { + (Some(account), Some(password)) => (*account, *password), + (Some(password), None) => (from.nick, *password), + _ => { + ctx.notice(me, from.uid, "Syntax: IDENTIFY [account] "); + return; + } }; - match db.authenticate(from.nick, password) { + match db.authenticate(account_name, password) { Some(account) => { // Already identified to this account: don't re-fire the login. if from.account == Some(account) { @@ -75,7 +81,7 @@ impl Service for NickServ { Some("HELP") => ctx.notice( me, from.uid, - "NickServ looks after your nickname. Commands: \x02REGISTER\x02 [email], \x02IDENTIFY\x02 , \x02LOGOUT\x02, \x02CERT\x02 ADD|DEL|LIST [fingerprint].", + "NickServ looks after your nickname. Commands: \x02REGISTER\x02 [email], \x02IDENTIFY\x02 [account] , \x02LOGOUT\x02, \x02CERT\x02 ADD|DEL|LIST [fingerprint].", ), Some(other) => ctx.notice(me, from.uid, format!("I don't know the command \x02{other}\x02. Try \x02HELP\x02.")), None => {} diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 2c77531..f8980bb 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -978,6 +978,25 @@ mod tests { assert!(out.iter().any(|a| matches!(a, NetAction::Metadata { key, value, .. } if key == "accountname" && value == "realnick")), "must log into the current nick's account: {out:?}"); } + // IDENTIFY accepts the two-arg account+password form: log into a named + // account even when the current nick differs; a wrong password is refused. + #[test] + fn identify_two_arg_account_form() { + let mut e = engine_with("twoarg", "realacct", "sesame"); + e.handle(NetEvent::UserConnect { uid: "000AAAAAB".into(), nick: "someguest".into(), host: "host.example".into() }); + let out = e.handle(NetEvent::Privmsg { + from: "000AAAAAB".into(), to: "42SAAAAAA".into(), text: "IDENTIFY realacct sesame".into(), + }); + assert!(out.iter().any(|a| matches!(a, NetAction::Metadata { key, value, .. } if key == "accountname" && value == "realacct")), "two-arg identify logs into the named account: {out:?}"); + + e.handle(NetEvent::UserConnect { uid: "000AAAAAC".into(), nick: "other".into(), host: "host.example".into() }); + let bad = e.handle(NetEvent::Privmsg { + from: "000AAAAAC".into(), to: "42SAAAAAA".into(), text: "IDENTIFY realacct wrongpw".into(), + }); + assert!(bad.iter().any(|a| matches!(a, NetAction::Notice { text, .. } if text.contains("Invalid password"))), "{bad:?}"); + assert!(!bad.iter().any(|a| matches!(a, NetAction::Metadata { .. })), "wrong password must not log in: {bad:?}"); + } + // ChanServ: registration needs identification, INFO shows the founder, and // only the founder can drop. #[test]