diff --git a/rustfmt.toml b/rustfmt.toml index d440511..51fbc7e 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,7 +1,7 @@ edition = "2021" ## not yet supported on stable -#imports_granularity = "Crate" +imports_granularity = "Crate" newline_style = "Unix" ## not yet supported on stable -#group_imports = "StdExternalCrate" +group_imports = "StdExternalCrate" use_field_init_shorthand = true diff --git a/src/client.rs b/src/client.rs index 0104b84..d452aa0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,7 +18,7 @@ use crate::{ channel::Channel, connection::{ sasl::SaslAlreadyAuthenticated, Capability, InitiatedConnection, MessageSink, - NickNotOwnedByUser, + NickNotOwnedByUser, UserMode, }, messages::{ Broadcast, ChannelFetchTopic, ChannelFetchWhoList, ChannelInvite, ChannelJoin, @@ -26,7 +26,7 @@ use crate::{ ChannelSetMode, ChannelUpdateTopic, ClientAway, ConnectedChannels, FetchClientDetails, FetchUserPermission, FetchWhoList, FetchWhois, MessageKind, PrivateMessage, ServerAdminInfo, ServerDisconnect, ServerFetchMotd, ServerListUsers, UserKickedFromChannel, - UserNickChange, UserNickChangeInternal, + UserNickChange, UserNickChangeInternal, Wallops, }, persistence::{ events::{ @@ -624,9 +624,6 @@ impl StreamHandler> for Client { // https://modern.ircdocs.horse/ #[allow(clippy::match_same_arms)] match item.command { - Command::USER(_, _, _) | Command::PASS(_) | Command::CAP(_, _, _, _) => { - // these were already handled by `negotiate_client_connection` - } Command::NICK(new_nick) => { ctx.notify(UserNickChangeInternal { old_nick: self.connection.nick.to_string(), @@ -883,7 +880,12 @@ impl StreamHandler> for Client { Command::DIE => {} Command::RESTART => {} Command::USERS(_) => {} - Command::WALLOPS(_) => {} + Command::WALLOPS(message) if self.connection.mode.contains(UserMode::OPER) => { + self.server.do_send(Wallops { + span: Span::current(), + message, + }); + } Command::USERHOST(_) => {} Command::SAJOIN(_, _) => {} Command::SAMODE(_, _, _) => {} diff --git a/src/connection.rs b/src/connection.rs index 2181b42..e37fc9a 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -44,7 +44,6 @@ pub struct ConnectionRequest { host: Option, nick: Option, user: Option, - mode: Option, real_name: Option, user_id: Option, capabilities: Capability, @@ -55,7 +54,7 @@ pub struct InitiatedConnection { pub host: SocketAddr, pub nick: String, pub user: String, - pub mode: String, + pub mode: UserMode, pub real_name: String, pub user_id: UserId, pub capabilities: Capability, @@ -82,7 +81,6 @@ impl TryFrom for InitiatedConnection { host: Some(host), nick: Some(nick), user: Some(user), - mode: Some(mode), real_name: Some(real_name), user_id: Some(user_id), capabilities, @@ -95,7 +93,7 @@ impl TryFrom for InitiatedConnection { host, nick, user, - mode, + mode: UserMode::empty(), real_name, user_id, capabilities, @@ -138,9 +136,8 @@ pub async fn negotiate_client_connection( match msg.command { Command::PASS(_) => {} Command::NICK(nick) => request.nick = Some(nick), - Command::USER(_user, mode, real_name) => { + Command::USER(_user, _mode, real_name) => { // we ignore the user here, as it will be set by the AUTHENTICATE command - request.mode = Some(mode); request.real_name = Some(real_name); } Command::CAP(_, CapSubCommand::LIST | CapSubCommand::LS, _, _) => { @@ -279,6 +276,26 @@ bitflags! { const USERHOST_IN_NAMES = 0b0000_0000_0000_0000_0000_0000_0000_0001; const SERVER_TIME = 0b0000_0000_0000_0000_0000_0000_0000_0010; } + + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] + pub struct UserMode: u32 { + /// a - user is flagged as away + const AWAY = 0b0000_0000_0000_0000_0000_0000_0000_0001; + /// i - marks a users as invisible + const INVISIBLE = 0b0000_0000_0000_0000_0000_0000_0000_0010; + /// w - user receives wallops + const WALLOPS = 0b0000_0000_0000_0000_0000_0000_0000_0100; + /// r - restricted user connection + const RESTRICTED = 0b0000_0000_0000_0000_0000_0000_0000_1000; + /// o - operator flag + const OPER = 0b0000_0000_0000_0000_0000_0000_0001_0000; + /// O - local operator flag + const LOCAL_OPER = 0b0000_0000_0000_0000_0000_0000_0010_0000; + /// s - marks a user for receipt of server notices + const SERVER_NOTICES = 0b0000_0000_0000_0000_0000_0000_0100_0000; + /// x - masked hostname + const MASKED_HOST = 0b0000_0000_0000_0000_0000_0000_1000_0000; + } } impl Capability { diff --git a/src/messages.rs b/src/messages.rs index 33d7158..7a575d3 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -47,6 +47,13 @@ pub struct UserNickChange { pub span: Span, } +#[derive(Message, Clone)] +#[rtype(result = "()")] +pub struct Wallops { + pub message: String, + pub span: Span, +} + /// List all the channels a user is connected to #[derive(Message, Clone)] #[rtype(result = "Vec<(crate::channel::permissions::Permission, String)>")] diff --git a/src/server.rs b/src/server.rs index 7eaf60f..cc0ec2f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -22,12 +22,13 @@ use crate::{ channel::{permissions::Permission, Channel, ChannelId}, client::Client, config::Config, - connection::InitiatedConnection, + connection::{InitiatedConnection, UserMode}, messages::{ Broadcast, ChannelFetchTopic, ChannelFetchWhoList, ChannelJoin, ChannelList, ChannelMemberList, ClientAway, ConnectedChannels, FetchClientByNick, FetchWhoList, FetchWhois, MessageKind, PrivateMessage, ServerAdminInfo, ServerDisconnect, ServerFetchMotd, ServerListUsers, UserConnected, UserNickChange, UserNickChangeInternal, + Wallops, }, persistence::Persistence, server::response::{AdminInfo, ListUsers, Motd, WhoList, Whois}, @@ -135,6 +136,28 @@ impl Handler for Server { } } +impl Handler for Server { + type Result = (); + + #[instrument(parent = &msg.span, skip_all)] + fn handle(&mut self, msg: Wallops, _ctx: &mut Self::Context) -> Self::Result { + for (handle, conn) in &self.clients { + if !conn.mode.contains(UserMode::WALLOPS) { + continue; + } + + handle.do_send(Broadcast { + message: Message { + tags: None, + prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())), + command: Command::WALLOPS(msg.message.clone()), + }, + span: msg.span.clone(), + }); + } + } +} + /// Returns the MOTD when requested. impl Handler for Server { type Result = MessageResult;