diff --git a/sable_ircd/src/command/client_command.rs b/sable_ircd/src/command/client_command.rs index 14050385..29a3e9a5 100644 --- a/sable_ircd/src/command/client_command.rs +++ b/sable_ircd/src/command/client_command.rs @@ -225,7 +225,8 @@ impl ClientCommand { LookupError::NoSuchChannelName(name) => Some(make_numeric!(NoSuchChannel, &name)), _ => None, }, - CommandError::InvalidNick(name) => Some(make_numeric!(ErroneousNickname, &name)), + CommandError::InvalidNickname(name) => Some(make_numeric!(ErroneousNickname, &name)), + CommandError::InvalidUsername(_name) => Some(make_numeric!(InvalidUsername)), CommandError::InvalidChannelName(name) => { Some(make_numeric!(InvalidChannelName, &name)) } diff --git a/sable_ircd/src/command/error.rs b/sable_ircd/src/command/error.rs index 6b55fc0c..2d40fc8c 100644 --- a/sable_ircd/src/command/error.rs +++ b/sable_ircd/src/command/error.rs @@ -27,7 +27,9 @@ pub enum CommandError { /// A required object wasn't found in the network state LookupError(LookupError), /// A nickname parameter wasn't a valid nick - InvalidNick(String), + InvalidNickname(String), + /// A username parameter wasn't a valid username + InvalidUsername(String), /// A channel name parameter wasn't a valid channel name InvalidChannelName(String), /// A services command was executed, but services aren't currently running @@ -120,7 +122,13 @@ impl From for CommandError { impl From for CommandError { fn from(e: InvalidNicknameError) -> Self { - Self::InvalidNick(e.0) + Self::InvalidNickname(e.0) + } +} + +impl From for CommandError { + fn from(e: InvalidUsernameError) -> Self { + Self::InvalidUsername(e.0) } } diff --git a/sable_ircd/src/command/handlers/services/mod.rs b/sable_ircd/src/command/handlers/services/mod.rs index bc172eba..aa71fb47 100644 --- a/sable_ircd/src/command/handlers/services/mod.rs +++ b/sable_ircd/src/command/handlers/services/mod.rs @@ -105,9 +105,12 @@ impl<'a> Command for ServicesCommand<'a> { } } } - CommandError::InvalidNick(name) => { + CommandError::InvalidNickname(name) => { self.notice(format_args!("Invalid nickname {}", name)); } + CommandError::InvalidUsername(name) => { + self.notice(format_args!("Invalid username {}", name)); + } CommandError::InvalidChannelName(name) => { self.notice(format_args!("Invalid channel name {}", name)); } diff --git a/sable_ircd/src/command/handlers/user.rs b/sable_ircd/src/command/handlers/user.rs index b1c59555..9b55d90c 100644 --- a/sable_ircd/src/command/handlers/user.rs +++ b/sable_ircd/src/command/handlers/user.rs @@ -5,14 +5,14 @@ fn handle_user( server: &ClientServer, source: PreClientSource, cmd: &dyn Command, - username: &str, + username: Username, _unused1: &str, _unused2: &str, realname: &str, ) -> CommandResult { // Ignore these results; they'll only fail if USER was already successfully processed // from this pre-client. If that happens we silently ignore the new values. - source.user.set(Username::new_coerce(username)).ok(); + source.user.set(username).ok(); source.realname.set(realname.to_owned()).ok(); if source.can_register() { diff --git a/sable_ircd/src/command/plumbing/argument_type.rs b/sable_ircd/src/command/plumbing/argument_type.rs index 9d9fd2d5..b35d14c5 100644 --- a/sable_ircd/src/command/plumbing/argument_type.rs +++ b/sable_ircd/src/command/plumbing/argument_type.rs @@ -52,6 +52,12 @@ impl<'a> PositionalArgument<'a> for Nickname { } } +impl<'a> PositionalArgument<'a> for Username { + fn parse_str(_ctx: &'a dyn Command, value: &'a str) -> Result { + Ok(Username::new_coerce(value)?) + } +} + impl<'a> PositionalArgument<'a> for state::ChannelRoleName { fn parse_str(_ctx: &'a dyn Command, value: &'a str) -> Result { value diff --git a/sable_ircd/src/messages/numeric.rs b/sable_ircd/src/messages/numeric.rs index 9140bccb..274239b0 100644 --- a/sable_ircd/src/messages/numeric.rs +++ b/sable_ircd/src/messages/numeric.rs @@ -64,6 +64,7 @@ define_messages! { 451(NotRegistered) => { () => ":You have not registered" }, 461(NotEnoughParameters) => { (command: &str) => "{command} :Not enough parameters" }, 462(AlreadyRegistered) => { () => ":You are already connected and cannot handshake again" }, + 468(InvalidUsername) => { () => ":Your username is not valid" }, 472(UnknownMode) => { (c: char) => "{c} :Unknown mode character" }, 479(InvalidChannelName) => { (name: &str) => "{name} :Illegal channel name" }, 482(ChanOpPrivsNeeded) => { (chan: &ChannelName) => "{chan} :You're not a channel operator" }, diff --git a/sable_network/src/validated.rs b/sable_network/src/validated.rs index e8c3f841..f36f7601 100644 --- a/sable_network/src/validated.rs +++ b/sable_network/src/validated.rs @@ -59,7 +59,11 @@ define_validated! { } Username(ArrayString<{ Username::LENGTH }>) { - Ok(()) + if value.len() == 0 { + Self::error(value) + } else { + Ok(()) + } } Hostname(ArrayString<{ Hostname::LENGTH }>) { @@ -129,13 +133,14 @@ impl Username { pub const LENGTH: usize = 10; /// Coerce the provided value into a valid `Username`, by truncating to the - /// permitted length and removing any invalid characters. - pub fn new_coerce(s: &str) -> Self { + /// permitted length, removing any invalid characters, and checking it is not empty. + pub fn new_coerce(s: &str) -> ::Result { let mut s = s.to_string(); s.retain(|c| c != '['); s.truncate(s.floor_char_boundary(Self::LENGTH)); // expect() is safe here; we've already truncated to the max length - Self(ArrayString::try_from(s.as_str()).expect("Failed to convert string")) + let val = ArrayString::try_from(s.as_str()).expect("Failed to convert string"); + Self::validate(&val).map(|()| Self(val)) } }