//! Filter accepts and denies based on cidr masks. use super::*; use cidr::{ Cidr, IpCidr, }; use std::{ net::{ IpAddr, }, error, fmt, }; #[derive(Debug)] pub struct IpFilterDeniedError(IpAddr, Option); impl warp::reject::Reject for IpFilterDeniedError{} impl error::Error for IpFilterDeniedError{} impl fmt::Display for IpFilterDeniedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Denied {} due to ", self.0)?; match &self.1 { Some(cidr) => write!(f, "matching rule {}", cidr), None => write!(f, "non-matching accept rule"), } } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, Serialize, Deserialize)] pub enum Rule { Accept, Deny, } impl Default for Rule { #[inline] fn default() -> Self { Self::Deny } } impl Rule { fn into_result<'a>(self, net: Option<&'a IpCidr>) -> Result, Option> { if let Self::Accept = self { Ok(net) } else { Err(net.cloned()) } } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct IpFilter { /// The default fallback rule pub default: Rule, #[serde(default)] accept: Vec, #[serde(default)] deny: Vec, } #[inline] fn find_in<'a>(needle: &IpAddr, haystack: &'a [IpCidr]) -> Option<&'a IpCidr> { for x in haystack.iter() { if x.contains(needle) { return Some(x); } } None } impl Default for IpFilter { #[inline] fn default() -> Self { Self { default: Rule::Deny, accept: vec![cidr::Cidr::new_host([127,0,0,1].into())], deny: Vec::default(), } } } impl IpFilter { /// Create a new CIDR filter with thie default rule. /// /// Use `default()` to use with default rule. pub fn new(fallback: Rule) -> Self { Self { default: fallback, accept: Vec::new(), deny: Vec::new(), } } /// Checks the rule for this IP, returns a result if it should accept or not. /// /// If acceptance rule is met, return the CIDR match that caused the acceptance if applicable /// /// If acceptance rule is not met, return in the error which CIDR match cause the deny if applicable pub fn check(&self, ip: &IpAddr) -> Result, IpFilterDeniedError> { let accept = find_in(ip, &self.accept[..]); let deny = find_in(ip, &self.deny[..]); let (rule, cidr) = match (accept, deny) { (None, Some(net)) => (Rule::Deny, Some(net)), (Some(net), None) => (Rule::Accept, Some(net)), (Some(ac), Some(den)) if ac != den => { if ac.network_length() > den.network_length() { (Rule::Accept, Some(ac)) } else { (Rule::Deny, Some(den)) } }, _ => (self.default, None) }; rule.into_result(cidr) .map_err(|cidr| IpFilterDeniedError(*ip, cidr)) } pub fn accept_mask(&self) -> &[IpCidr] { &self.accept[..] } pub fn deny_mask(&self) -> &[IpCidr] { &self.deny[..] } pub fn accept_range(&mut self, items: impl IntoIterator) { self.accept.extend(items) } pub fn deny_range(&mut self, items: impl IntoIterator) { self.deny.extend(items) } pub fn accept_one(&mut self, item: IpCidr) { self.accept.push(item) } pub fn deny_one(&mut self, items: IpCidr) { self.deny.push(items) } /// Can any connection ever be accepted? pub fn possible(&self) -> bool { //TODO: Test this !(self.default == Rule::Deny && self.accept.len() == 0) && !(self.deny.iter().find(|x| x.network_length() == 0).is_some() && self.accept.len() == 0) } } pub async fn recover(err: warp::Rejection) -> Result { if let Some(t) = err.find::() { error!("Denying access to {} because of {:?} (403)", t.0, t.1); Ok(warp::http::Response::builder() .status(status!(403)) .body(format!("Access denied: {}", t))) } else { Err(err) } }