Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WHO: Add support for nick masks #118

Merged
merged 1 commit into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
}
}
}
Loading