Skip to content

Commit

Permalink
WHO: Add support for nick masks
Browse files Browse the repository at this point in the history
And some random rustfmt changes to `matchers.rs` because it was not
imported before.
  • Loading branch information
progval authored and spb committed May 4, 2024
1 parent 6ba5241 commit b9deaa9
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 44 deletions.
14 changes: 14 additions & 0 deletions sable_ircd/src/command/handlers/who.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use super::*;
use crate::capability::ClientCapability;
use crate::utils::make_numeric;

const MAX_RESULTS: usize = 10;

#[command_handler("WHO")]
fn handle_who(
server: &ClientServer,
Expand Down Expand Up @@ -31,6 +33,18 @@ fn handle_who(
None, // membership
);
}
} else {
let nick_pattern = NicknameMatcher::new(Pattern::new(target.to_owned()));
network
.users_by_nick_pattern(&nick_pattern)
.filter(|user| server.policy().can_list_user(&source, user).is_ok())
.take(MAX_RESULTS)
.for_each(|user| {
send_who_reply(
response, &user, None, // channel
None, // membership
);
});
}

// If nick/channel is not found, EndOfWho should be the only numeric we send
Expand Down
3 changes: 3 additions & 0 deletions sable_network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub mod config;
pub mod audit;

pub mod types {
mod matchers;
pub use matchers::*;

mod pattern;
pub use pattern::*;
}
Expand Down
20 changes: 20 additions & 0 deletions sable_network/src/network/network/accessors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ impl Network {
})
}

/// Look up the user currently using the given nickname pattern
pub fn users_by_nick_pattern<'a>(
&'a self,
pattern: &'a NicknameMatcher,
) -> impl Iterator<Item = wrapper::User> + 'a {
let pattern = std::rc::Rc::new(pattern);
let pattern2 = pattern.clone();
let alias_users = self
.get_alias_users()
.iter()
.filter(move |(nick, _)| pattern2.matches(nick))
.map(move |(_, user)| User::wrap(self, user));
let users = self
.nick_bindings
.iter()
.filter(move |(nick, _)| pattern.matches(nick))
.map(move |(_, user)| self.user(user.user).unwrap());
alias_users.chain(users)
}

/// Remove a user from nick bindings and add it to historical users for that nick
/// Return a nickname binding for the given nick.
Expand Down
2 changes: 2 additions & 0 deletions sable_network/src/policy/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub enum UserPermissionError {
ReadOnlyUmode,
/// User isn't logged in (and needs to be)
NotLoggedIn,
/// That user is invisible, and does not share any channel with the requested
Invisible,
}

/// A permission error for a registration-related operation
Expand Down
28 changes: 28 additions & 0 deletions sable_network/src/policy/standard_user_policy.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashSet;

use super::*;

use UserPermissionError::*;
Expand All @@ -24,4 +26,30 @@ impl UserPolicyService for StandardUserPolicy {
fn can_unset_umode(&self, _user: &wrapper::User, _mode: UserModeFlag) -> PermissionResult {
Ok(())
}

fn can_list_user(&self, to_user: &User, user: &User) -> PermissionResult {
if !user.mode().has_mode(UserModeFlag::Invisible) {
return Ok(());
}

// If the target user is invisible, check whether they share any channel
let mut channels1: HashSet<_> = to_user
.channels()
.flat_map(|membership| membership.channel().map(|chan| chan.id()))
.collect();
let mut channels2: HashSet<_> = user
.channels()
.flat_map(|membership| membership.channel().map(|chan| chan.id()))
.collect();
if channels1.len() <= channels2.len() {
std::mem::swap(&mut channels1, &mut channels2);
}
for chan in channels2 {
if channels1.contains(&chan) {
return Ok(());
}
}

Err(PermissionError::User(Invisible))
}
}
3 changes: 3 additions & 0 deletions sable_network/src/policy/user_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ pub trait UserPolicyService {
fn can_set_umode(&self, user: &wrapper::User, mode: UserModeFlag) -> PermissionResult;
/// Determine whether a given user can unset a given user mode on themselves
fn can_unset_umode(&self, user: &wrapper::User, mode: UserModeFlag) -> PermissionResult;
/// Determine whether one user can discover another without knowing their nick
/// (eg. with `WHO *`)
fn can_list_user(&self, touser: &User, user: &User) -> PermissionResult;
}
85 changes: 41 additions & 44 deletions sable_network/src/types/matchers.rs
Original file line number Diff line number Diff line change
@@ -1,94 +1,91 @@
use crate::prelude::*;
use std::net::IpAddr;
use std::ops::Deref;

use ipnet::IpNet;
use serde::{Deserialize, Serialize};

use crate::prelude::*;

#[derive(Debug,Clone,Serialize,Deserialize)]
pub enum HostMatcher
{
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum HostMatcher {
Hostname(Pattern),
Ip(IpNet),
}

impl HostMatcher
{
pub fn is_host(&self) -> bool
{
impl HostMatcher {
pub fn is_host(&self) -> bool {
matches!(self, Self::Hostname(_))
}

pub fn is_ip(&self) -> bool
{
pub fn is_ip(&self) -> bool {
matches!(self, Self::Ip(_))
}

pub fn matches_host(&self, hostname: &str) -> bool
{
match self
{
pub fn matches_host(&self, hostname: &str) -> bool {
match self {
Self::Hostname(pat) => pat.matches(hostname),
_ => false
_ => false,
}
}

pub fn matches_ip(&self, addr: &IpAddr) -> bool
{
match self
{
pub fn matches_ip(&self, addr: &IpAddr) -> bool {
match self {
Self::Ip(mask) => mask.contains(addr),
_ => false
_ => false,
}
}

pub fn matches(&self, hostname: &str, addr: &IpAddr) -> bool
{
match self
{
pub fn matches(&self, hostname: &str, addr: &IpAddr) -> bool {
match self {
Self::Hostname(pat) => pat.matches(hostname),
Self::Ip(mask) => mask.contains(addr),
}
}
}

pub struct UserHostMatcher
{
pub struct UserHostMatcher {
user: Pattern,
host: HostMatcher
host: HostMatcher,
}

impl UserHostMatcher
{
pub fn matches(&self, username: &str, hostname: &str, addr: &IpAddr) -> bool
{
impl UserHostMatcher {
pub fn matches(&self, username: &str, hostname: &str, addr: &IpAddr) -> bool {
self.user.matches(username) && self.host.matches(hostname, addr)
}
}

pub struct IpMatcher(IpNet);

impl IpMatcher
{
pub fn matches(&self, addr: &IpAddr) -> bool
{
impl IpMatcher {
pub fn matches(&self, addr: &IpAddr) -> bool {
self.0.contains(addr)
}
}

pub struct ExactIpMatcher(IpAddr);

impl ExactIpMatcher
{
pub fn matches(&self, addr: &IpAddr) -> bool
{
impl ExactIpMatcher {
pub fn matches(&self, addr: &IpAddr) -> bool {
&self.0 == addr
}
}

pub struct NicknameMatcher(Pattern);

impl NicknameMatcher
{
pub fn matches(&self, nick: &Nickname) -> bool
{
impl Deref for NicknameMatcher {
type Target = Pattern;

fn deref(&self) -> &Pattern {
&self.0
}
}

impl NicknameMatcher {
pub fn new(pattern: Pattern) -> NicknameMatcher {
NicknameMatcher(pattern)
}

pub fn matches(&self, nick: &Nickname) -> bool {
self.0.matches(nick.as_ref())
}
}
}

0 comments on commit b9deaa9

Please sign in to comment.