port whoisport, ircv3_network_icon, profileLink, hidewhois (all config-driven)
This commit is contained in:
parent
f6fbff5270
commit
0d4c9297b5
5 changed files with 135 additions and 13 deletions
|
|
@ -104,3 +104,16 @@ amu_target = both
|
||||||
# reputationexpire = 2 1h # score<=2 decays after 1h (repeatable; * = any)
|
# reputationexpire = 2 1h # score<=2 decays after 1h (repeatable; * = any)
|
||||||
# reputationexpire = * 90d # any score decays after 90d
|
# reputationexpire = * 90d # any score decays after 90d
|
||||||
# extban usage: MODE #chan +b y:<100 (ban score below 100) +b y:>500 (above 500)
|
# extban usage: MODE #chan +b y:<100 (ban score below 100) +b y:>500 (above 500)
|
||||||
|
|
||||||
|
# --- whoisport: opers see the target's listener port in WHOIS (always on) ---
|
||||||
|
# --- ircv3_network_icon: advertise a network icon via draft/ICON ISUPPORT ---
|
||||||
|
# network_icon = https://example.org/icon.png
|
||||||
|
# --- profileLink: a profile URL in WHOIS for logged-in users ---
|
||||||
|
# profilelink_baseurl = https://example.org/profile/
|
||||||
|
# --- hidewhois: hide sensitive WHOIS lines from ordinary users ---
|
||||||
|
# hidewhois = yes
|
||||||
|
# hidewhois_opers = yes # opers still see everything
|
||||||
|
# hidewhois_selfview = yes # a user sees their own full WHOIS
|
||||||
|
# hidewhois_hide_server = yes # hide 312
|
||||||
|
# hidewhois_hide_idle = yes # hide 317
|
||||||
|
# hidewhois_hide_secure = yes # hide 671
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,14 @@
|
||||||
//! oper = god secret
|
//! oper = god secret
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
/// Parse a boolean config value (`yes`/`no`/`true`/`false`/`on`/`off`/`1`/`0`).
|
||||||
|
fn yesish(v: &str) -> bool {
|
||||||
|
!matches!(
|
||||||
|
v.to_ascii_lowercase().as_str(),
|
||||||
|
"off" | "no" | "false" | "0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Tri-state for a security-group criterion: don't-care / must-be / must-not-be.
|
/// Tri-state for a security-group criterion: don't-care / must-be / must-not-be.
|
||||||
#[derive(Clone, Copy, PartialEq, Default)]
|
#[derive(Clone, Copy, PartialEq, Default)]
|
||||||
pub enum Tri {
|
pub enum Tri {
|
||||||
|
|
@ -124,6 +132,15 @@ pub struct Config {
|
||||||
pub rep_minchanmembers: usize, // reputation: only bump if in a chan this big (3)
|
pub rep_minchanmembers: usize, // reputation: only bump if in a chan this big (3)
|
||||||
pub rep_whois: String, // reputation: whois visibility all|opers|self|none
|
pub rep_whois: String, // reputation: whois visibility all|opers|self|none
|
||||||
pub rep_expire_rules: Vec<(i32, u64)>, // (score-threshold, age-secs) decay rules
|
pub rep_expire_rules: Vec<(i32, u64)>, // (score-threshold, age-secs) decay rules
|
||||||
|
pub network_icon: String, // ircv3_network_icon: draft/ICON ISUPPORT url
|
||||||
|
pub profilelink_baseurl: String, // profileLink: WHOIS profile url base
|
||||||
|
pub hidewhois: bool, // hidewhois: hide sensitive WHOIS lines from users
|
||||||
|
pub hidewhois_opers: bool, // hidewhois: opers still see everything (default yes)
|
||||||
|
pub hidewhois_selfview: bool, // hidewhois: a user sees their own full WHOIS (yes)
|
||||||
|
pub hidewhois_server: bool, // hidewhois: hide 312 server line
|
||||||
|
pub hidewhois_idle: bool, // hidewhois: hide 317 idle line
|
||||||
|
pub hidewhois_away: bool, // hidewhois: hide 301 away line
|
||||||
|
pub hidewhois_secure: bool, // hidewhois: hide 671 secure line
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
|
@ -174,6 +191,15 @@ impl Default for Config {
|
||||||
rep_minchanmembers: 3,
|
rep_minchanmembers: 3,
|
||||||
rep_whois: "all".to_string(),
|
rep_whois: "all".to_string(),
|
||||||
rep_expire_rules: Vec::new(),
|
rep_expire_rules: Vec::new(),
|
||||||
|
network_icon: String::new(),
|
||||||
|
profilelink_baseurl: String::new(),
|
||||||
|
hidewhois: false,
|
||||||
|
hidewhois_opers: true,
|
||||||
|
hidewhois_selfview: true,
|
||||||
|
hidewhois_server: true,
|
||||||
|
hidewhois_idle: true,
|
||||||
|
hidewhois_away: true,
|
||||||
|
hidewhois_secure: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,6 +231,8 @@ impl Config {
|
||||||
Some(c)
|
Some(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// small helper is defined at module scope (see `yesish`).
|
||||||
|
|
||||||
/// Parse `key = value` lines into `c`; unknown keys and comments are ignored.
|
/// Parse `key = value` lines into `c`; unknown keys and comments are ignored.
|
||||||
fn parse_into(c: &mut Config, text: &str) {
|
fn parse_into(c: &mut Config, text: &str) {
|
||||||
for line in text.lines() {
|
for line in text.lines() {
|
||||||
|
|
@ -419,6 +447,20 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"network_icon" | "networkicon" => c.network_icon = v.to_string(),
|
||||||
|
"profilelink" | "profilelink_baseurl" => c.profilelink_baseurl = v.to_string(),
|
||||||
|
"hidewhois" => {
|
||||||
|
c.hidewhois = !matches!(
|
||||||
|
v.to_ascii_lowercase().as_str(),
|
||||||
|
"off" | "no" | "false" | "0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"hidewhois_opers" => c.hidewhois_opers = yesish(v),
|
||||||
|
"hidewhois_selfview" => c.hidewhois_selfview = yesish(v),
|
||||||
|
"hidewhois_hide_server" => c.hidewhois_server = yesish(v),
|
||||||
|
"hidewhois_hide_idle" => c.hidewhois_idle = yesish(v),
|
||||||
|
"hidewhois_hide_away" => c.hidewhois_away = yesish(v),
|
||||||
|
"hidewhois_hide_secure" => c.hidewhois_secure = yesish(v),
|
||||||
"securitygroup" | "secgroup" => {
|
"securitygroup" | "secgroup" => {
|
||||||
// securitygroup = <name> [public] [tls|insecure] [account|unregistered]
|
// securitygroup = <name> [public] [tls|insecure] [account|unregistered]
|
||||||
// [oper|exclude-oper] [bot|exclude-bot] [webirc|exclude-webirc]
|
// [oper|exclude-oper] [bot|exclude-bot] [webirc|exclude-webirc]
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,9 @@ impl Command for Whois {
|
||||||
};
|
};
|
||||||
let asker_oper = s.is_oper(uid);
|
let asker_oper = s.is_oper(uid);
|
||||||
let is_self = tuid == uid;
|
let is_self = tuid == uid;
|
||||||
|
// hidewhois: hide sensitive lines from ordinary users (opers/self exempt per config)
|
||||||
|
let hide =
|
||||||
|
s.hidewhois && !(is_self && s.hidewhois_selfview) && !(asker_oper && s.hidewhois_opers);
|
||||||
let keys: Vec<String> = s.users[&tuid].channels.iter().cloned().collect();
|
let keys: Vec<String> = s.users[&tuid].channels.iter().cloned().collect();
|
||||||
let (
|
let (
|
||||||
nick,
|
nick,
|
||||||
|
|
@ -203,11 +206,13 @@ impl Command for Whois {
|
||||||
if bot {
|
if bot {
|
||||||
s.numeric(uid, RPL_WHOISBOT, &format!("{nick} :is a bot"));
|
s.numeric(uid, RPL_WHOISBOT, &format!("{nick} :is a bot"));
|
||||||
}
|
}
|
||||||
|
if !(hide && s.hidewhois_server) {
|
||||||
s.numeric(
|
s.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_WHOISSERVER,
|
RPL_WHOISSERVER,
|
||||||
&format!("{nick} {} :echoIRCd", s.name),
|
&format!("{nick} {} :echoIRCd", s.name),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// +I hides the channel list from everyone but the user themselves + opers
|
// +I hides the channel list from everyone but the user themselves + opers
|
||||||
if !chans.is_empty() && (is_self || asker_oper || !hidechans) {
|
if !chans.is_empty() && (is_self || asker_oper || !hidechans) {
|
||||||
s.numeric(
|
s.numeric(
|
||||||
|
|
@ -245,6 +250,28 @@ impl Command for Whois {
|
||||||
s.numeric(uid, RPL_WHOISSPECIAL, &format!(":Score: {score}"));
|
s.numeric(uid, RPL_WHOISSPECIAL, &format!(":Score: {score}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// profileLink: a profile URL for logged-in users (when configured)
|
||||||
|
if !s.profilelink_baseurl.is_empty() {
|
||||||
|
match &account {
|
||||||
|
Some(acct) => s.numeric(
|
||||||
|
uid,
|
||||||
|
RPL_WHOISSPECIAL,
|
||||||
|
&format!(":Profil: {}{acct}", s.profilelink_baseurl),
|
||||||
|
),
|
||||||
|
None => s.numeric(
|
||||||
|
uid,
|
||||||
|
RPL_WHOISSPECIAL,
|
||||||
|
":Profile: The user is not logged in or the account is not registered.",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// whoisport: the listener port (+ TLS/plain) — opers only
|
||||||
|
if asker_oper {
|
||||||
|
let port = if secure { s.tls_port } else { s.plain_port };
|
||||||
|
if port != 0 {
|
||||||
|
s.numeric(uid, RPL_WHOISSPECIAL, &format!(":is using port {port}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
// opers can see through the cloak to the real host/ip
|
// opers can see through the cloak to the real host/ip
|
||||||
if asker_oper && disp != realhost {
|
if asker_oper && disp != realhost {
|
||||||
s.numeric(
|
s.numeric(
|
||||||
|
|
@ -262,7 +289,7 @@ impl Command for Whois {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// sslinfo: advertise a secure (TLS) connection
|
// sslinfo: advertise a secure (TLS) connection
|
||||||
if secure {
|
if secure && !(hide && s.hidewhois_secure) {
|
||||||
s.numeric(
|
s.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_WHOISSECURE,
|
RPL_WHOISSECURE,
|
||||||
|
|
@ -279,13 +306,15 @@ impl Command for Whois {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 317: idle time + signon time
|
// 317: idle time + signon time (hidewhois may suppress it)
|
||||||
|
if !(hide && s.hidewhois_idle) {
|
||||||
let idle = crate::server::now().saturating_sub(last_active);
|
let idle = crate::server::now().saturating_sub(last_active);
|
||||||
s.numeric(
|
s.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_WHOISIDLE,
|
RPL_WHOISIDLE,
|
||||||
&format!("{nick} {idle} {signon} :seconds idle, signon time"),
|
&format!("{nick} {idle} {signon} :seconds idle, signon time"),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// +W showwhois — tell the target that someone looked them up
|
// +W showwhois — tell the target that someone looked them up
|
||||||
if showwhois && !is_self {
|
if showwhois && !is_self {
|
||||||
let by = s.users.get(&uid).map(|u| u.prefix()).unwrap_or_default();
|
let by = s.users.get(&uid).map(|u| u.prefix()).unwrap_or_default();
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,14 @@ pub const PING_AFTER: u64 = 90;
|
||||||
pub const PING_TIMEOUT: u64 = 60;
|
pub const PING_TIMEOUT: u64 = 60;
|
||||||
pub const REG_TIMEOUT: u64 = 60;
|
pub const REG_TIMEOUT: u64 = 60;
|
||||||
|
|
||||||
|
/// The port from a `host:port` bind string (0 if unparseable) — for whoisport.
|
||||||
|
fn port_of(addr: &str) -> u16 {
|
||||||
|
addr.rsplit(':')
|
||||||
|
.next()
|
||||||
|
.and_then(|p| p.parse().ok())
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn now() -> u64 {
|
pub fn now() -> u64 {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
|
|
@ -155,6 +163,17 @@ pub struct Server {
|
||||||
pub rep_minchanmembers: usize, // reputation: min channel size to bump
|
pub rep_minchanmembers: usize, // reputation: min channel size to bump
|
||||||
pub rep_whois: String, // reputation: whois visibility mode
|
pub rep_whois: String, // reputation: whois visibility mode
|
||||||
pub rep_expire_rules: Vec<(i32, u64)>, // reputation: (score, age) decay rules
|
pub rep_expire_rules: Vec<(i32, u64)>, // reputation: (score, age) decay rules
|
||||||
|
pub network_icon: String, // ircv3_network_icon: draft/ICON url
|
||||||
|
pub profilelink_baseurl: String, // profileLink: WHOIS profile url base
|
||||||
|
pub hidewhois: bool, // hidewhois: enabled
|
||||||
|
pub hidewhois_opers: bool, // hidewhois: opers exempt
|
||||||
|
pub hidewhois_selfview: bool, // hidewhois: self exempt
|
||||||
|
pub hidewhois_server: bool, // hidewhois: hide 312
|
||||||
|
pub hidewhois_idle: bool, // hidewhois: hide 317
|
||||||
|
pub hidewhois_away: bool, // hidewhois: hide 301
|
||||||
|
pub hidewhois_secure: bool, // hidewhois: hide 671
|
||||||
|
pub plain_port: u16, // whoisport: the plaintext listener port
|
||||||
|
pub tls_port: u16, // whoisport: the TLS listener port (0 = none)
|
||||||
// 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
|
||||||
|
|
@ -228,6 +247,17 @@ impl Server {
|
||||||
rep_save_secs: cfg.rep_save_secs,
|
rep_save_secs: cfg.rep_save_secs,
|
||||||
rep_minchanmembers: cfg.rep_minchanmembers,
|
rep_minchanmembers: cfg.rep_minchanmembers,
|
||||||
rep_whois: cfg.rep_whois,
|
rep_whois: cfg.rep_whois,
|
||||||
|
network_icon: cfg.network_icon,
|
||||||
|
profilelink_baseurl: cfg.profilelink_baseurl,
|
||||||
|
hidewhois: cfg.hidewhois,
|
||||||
|
hidewhois_opers: cfg.hidewhois_opers,
|
||||||
|
hidewhois_selfview: cfg.hidewhois_selfview,
|
||||||
|
hidewhois_server: cfg.hidewhois_server,
|
||||||
|
hidewhois_idle: cfg.hidewhois_idle,
|
||||||
|
hidewhois_away: cfg.hidewhois_away,
|
||||||
|
hidewhois_secure: cfg.hidewhois_secure,
|
||||||
|
plain_port: port_of(&cfg.bind),
|
||||||
|
tls_port: cfg.bind_tls.as_deref().map(port_of).unwrap_or(0),
|
||||||
rep_expire_rules: if cfg.rep_expire_rules.is_empty() {
|
rep_expire_rules: if cfg.rep_expire_rules.is_empty() {
|
||||||
// Unreal defaults: score<=2 after 1h, <=6 after 7d, <=12 after 30d, any after 90d
|
// Unreal defaults: score<=2 after 1h, <=6 after 7d, <=12 after 30d, any after 90d
|
||||||
vec![(2, 3600), (6, 604800), (12, 2592000), (-1, 7776000)]
|
vec![(2, 3600), (6, 604800), (12, 2592000), (-1, 7776000)]
|
||||||
|
|
|
||||||
|
|
@ -447,6 +447,14 @@ impl Server {
|
||||||
self.network
|
self.network
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// ircv3_network_icon: advertise draft/ICON when configured
|
||||||
|
if !self.network_icon.is_empty() {
|
||||||
|
self.numeric(
|
||||||
|
uid,
|
||||||
|
RPL_ISUPPORT,
|
||||||
|
&format!("ICON={} :are supported by this server", self.network_icon),
|
||||||
|
);
|
||||||
|
}
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_LUSERCLIENT,
|
RPL_LUSERCLIENT,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue