diff --git a/src/engine/db/account.rs b/src/engine/db/account.rs index a9736d0..90d5b49 100644 --- a/src/engine/db/account.rs +++ b/src/engine/db/account.rs @@ -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, diff --git a/src/engine/db/network.rs b/src/engine/db/network.rs index cd4a3bb..fca90ad 100644 --- a/src/engine/db/network.rs +++ b/src/engine/db/network.rs @@ -5,13 +5,17 @@ impl Db { pub fn akill_add(&mut self, kind: XlineKind, mask: &str, setter: &str, reason: &str, expires: Option) -> Result { // 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 { // 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) }