db: one timestamp for a ban/account's logged event and in-memory record, so a peer replaying it converges (two now() calls could straddle a second)
All checks were successful
CI / check (push) Successful in 3m55s

This commit is contained in:
Jean Chevronnet 2026-07-17 20:38:50 +00:00
parent 47dfa76fd5
commit 9a197fca3d
No known key found for this signature in database
2 changed files with 17 additions and 9 deletions

View file

@ -9,10 +9,11 @@ impl Db {
// Unverified only when email confirmation actually applies (email is
// configured and an address was given); otherwise verified immediately.
let verified = !(self.email_enabled && email.is_some());
let ts = now(); // one timestamp: registered-at == last-seen at creation
let account = Account {
name: name.to_string(),
email,
ts: now(),
ts,
home: self.log.origin.clone(),
scram256: Some(creds.scram256),
scram512: Some(creds.scram512),
@ -24,7 +25,7 @@ impl Db {
greet: String::new(), no_autoop: false, no_protect: false, hide_status: false,
vhost: None,
vhost_request: None,
last_seen: now(),
last_seen: ts,
noexpire: false,
expiry_warned: false,
oper_note: None,
@ -64,10 +65,11 @@ impl Db {
}
return Ok(());
}
let ts = now(); // one timestamp: registered-at == last-seen at creation
let account = Account {
name: name.to_string(),
email,
ts: now(),
ts,
home: self.log.origin.clone(),
scram256: Some(scram256.to_string()),
scram512: (!scram512.is_empty()).then(|| scram512.to_string()),
@ -79,7 +81,7 @@ impl Db {
greet: String::new(), no_autoop: false, no_protect: false, hide_status: false,
vhost: None,
vhost_request: None,
last_seen: now(),
last_seen: ts,
noexpire: false,
expiry_warned: false,
oper_note: None,

View file

@ -5,13 +5,17 @@ impl Db {
pub fn akill_add(&mut self, kind: XlineKind, mask: &str, setter: &str, reason: &str, expires: Option<u64>) -> Result<bool, RegError> {
// Storage/gossip keep the ircd's line token; the API is typed.
let wire = kind.wire();
// One timestamp for the logged event AND the in-memory record: two `now()`
// calls could straddle a second, so a peer replaying the event would store a
// different ts than us and gossip state would never converge.
let ts = now();
let same = |a: &Akill| a.kind == wire && a.mask.eq_ignore_ascii_case(mask);
let fresh = !self.net.akills.iter().any(|a| same(a) && a.expires.is_none_or(|e| e > now()));
let fresh = !self.net.akills.iter().any(|a| same(a) && a.expires.is_none_or(|e| e > ts));
self.log
.append(Event::AkillAdded { kind: wire.to_string(), mask: mask.to_string(), setter: setter.to_string(), reason: reason.to_string(), ts: now(), expires })
.append(Event::AkillAdded { kind: wire.to_string(), mask: mask.to_string(), setter: setter.to_string(), reason: reason.to_string(), ts, expires })
.map_err(|_| RegError::Internal)?;
self.net.akills.retain(|a| !same(a));
self.net.akills.push(Akill { kind: wire.to_string(), mask: mask.to_string(), setter: setter.to_string(), reason: reason.to_string(), ts: now(), expires });
self.net.akills.push(Akill { kind: wire.to_string(), mask: mask.to_string(), setter: setter.to_string(), reason: reason.to_string(), ts, expires });
Ok(fresh)
}
@ -46,13 +50,15 @@ impl Db {
pub fn forbid_add(&mut self, kind: ForbidKind, mask: &str, setter: &str, reason: &str) -> Result<bool, RegError> {
// The store persists/gossips the wire token; the API is typed.
let wire = kind.wire();
// One timestamp for the event AND the in-memory record (see akill_add).
let ts = now();
let same = |f: &Forbid| f.kind == wire && f.mask.eq_ignore_ascii_case(mask);
let fresh = !self.net.forbids.iter().any(same);
self.log
.append(Event::ForbidAdded { kind: wire.to_string(), mask: mask.to_string(), setter: setter.to_string(), reason: reason.to_string(), ts: now() })
.append(Event::ForbidAdded { kind: wire.to_string(), mask: mask.to_string(), setter: setter.to_string(), reason: reason.to_string(), ts })
.map_err(|_| RegError::Internal)?;
self.net.forbids.retain(|f| !same(f));
self.net.forbids.push(Forbid { kind: wire.to_string(), mask: mask.to_string(), setter: setter.to_string(), reason: reason.to_string(), ts: now() });
self.net.forbids.push(Forbid { kind: wire.to_string(), mask: mask.to_string(), setter: setter.to_string(), reason: reason.to_string(), ts });
Ok(fresh)
}