mod stanzafilter; pub use stanzafilter::*; mod slicesubsequence; use slicesubsequence::*; use std::net::SocketAddr; pub use log::{debug, error, info, log_enabled, trace}; use rustls::{Certificate, ServerConnection}; use tokio_rustls::webpki::DnsNameRef; pub fn to_str(buf: &[u8]) -> std::borrow::Cow<'_, str> { String::from_utf8_lossy(buf) } pub fn c2s(is_c2s: bool) -> &'static str { if is_c2s { "c2s" } else { "s2s" } } #[derive(Clone)] pub struct Context<'a> { conn_id: String, log_from: String, log_to: String, proto: &'a str, is_c2s: Option, to: Option, to_addr: Option, from: Option, client_addr: SocketAddr, } impl<'a> Context<'a> { pub fn new(proto: &'static str, client_addr: SocketAddr) -> Context { let (log_to, log_from, conn_id) = if log_enabled!(log::Level::Info) { #[cfg(feature = "logging")] let conn_id = { use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; thread_rng().sample_iter(&Alphanumeric).take(10).map(char::from).collect() }; #[cfg(not(feature = "logging"))] let conn_id = "".to_string(); ( format!("{}: ({} <- ({}-unk)):", conn_id, client_addr, proto), format!("{}: ({} -> ({}-unk)):", conn_id, client_addr, proto), conn_id, ) } else { ("".to_string(), "".to_string(), "".to_string()) }; Context { conn_id, log_from, log_to, proto, client_addr, is_c2s: None, to: None, to_addr: None, from: None, } } fn re_calc(&mut self) { // todo: make this good self.log_from = format!( "{}: ({} ({}) -> ({}-{}) -> {} ({})):", self.conn_id, self.client_addr, if self.from.is_some() { self.from.as_ref().unwrap() } else { "unk" }, self.proto, if self.is_c2s.is_some() { c2s(self.is_c2s.unwrap()) } else { "unk" }, if self.to_addr.is_some() { self.to_addr.as_ref().unwrap().to_string() } else { "unk".to_string() }, if self.to.is_some() { self.to.as_ref().unwrap() } else { "unk" }, ); self.log_to = self.log_from.replace(" -> ", " <- "); } pub fn log_from(&self) -> &str { &self.log_from } pub fn log_to(&self) -> &str { &self.log_to } pub fn client_addr(&self) -> &SocketAddr { &self.client_addr } pub fn set_proto(&mut self, proto: &'static str) { if log_enabled!(log::Level::Info) { self.proto = proto; self.to_addr = None; self.re_calc(); } } pub fn set_c2s_stream_open(&mut self, is_c2s: bool, stream_open: &[u8]) { if log_enabled!(log::Level::Info) { self.is_c2s = Some(is_c2s); self.from = stream_open .extract_between(b" from='", b"'") .or_else(|_| stream_open.extract_between(b" from=\"", b"\"")) .map(|b| to_str(b).to_string()) .ok(); self.to = stream_open .extract_between(b" to='", b"'") .or_else(|_| stream_open.extract_between(b" to=\"", b"\"")) .map(|b| to_str(b).to_string()) .ok(); self.re_calc(); info!("{} stream data set", &self.log_from()); } } pub fn set_to_addr(&mut self, to_addr: SocketAddr) { if log_enabled!(log::Level::Info) { self.to_addr = Some(to_addr); self.re_calc(); } } } #[cfg(feature = "incoming")] #[derive(Clone)] pub enum ServerCerts { Tls(&'static ServerConnection), #[cfg(feature = "quic")] Quic(quinn::Connection), } impl ServerCerts { pub fn valid(&self, dns_name: DnsNameRef) -> bool { use std::convert::TryFrom; use tokio_rustls::webpki; self.first_peer_cert() .and_then(|c| { if let Ok(cert) = webpki::EndEntityCert::try_from(c.0.as_ref()) { cert.verify_is_valid_for_dns_name(dns_name).map(|_| true).ok() } else { Some(false) } }) .unwrap_or(false) } pub fn first_peer_cert(&self) -> Option { match self { ServerCerts::Tls(c) => c.peer_certificates().map(|c| c[0].clone()), ServerCerts::Quic(c) => c.peer_identity().and_then(|v| v.downcast::>().ok()).map(|v| v[0].clone()), } } pub fn sni(&self) -> Option { match self { ServerCerts::Tls(c) => c.sni_hostname().map(|s| s.to_string()), ServerCerts::Quic(c) => c.handshake_data().and_then(|v| v.downcast::().ok()).and_then(|h| h.server_name), } } pub fn alpn(&self) -> Option> { match self { ServerCerts::Tls(c) => c.alpn_protocol().map(|s| s.to_vec()), ServerCerts::Quic(c) => c.handshake_data().and_then(|v| v.downcast::().ok()).and_then(|h| h.protocol), } } pub fn is_tls(&self) -> bool { match self { ServerCerts::Tls(_) => true, ServerCerts::Quic(_) => false, } } }