brand: per-SNI server/network identity (welcome, ISUPPORT NETWORK, numeric source prefix)
This commit is contained in:
parent
f36f803d42
commit
b6ada854cc
10 changed files with 153 additions and 27 deletions
|
|
@ -56,6 +56,16 @@ pub struct OperBlock {
|
||||||
pub oper_type: Option<String>,
|
pub oper_type: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Per-SNI branding: a client that connected via `host` (TLS SNI) is shown
|
||||||
|
/// `servername`/`network` instead of the global ones — one daemon, multiple
|
||||||
|
/// network identities. Repeatable.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct BrandBlock {
|
||||||
|
pub host: String,
|
||||||
|
pub servername: String,
|
||||||
|
pub network: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// A trusted WEBIRC gateway: after presenting `password` it may rewrite a client's
|
/// A trusted WEBIRC gateway: after presenting `password` it may rewrite a client's
|
||||||
/// real host + IP. `ipmask` (empty = any) restricts which source addresses may use
|
/// real host + IP. `ipmask` (empty = any) restricts which source addresses may use
|
||||||
/// this block.
|
/// this block.
|
||||||
|
|
@ -116,6 +126,7 @@ pub struct Config {
|
||||||
pub tls_key: Option<String>, // PEM private key
|
pub tls_key: Option<String>, // PEM private key
|
||||||
pub motd: Vec<String>,
|
pub motd: Vec<String>,
|
||||||
pub opers: Vec<OperBlock>, // oper logins (see OperBlock)
|
pub opers: Vec<OperBlock>, // oper logins (see OperBlock)
|
||||||
|
pub brands: Vec<BrandBlock>, // per-SNI server/network branding
|
||||||
pub cloak_key: Option<String>, // secret key for host cloaking (+x); None = off
|
pub cloak_key: Option<String>, // secret key for host cloaking (+x); None = off
|
||||||
pub sid: String, // this server's 3-char server id (S2S)
|
pub sid: String, // this server's 3-char server id (S2S)
|
||||||
pub serverdesc: String, // this server's description
|
pub serverdesc: String, // this server's description
|
||||||
|
|
@ -147,6 +158,7 @@ impl Default for Config {
|
||||||
tls_key: None,
|
tls_key: None,
|
||||||
motd: Vec::new(),
|
motd: Vec::new(),
|
||||||
opers: Vec::new(),
|
opers: Vec::new(),
|
||||||
|
brands: Vec::new(),
|
||||||
cloak_key: None,
|
cloak_key: None,
|
||||||
sid: "0AA".to_string(),
|
sid: "0AA".to_string(),
|
||||||
serverdesc: "echoIRCd server".to_string(),
|
serverdesc: "echoIRCd server".to_string(),
|
||||||
|
|
@ -265,6 +277,24 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"motd" => c.motd.push(v.to_string()),
|
"motd" => c.motd.push(v.to_string()),
|
||||||
|
"brand" => {
|
||||||
|
// brand = <host> [servername=<sv>] [network=<nw>]
|
||||||
|
let mut it = v.split_whitespace();
|
||||||
|
if let Some(host) = it.next() {
|
||||||
|
let mut b = BrandBlock {
|
||||||
|
host: host.to_ascii_lowercase(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
for tok in it {
|
||||||
|
if let Some(s) = tok.strip_prefix("servername=") {
|
||||||
|
b.servername = s.to_string();
|
||||||
|
} else if let Some(n) = tok.strip_prefix("network=") {
|
||||||
|
b.network = n.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.brands.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
"oper" => {
|
"oper" => {
|
||||||
let mut it = v.split_whitespace().peekable();
|
let mut it = v.split_whitespace().peekable();
|
||||||
if let Some(n) = it.next() {
|
if let Some(n) = it.next() {
|
||||||
|
|
@ -632,6 +662,20 @@ fn emit_block(out: &mut String, name: &str, fields: &[(String, String)]) {
|
||||||
emit_line(out, "cloak_cert_prefix", v);
|
emit_line(out, "cloak_cert_prefix", v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"brand" => {
|
||||||
|
if let Some(host) = get("host") {
|
||||||
|
let mut line = host.to_string();
|
||||||
|
if let Some(v) = get("servername") {
|
||||||
|
line.push_str(" servername=");
|
||||||
|
line.push_str(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = get("network") {
|
||||||
|
line.push_str(" network=");
|
||||||
|
line.push_str(v);
|
||||||
|
}
|
||||||
|
emit_line(out, "brand", &line);
|
||||||
|
}
|
||||||
|
}
|
||||||
"listen" => {
|
"listen" => {
|
||||||
if let (Some(ip), Some(port)) = (get("ip"), get("port")) {
|
if let (Some(ip), Some(port)) = (get("ip"), get("port")) {
|
||||||
let addr = if ip.contains(':') && !ip.starts_with('[') {
|
let addr = if ip.contains(':') && !ip.starts_with('[') {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ pub enum Event {
|
||||||
secure: bool,
|
secure: bool,
|
||||||
certfp: Option<String>, // TLS client-cert fingerprint (clients only)
|
certfp: Option<String>, // TLS client-cert fingerprint (clients only)
|
||||||
tls_info: Option<String>, // negotiated TLS version/group/cipher (WHOIS 671)
|
tls_info: Option<String>, // negotiated TLS version/group/cipher (WHOIS 671)
|
||||||
|
sni: Option<String>, // TLS SNI hostname the client used (per-SNI branding)
|
||||||
local_port: u16, // the listener port the client connected to
|
local_port: u16, // the listener port the client connected to
|
||||||
link: bool, // a server-to-server connection, not a client
|
link: bool, // a server-to-server connection, not a client
|
||||||
outbound: bool, // (link) we dialed them
|
outbound: bool, // (link) we dialed them
|
||||||
|
|
@ -191,6 +192,7 @@ impl Ircd {
|
||||||
secure,
|
secure,
|
||||||
certfp,
|
certfp,
|
||||||
tls_info,
|
tls_info,
|
||||||
|
sni,
|
||||||
local_port,
|
local_port,
|
||||||
link,
|
link,
|
||||||
outbound,
|
outbound,
|
||||||
|
|
@ -200,7 +202,7 @@ impl Ircd {
|
||||||
self.server.add_link(uid, addr, out, sock, outbound);
|
self.server.add_link(uid, addr, out, sock, outbound);
|
||||||
} else {
|
} else {
|
||||||
self.server
|
self.server
|
||||||
.add_conn(uid, addr, out, sock, secure, certfp, tls_info, local_port);
|
.add_conn(uid, addr, out, sock, secure, certfp, tls_info, sni, local_port);
|
||||||
if websocket {
|
if websocket {
|
||||||
if let Some(u) = self.server.users.get_mut(&uid) {
|
if let Some(u) = self.server.users.get_mut(&uid) {
|
||||||
u.flags.via_websocket = true;
|
u.flags.via_websocket = true;
|
||||||
|
|
|
||||||
|
|
@ -2828,6 +2828,8 @@ mod tests {
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
brand_server: None,
|
||||||
|
brand_network: None,
|
||||||
account: None,
|
account: None,
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
@ -2918,6 +2920,8 @@ mod tests {
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
brand_server: None,
|
||||||
|
brand_network: None,
|
||||||
account: None,
|
account: None,
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
|
||||||
|
|
@ -375,6 +375,8 @@ mod tests {
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
brand_server: None,
|
||||||
|
brand_network: None,
|
||||||
account: Some("reverse".into()),
|
account: Some("reverse".into()),
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,8 @@ impl Node {
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
brand_server: None,
|
||||||
|
brand_network: None,
|
||||||
account: None,
|
account: None,
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,7 @@ pub struct Server {
|
||||||
pub channels: HashMap<String, Channel>, // lower name -> channel
|
pub channels: HashMap<String, Channel>, // lower name -> channel
|
||||||
pub events: VecDeque<Hook>,
|
pub events: VecDeque<Hook>,
|
||||||
pub opers: Vec<crate::config::OperBlock>, // oper logins from config
|
pub opers: Vec<crate::config::OperBlock>, // oper logins from config
|
||||||
|
pub brands: Vec<crate::config::BrandBlock>, // per-SNI server/network branding
|
||||||
pub cloak_key: Option<String>, // host-cloaking key (see modules::cloak)
|
pub cloak_key: Option<String>, // host-cloaking key (see modules::cloak)
|
||||||
pub line_ctags: String, // client-only tags of the line being handled
|
pub line_ctags: String, // client-only tags of the line being handled
|
||||||
// --- server-to-server (see crate::link) ---
|
// --- server-to-server (see crate::link) ---
|
||||||
|
|
@ -241,6 +242,7 @@ impl Server {
|
||||||
channels: HashMap::default(),
|
channels: HashMap::default(),
|
||||||
events: VecDeque::new(),
|
events: VecDeque::new(),
|
||||||
opers: cfg.opers,
|
opers: cfg.opers,
|
||||||
|
brands: cfg.brands,
|
||||||
cloak_key: cfg.cloak_key,
|
cloak_key: cfg.cloak_key,
|
||||||
line_ctags: String::new(),
|
line_ctags: String::new(),
|
||||||
sid: cfg.sid,
|
sid: cfg.sid,
|
||||||
|
|
@ -316,6 +318,7 @@ impl Server {
|
||||||
pub fn apply_config(&mut self, fresh: crate::config::Config) {
|
pub fn apply_config(&mut self, fresh: crate::config::Config) {
|
||||||
self.motd = fresh.motd;
|
self.motd = fresh.motd;
|
||||||
self.opers = fresh.opers;
|
self.opers = fresh.opers;
|
||||||
|
self.brands = fresh.brands;
|
||||||
self.cloak_key = fresh.cloak_key;
|
self.cloak_key = fresh.cloak_key;
|
||||||
self.censor = fresh.censor;
|
self.censor = fresh.censor;
|
||||||
self.amu = fresh.amu;
|
self.amu = fresh.amu;
|
||||||
|
|
@ -388,8 +391,10 @@ impl Server {
|
||||||
secure: bool,
|
secure: bool,
|
||||||
certfp: Option<String>,
|
certfp: Option<String>,
|
||||||
tls_info: Option<String>,
|
tls_info: Option<String>,
|
||||||
|
sni: Option<String>,
|
||||||
local_port: u16,
|
local_port: u16,
|
||||||
) {
|
) {
|
||||||
|
let (brand_server, brand_network) = self.resolve_brand(sni.as_deref());
|
||||||
let uuid = self.next_uuid();
|
let uuid = self.next_uuid();
|
||||||
self.uuid_local.insert(uuid.clone(), uid);
|
self.uuid_local.insert(uuid.clone(), uid);
|
||||||
let ip = addr.ip();
|
let ip = addr.ip();
|
||||||
|
|
@ -407,6 +412,8 @@ impl Server {
|
||||||
secure,
|
secure,
|
||||||
certfp,
|
certfp,
|
||||||
tls_info,
|
tls_info,
|
||||||
|
brand_server,
|
||||||
|
brand_network,
|
||||||
account: None,
|
account: None,
|
||||||
signon: now(),
|
signon: now(),
|
||||||
nick_ts: now(),
|
nick_ts: now(),
|
||||||
|
|
@ -890,7 +897,7 @@ impl Server {
|
||||||
/// the config-driven module tokens (ICON, FILEHOST). Each entry is a token block
|
/// the config-driven module tokens (ICON, FILEHOST). Each entry is a token block
|
||||||
/// without the trailing `:are supported by this server`. Shared by the welcome
|
/// without the trailing `:are supported by this server`. Shared by the welcome
|
||||||
/// burst and the `ISUPPORT` command (draft/extended-isupport).
|
/// burst and the `ISUPPORT` command (draft/extended-isupport).
|
||||||
pub fn isupport_lines(&self) -> Vec<String> {
|
pub fn isupport_lines(&self, network: &str) -> Vec<String> {
|
||||||
// advertised limits mirror the (config-driven) values actually enforced
|
// advertised limits mirror the (config-driven) values actually enforced
|
||||||
let maxwatch = self.conf_num("maxwatch", crate::watch::WATCH_MAX);
|
let maxwatch = self.conf_num("maxwatch", crate::watch::WATCH_MAX);
|
||||||
let maxmon = self.conf_num("maxmonitor", crate::watch::MONITOR_MAX);
|
let maxmon = self.conf_num("maxmonitor", crate::watch::MONITOR_MAX);
|
||||||
|
|
@ -905,7 +912,7 @@ impl Server {
|
||||||
let prefix = crate::modules::customprefix::isupport(include_oper);
|
let prefix = crate::modules::customprefix::isupport(include_oper);
|
||||||
let mut tokens: Vec<String> = format!(
|
let mut tokens: Vec<String> = format!(
|
||||||
"CHANTYPES=# PREFIX={prefix} CHANMODES=beIgXw,k,lfjFLHBJdK,ACDGMNOPQRSTUcimnprstuz EXTBAN=,aGbcgjmnrsy ACCOUNTEXTBAN=a BOT=B WATCH={maxwatch} MONITOR={maxmon} SILENCE={maxsil} CALLERID=g WHOX CHATHISTORY={chathist} MSGREFTYPES=timestamp,msgid UTF8ONLY CASEMAPPING=ascii NICKLEN={maxnick} CHANNELLEN={maxchan} MODES={maxmodes} NETWORK={}",
|
"CHANTYPES=# PREFIX={prefix} CHANMODES=beIgXw,k,lfjFLHBJdK,ACDGMNOPQRSTUcimnprstuz EXTBAN=,aGbcgjmnrsy ACCOUNTEXTBAN=a BOT=B WATCH={maxwatch} MONITOR={maxmon} SILENCE={maxsil} CALLERID=g WHOX CHATHISTORY={chathist} MSGREFTYPES=timestamp,msgid UTF8ONLY CASEMAPPING=ascii NICKLEN={maxnick} CHANNELLEN={maxchan} MODES={maxmodes} NETWORK={}",
|
||||||
self.network
|
network
|
||||||
)
|
)
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map(String::from)
|
.map(String::from)
|
||||||
|
|
@ -925,7 +932,7 @@ impl Server {
|
||||||
/// `draft/extended-isupport` + `batch`), wrap them in a `draft/isupport` BATCH so
|
/// `draft/extended-isupport` + `batch`), wrap them in a `draft/isupport` BATCH so
|
||||||
/// the multi-line set arrives atomically.
|
/// the multi-line set arrives atomically.
|
||||||
pub fn send_isupport(&mut self, uid: Uid, batched: bool) {
|
pub fn send_isupport(&mut self, uid: Uid, batched: bool) {
|
||||||
let lines = self.isupport_lines();
|
let lines = self.isupport_lines(self.disp_network(uid));
|
||||||
if batched {
|
if batched {
|
||||||
let nick = self
|
let nick = self
|
||||||
.users
|
.users
|
||||||
|
|
@ -956,22 +963,53 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn numeric(&self, uid: Uid, code: u16, rest: &str) {
|
/// The server name shown to `uid`: its per-SNI brand, or the global name.
|
||||||
let target = self
|
pub fn disp_name(&self, uid: Uid) -> &str {
|
||||||
.users
|
self.users
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
.map(|u| {
|
.and_then(|u| u.brand_server.as_deref())
|
||||||
if u.nick.is_empty() {
|
.unwrap_or(&self.name)
|
||||||
"*".to_string()
|
}
|
||||||
} else {
|
|
||||||
u.nick.clone()
|
/// The network name shown to `uid`: its per-SNI brand, or the global network.
|
||||||
}
|
pub fn disp_network(&self, uid: Uid) -> &str {
|
||||||
})
|
self.users
|
||||||
.unwrap_or_else(|| "*".to_string());
|
.get(&uid)
|
||||||
self.send(
|
.and_then(|u| u.brand_network.as_deref())
|
||||||
uid,
|
.unwrap_or(&self.network)
|
||||||
format!(":{} {:03} {} {}", self.name, code, target, rest),
|
}
|
||||||
);
|
|
||||||
|
/// Resolve the per-SNI brand for a new connection: match its TLS SNI host
|
||||||
|
/// against the configured `brand` blocks, returning the display servername +
|
||||||
|
/// network (each `None` = no brand, use the globals).
|
||||||
|
fn resolve_brand(&self, sni: Option<&str>) -> (Option<String>, Option<String>) {
|
||||||
|
let host = match sni {
|
||||||
|
Some(h) if !h.is_empty() => h.to_ascii_lowercase(),
|
||||||
|
_ => return (None, None),
|
||||||
|
};
|
||||||
|
for b in &self.brands {
|
||||||
|
if b.host == host {
|
||||||
|
let sv = (!b.servername.is_empty()).then(|| b.servername.clone());
|
||||||
|
let nw = (!b.network.is_empty()).then(|| b.network.clone());
|
||||||
|
return (sv, nw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn numeric(&self, uid: Uid, code: u16, rest: &str) {
|
||||||
|
let line = {
|
||||||
|
let u = self.users.get(&uid);
|
||||||
|
let target = u
|
||||||
|
.map(|u| if u.nick.is_empty() { "*" } else { u.nick.as_str() })
|
||||||
|
.unwrap_or("*");
|
||||||
|
// per-SNI brand as the message source (falls back to the global name)
|
||||||
|
let srv = u
|
||||||
|
.and_then(|u| u.brand_server.as_deref())
|
||||||
|
.unwrap_or(&self.name);
|
||||||
|
format!(":{srv} {code:03} {target} {rest}")
|
||||||
|
};
|
||||||
|
self.send(uid, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a server notice to every operator who has snomask (+s) on.
|
/// Send a server notice to every operator who has snomask (+s) on.
|
||||||
|
|
@ -1547,6 +1585,8 @@ mod tests {
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
brand_server: None,
|
||||||
|
brand_network: None,
|
||||||
account: None,
|
account: None,
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
@ -1894,7 +1934,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn isupport_advertises_bot_and_account_extban() {
|
fn isupport_advertises_bot_and_account_extban() {
|
||||||
let s = srv();
|
let s = srv();
|
||||||
let joined = s.isupport_lines().join(" ");
|
let joined = s.isupport_lines(&s.network).join(" ");
|
||||||
assert!(joined.contains("BOT=B"), "bot-mode letter: {joined}");
|
assert!(joined.contains("BOT=B"), "bot-mode letter: {joined}");
|
||||||
assert!(joined.contains("ACCOUNTEXTBAN=a"), "account-extban token: {joined}");
|
assert!(joined.contains("ACCOUNTEXTBAN=a"), "account-extban token: {joined}");
|
||||||
assert!(joined.contains("EXTBAN=,aG"), "'a' listed in EXTBAN: {joined}");
|
assert!(joined.contains("EXTBAN=,aG"), "'a' listed in EXTBAN: {joined}");
|
||||||
|
|
|
||||||
|
|
@ -657,6 +657,7 @@ fn reactor_loop(
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
sni: None,
|
||||||
local_port: a.local_port,
|
local_port: a.local_port,
|
||||||
link: false,
|
link: false,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
@ -789,8 +790,15 @@ fn try_handshake(
|
||||||
core: &Sender<Event>,
|
core: &Sender<Event>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut close = false;
|
let mut close = false;
|
||||||
let mut connect: Option<(Uid, SocketAddr, u16, Option<String>, Option<String>, OutSink)> =
|
let mut connect: Option<(
|
||||||
None;
|
Uid,
|
||||||
|
SocketAddr,
|
||||||
|
u16,
|
||||||
|
Option<String>,
|
||||||
|
Option<String>,
|
||||||
|
Option<String>,
|
||||||
|
OutSink,
|
||||||
|
)> = None;
|
||||||
if let Some(c) = conns.get_mut(&t) {
|
if let Some(c) = conns.get_mut(&t) {
|
||||||
if !c.handshaking {
|
if !c.handshaking {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -801,10 +809,11 @@ fn try_handshake(
|
||||||
c.handshaking = false;
|
c.handshaking = false;
|
||||||
let certfp = sess.peer_cert_fp();
|
let certfp = sess.peer_cert_fp();
|
||||||
let tls_info = sess.tls_info();
|
let tls_info = sess.tls_info();
|
||||||
|
let sni = sess.sni();
|
||||||
connect = c
|
connect = c
|
||||||
.pending_out
|
.pending_out
|
||||||
.take()
|
.take()
|
||||||
.map(|out| (c.uid, c.addr, c.local_port, certfp, tls_info, out));
|
.map(|out| (c.uid, c.addr, c.local_port, certfp, tls_info, sni, out));
|
||||||
set_interest(poll, c, t); // handshake done: drop the extra WRITABLE
|
set_interest(poll, c, t); // handshake done: drop the extra WRITABLE
|
||||||
}
|
}
|
||||||
Ok(false) => return false, // still negotiating
|
Ok(false) => return false, // still negotiating
|
||||||
|
|
@ -816,7 +825,7 @@ fn try_handshake(
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if let Some((uid, addr, local_port, certfp, tls_info, out)) = connect {
|
if let Some((uid, addr, local_port, certfp, tls_info, sni, out)) = connect {
|
||||||
let _ = core.send(Event::Connect {
|
let _ = core.send(Event::Connect {
|
||||||
uid,
|
uid,
|
||||||
addr,
|
addr,
|
||||||
|
|
@ -825,6 +834,7 @@ fn try_handshake(
|
||||||
secure: true,
|
secure: true,
|
||||||
certfp,
|
certfp,
|
||||||
tls_info,
|
tls_info,
|
||||||
|
sni,
|
||||||
local_port,
|
local_port,
|
||||||
link: false,
|
link: false,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
@ -948,6 +958,7 @@ fn read_conn(poll: &mut Poll, conns: &mut HashMap<usize, Conn>, t: usize, core:
|
||||||
secure,
|
secure,
|
||||||
certfp,
|
certfp,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
sni: None,
|
||||||
local_port,
|
local_port,
|
||||||
link: false,
|
link: false,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
@ -1102,6 +1113,7 @@ pub fn accept_loop(
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
sni: None,
|
||||||
local_port,
|
local_port,
|
||||||
link,
|
link,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
@ -1187,6 +1199,7 @@ pub fn connect_link(addr: &str, core: Sender<Event>, counter: Arc<AtomicU64>, ma
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
sni: None,
|
||||||
local_port: 0,
|
local_port: 0,
|
||||||
link: true,
|
link: true,
|
||||||
outbound: true,
|
outbound: true,
|
||||||
|
|
@ -1295,6 +1308,7 @@ fn tls_conn(
|
||||||
};
|
};
|
||||||
let certfp = conn.peer_cert_fp();
|
let certfp = conn.peer_cert_fp();
|
||||||
let tls_info = conn.tls_info();
|
let tls_info = conn.tls_info();
|
||||||
|
let sni = conn.sni();
|
||||||
let (out_tx, out_rx) = mpsc::channel::<String>();
|
let (out_tx, out_rx) = mpsc::channel::<String>();
|
||||||
if core
|
if core
|
||||||
.send(Event::Connect {
|
.send(Event::Connect {
|
||||||
|
|
@ -1305,6 +1319,7 @@ fn tls_conn(
|
||||||
secure: true,
|
secure: true,
|
||||||
certfp,
|
certfp,
|
||||||
tls_info,
|
tls_info,
|
||||||
|
sni,
|
||||||
local_port,
|
local_port,
|
||||||
link,
|
link,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
|
||||||
14
src/tls.rs
14
src/tls.rs
|
|
@ -46,6 +46,10 @@ pub trait TlsConn: Send {
|
||||||
fn tls_info(&self) -> Option<String> {
|
fn tls_info(&self) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
/// The SNI hostname the client requested during the TLS handshake, if any.
|
||||||
|
fn sni(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A non-blocking TLS session the reactor drives itself over a mio socket. The
|
/// A non-blocking TLS session the reactor drives itself over a mio socket. The
|
||||||
|
|
@ -79,6 +83,10 @@ pub trait TlsSession: Send {
|
||||||
fn tls_info(&self) -> Option<String> {
|
fn tls_info(&self) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
/// The SNI hostname the client requested during the TLS handshake, if any.
|
||||||
|
fn sni(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
fn shutdown(&mut self);
|
fn shutdown(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,6 +266,9 @@ impl TlsSession for OpensslSession {
|
||||||
fn tls_info(&self) -> Option<String> {
|
fn tls_info(&self) -> Option<String> {
|
||||||
openssl_tls_info(self.0.ssl())
|
openssl_tls_info(self.0.ssl())
|
||||||
}
|
}
|
||||||
|
fn sni(&self) -> Option<String> {
|
||||||
|
self.0.ssl().servername(NameType::HOST_NAME).map(String::from)
|
||||||
|
}
|
||||||
fn shutdown(&mut self) {
|
fn shutdown(&mut self) {
|
||||||
// best-effort TLS close_notify, then close the socket. Non-blocking, so a
|
// best-effort TLS close_notify, then close the socket. Non-blocking, so a
|
||||||
// WouldBlock just means the alert is queued — we don't wait for the peer's.
|
// WouldBlock just means the alert is queued — we don't wait for the peer's.
|
||||||
|
|
@ -292,4 +303,7 @@ impl TlsConn for OpensslConn {
|
||||||
fn tls_info(&self) -> Option<String> {
|
fn tls_info(&self) -> Option<String> {
|
||||||
openssl_tls_info(self.0.ssl())
|
openssl_tls_info(self.0.ssl())
|
||||||
}
|
}
|
||||||
|
fn sni(&self) -> Option<String> {
|
||||||
|
self.0.ssl().servername(NameType::HOST_NAME).map(String::from)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,8 @@ pub struct User {
|
||||||
pub secure: bool, // connected over TLS (drives WHOIS 671 / sslinfo)
|
pub secure: bool, // connected over TLS (drives WHOIS 671 / sslinfo)
|
||||||
pub certfp: Option<String>, // TLS client-cert fingerprint (SASL EXTERNAL / CertFP)
|
pub certfp: Option<String>, // TLS client-cert fingerprint (SASL EXTERNAL / CertFP)
|
||||||
pub tls_info: Option<String>, // negotiated TLS version/group/cipher (WHOIS 671)
|
pub tls_info: Option<String>, // negotiated TLS version/group/cipher (WHOIS 671)
|
||||||
|
pub brand_server: Option<String>, // per-SNI display server name (None = global)
|
||||||
|
pub brand_network: Option<String>, // per-SNI display network name (None = global)
|
||||||
pub account: Option<String>, // logged-in account name (set by services)
|
pub account: Option<String>, // logged-in account name (set by services)
|
||||||
pub signon: u64, // unix secs at registration (WHOIS 317)
|
pub signon: u64, // unix secs at registration (WHOIS 317)
|
||||||
pub nick_ts: u64, // unix secs the current nick was taken (nick-collision arbitration)
|
pub nick_ts: u64, // unix secs the current nick was taken (nick-collision arbitration)
|
||||||
|
|
@ -460,12 +462,12 @@ impl Server {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_WELCOME,
|
RPL_WELCOME,
|
||||||
&format!(":Welcome to the {} IRC Network, {nick}", self.network),
|
&format!(":Welcome to the {} IRC Network, {nick}", self.disp_network(uid)),
|
||||||
);
|
);
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_YOURHOST,
|
RPL_YOURHOST,
|
||||||
&format!(":Your host is {}, running echoircd-{RELEASE}", self.name),
|
&format!(":Your host is {}, running echoircd-{RELEASE}", self.disp_name(uid)),
|
||||||
);
|
);
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
|
|
@ -477,7 +479,7 @@ impl Server {
|
||||||
RPL_MYINFO,
|
RPL_MYINFO,
|
||||||
&format!(
|
&format!(
|
||||||
"{} echoircd-{RELEASE} iowxsgBkDIHrRzWhc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdKXwD",
|
"{} echoircd-{RELEASE} iowxsgBkDIHrRzWhc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdKXwD",
|
||||||
self.name
|
self.disp_name(uid)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// ISUPPORT (005): the fixed set + config-driven module tokens (ICON/FILEHOST).
|
// ISUPPORT (005): the fixed set + config-driven module tokens (ICON/FILEHOST).
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,7 @@ fn ws_session<S: WsStream>(
|
||||||
secure,
|
secure,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
tls_info: None,
|
tls_info: None,
|
||||||
|
sni: None,
|
||||||
local_port,
|
local_port,
|
||||||
link: false,
|
link: false,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue