OPERMOTD, self-service VHOST, and command aliases (config-driven)
This commit is contained in:
parent
6be8e5ebcb
commit
f4c01e1bf8
6 changed files with 138 additions and 0 deletions
|
|
@ -70,3 +70,13 @@ amu_target = both
|
||||||
|
|
||||||
# +G censor words: `badword = <find> [replacement]` (omit replacement to block).
|
# +G censor words: `badword = <find> [replacement]` (omit replacement to block).
|
||||||
# badword = examplebadword ***
|
# badword = examplebadword ***
|
||||||
|
|
||||||
|
# --- OPERMOTD: message shown to opers via /OPERMOTD (one line per entry) ---
|
||||||
|
# opermotd = Welcome to the staff team.
|
||||||
|
|
||||||
|
# --- self-service vhosts: /VHOST <user> <pass> sets your displayed host ---
|
||||||
|
# vhost = alice s3cret alice.staff.example
|
||||||
|
|
||||||
|
# --- command aliases: /NS ... -> PRIVMSG <target> :... (services shortcuts) ---
|
||||||
|
# alias = NS NickServ
|
||||||
|
# alias = CS ChanServ
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@ pub struct Config {
|
||||||
pub dnsbl_reason: String, // ban reason for a DNSBL hit
|
pub dnsbl_reason: String, // ban reason for a DNSBL hit
|
||||||
pub sasl_server: String, // linked services server that handles SASL ("" = none)
|
pub sasl_server: String, // linked services server that handles SASL ("" = none)
|
||||||
pub webirc: Vec<(String, String, String)>, // web gateways: (password, name, ip-mask)
|
pub webirc: Vec<(String, String, String)>, // web gateways: (password, name, ip-mask)
|
||||||
|
pub opermotd: Vec<String>, // OPERMOTD text, one line per entry
|
||||||
|
pub vhosts: Vec<(String, String, String)>, // self-service vhosts: (user, pass, host)
|
||||||
|
pub aliases: Vec<(String, String)>, // command aliases: (name, target-nick)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
|
@ -104,6 +107,9 @@ impl Default for Config {
|
||||||
dnsbl_reason: "Your host is listed in a DNS blocklist".to_string(),
|
dnsbl_reason: "Your host is listed in a DNS blocklist".to_string(),
|
||||||
sasl_server: String::new(),
|
sasl_server: String::new(),
|
||||||
webirc: Vec::new(),
|
webirc: Vec::new(),
|
||||||
|
opermotd: Vec::new(),
|
||||||
|
vhosts: Vec::new(),
|
||||||
|
aliases: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -246,6 +252,22 @@ impl Config {
|
||||||
c.webirc.push((pass.to_string(), gw, mask));
|
c.webirc.push((pass.to_string(), gw, mask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"opermotd" => c.opermotd.push(v.to_string()),
|
||||||
|
"vhost" => {
|
||||||
|
// vhost = <user> <pass> <host>
|
||||||
|
let mut it = v.split_whitespace();
|
||||||
|
if let (Some(u), Some(p), Some(h)) = (it.next(), it.next(), it.next()) {
|
||||||
|
c.vhosts.push((u.to_string(), p.to_string(), h.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"alias" => {
|
||||||
|
// alias = <command> <target-nick> (e.g. `alias = NS NickServ`)
|
||||||
|
let mut it = v.split_whitespace();
|
||||||
|
if let (Some(name), Some(target)) = (it.next(), it.next()) {
|
||||||
|
c.aliases
|
||||||
|
.push((name.to_ascii_uppercase(), target.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,47 @@ pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
Box::new(SetIdle),
|
Box::new(SetIdle),
|
||||||
Box::new(NickLock),
|
Box::new(NickLock),
|
||||||
Box::new(NickUnlock),
|
Box::new(NickUnlock),
|
||||||
|
Box::new(OperMotd),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OPERMOTD — show the IRC-operators' message of the day (InspIRCd `m_opermotd`),
|
||||||
|
/// configured with repeated `opermotd = <line>` entries.
|
||||||
|
struct OperMotd;
|
||||||
|
impl Command for OperMotd {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"OPERMOTD"
|
||||||
|
}
|
||||||
|
fn handle(&self, s: &mut Server, uid: Uid, _params: &[String]) -> CmdResult {
|
||||||
|
if !require_oper(s, uid) {
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
let nick = oper_nick(s, uid);
|
||||||
|
if s.opermotd.is_empty() {
|
||||||
|
s.send(
|
||||||
|
uid,
|
||||||
|
format!(":{} NOTICE {nick} :No OPERMOTD is set", s.name),
|
||||||
|
);
|
||||||
|
return CmdResult::Ok;
|
||||||
|
}
|
||||||
|
s.send(
|
||||||
|
uid,
|
||||||
|
format!(
|
||||||
|
":{} NOTICE {nick} :- IRC Operators Message of the Day -",
|
||||||
|
s.name
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for line in s.opermotd.clone() {
|
||||||
|
s.send(uid, format!(":{} NOTICE {nick} :- {line}", s.name));
|
||||||
|
}
|
||||||
|
s.send(
|
||||||
|
uid,
|
||||||
|
format!(":{} NOTICE {nick} :- End of OPERMOTD -", s.name),
|
||||||
|
);
|
||||||
|
CmdResult::Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An oper-set WHOIS line, stored per-user in `User.ext` and rendered by WHOIS
|
/// An oper-set WHOIS line, stored per-user in `User.ext` and rendered by WHOIS
|
||||||
/// (RPL_WHOISSPECIAL 320). InspIRCd `m_swhois`.
|
/// (RPL_WHOISSPECIAL 320). InspIRCd `m_swhois`.
|
||||||
pub struct Swhois(pub String);
|
pub struct Swhois(pub String);
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,52 @@ pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
Box::new(Away),
|
Box::new(Away),
|
||||||
Box::new(SetName),
|
Box::new(SetName),
|
||||||
Box::new(WebIrc),
|
Box::new(WebIrc),
|
||||||
|
Box::new(Vhost),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// VHOST — claim a self-service virtual host with `VHOST <user> <pass>` matching a
|
||||||
|
/// configured `vhost = <user> <pass> <host>` block (InspIRCd `m_vhost`).
|
||||||
|
struct Vhost;
|
||||||
|
impl Command for Vhost {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"VHOST"
|
||||||
|
}
|
||||||
|
fn min_params(&self) -> usize {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
|
let (user, pass) = (¶ms[0], ¶ms[1]);
|
||||||
|
let host = s
|
||||||
|
.vhosts
|
||||||
|
.iter()
|
||||||
|
.find(|(u, p, _)| u == user && p == pass)
|
||||||
|
.map(|(_, _, h)| h.clone());
|
||||||
|
let nick = s
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.nick.clone())
|
||||||
|
.unwrap_or_else(|| "*".to_string());
|
||||||
|
match host {
|
||||||
|
Some(h) => {
|
||||||
|
s.change_host_ident(uid, None, Some(&h));
|
||||||
|
s.send(
|
||||||
|
uid,
|
||||||
|
format!(":{} NOTICE {nick} :Your vhost is now {h}", s.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
s.send(
|
||||||
|
uid,
|
||||||
|
format!(":{} NOTICE {nick} :Invalid vhost credentials", s.name),
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CmdResult::Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// WEBIRC — a trusted web gateway declares the real client's host + IP, so users
|
/// WEBIRC — a trusted web gateway declares the real client's host + IP, so users
|
||||||
/// behind it don't all share the gateway's address. `WEBIRC <password> <gateway>
|
/// behind it don't all share the gateway's address. `WEBIRC <password> <gateway>
|
||||||
/// <hostname> <ip> [:flags]`; must precede registration and the password must
|
/// <hostname> <ip> [:flags]`; must precede registration and the password must
|
||||||
|
|
|
||||||
19
src/ircd.rs
19
src/ircd.rs
|
|
@ -228,6 +228,25 @@ impl Ircd {
|
||||||
|
|
||||||
let Some(handler) = self.commands.get(cmd) else {
|
let Some(handler) = self.commands.get(cmd) else {
|
||||||
if registered {
|
if registered {
|
||||||
|
// command aliases (m_alias): `/NS help` -> PRIVMSG NickServ :help
|
||||||
|
if let Some(target) = self
|
||||||
|
.server
|
||||||
|
.aliases
|
||||||
|
.iter()
|
||||||
|
.find(|(n, _)| n == cmd)
|
||||||
|
.map(|(_, t)| t.clone())
|
||||||
|
{
|
||||||
|
if !msg.params.is_empty() {
|
||||||
|
let text = msg.params.join(" ");
|
||||||
|
crate::coremods::core_message::deliver(
|
||||||
|
&mut self.server,
|
||||||
|
uid,
|
||||||
|
&[target, text],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.server
|
self.server
|
||||||
.numeric(uid, ERR_UNKNOWNCOMMAND, &format!("{cmd} :Unknown command"));
|
.numeric(uid, ERR_UNKNOWNCOMMAND, &format!("{cmd} :Unknown command"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,9 @@ pub struct Server {
|
||||||
pub dnsbl_reason: String, // ban reason on a DNSBL hit
|
pub dnsbl_reason: String, // ban reason on a DNSBL hit
|
||||||
pub sasl_server: String, // services server that handles SASL
|
pub sasl_server: String, // services server that handles SASL
|
||||||
pub webirc: Vec<(String, String, String)>, // web gateways: (password, name, ip-mask)
|
pub webirc: Vec<(String, String, String)>, // web gateways: (password, name, ip-mask)
|
||||||
|
pub opermotd: Vec<String>, // OPERMOTD text
|
||||||
|
pub vhosts: Vec<(String, String, String)>, // self-service vhosts: (user, pass, host)
|
||||||
|
pub aliases: Vec<(String, String)>, // command aliases: (name, target-nick)
|
||||||
// labeled-response: while Some((uid, buf)), that client's own responses are
|
// labeled-response: while Some((uid, buf)), that client's own responses are
|
||||||
// diverted into `buf` instead of the socket, so `on_line` can wrap them with
|
// diverted into `buf` instead of the socket, so `on_line` can wrap them with
|
||||||
// the command's `label` (single tag, BATCH, or ACK). RefCell because the
|
// the command's `label` (single tag, BATCH, or ACK). RefCell because the
|
||||||
|
|
@ -183,6 +186,9 @@ impl Server {
|
||||||
dnsbl_reason: cfg.dnsbl_reason,
|
dnsbl_reason: cfg.dnsbl_reason,
|
||||||
sasl_server: cfg.sasl_server,
|
sasl_server: cfg.sasl_server,
|
||||||
webirc: cfg.webirc,
|
webirc: cfg.webirc,
|
||||||
|
opermotd: cfg.opermotd,
|
||||||
|
vhosts: cfg.vhosts,
|
||||||
|
aliases: cfg.aliases,
|
||||||
label_capture: RefCell::new(None),
|
label_capture: RefCell::new(None),
|
||||||
event_tx,
|
event_tx,
|
||||||
conn_counter,
|
conn_counter,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue