NickServ: RECOVER regains your nick
All checks were successful
CI / check (push) Successful in 3m41s
All checks were successful
CI / check (push) Successful in 3m41s
Split RECOVER from GHOST: GHOST just frees a session off a nick you own, while RECOVER also puts you back onto it (frees the ghost if present, then SVSNICKs you to the nick). RECOVER works on an already-free nick too.
This commit is contained in:
parent
b5e08c33cf
commit
125b8c7701
3 changed files with 52 additions and 17 deletions
|
|
@ -4,12 +4,14 @@ use echo_api::NetView;
|
||||||
|
|
||||||
// GHOST/RECOVER <nick> [password]: rename off a session using a nick you own,
|
// GHOST/RECOVER <nick> [password]: rename off a session using a nick you own,
|
||||||
// either by being identified to its account or giving that account's password.
|
// either by being identified to its account or giving that account's password.
|
||||||
// Every argument is a distinct input the guest-rename needs (the guest nick and
|
// GHOST just frees the nick; RECOVER (`regain`) also puts the caller back onto
|
||||||
// its sequence counter among them), so the count is inherent.
|
// it. Every argument is a distinct input the guest-rename needs, so the count is
|
||||||
|
// inherent.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store) {
|
pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store, regain: bool) {
|
||||||
|
let cmd = if regain { "RECOVER" } else { "GHOST" };
|
||||||
let Some(&target) = args.get(1) else {
|
let Some(&target) = args.get(1) else {
|
||||||
ctx.notice(me, from.uid, "Syntax: GHOST <nick> [password]");
|
ctx.notice(me, from.uid, format!("Syntax: {cmd} <nick> [password]"));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(account) = db.resolve_account(target).map(str::to_string) else {
|
let Some(account) = db.resolve_account(target).map(str::to_string) else {
|
||||||
|
|
@ -21,16 +23,27 @@ pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, ar
|
||||||
ctx.notice(me, from.uid, format!("Access denied. Identify to \x02{account}\x02 or give its password."));
|
ctx.notice(me, from.uid, format!("Access denied. Identify to \x02{account}\x02 or give its password."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(ghost) = net.uid_by_nick(target).map(str::to_string) else {
|
// Free the nick if someone else is holding it.
|
||||||
ctx.notice(me, from.uid, format!("Nobody is using \x02{target}\x02."));
|
match net.uid_by_nick(target).map(str::to_string) {
|
||||||
return;
|
Some(ghost) if ghost == from.uid => {
|
||||||
};
|
|
||||||
if ghost == from.uid {
|
|
||||||
ctx.notice(me, from.uid, "That's you.");
|
ctx.notice(me, from.uid, "That's you.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Some(ghost) => {
|
||||||
let guest = format!("{guest_nick}{guest_seq}");
|
let guest = format!("{guest_nick}{guest_seq}");
|
||||||
*guest_seq = guest_seq.wrapping_add(1);
|
*guest_seq = guest_seq.wrapping_add(1);
|
||||||
ctx.force_nick(&ghost, &guest);
|
ctx.force_nick(&ghost, &guest);
|
||||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been freed."));
|
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been freed."));
|
||||||
|
}
|
||||||
|
None if !regain => {
|
||||||
|
ctx.notice(me, from.uid, format!("Nobody is using \x02{target}\x02."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
None => {} // already free — RECOVER will simply regain it
|
||||||
|
}
|
||||||
|
// RECOVER puts the caller back onto the nick (queued after any guest-rename).
|
||||||
|
if regain {
|
||||||
|
ctx.force_nick(from.uid, target);
|
||||||
|
ctx.notice(me, from.uid, format!("You have regained \x02{target}\x02."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,8 @@ const TOPICS: &[HelpEntry] = &[
|
||||||
HelpEntry { cmd: "GROUP", summary: "link this nick to an account", detail: "Syntax: \x02GROUP <account> <password>\x02\nLinks your current nick to an account as an alias, so identifying under it logs into that account." },
|
HelpEntry { cmd: "GROUP", summary: "link this nick to an account", detail: "Syntax: \x02GROUP <account> <password>\x02\nLinks your current nick to an account as an alias, so identifying under it logs into that account." },
|
||||||
HelpEntry { cmd: "GLIST", summary: "list your grouped nicks", detail: "Syntax: \x02GLIST\x02\nLists the nicks grouped to your account." },
|
HelpEntry { cmd: "GLIST", summary: "list your grouped nicks", detail: "Syntax: \x02GLIST\x02\nLists the nicks grouped to your account." },
|
||||||
HelpEntry { cmd: "UNGROUP", summary: "remove a grouped nick", detail: "Syntax: \x02UNGROUP [nick]\x02\nRemoves a grouped nick (your current one by default)." },
|
HelpEntry { cmd: "UNGROUP", summary: "remove a grouped nick", detail: "Syntax: \x02UNGROUP [nick]\x02\nRemoves a grouped nick (your current one by default)." },
|
||||||
HelpEntry { cmd: "GHOST", summary: "disconnect a session on your nick", detail: "Syntax: \x02GHOST <nick> [password]\x02\nDisconnects a session using a nick you own. Also \x02RECOVER\x02." },
|
HelpEntry { cmd: "GHOST", summary: "free a session on your nick", detail: "Syntax: \x02GHOST <nick> [password]\x02\nRenames off a session using a nick you own." },
|
||||||
|
HelpEntry { cmd: "RECOVER", summary: "reclaim your nick", detail: "Syntax: \x02RECOVER <nick> [password]\x02\nFrees a nick you own and puts you back onto it." },
|
||||||
HelpEntry { cmd: "RESETPASS", summary: "reset your password by email", detail: "Syntax: \x02RESETPASS <account>\x02, then \x02RESETPASS <account> <code> <newpassword>\x02\nEmails a reset code, then sets a new password with it." },
|
HelpEntry { cmd: "RESETPASS", summary: "reset your password by email", detail: "Syntax: \x02RESETPASS <account>\x02, then \x02RESETPASS <account> <code> <newpassword>\x02\nEmails a reset code, then sets a new password with it." },
|
||||||
HelpEntry { cmd: "CONFIRM", summary: "confirm your email", detail: "Syntax: \x02CONFIRM <code>\x02\nConfirms the email on a newly registered account with the code you were sent." },
|
HelpEntry { cmd: "CONFIRM", summary: "confirm your email", detail: "Syntax: \x02CONFIRM <code>\x02\nConfirms the email on a newly registered account with the code you were sent." },
|
||||||
HelpEntry { cmd: "DROP", summary: "delete your account", detail: "Syntax: \x02DROP <password>\x02\nDeletes your account and releases the channels you founded." },
|
HelpEntry { cmd: "DROP", summary: "delete your account", detail: "Syntax: \x02DROP <password>\x02\nDeletes your account and releases the channels you founded." },
|
||||||
|
|
@ -117,7 +118,8 @@ impl Service for NickServ {
|
||||||
Some("GROUP") => group::handle(me, from, args, ctx, db),
|
Some("GROUP") => group::handle(me, from, args, ctx, db),
|
||||||
Some("GLIST") => glist::handle(me, from, ctx, db),
|
Some("GLIST") => glist::handle(me, from, ctx, db),
|
||||||
Some("UNGROUP") => ungroup::handle(me, from, args, ctx, db),
|
Some("UNGROUP") => ungroup::handle(me, from, args, ctx, db),
|
||||||
Some("GHOST") | Some("RECOVER") => ghost::handle(me, &self.guest_nick, &mut self.guest_seq, from, args, ctx, net, db),
|
Some("GHOST") => ghost::handle(me, &self.guest_nick, &mut self.guest_seq, from, args, ctx, net, db, false),
|
||||||
|
Some("RECOVER") => ghost::handle(me, &self.guest_nick, &mut self.guest_seq, from, args, ctx, net, db, true),
|
||||||
Some("RESETPASS") => resetpass::handle(me, from, args, ctx, db),
|
Some("RESETPASS") => resetpass::handle(me, from, args, ctx, db),
|
||||||
Some("CONFIRM") => confirm::handle(me, from, args, ctx, db),
|
Some("CONFIRM") => confirm::handle(me, from, args, ctx, db),
|
||||||
Some("AJOIN") => ajoin::handle(me, from, args, ctx, db),
|
Some("AJOIN") => ajoin::handle(me, from, args, ctx, db),
|
||||||
|
|
|
||||||
|
|
@ -1073,6 +1073,26 @@
|
||||||
assert!(notice(&ns(&mut e, "000AAAAAB", "GETEMAIL nobody@z.com"), "No accounts"), "no match reported");
|
assert!(notice(&ns(&mut e, "000AAAAAB", "GETEMAIL nobody@z.com"), "No accounts"), "no match reported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NickServ RECOVER frees a ghost off your nick and puts you back onto it.
|
||||||
|
#[test]
|
||||||
|
fn nickserv_recover_regains_nick() {
|
||||||
|
use echo_nickserv::NickServ;
|
||||||
|
let path = std::env::temp_dir().join("echo-recover.jsonl");
|
||||||
|
let _ = std::fs::remove_file(&path);
|
||||||
|
let mut db = Db::open(&path, "42S");
|
||||||
|
db.scram_iterations = 4096;
|
||||||
|
db.register("alice", "sesame", None).unwrap();
|
||||||
|
let mut e = Engine::new(vec![Box::new(NickServ { uid: "42SAAAAAA".into(), guest_nick: "Guest".into(), guest_seq: 0 })], db);
|
||||||
|
// A ghost sits on nick "alice"; the owner is on another nick and identifies.
|
||||||
|
e.handle(NetEvent::UserConnect { uid: "000AAAAAB".into(), nick: "alice".into(), host: "h".into(), ip: "0.0.0.0".into() });
|
||||||
|
e.handle(NetEvent::UserConnect { uid: "000AAAAAC".into(), nick: "owner".into(), host: "h".into(), ip: "0.0.0.0".into() });
|
||||||
|
e.handle(NetEvent::Privmsg { from: "000AAAAAC".into(), to: "42SAAAAAA".into(), text: "IDENTIFY alice sesame".into() });
|
||||||
|
|
||||||
|
let out = e.handle(NetEvent::Privmsg { from: "000AAAAAC".into(), to: "42SAAAAAA".into(), text: "RECOVER alice".into() });
|
||||||
|
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAB" && nick.starts_with("Guest"))), "ghost freed: {out:?}");
|
||||||
|
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAC" && nick == "alice")), "caller regained the nick: {out:?}");
|
||||||
|
}
|
||||||
|
|
||||||
// ChanServ SET RESTRICTED kicks joining users who have no channel access.
|
// ChanServ SET RESTRICTED kicks joining users who have no channel access.
|
||||||
#[test]
|
#[test]
|
||||||
fn chanserv_restricted_kicks_users_without_access() {
|
fn chanserv_restricted_kicks_users_without_access() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue