193 lines
5.5 KiB
Rust
193 lines
5.5 KiB
Rust
use std::collections::{HashSet, HashMap};
|
|
|
|
use async_trait::async_trait;
|
|
use log::{debug, warn};
|
|
use serde::Deserialize;
|
|
use tokio::{net::TcpStream, io::{BufReader, BufWriter, AsyncWrite}, select};
|
|
|
|
use crate::{supervisor::{Id, TaskResult, Sender, Task, Receiver}, linkmap::Linkmap};
|
|
use crate::supervisor::Message as BMessage;
|
|
|
|
use super::{IrcSpConfig, message::{Message, Command}, network::{MessageReader, MessageWriter}};
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct IrcSpTask {
|
|
config: IrcSpConfig,
|
|
links: Linkmap<String>,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Task for IrcSpTask {
|
|
async fn start(&self, id: Id, tx: Sender, mut rx: Receiver) -> TaskResult {
|
|
let stream = TcpStream::connect((self.config.peer_host.as_ref(), self.config.peer_port)).await?;
|
|
let (reader, writer) = stream.into_split();
|
|
let mut reader = MessageReader::new(BufReader::new(reader));
|
|
|
|
let mut ctx = Context {
|
|
id,
|
|
links: &self.links,
|
|
cfg: &self.config,
|
|
writer: MessageWriter::new(BufWriter::new(writer)),
|
|
tx,
|
|
servers: HashSet::from([self.config.server_name.clone()]),
|
|
nicks: HashSet::from([self.config.user_nick.clone()]),
|
|
local_users: HashSet::from([self.config.user_nick.clone()]),
|
|
};
|
|
|
|
|
|
debug!("{id}: identifying");
|
|
|
|
ctx.writer.write_msg(Message::new(Command::PASS, vec![&ctx.cfg.peer_password, "0210", "abridged|"])).await?;
|
|
ctx.writer.write_msg(Message::new(Command::SERVER, vec![&ctx.cfg.server_name, ":Abridged"])).await?;
|
|
ctx.writer.flush().await?;
|
|
|
|
debug!("{id}: checking peer identification");
|
|
|
|
let passmsg = reader.read_message().await?;
|
|
|
|
if passmsg.command != Command::PASS {
|
|
return Err("Invalid handshake, expected PASS".into())
|
|
}
|
|
|
|
if passmsg.params.first() != Some(&ctx.cfg.password.as_str()) {
|
|
return Err("Configured local password does not match password from peer".into())
|
|
}
|
|
|
|
ctx.writer.write_msg(Message::with_prefix(
|
|
&ctx.cfg.server_name,
|
|
Command::NICK,
|
|
vec![&ctx.cfg.user_nick, "0", &ctx.cfg.user_nick, "server", "1", "+", &ctx.cfg.user_nick]
|
|
)).await?;
|
|
|
|
for channel in self.links.iter_channels() {
|
|
ctx.writer.write_msg(Message::with_prefix(
|
|
&ctx.cfg.user_nick,
|
|
Command::JOIN,
|
|
vec![channel],
|
|
)).await?;
|
|
}
|
|
ctx.writer.flush().await?;
|
|
|
|
debug!("{id}: entering event loop");
|
|
loop { select! {
|
|
msg = reader.read_message() => handle_irc_msg(msg?, &mut ctx).await?,
|
|
msg = rx.recv() => handle_bridge_msg(msg?, &mut ctx).await?,
|
|
}}
|
|
}
|
|
}
|
|
|
|
struct Context<'a, W: AsyncWrite + Unpin> {
|
|
id: Id,
|
|
links: &'a Linkmap<String>,
|
|
cfg: &'a IrcSpConfig,
|
|
writer: MessageWriter<W>,
|
|
tx: Sender,
|
|
servers: HashSet<String>,
|
|
nicks: HashSet<String>,
|
|
local_users: HashMap<(String, &str), String>,
|
|
}
|
|
|
|
const MALFORMED: &str = "Malformed IRC message";
|
|
|
|
async fn handle_bridge_msg(msg: BMessage, ctx: &mut Context<'_, impl AsyncWrite + Unpin>) -> TaskResult {
|
|
let key = &(msg.author, msg.suffix.as_ref());
|
|
let nick = match ctx.local_users.get(&key) {
|
|
Some(nick) => nick,
|
|
None => {
|
|
let nick = msg.author + msg.suffix.as_ref();
|
|
ctx.writer.write_msg(Message::with_prefix(
|
|
&ctx.cfg.server_name,
|
|
Command::NICK,
|
|
vec![&nick, "0", &nick, "server", "1", "+", &nick]
|
|
)).await?;
|
|
ctx.local_users.insert(todo!(), nick.clone());
|
|
ctx.nicks.insert(nick.clone());
|
|
&nick
|
|
}
|
|
};
|
|
for channel in ctx.links.get_channels(&msg.link) {
|
|
if msg.origin.0 == ctx.id && &msg.origin.1 == channel {
|
|
continue
|
|
}
|
|
ctx.writer.write_msg(Message::with_prefix(
|
|
&nick,
|
|
Command::PRIVMSG,
|
|
vec![channel, &msg.content]
|
|
)).await?;
|
|
ctx.writer.flush().await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_irc_msg<'a>(msg: Message<'a>, ctx: &mut Context<'_, impl AsyncWrite + Unpin>) -> TaskResult {
|
|
debug!("irc message: {:?}", msg);
|
|
match msg.command {
|
|
Command::PASS => todo!(),
|
|
|
|
Command::SERVER => {
|
|
let name = *msg.params.first().ok_or(MALFORMED)?;
|
|
debug!("linked server {}", name);
|
|
ctx.servers.insert(name.to_owned());
|
|
}
|
|
|
|
Command::NICK => {
|
|
let prefix = msg.prefix.ok_or(MALFORMED)?;
|
|
let name = *msg.params.first().ok_or(MALFORMED)?;
|
|
if ctx.nicks.contains(prefix) {
|
|
ctx.nicks.remove(prefix);
|
|
debug!("updated nick for {} to {}", prefix, name);
|
|
} else {
|
|
debug!("connecting user {}", name);
|
|
}
|
|
ctx.nicks.insert(name.to_owned());
|
|
},
|
|
|
|
Command::PING => {
|
|
let prefix = msg.prefix.ok_or(MALFORMED)?;
|
|
let params = match msg.params.first() {
|
|
Some(m) => vec![prefix, m],
|
|
None => vec![prefix],
|
|
};
|
|
ctx.writer.write_msg(Message::with_prefix(&ctx.cfg.server_name, Command::PONG, params)).await?;
|
|
ctx.writer.flush().await?;
|
|
}
|
|
|
|
Command::PRIVMSG => {
|
|
let channel = msg.params.first().ok_or(MALFORMED)?.to_string();
|
|
if let Some(link) = ctx.links.get_link(&channel) {
|
|
let author = msg.prefix.ok_or(MALFORMED)?.to_owned();
|
|
let content = msg.params.get(1).ok_or(MALFORMED)?.to_string();
|
|
ctx.tx.send(BMessage {
|
|
origin: (ctx.id, channel.clone()),
|
|
link: link.clone(),
|
|
author,
|
|
suffix: ctx.cfg.suffix.clone(),
|
|
content,
|
|
avatar: None,
|
|
})?;
|
|
}
|
|
}
|
|
|
|
Command::ERROR => {
|
|
warn!("Error: {:?}", msg.params.first());
|
|
}
|
|
|
|
_ => (),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/*
|
|
+ PASS A_pitowoVsjRgZ 0210 proxy|
|
|
+ SERVER c.trimill.xyz :Server Info Text
|
|
- :trimill.xyz PASS C_kTNGh3SlfpJ5 0210 proxy|
|
|
- :trimill.xyz SERVER trimill.xyz 1 :Server Info Text
|
|
- :trimill.xyz NICK trimill 1 trimill server 1 + :trimill
|
|
- :trimill.xyz NJOIN #test2 :@trimill
|
|
- :trimill.xyz NJOIN #test :@trimill
|
|
- :trimill.xyz PING :trimill.xyz
|
|
+ :c.trimill.xyz PING :c.trimill.xyz
|
|
+ :c.trimill.xyz PONG trimill.xyz :trimill.xyz
|
|
- :trimill.xyz PONG c.trimill.xyz :c.trimill.xyz
|
|
*/
|