🏡 index : ~doyle/titanirc.git

author Jordan Doyle <jordan@doyle.la> 2023-02-02 1:32:46.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2023-02-02 1:32:48.0 +00:00:00
commit
4d57bff5266dfc0ea5db9d76e647c88bd00b3c4f [patch]
tree
5d144a40ab01eb18902f583b0d8eda0ce4583d6f
parent
d856c39e22ed1afdfe7c7d58d72eb318bf554f2b
download
4d57bff5266dfc0ea5db9d76e647c88bd00b3c4f.tar.gz

Add support for userhost-in-names capability



Diff

 Cargo.lock              | 17 ++++++++++-----
 Cargo.toml              |  1 +-
 src/channel.rs          |  9 ++++++--
 src/channel/response.rs | 32 +++++++++++++++++++----------
 src/client.rs           |  9 ++++++--
 src/connection.rs       | 56 +++++++++++++++++++++++++++++++++++++++++++-------
 6 files changed, 97 insertions(+), 27 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 98a5cac..4918ac9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10,7 +10,7 @@ checksum = "f728064aca1c318585bf4bb04ffcfac9e75e508ab4e8b1bd9ba5dfe04e2cbed5"
dependencies = [
 "actix-rt",
 "actix_derive",
 "bitflags",
 "bitflags 1.3.2",
 "bytes",
 "crossbeam-channel",
 "futures-core",
@@ -135,6 +135,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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 @@ version = "4.0.32"
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 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
 "bitflags",
 "bitflags 1.3.2",
]

[[package]]
@@ -1199,7 +1205,7 @@ version = "0.36.6"
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 @@ checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105"
dependencies = [
 "ahash",
 "atoi",
 "bitflags",
 "bitflags 1.3.2",
 "byteorder",
 "bytes",
 "crc",
@@ -1569,6 +1575,7 @@ dependencies = [
 "anyhow",
 "argon2",
 "base64 0.21.0-rc.1",
 "bitflags 2.0.0-rc.1",
 "bytes",
 "chrono",
 "clap",
diff --git a/Cargo.toml b/Cargo.toml
index 5f9fb3f..83ced0e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ actix-rt = "2.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"
diff --git a/src/channel.rs b/src/channel.rs
index a914628..ba38a8d 100644
--- a/src/channel.rs
+++ b/src/channel.rs
@@ -20,7 +20,7 @@ use crate::{
        },
    },
    client::Client,
    connection::{InitiatedConnection, UserId},
    connection::{Capability, InitiatedConnection, UserId},
    messages::{
        Broadcast, ChannelFetchTopic, ChannelInvite, ChannelJoin, ChannelKickUser,
        ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode, ChannelUpdateTopic,
@@ -381,7 +381,12 @@ impl Handler<ChannelJoin> for Channel {
        }

        // send the user list to the user
        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(),
diff --git a/src/channel/response.rs b/src/channel/response.rs
index b0d2357..2bec2e8 100644
--- a/src/channel/response.rs
+++ b/src/channel/response.rs
@@ -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 @@ impl ChannelTopic {

pub struct ChannelNamesList {
    pub channel_name: String,
    pub nick_list: Vec<String>,
    pub nick_list: Vec<(Permission, InitiatedConnection)>,
}

impl ChannelNamesList {
@@ -77,13 +79,7 @@ impl ChannelNamesList {
            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 @@ impl ChannelNamesList {
    }

    #[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 @@ impl ChannelNamesList {
                        for_user.to_string(),
                        "=".to_string(),
                        self.channel_name,
                        self.nick_list.join(" "),
                        nick_list,
                    ],
                ),
            },
diff --git a/src/client.rs b/src/client.rs
index 60665d0..0f76b8a 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -12,7 +12,7 @@ use tracing::{debug, error, info_span, instrument, warn, Instrument, Span};

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 @@ impl Handler<ListChannelMemberRequest> for Client {
            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);
                }
            }
diff --git a/src/connection.rs b/src/connection.rs
index b1d707c..ef5abca 100644
--- a/src/connection.rs
+++ b/src/connection.rs
@@ -4,9 +4,11 @@ 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 @@ use crate::{
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 @@ pub struct ConnectionRequest {
    mode: Option<String>,
    real_name: Option<String>,
    user_id: Option<UserId>,
    capabilities: Capability,
}

#[derive(Clone)]
@@ -54,6 +55,7 @@ pub struct InitiatedConnection {
    pub mode: String,
    pub real_name: String,
    pub user_id: UserId,
    pub capabilities: Capability,
}

impl InitiatedConnection {
@@ -78,6 +80,7 @@ impl TryFrom<ConnectionRequest> for InitiatedConnection {
            mode: Some(mode),
            real_name: Some(real_name),
            user_id: Some(user_id),
            capabilities,
        } = value else {
            return Err(value);
        };
@@ -89,6 +92,7 @@ impl TryFrom<ConnectionRequest> for InitiatedConnection {
            mode,
            real_name,
            user_id,
            capabilities,
        })
    }
}
@@ -140,15 +144,24 @@ pub async fn negotiate_client_connection(
                            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 @@ impl NickNotOwnedByUser {
    }
}

/// Return an acknowledgement to the client for their requested capabilities.
pub struct AcknowledgedCapabilities(String);
/// Return an ACK (or NAK) to the client for their requested capabilities.
pub struct AcknowledgedCapabilities(String, bool);

impl AcknowledgedCapabilities {
    #[must_use]
@@ -240,10 +253,39 @@ impl AcknowledgedCapabilities {
            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(()),
        }
    }
}