Add support for userhost-in-names capability
Diff
Cargo.lock | 17 +++++++++++++----
Cargo.toml | 1 +
src/channel.rs | 9 +++++++--
src/client.rs | 9 +++++++--
src/connection.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
src/channel/response.rs | 32 ++++++++++++++++++++++++++------
6 files changed, 97 insertions(+), 27 deletions(-)
@@ -10,7 +10,7 @@
dependencies = [
"actix-rt",
"actix_derive",
"bitflags",
"bitflags 1.3.2",
"bytes",
"crossbeam-channel",
"futures-core",
@@ -135,6 +135,12 @@
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e196f430604fcd7503056c5aaa7dfc5bc570582b928240622b075b5cad3b90dd"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -203,7 +209,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"clap_derive",
"clap_lex",
"is-terminal",
@@ -1151,7 +1157,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@@ -1199,7 +1205,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
@@ -1395,7 +1401,7 @@
dependencies = [
"ahash",
"atoi",
"bitflags",
"bitflags 1.3.2",
"byteorder",
"bytes",
"crc",
@@ -1569,6 +1575,7 @@
"anyhow",
"argon2",
"base64 0.21.0-rc.1",
"bitflags 2.0.0-rc.1",
"bytes",
"chrono",
"clap",
@@ -11,6 +11,7 @@
anyhow = "1.0"
argon2 = "0.4"
base64 = "0.21.0-rc.1"
bitflags = "2.0.0-rc.1"
bytes = "1.3"
const_format = "0.2"
chrono = "0.4"
@@ -20,7 +20,7 @@
},
},
client::Client,
connection::{InitiatedConnection, UserId},
connection::{Capability, InitiatedConnection, UserId},
messages::{
Broadcast, ChannelFetchTopic, ChannelInvite, ChannelJoin, ChannelKickUser,
ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode, ChannelUpdateTopic,
@@ -381,7 +381,12 @@
}
for message in ChannelNamesList::new(self).into_messages(msg.connection.nick.to_string()) {
for message in ChannelNamesList::new(self).into_messages(
msg.connection.nick.to_string(),
msg.connection
.capabilities
.contains(Capability::USERHOST_IN_NAMES),
) {
msg.client.do_send(Broadcast {
message,
span: Span::current(),
@@ -12,7 +12,7 @@
use crate::{
channel::Channel,
connection::{sasl::SaslAlreadyAuthenticated, InitiatedConnection, MessageSink},
connection::{sasl::SaslAlreadyAuthenticated, Capability, InitiatedConnection, MessageSink},
messages::{
Broadcast, ChannelFetchTopic, ChannelInvite, ChannelJoin, ChannelKickUser, ChannelList,
ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode, ChannelUpdateTopic,
@@ -281,7 +281,12 @@
for list in result {
let list = list.unwrap();
for message in list.into_messages(this.connection.nick.clone()) {
for message in list.into_messages(
this.connection.nick.clone(),
this.connection
.capabilities
.contains(Capability::USERHOST_IN_NAMES),
) {
this.writer.write(message);
}
}
@@ -1,12 +1,14 @@
mod authenticate;
pub mod sasl;
use std::{
io::{Error, ErrorKind},
net::SocketAddr,
str::FromStr,
};
use actix::{io::FramedWrite, Actor, Addr};
use bitflags::bitflags;
use const_format::concatcp;
use futures::{SinkExt, TryStreamExt};
use irc_proto::{
@@ -30,8 +32,6 @@
pub type MessageStream = FramedRead<ReadHalf<TcpStream>, irc_proto::IrcCodec>;
pub type MessageSink = FramedWrite<Message, WriteHalf<TcpStream>, irc_proto::IrcCodec>;
pub const SUPPORTED_CAPABILITIES: &[&str] = &[concatcp!("sasl=", AuthStrategy::SUPPORTED)];
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, sqlx::Type)]
#[sqlx(transparent)]
pub struct UserId(pub i64);
@@ -44,6 +44,7 @@
mode: Option<String>,
real_name: Option<String>,
user_id: Option<UserId>,
capabilities: Capability,
}
#[derive(Clone)]
@@ -54,6 +55,7 @@
pub mode: String,
pub real_name: String,
pub user_id: UserId,
pub capabilities: Capability,
}
impl InitiatedConnection {
@@ -78,6 +80,7 @@
mode: Some(mode),
real_name: Some(real_name),
user_id: Some(user_id),
capabilities,
} = value else {
return Err(value);
};
@@ -89,6 +92,7 @@
mode,
real_name,
user_id,
capabilities,
})
}
}
@@ -140,15 +144,24 @@
Some("*".to_string()),
CapSubCommand::LS,
None,
Some(SUPPORTED_CAPABILITIES.join(" ")),
Some(Capability::SUPPORTED.join(" ")),
),
})
.await
.unwrap();
}
Command::CAP(_, CapSubCommand::REQ, Some(arguments), None) => {
let acked = if arguments == "sasl" {
true
} else if let Ok(capability) = Capability::from_str(&arguments) {
request.capabilities |= capability;
true
} else {
false
};
write
.send(AcknowledgedCapabilities(arguments).into_message())
.send(AcknowledgedCapabilities(arguments, acked).into_message())
.await?;
}
Command::AUTHENTICATE(msg) => {
@@ -229,8 +242,8 @@
}
}
pub struct AcknowledgedCapabilities(String);
pub struct AcknowledgedCapabilities(String, bool);
impl AcknowledgedCapabilities {
#[must_use]
@@ -240,10 +253,39 @@
prefix: None,
command: Command::CAP(
Some("*".to_string()),
CapSubCommand::ACK,
if self.1 {
CapSubCommand::ACK
} else {
CapSubCommand::NAK
},
None,
Some(self.0),
),
}
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct Capability: u32 {
const USERHOST_IN_NAMES = 0b0000_0000_0000_0000_0000_0000_0000_0001;
}
}
impl Capability {
pub const SUPPORTED: &'static [&'static str] = &[
"userhost-in-names",
concatcp!("sasl=", AuthStrategy::SUPPORTED),
];
}
impl FromStr for Capability {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"userhost-in-names" => Ok(Self::USERHOST_IN_NAMES),
_ => Err(()),
}
}
}
@@ -1,7 +1,9 @@
use irc_proto::{Command, Message, Prefix, Response};
use itertools::Itertools;
use crate::{
channel::{Channel, CurrentChannelTopic},
channel::{permissions::Permission, Channel, CurrentChannelTopic},
connection::InitiatedConnection,
SERVER_NAME,
};
@@ -66,7 +68,7 @@
pub struct ChannelNamesList {
pub channel_name: String,
pub nick_list: Vec<String>,
pub nick_list: Vec<(Permission, InitiatedConnection)>,
}
impl ChannelNamesList {
@@ -77,13 +79,7 @@
nick_list: channel
.clients
.values()
.map(|v| {
format!(
"{}{}",
channel.get_user_permissions(v.user_id).into_prefix(),
v.nick
)
})
.map(|v| (channel.get_user_permissions(v.user_id), v.clone()))
.collect(),
}
}
@@ -97,7 +93,21 @@
}
#[must_use]
pub fn into_messages(self, for_user: String) -> Vec<Message> {
pub fn into_messages(self, for_user: String, with_hostnames: bool) -> Vec<Message> {
let nick_list = self
.nick_list
.into_iter()
.map(|(permission, connection)| {
let permission = permission.into_prefix();
if with_hostnames {
format!("{permission}{}", connection.to_nick())
} else {
format!("{permission}{}", connection.nick)
}
})
.join(" ");
vec![
Message {
tags: None,
@@ -108,7 +118,7 @@
for_user.to_string(),
"=".to_string(),
self.channel_name,
self.nick_list.join(" "),
nick_list,
],
),
},