Skip to content

Commit

Permalink
Add support for soju.im/bouncer-networks
Browse files Browse the repository at this point in the history
This commit allows for specifying a single network for a bouncer, which
will be queried for all other bounced networks. There should be no
change for servers and bouncers which do not implement the bouncer
network specification.

`soju.im/bouncer-networks` requires that SASL be enabled, so a precursor
for this being widely rolled out is #624.

Resolves #77
  • Loading branch information
4e554c4c committed Jan 24, 2025
1 parent 4e066dd commit 6807a01
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 21 deletions.
80 changes: 80 additions & 0 deletions data/src/bouncer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::collections::HashMap;

use std::str::FromStr;

#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub enum NetworkState {
Connected,
Connecting,
#[default]
Disconnected,
}

impl FromStr for NetworkState {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"connected" => Ok(Self::Connected),
"connecting" => Ok(Self::Connecting),
"disconnected" => Ok(Self::Disconnected),
_ => Err(()),
}
}
}

#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct BouncerNetwork {
pub id: String,
pub name: String,
pub host: String,
pub state: NetworkState,
pub port: Option<u16>,
pub use_tls: Option<bool>,
pub pass: Option<String>,
pub nickname: Option<String>,
pub realname: Option<String>,
pub error: Option<String>,
}

impl BouncerNetwork {
pub fn parse(id: &str, s: &str) -> Option<Self> {
// TODO these need to be tag-decoded, but this functionality is locked in the message
// parsing module
let parameter_map: HashMap<_, _> =
s.split(';').filter_map(|k| k.split_once('=')).collect();

Some(BouncerNetwork {
id: id.to_owned(),
name: parameter_map.get("name")?.to_string(),
host: parameter_map.get("host")?.to_string(),
port: parameter_map.get("port").and_then(|s| s.parse().ok()),
nickname: parameter_map.get("nickname").map(|s| s.to_string()),
realname: parameter_map.get("realname").map(|s| s.to_string()),
pass: parameter_map.get("pass").map(|s| s.to_string()),
state: parameter_map.get("state")?.parse().ok()?,
use_tls: match parameter_map.get("port").copied() {
Some("1") => Some(true),
Some("0") => Some(false),
_ => None,
},
error: parameter_map.get("error").map(|s| s.to_string()),
})
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_network() {
assert_eq!(BouncerNetwork {
id: 44.to_string(),
name: "OFTC".to_owned(),
host: "irc.oftc.net".to_owned(),
state: NetworkState::Connecting,
..Default::default()
}, BouncerNetwork::parse("44", "name=OFTC;host=irc.oftc.net;state=connecting").unwrap());
}
}
38 changes: 38 additions & 0 deletions data/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use tokio::fs;

use anyhow::{anyhow, bail, Context as ErrorContext, Result};

use crate::bouncer::BouncerNetwork;
use crate::history::ReadMarker;
use crate::isupport::{ChatHistoryState, ChatHistorySubcommand, MessageReference};
use crate::message::{message_id, server_time, source};
Expand Down Expand Up @@ -114,6 +115,7 @@ pub enum Event {
ChatHistoryAcknowledged(DateTime<Utc>),
ChatHistoryTargetReceived(Target, DateTime<Utc>),
ChatHistoryTargetsReceived(DateTime<Utc>),
NewBouncerNetwork(Server, BouncerNetwork),
}

struct ChatHistoryRequest {
Expand Down Expand Up @@ -142,6 +144,7 @@ pub struct Client {
supports_extended_join: bool,
supports_read_marker: bool,
supports_chathistory: bool,
supports_bouncer_networks: bool,
chathistory_requests: HashMap<Target, ChatHistoryRequest>,
chathistory_exhausted: HashMap<Target, bool>,
chathistory_targets_request: Option<ChatHistoryRequest>,
Expand Down Expand Up @@ -197,6 +200,7 @@ impl Client {
supports_extended_join: false,
supports_read_marker: false,
supports_chathistory: false,
supports_bouncer_networks: false,
chathistory_requests: HashMap::new(),
chathistory_exhausted: HashMap::new(),
chathistory_targets_request: None,
Expand Down Expand Up @@ -224,6 +228,10 @@ impl Client {
Ok(())
}

fn is_primary(&self) -> bool {
!self.server.is_bouncer_network()
}

fn quit(&mut self, reason: Option<String>) {
if let Err(e) = if let Some(reason) = reason {
self.handle.try_send(command!("QUIT", reason))
Expand Down Expand Up @@ -660,6 +668,17 @@ impl Client {
)]);
}
}
Command::BOUNCER(subcommand, params) if subcommand == "NETWORK" => {
let [netid, network] = params.as_slice() else {
bail!("Invalid BOUNCER NEWORKS message {:?}", &message.command);
};
return Ok(vec![Event::NewBouncerNetwork(
self.server.clone(),
ok!(BouncerNetwork::parse(
netid,
network,
)))]);
}
Command::CAP(_, sub, Some(_asterisk), Some(caps)) if sub == "LS" => {
self.listed_caps.extend(caps.split(' ').map(String::from));
}
Expand Down Expand Up @@ -694,6 +713,9 @@ impl Client {
requested.push("sasl");
}

if contains("soju.im/bouncer-networks") && self.is_primary() {
requested.push("soju.im/bouncer-networks-notify");
}

if !requested.is_empty() {
// Request
Expand Down Expand Up @@ -731,6 +753,9 @@ impl Client {
if caps.contains(&"draft/read-marker") {
self.supports_read_marker = true;
}
if caps.contains(&"soju.im/bouncer-networks") {
self.supports_bouncer_networks = true;
}

let supports_sasl = caps.iter().any(|cap| cap.contains("sasl"));

Expand Down Expand Up @@ -851,6 +876,14 @@ impl Client {
for param in sasl.params() {
self.handle.try_send(command!("AUTHENTICATE", param))?;
}
// now that we are authenticated, we can connect to our desired network
if let Some(id) = &self.server.bouncer_id() {
self.handle.try_send(command!(
"BOUNCER",
"BIND",
*id
))?
}
}
}
Command::Numeric(RPL_LOGGEDIN, args) => {
Expand Down Expand Up @@ -1158,6 +1191,11 @@ impl Client {
})
.collect::<Vec<_>>();

// Check for bouncer network info
if self.is_primary() && self.supports_bouncer_networks {
self.handle.try_send(command!("BOUNCER", "LISTNETWORKS"))?;
}

// Send JOIN
for message in group_joins(&channels, &self.config.channel_keys) {
self.handle.try_send(message)?;
Expand Down
21 changes: 20 additions & 1 deletion data/src/config/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::time::Duration;
use irc::connection;
use serde::{Deserialize, Deserializer};

use crate::config;
use crate::{bouncer::BouncerNetwork, config};

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Server {
Expand Down Expand Up @@ -142,6 +142,25 @@ impl Server {
proxy: proxy.map(From::from),
}
}

pub fn bouncer_server(&self, _network: &BouncerNetwork) -> Self {
Self {
// nickserv info not relevant to the bounced network
nick_password_file: Default::default(),
nick_password_command: Default::default(),
nick_identify_syntax: Default::default(),

// channels not relevant
channels: Default::default(),
channel_keys: Default::default(),

// ghost sequence not relevant
should_ghost: Default::default(),
ghost_sequence: default_ghost_sequence(),

..self.clone()
}
}
}

impl Default for Server {
Expand Down
3 changes: 3 additions & 0 deletions data/src/isupport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{target::Target, Message};
pub enum Kind {
AWAYLEN,
CASEMAPPING,
BOUNCER_NETID,
CHANLIMIT,
CHANNELLEN,
CHANTYPES,
Expand Down Expand Up @@ -74,6 +75,7 @@ impl FromStr for Operation {
"BOT" => Ok(Operation::Add(Parameter::BOT(parse_required_letter(
value, None,
)?))),
"BOUNCER_NETID" => Ok(Operation::Add(Parameter::BOUNCER_NETID(value.to_owned()))),
"CALLERID" => Ok(Operation::Add(Parameter::CALLERID(
parse_required_letter(value, Some(DEFAULT_CALLER_ID_LETTER))?,
))),
Expand Down Expand Up @@ -520,6 +522,7 @@ pub enum Parameter {
ACCOUNTEXTBAN(Vec<String>),
AWAYLEN(Option<u16>),
BOT(char),
BOUNCER_NETID(String),
CALLERID(char),
CASEMAPPING(CaseMap),
CHANLIMIT(Vec<ChannelLimit>),
Expand Down
1 change: 1 addition & 0 deletions data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub use self::window::Window;

pub mod appearance;
pub mod audio;
pub mod bouncer;
pub mod buffer;
pub mod channel;
pub mod client;
Expand Down
1 change: 1 addition & 0 deletions data/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,7 @@ fn target(
| Command::USERIP(_)
| Command::HELP(_)
| Command::Numeric(_, _)
| Command::BOUNCER(..)
| Command::Unknown(_, _)
| Command::Raw(_) => Some(Target::Server {
source: Source::Server(None),
Expand Down
Loading

0 comments on commit 6807a01

Please sign in to comment.