standard replies: emit IRCv3 FAIL/WARN/NOTE for service errors, config-gated
All checks were successful
CI / check (push) Successful in 3m42s
All checks were successful
CI / check (push) Successful in 3m42s
This commit is contained in:
parent
fcca7ac12b
commit
75c6537532
8 changed files with 153 additions and 5 deletions
|
|
@ -14,7 +14,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
// Distinguish an unregistered account from a wrong password.
|
||||
if !db.exists(account_name) {
|
||||
ctx.notice(me, from.uid, format!("\x02{account_name}\x02 isn't registered."));
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "ACCOUNT_NOT_REGISTERED", format!("\x02{account_name}\x02 isn't registered."));
|
||||
return;
|
||||
}
|
||||
// A suspended account can't be logged into (checked before the password so it
|
||||
|
|
@ -33,14 +33,14 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
msg.push_str(&format!(" The suspension is due to lift on {}.", human_time(exp)));
|
||||
}
|
||||
msg.push_str(" If you think this is a mistake, please contact the network staff.");
|
||||
ctx.notice(me, from.uid, msg);
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "ACCOUNT_SUSPENDED", msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Refuse while throttled, so a password can't be brute-forced.
|
||||
if let Some(secs) = db.auth_lockout(account_name) {
|
||||
ctx.notice(me, from.uid, format!("Too many failed attempts. Please wait {secs}s and try again."));
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "RATE_LIMITED", format!("Too many failed attempts. Please wait {secs}s and try again."));
|
||||
return;
|
||||
}
|
||||
// Fetch the verifier cheaply and hand the (~1s) PBKDF2 verify to the engine to
|
||||
|
|
@ -51,7 +51,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
// Exists but has no verifier (e.g. cert-only) — a password can't match.
|
||||
db.note_auth(account_name, false);
|
||||
ctx.count("nickserv.identify_fail");
|
||||
ctx.notice(me, from.uid, "Invalid password. Please try again.");
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "INVALID_CREDENTIALS", "Invalid password. Please try again.");
|
||||
}
|
||||
Some((account, verifier)) => {
|
||||
// Already identified to this account: skip the (wasted) verify.
|
||||
|
|
|
|||
|
|
@ -315,6 +315,17 @@ impl Protocol for InspIrcd {
|
|||
NetAction::Notice { from, to, text } => {
|
||||
vec![format!(":{} NOTICE {} :{}", from, to, text)]
|
||||
}
|
||||
// Standard reply: encapsulate for the target's server to re-emit locally
|
||||
// (FAIL/WARN/NOTE aren't s2s-routable). ENCAP * so only the server the
|
||||
// target is local to acts. "*" = no source service / no command.
|
||||
NetAction::StandardReply { kind, from, to, command, code, text } => {
|
||||
let src = if from.is_empty() { "*" } else { from.as_str() };
|
||||
let cmd = if command.is_empty() { "*" } else { command.as_str() };
|
||||
vec![self.sourced(format!(
|
||||
"ENCAP * SWSTDRPL {to} {src} {verb} {cmd} {code} :{text}",
|
||||
verb = kind.verb()
|
||||
))]
|
||||
}
|
||||
// Answer the origin server: ENCAP <origin> SWACCTRES <reqid> <kind>
|
||||
// <account> <status> <code> :<message>
|
||||
NetAction::AccountResponse { reqid, origin, kind, account, status, code, message } => {
|
||||
|
|
@ -623,6 +634,33 @@ mod tests {
|
|||
assert!(!lines[0].contains('\n') && !lines[0].contains('\r'));
|
||||
}
|
||||
|
||||
// A standard reply encapsulates as ENCAP * SWSTDRPL <target> <source> <verb>
|
||||
// <command> <code> :<text>, sourced from the services server so the target's
|
||||
// server (m_services_stdrpl) can re-emit it locally.
|
||||
#[test]
|
||||
fn standard_reply_serializes_to_encap() {
|
||||
let lines = proto().serialize(&NetAction::StandardReply {
|
||||
kind: echo_api::ReplyKind::Fail,
|
||||
from: "42SAAAAAB".into(),
|
||||
to: "0IRAAAAAB".into(),
|
||||
command: "IDENTIFY".into(),
|
||||
code: "INVALID_CREDENTIALS".into(),
|
||||
text: "Invalid password. Please try again.".into(),
|
||||
});
|
||||
assert_eq!(lines, vec![":42S ENCAP * SWSTDRPL 0IRAAAAAB 42SAAAAAB FAIL IDENTIFY INVALID_CREDENTIALS :Invalid password. Please try again.".to_string()]);
|
||||
|
||||
// Empty source/command collapse to "*".
|
||||
let lines = proto().serialize(&NetAction::StandardReply {
|
||||
kind: echo_api::ReplyKind::Note,
|
||||
from: String::new(),
|
||||
to: "0IRAAAAAB".into(),
|
||||
command: String::new(),
|
||||
code: "INFO".into(),
|
||||
text: "heads up".into(),
|
||||
});
|
||||
assert_eq!(lines, vec![":42S ENCAP * SWSTDRPL 0IRAAAAAB * NOTE * INFO :heads up".to_string()]);
|
||||
}
|
||||
|
||||
// Account-registration relay wire format, matching the ircd's account module:
|
||||
// a leaf forwards ENCAP <us> SWACCTREG <reqid> <origin> <kind> <account> <p2>
|
||||
// :<p3>, and we answer the origin server with ENCAP <origin> SWACCTRES. The
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue