operserv: type the X-line kind as an enum at the command/store boundary, wire token in storage
This commit is contained in:
parent
2c8ba4460d
commit
ef34c04c8d
9 changed files with 96 additions and 45 deletions
|
|
@ -551,9 +551,9 @@ impl ServiceCtx {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a network ban (X-line) at the ircd. `duration` seconds, 0 = permanent.
|
// Set a network ban (X-line) at the ircd. `duration` seconds, 0 = permanent.
|
||||||
pub fn add_line(&mut self, kind: &str, mask: &str, setter: &str, duration: u64, reason: &str) {
|
pub fn add_line(&mut self, kind: XlineKind, mask: &str, setter: &str, duration: u64, reason: &str) {
|
||||||
self.actions.push(NetAction::AddLine {
|
self.actions.push(NetAction::AddLine {
|
||||||
kind: kind.to_string(),
|
kind: kind.wire().to_string(),
|
||||||
mask: mask.to_string(),
|
mask: mask.to_string(),
|
||||||
setter: setter.to_string(),
|
setter: setter.to_string(),
|
||||||
duration,
|
duration,
|
||||||
|
|
@ -562,8 +562,8 @@ impl ServiceCtx {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lift a network ban previously set with `add_line`.
|
// Lift a network ban previously set with `add_line`.
|
||||||
pub fn del_line(&mut self, kind: &str, mask: &str) {
|
pub fn del_line(&mut self, kind: XlineKind, mask: &str) {
|
||||||
self.actions.push(NetAction::DelLine { kind: kind.to_string(), mask: mask.to_string() });
|
self.actions.push(NetAction::DelLine { kind: kind.wire().to_string(), mask: mask.to_string() });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect a user, sourced from pseudoclient `from`.
|
// Disconnect a user, sourced from pseudoclient `from`.
|
||||||
|
|
@ -787,7 +787,7 @@ pub struct VhostView {
|
||||||
// A network ban held by OperServ. `kind` is the ircd X-line type ("G", "Q", …).
|
// A network ban held by OperServ. `kind` is the ircd X-line type ("G", "Q", …).
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AkillView {
|
pub struct AkillView {
|
||||||
pub kind: String,
|
pub kind: XlineKind,
|
||||||
pub mask: String,
|
pub mask: String,
|
||||||
pub setter: String,
|
pub setter: String,
|
||||||
pub reason: String,
|
pub reason: String,
|
||||||
|
|
@ -795,6 +795,55 @@ pub struct AkillView {
|
||||||
pub expires: Option<u64>,
|
pub expires: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A network-ban type OperServ manages, identified by the ircd's X-line token.
|
||||||
|
// The wire (`NetAction::AddLine`), storage, and gossip stay the string token
|
||||||
|
// (a transparent passthrough — the ircd knows more line types than we model);
|
||||||
|
// this types OperServ's own command surface so a kind can't be mistyped.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum XlineKind {
|
||||||
|
Gline, // "G" — user@host ban (AKILL)
|
||||||
|
Qline, // "Q" — nick ban (SQLINE)
|
||||||
|
Rline, // "R" — realname ban (SNLINE)
|
||||||
|
Shun, // "SHUN"
|
||||||
|
Cban, // "CBAN" — channel-name ban
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XlineKind {
|
||||||
|
/// The ircd X-line token, as stored/gossiped and sent on the wire.
|
||||||
|
pub fn wire(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Gline => "G",
|
||||||
|
Self::Qline => "Q",
|
||||||
|
Self::Rline => "R",
|
||||||
|
Self::Shun => "SHUN",
|
||||||
|
Self::Cban => "CBAN",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a stored/wire token; `None` for a kind we don't model.
|
||||||
|
pub fn from_wire(s: &str) -> Option<Self> {
|
||||||
|
match s {
|
||||||
|
"G" => Some(Self::Gline),
|
||||||
|
"Q" => Some(Self::Qline),
|
||||||
|
"R" => Some(Self::Rline),
|
||||||
|
"SHUN" => Some(Self::Shun),
|
||||||
|
"CBAN" => Some(Self::Cban),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A human label for notices/audit lines.
|
||||||
|
pub fn label(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Gline => "network ban",
|
||||||
|
Self::Qline => "nick ban",
|
||||||
|
Self::Rline => "realname ban",
|
||||||
|
Self::Shun => "shun",
|
||||||
|
Self::Cban => "channel ban",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A registration-ban target for OperServ FORBID. The persistence/gossip layer
|
// A registration-ban target for OperServ FORBID. The persistence/gossip layer
|
||||||
// stores the wire token (`wire()`); the Store API is typed so a call site can't
|
// stores the wire token (`wire()`); the Store API is typed so a call site can't
|
||||||
// pass a mistyped kind like "NCK" (it wouldn't compile).
|
// pass a mistyped kind like "NCK" (it wouldn't compile).
|
||||||
|
|
@ -1167,8 +1216,8 @@ pub trait Store {
|
||||||
// Network bans (AKILL / G-lines, oper-only). `akill_add` returns whether the
|
// Network bans (AKILL / G-lines, oper-only). `akill_add` returns whether the
|
||||||
// mask was newly added (false = an existing entry was refreshed); `akills`
|
// mask was newly added (false = an existing entry was refreshed); `akills`
|
||||||
// lists only entries that haven't lazily expired, oldest first.
|
// lists only entries that haven't lazily expired, oldest first.
|
||||||
fn akill_add(&mut self, kind: &str, mask: &str, setter: &str, reason: &str, expires: Option<u64>) -> Result<bool, RegError>;
|
fn akill_add(&mut self, kind: XlineKind, mask: &str, setter: &str, reason: &str, expires: Option<u64>) -> Result<bool, RegError>;
|
||||||
fn akill_del(&mut self, kind: &str, mask: &str) -> Result<bool, RegError>;
|
fn akill_del(&mut self, kind: XlineKind, mask: &str) -> Result<bool, RegError>;
|
||||||
fn akills(&self) -> Vec<AkillView>;
|
fn akills(&self) -> Vec<AkillView>;
|
||||||
// Registration bans (OperServ FORBID).
|
// Registration bans (OperServ FORBID).
|
||||||
fn forbid_add(&mut self, kind: ForbidKind, mask: &str, setter: &str, reason: &str) -> Result<bool, RegError>;
|
fn forbid_add(&mut self, kind: ForbidKind, mask: &str, setter: &str, reason: &str) -> Result<bool, RegError>;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use echo_api::{NetView, Priv, Sender, ServiceCtx, Store};
|
use echo_api::{NetView, Priv, Sender, ServiceCtx, Store, XlineKind};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
// CHANKILL <#channel> [reason]: AKILL every user in a channel by host, clearing
|
// CHANKILL <#channel> [reason]: AKILL every user in a channel by host, clearing
|
||||||
|
|
@ -29,8 +29,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
||||||
let Some(host) = net.host_of(uid) else { continue };
|
let Some(host) = net.host_of(uid) else { continue };
|
||||||
if seen.insert(host.to_string()) {
|
if seen.insert(host.to_string()) {
|
||||||
let mask = format!("*@{host}");
|
let mask = format!("*@{host}");
|
||||||
let _ = db.akill_add("G", &mask, setter, &reason, None);
|
let _ = db.akill_add(XlineKind::Gline, &mask, setter, &reason, None);
|
||||||
ctx.add_line("G", &mask, from.nick, 0, &reason);
|
ctx.add_line(XlineKind::Gline, &mask, from.nick, 0, &reason);
|
||||||
banned += 1;
|
banned += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use echo_api::{Sender, ServiceCtx, Store};
|
use echo_api::{Sender, ServiceCtx, Store, XlineKind};
|
||||||
|
|
||||||
// STATS: an at-a-glance summary of the enforcement state OperServ holds — how
|
// STATS: an at-a-glance summary of the enforcement state OperServ holds — how
|
||||||
// many network bans of each kind and how many services ignores are live.
|
// many network bans of each kind and how many services ignores are live.
|
||||||
// Read-only, so any operator may run it (the module is already oper-gated).
|
// Read-only, so any operator may run it (the module is already oper-gated).
|
||||||
pub fn handle(me: &str, from: &Sender, db: &mut dyn Store, ctx: &mut ServiceCtx) {
|
pub fn handle(me: &str, from: &Sender, db: &mut dyn Store, ctx: &mut ServiceCtx) {
|
||||||
let akills = db.akills();
|
let akills = db.akills();
|
||||||
let glines = akills.iter().filter(|a| a.kind == "G").count();
|
let glines = akills.iter().filter(|a| a.kind == XlineKind::Gline).count();
|
||||||
let qlines = akills.iter().filter(|a| a.kind == "Q").count();
|
let qlines = akills.iter().filter(|a| a.kind == XlineKind::Qline).count();
|
||||||
let rlines = akills.iter().filter(|a| a.kind == "R").count();
|
let rlines = akills.iter().filter(|a| a.kind == XlineKind::Rline).count();
|
||||||
let ignores = db.ignores().len();
|
let ignores = db.ignores().len();
|
||||||
ctx.notice(me, from.uid, format!("Live network bans: \x02{glines}\x02 AKILL, \x02{qlines}\x02 SQLINE, \x02{rlines}\x02 SNLINE. Services ignores: \x02{ignores}\x02."));
|
ctx.notice(me, from.uid, format!("Live network bans: \x02{glines}\x02 AKILL, \x02{qlines}\x02 SQLINE, \x02{rlines}\x02 SNLINE. Services ignores: \x02{ignores}\x02."));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use echo_api::{parse_duration, Sender, ServiceCtx, Store};
|
use echo_api::{parse_duration, Sender, ServiceCtx, Store, XlineKind};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
// A network-ban command family (AKILL, SQLINE, …): the ircd X-line `kind`, the
|
// A network-ban command family (AKILL, SQLINE, …): the ircd X-line `kind`, the
|
||||||
// user-facing command `name`, and the mask shape it accepts. One implementation
|
// user-facing command `name`, and the mask shape it accepts. One implementation
|
||||||
// drives ADD / DEL / LIST for every kind so they stay consistent.
|
// drives ADD / DEL / LIST for every kind so they stay consistent.
|
||||||
pub struct Xline {
|
pub struct Xline {
|
||||||
pub kind: &'static str,
|
pub kind: XlineKind,
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub target: &'static str, // e.g. "user@host" or "nick" — shown in syntax
|
pub target: &'static str, // e.g. "user@host" or "nick" — shown in syntax
|
||||||
pub normalize: fn(&str) -> Option<String>,
|
pub normalize: fn(&str) -> Option<String>,
|
||||||
|
|
@ -114,22 +114,22 @@ impl Xline {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AKILL: a user@host G-line. Strips any nick! prefix, lowercases both sides.
|
// AKILL: a user@host G-line. Strips any nick! prefix, lowercases both sides.
|
||||||
pub const AKILL: Xline = Xline { kind: "G", name: "AKILL", target: "user@host", normalize: norm_userhost };
|
pub const AKILL: Xline = Xline { kind: XlineKind::Gline, name: "AKILL", target: "user@host", normalize: norm_userhost };
|
||||||
|
|
||||||
// SQLINE: a nick Q-line. The mask is a nick glob (no '@'); wildcards allowed.
|
// SQLINE: a nick Q-line. The mask is a nick glob (no '@'); wildcards allowed.
|
||||||
pub const SQLINE: Xline = Xline { kind: "Q", name: "SQLINE", target: "nick", normalize: norm_nick };
|
pub const SQLINE: Xline = Xline { kind: XlineKind::Qline, name: "SQLINE", target: "nick", normalize: norm_nick };
|
||||||
|
|
||||||
// SNLINE: a realname R-line. The mask is a regex the ircd matches against a
|
// SNLINE: a realname R-line. The mask is a regex the ircd matches against a
|
||||||
// connecting user's realname (use `.` for spaces, e.g. `.*free.money.*`).
|
// connecting user's realname (use `.` for spaces, e.g. `.*free.money.*`).
|
||||||
pub const SNLINE: Xline = Xline { kind: "R", name: "SNLINE", target: "realname-regex", normalize: norm_realname };
|
pub const SNLINE: Xline = Xline { kind: XlineKind::Rline, name: "SNLINE", target: "realname-regex", normalize: norm_realname };
|
||||||
|
|
||||||
// SHUN: a user@host shun. A matching user stays connected but the ircd silently
|
// SHUN: a user@host shun. A matching user stays connected but the ircd silently
|
||||||
// drops their commands — a quieter alternative to an AKILL.
|
// drops their commands — a quieter alternative to an AKILL.
|
||||||
pub const SHUN: Xline = Xline { kind: "SHUN", name: "SHUN", target: "user@host", normalize: norm_userhost };
|
pub const SHUN: Xline = Xline { kind: XlineKind::Shun, name: "SHUN", target: "user@host", normalize: norm_userhost };
|
||||||
|
|
||||||
// CBAN: a channel-name ban. Users can't join (or create) a channel matching the
|
// CBAN: a channel-name ban. Users can't join (or create) a channel matching the
|
||||||
// mask, which is a channel glob like `#warez*`.
|
// mask, which is a channel glob like `#warez*`.
|
||||||
pub const CBAN: Xline = Xline { kind: "CBAN", name: "CBAN", target: "#channel", normalize: norm_channel };
|
pub const CBAN: Xline = Xline { kind: XlineKind::Cban, name: "CBAN", target: "#channel", normalize: norm_channel };
|
||||||
|
|
||||||
fn norm_userhost(input: &str) -> Option<String> {
|
fn norm_userhost(input: &str) -> Option<String> {
|
||||||
let body = input.rsplit('!').next().unwrap_or(input);
|
let body = input.rsplit('!').next().unwrap_or(input);
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ pub(crate) use event::{apply, Scope};
|
||||||
// echo-api SDK crate; re-exported so the engine keeps naming them locally and
|
// echo-api SDK crate; re-exported so the engine keeps naming them locally and
|
||||||
// modules importing `crate::engine::db::{ChanError, ...}` are unaffected.
|
// modules importing `crate::engine::db::{ChanError, ...}` are unaffected.
|
||||||
pub use echo_api::{
|
pub use echo_api::{
|
||||||
AccountView, AjoinView, AkillView, BotView, Caps, ForbidKind, ForbidView, GroupView, HelpView, IgnoreView, MemoView, NewsView, Privs, ReportView, SuspensionView, ChanAccessView, ChanAkickView, ChanError, ChanSetting, ChannelView, CertError, CodeKind, Kicker, RegError, Store, TriggerView, VhostView,
|
AccountView, AjoinView, AkillView, BotView, Caps, ForbidKind, ForbidView, GroupView, HelpView, IgnoreView, MemoView, NewsView, Privs, ReportView, SuspensionView, ChanAccessView, ChanAkickView, ChanError, ChanSetting, ChannelView, CertError, CodeKind, Kicker, RegError, Store, TriggerView, VhostView, XlineKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,42 @@ use super::*;
|
||||||
|
|
||||||
impl Db {
|
impl Db {
|
||||||
/// Add (or refresh) a `kind` network ban. Returns whether it was newly added.
|
/// Add (or refresh) a `kind` network ban. Returns whether it was newly added.
|
||||||
pub fn akill_add(&mut self, kind: &str, mask: &str, setter: &str, reason: &str, expires: Option<u64>) -> Result<bool, RegError> {
|
pub fn akill_add(&mut self, kind: XlineKind, mask: &str, setter: &str, reason: &str, expires: Option<u64>) -> Result<bool, RegError> {
|
||||||
let same = |a: &Akill| a.kind == kind && a.mask.eq_ignore_ascii_case(mask);
|
// Storage/gossip keep the ircd's line token; the API is typed.
|
||||||
|
let wire = kind.wire();
|
||||||
|
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 > now()));
|
||||||
self.log
|
self.log
|
||||||
.append(Event::AkillAdded { kind: kind.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: now(), expires })
|
||||||
.map_err(|_| RegError::Internal)?;
|
.map_err(|_| RegError::Internal)?;
|
||||||
self.net.akills.retain(|a| !same(a));
|
self.net.akills.retain(|a| !same(a));
|
||||||
self.net.akills.push(Akill { kind: kind.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: now(), expires });
|
||||||
Ok(fresh)
|
Ok(fresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lift a `kind` network ban. Returns whether a live one was removed.
|
/// Lift a `kind` network ban. Returns whether a live one was removed.
|
||||||
pub fn akill_del(&mut self, kind: &str, mask: &str) -> Result<bool, RegError> {
|
pub fn akill_del(&mut self, kind: XlineKind, mask: &str) -> Result<bool, RegError> {
|
||||||
let same = |a: &Akill| a.kind == kind && a.mask.eq_ignore_ascii_case(mask);
|
let wire = kind.wire();
|
||||||
|
let same = |a: &Akill| a.kind == wire && a.mask.eq_ignore_ascii_case(mask);
|
||||||
let existed = self.net.akills.iter().any(|a| same(a) && a.expires.is_none_or(|e| e > now()));
|
let existed = self.net.akills.iter().any(|a| same(a) && a.expires.is_none_or(|e| e > now()));
|
||||||
if !existed {
|
if !existed {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
self.log.append(Event::AkillRemoved { kind: kind.to_string(), mask: mask.to_string() }).map_err(|_| RegError::Internal)?;
|
self.log.append(Event::AkillRemoved { kind: wire.to_string(), mask: mask.to_string() }).map_err(|_| RegError::Internal)?;
|
||||||
self.net.akills.retain(|a| !same(a));
|
self.net.akills.retain(|a| !same(a));
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The live network bans (expired ones hidden lazily), oldest first.
|
/// The live network bans (expired ones hidden lazily), oldest first. A stored
|
||||||
|
/// kind we don't model (peer-gossiped) is skipped from the typed view.
|
||||||
pub fn akills(&self) -> Vec<AkillView> {
|
pub fn akills(&self) -> Vec<AkillView> {
|
||||||
let now = now();
|
let now = now();
|
||||||
self.net.akills
|
self.net.akills
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| a.expires.is_none_or(|e| e > now))
|
.filter(|a| a.expires.is_none_or(|e| e > now))
|
||||||
.map(|a| AkillView { kind: a.kind.clone(), mask: a.mask.clone(), setter: a.setter.clone(), reason: a.reason.clone(), ts: a.ts, expires: a.expires })
|
.filter_map(|a| {
|
||||||
|
XlineKind::from_wire(&a.kind).map(|kind| AkillView { kind, mask: a.mask.clone(), setter: a.setter.clone(), reason: a.reason.clone(), ts: a.ts, expires: a.expires })
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,10 +209,10 @@ impl Store for Db {
|
||||||
fn set_channel_noexpire(&mut self, channel: &str, on: bool) -> Result<bool, ChanError> {
|
fn set_channel_noexpire(&mut self, channel: &str, on: bool) -> Result<bool, ChanError> {
|
||||||
Db::set_channel_noexpire(self, channel, on)
|
Db::set_channel_noexpire(self, channel, on)
|
||||||
}
|
}
|
||||||
fn akill_add(&mut self, kind: &str, mask: &str, setter: &str, reason: &str, expires: Option<u64>) -> Result<bool, RegError> {
|
fn akill_add(&mut self, kind: XlineKind, mask: &str, setter: &str, reason: &str, expires: Option<u64>) -> Result<bool, RegError> {
|
||||||
Db::akill_add(self, kind, mask, setter, reason, expires)
|
Db::akill_add(self, kind, mask, setter, reason, expires)
|
||||||
}
|
}
|
||||||
fn akill_del(&mut self, kind: &str, mask: &str) -> Result<bool, RegError> {
|
fn akill_del(&mut self, kind: XlineKind, mask: &str) -> Result<bool, RegError> {
|
||||||
Db::akill_del(self, kind, mask)
|
Db::akill_del(self, kind, mask)
|
||||||
}
|
}
|
||||||
fn akills(&self) -> Vec<AkillView> {
|
fn akills(&self) -> Vec<AkillView> {
|
||||||
|
|
|
||||||
|
|
@ -609,7 +609,7 @@
|
||||||
db.group_set_flags("!grp", "carol", "").unwrap();
|
db.group_set_flags("!grp", "carol", "").unwrap();
|
||||||
db.group_register("!other", "bob").unwrap();
|
db.group_register("!other", "bob").unwrap();
|
||||||
db.group_set_flags("!other", "alice", "").unwrap();
|
db.group_set_flags("!other", "alice", "").unwrap();
|
||||||
db.akill_add("G", "*@evil", "oper", "spam", None).unwrap();
|
db.akill_add(XlineKind::Gline, "*@evil", "oper", "spam", None).unwrap();
|
||||||
db.forbid_add(ForbidKind::Nick, "bad*", "oper", "squat").unwrap();
|
db.forbid_add(ForbidKind::Nick, "bad*", "oper", "squat").unwrap();
|
||||||
db.vhost_offer_add("cool.vhost").unwrap();
|
db.vhost_offer_add("cool.vhost").unwrap();
|
||||||
db.vhost_forbid_add("(?i)admin").unwrap();
|
db.vhost_forbid_add("(?i)admin").unwrap();
|
||||||
|
|
@ -640,8 +640,8 @@
|
||||||
db.del_vhost("bob").unwrap();
|
db.del_vhost("bob").unwrap();
|
||||||
db.suspend_account("carol", "oper", "oops", None).unwrap();
|
db.suspend_account("carol", "oper", "oops", None).unwrap();
|
||||||
db.unsuspend_account("carol").unwrap();
|
db.unsuspend_account("carol").unwrap();
|
||||||
db.akill_add("Q", "spam*", "oper", "nick", None).unwrap();
|
db.akill_add(XlineKind::Qline, "spam*", "oper", "nick", None).unwrap();
|
||||||
db.akill_del("Q", "spam*").unwrap();
|
db.akill_del(XlineKind::Qline, "spam*").unwrap();
|
||||||
db.forbid_add(ForbidKind::Chan, "#evil*", "oper", "bad").unwrap();
|
db.forbid_add(ForbidKind::Chan, "#evil*", "oper", "bad").unwrap();
|
||||||
db.forbid_del(ForbidKind::Chan, "#evil*").unwrap();
|
db.forbid_del(ForbidKind::Chan, "#evil*").unwrap();
|
||||||
let nid = db.news_add("logon", "temp notice", "oper");
|
let nid = db.news_add("logon", "temp notice", "oper");
|
||||||
|
|
@ -703,7 +703,7 @@
|
||||||
a.set_greet("alice", "hi there").unwrap();
|
a.set_greet("alice", "hi there").unwrap();
|
||||||
a.set_account_kill("alice", false).unwrap();
|
a.set_account_kill("alice", false).unwrap();
|
||||||
a.suspend_account("bob", "op", "bad", None).unwrap();
|
a.suspend_account("bob", "op", "bad", None).unwrap();
|
||||||
a.akill_add("G", "*@evil", "op", "spam", None).unwrap();
|
a.akill_add(XlineKind::Gline, "*@evil", "op", "spam", None).unwrap();
|
||||||
a.forbid_add(ForbidKind::Nick, "bad*", "op", "squat").unwrap();
|
a.forbid_add(ForbidKind::Nick, "bad*", "op", "squat").unwrap();
|
||||||
a.group_register("!g", "alice").unwrap();
|
a.group_register("!g", "alice").unwrap();
|
||||||
a.group_set_flags("!g", "bob", "").unwrap();
|
a.group_set_flags("!g", "bob", "").unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1044,7 +1044,7 @@ impl Engine {
|
||||||
let now = self.now_secs();
|
let now = self.now_secs();
|
||||||
for a in self.db.akills() {
|
for a in self.db.akills() {
|
||||||
let duration = a.expires.map(|e| e.saturating_sub(now)).unwrap_or(0);
|
let duration = a.expires.map(|e| e.saturating_sub(now)).unwrap_or(0);
|
||||||
out.push(NetAction::AddLine { kind: a.kind, mask: a.mask, setter: a.setter, duration, reason: a.reason });
|
out.push(NetAction::AddLine { kind: a.kind.wire().to_string(), mask: a.mask, setter: a.setter, duration, reason: a.reason });
|
||||||
}
|
}
|
||||||
// Re-introduce our juped servers over the fresh link.
|
// Re-introduce our juped servers over the fresh link.
|
||||||
for (name, sid, reason) in self.db.jupes() {
|
for (name, sid, reason) in self.db.jupes() {
|
||||||
|
|
@ -1524,14 +1524,10 @@ impl Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
// A human label for a network-ban X-line kind, for the audit feed.
|
// A human label for a network-ban X-line kind, for the audit feed.
|
||||||
|
// The audit feed labels the ban kind from the persisted (string) event token; an
|
||||||
|
// unmodelled/peer kind falls back to the generic label.
|
||||||
fn ban_kind_label(kind: &str) -> &'static str {
|
fn ban_kind_label(kind: &str) -> &'static str {
|
||||||
match kind {
|
echo_api::XlineKind::from_wire(kind).map(|k| k.label()).unwrap_or("network ban")
|
||||||
"Q" => "nick ban",
|
|
||||||
"R" => "realname ban",
|
|
||||||
"SHUN" => "shun",
|
|
||||||
"CBAN" => "channel ban",
|
|
||||||
_ => "network ban",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A rough human span for an expiry deadline in an email ("7 days", "12 hours").
|
// A rough human span for an expiry deadline in an email ("7 days", "12 hours").
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue