You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
genmarkov/src/ipfilt.rs

182 lines
4.0 KiB

//! 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<IpCidr>);
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<&'a IpCidr>, Option<IpCidr>>
{
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<IpCidr>,
#[serde(default)]
deny: Vec<IpCidr>,
}
#[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<Option<&'_ IpCidr>, 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<Item = IpCidr>)
{
self.accept.extend(items)
}
pub fn deny_range(&mut self, items: impl IntoIterator<Item = IpCidr>)
{
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<impl warp::Reply, warp::Rejection>
{
if let Some(t) = err.find::<IpFilterDeniedError>() {
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)
}
}