|
@@ -0,0 +1,634 @@
|
|
|
|
|
+/* This file is part of DarkFi (https://dark.fi)
|
|
|
|
|
+ *
|
|
|
|
|
+ * Copyright (C) 2020-2023 Dyne.org foundation
|
|
|
|
|
+ *
|
|
|
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
|
|
|
+ * it under the terms of the GNU Affero General Public License as
|
|
|
|
|
+ * published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
+ * License, or (at your option) any later version.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
+ * GNU Affero General Public License for more details.
|
|
|
|
|
+ *
|
|
|
|
|
+ * You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+use std::net::SocketAddr;
|
|
|
|
|
+
|
|
|
|
|
+use futures::{
|
|
|
|
|
+ io::{BufReader, ReadHalf, WriteHalf},
|
|
|
|
|
+ AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+use log::{debug, error, info, warn};
|
|
|
|
|
+
|
|
|
|
|
+use darkfi::{system::Subscription, Error, Result};
|
|
|
|
|
+
|
|
|
|
|
+use crate::{
|
|
|
|
|
+ crypto::{decrypt_privmsg, decrypt_target, encrypt_privmsg},
|
|
|
|
|
+ model::Event,
|
|
|
|
|
+ privmsg::{EventAction, PrivMsgEvent},
|
|
|
|
|
+ protocol_event::UnreadEventsPtr,
|
|
|
|
|
+ settings,
|
|
|
|
|
+ settings::RPL,
|
|
|
|
|
+ ChannelInfo,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+use super::{ClientSubMsg, IrcConfig, NotifierMsg};
|
|
|
|
|
+
|
|
|
|
|
+pub struct IrcClient<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> {
|
|
|
|
|
+ // network stream
|
|
|
|
|
+ write_stream: WriteHalf<C>,
|
|
|
|
|
+ read_stream: BufReader<ReadHalf<C>>,
|
|
|
|
|
+ pub address: SocketAddr,
|
|
|
|
|
+
|
|
|
|
|
+ // irc config
|
|
|
|
|
+ irc_config: IrcConfig,
|
|
|
|
|
+
|
|
|
|
|
+ server_notifier: smol::channel::Sender<(NotifierMsg, u64)>,
|
|
|
|
|
+ subscription: Subscription<ClientSubMsg>,
|
|
|
|
|
+
|
|
|
|
|
+ unread_events: UnreadEventsPtr,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcClient<C> {
|
|
|
|
|
+ pub fn new(
|
|
|
|
|
+ write_stream: WriteHalf<C>,
|
|
|
|
|
+ read_stream: BufReader<ReadHalf<C>>,
|
|
|
|
|
+ address: SocketAddr,
|
|
|
|
|
+ irc_config: IrcConfig,
|
|
|
|
|
+ server_notifier: smol::channel::Sender<(NotifierMsg, u64)>,
|
|
|
|
|
+ subscription: Subscription<ClientSubMsg>,
|
|
|
|
|
+ unread_events: UnreadEventsPtr,
|
|
|
|
|
+ ) -> Self {
|
|
|
|
|
+ Self {
|
|
|
|
|
+ write_stream,
|
|
|
|
|
+ read_stream,
|
|
|
|
|
+ address,
|
|
|
|
|
+ irc_config,
|
|
|
|
|
+ subscription,
|
|
|
|
|
+ server_notifier,
|
|
|
|
|
+ unread_events,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Start listening for messages came from View or irc client
|
|
|
|
|
+ pub async fn listen(&mut self) {
|
|
|
|
|
+ loop {
|
|
|
|
|
+ let mut line = String::new();
|
|
|
|
|
+
|
|
|
|
|
+ // TODO fix this lock
|
|
|
|
|
+ // let mut view = view.lock().await;
|
|
|
|
|
+ futures::select! {
|
|
|
|
|
+ // Process msg from View
|
|
|
|
|
+ // evnt = view.process().fuse() => {
|
|
|
|
|
+ // if let Err(e) = evnt {
|
|
|
|
|
+ // error!("[CLIENT {}] Read line error: {}", self.address, e);
|
|
|
|
|
+ // break
|
|
|
|
|
+ // }
|
|
|
|
|
+ // let event = evnt.unwrap();
|
|
|
|
|
+ // match event.action {
|
|
|
|
|
+ // EventAction::PrivMsg(mut m) => {
|
|
|
|
|
+ // if let Err(e) = self.process_msg(&mut m).await {
|
|
|
|
|
+ // error!("[CLIENT {}] Process msg: {}", self.address, e);
|
|
|
|
|
+ // break
|
|
|
|
|
+ // }
|
|
|
|
|
+ // }
|
|
|
|
|
+ // }
|
|
|
|
|
+ // }
|
|
|
|
|
+ // Process msgs from other client connnected to the same irc server
|
|
|
|
|
+ msg = self.subscription.receive().fuse() => {
|
|
|
|
|
+ match msg {
|
|
|
|
|
+ ClientSubMsg::Privmsg(mut m) => {
|
|
|
|
|
+ if let Err(e) = self.process_msg(&mut m).await {
|
|
|
|
|
+ error!("[CLIENT {}] Process msg: {}", self.address, e);
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ClientSubMsg::Config(c) => self.update_config(c).await,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ // Process msg from IRC client
|
|
|
|
|
+ err = self.read_stream.read_line(&mut line).fuse() => {
|
|
|
|
|
+ if let Err(e) = err {
|
|
|
|
|
+ error!("[CLIENT {}] Read line error: {}", self.address, e);
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ if let Err(e) = self.process_line(line).await {
|
|
|
|
|
+ error!("[CLIENT {}] Process line failed: {}", self.address, e);
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ warn!("[CLIENT {}] Close connection", self.address);
|
|
|
|
|
+ self.subscription.unsubscribe().await;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pub async fn update_config(&mut self, new_config: IrcConfig) {
|
|
|
|
|
+ self.irc_config.channels.extend(new_config.channels);
|
|
|
|
|
+ self.irc_config.contacts.extend(new_config.contacts);
|
|
|
|
|
+ self.irc_config.nickname = new_config.nickname;
|
|
|
|
|
+ self.irc_config.private_key = new_config.private_key;
|
|
|
|
|
+ self.irc_config.password = new_config.password;
|
|
|
|
|
+
|
|
|
|
|
+ if self.on_receive_join(self.irc_config.channels.keys().cloned().collect()).await.is_err() {
|
|
|
|
|
+ warn!("Error to join updated channels");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pub async fn process_msg(&mut self, msg: &mut PrivMsgEvent) -> Result<()> {
|
|
|
|
|
+ info!("[CLIENT {}] msg from View: {:?}", self.address, msg.to_string());
|
|
|
|
|
+
|
|
|
|
|
+ decrypt_target(
|
|
|
|
|
+ msg,
|
|
|
|
|
+ &self.irc_config.channels,
|
|
|
|
|
+ &self.irc_config.contacts,
|
|
|
|
|
+ &self.irc_config.private_key,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if msg.target.starts_with('#') {
|
|
|
|
|
+ // Try to potentially decrypt the incoming message.
|
|
|
|
|
+ if !self.irc_config.channels.contains_key(&msg.target) {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let chan_info = self.irc_config.channels.get_mut(&msg.target).unwrap();
|
|
|
|
|
+ if !chan_info.joined {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if let Some(salt_box) = &chan_info.salt_box(&msg.target) {
|
|
|
|
|
+ decrypt_privmsg(salt_box, msg);
|
|
|
|
|
+ info!(
|
|
|
|
|
+ "[CLIENT {}] Decrypted received message: {:?}",
|
|
|
|
|
+ self.address,
|
|
|
|
|
+ msg.to_string()
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // add the nickname to the channel's names
|
|
|
|
|
+ if !chan_info.names.contains(&msg.nick) {
|
|
|
|
|
+ chan_info.names.push(msg.nick.clone());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.reply(&msg.to_string()).await?;
|
|
|
|
|
+ } else if self.irc_config.private_key.is_some() {
|
|
|
|
|
+ if let Some(contact_info) = self.irc_config.contacts.get(&msg.target) {
|
|
|
|
|
+ let salt_box = &contact_info
|
|
|
|
|
+ .salt_box(self.irc_config.private_key.as_ref().unwrap(), &msg.target);
|
|
|
|
|
+
|
|
|
|
|
+ if salt_box.is_none() {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ decrypt_privmsg(salt_box.as_ref().unwrap(), msg);
|
|
|
|
|
+
|
|
|
|
|
+ info!(
|
|
|
|
|
+ "[CLIENT {}] Decrypted received message: {:?}",
|
|
|
|
|
+ self.address,
|
|
|
|
|
+ msg.to_string()
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ self.reply(&msg.to_string()).await?;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pub async fn process_line(&mut self, line: String) -> Result<()> {
|
|
|
|
|
+ let irc_msg = match clean_input_line(line) {
|
|
|
|
|
+ Ok(msg) => msg,
|
|
|
|
|
+ Err(e) => {
|
|
|
|
|
+ warn!("[CLIENT {}] Connection error: {}", self.address, e);
|
|
|
|
|
+ return Err(Error::ChannelStopped)
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ info!("[CLIENT {}] Process msg: {}", self.address, irc_msg);
|
|
|
|
|
+
|
|
|
|
|
+ if let Err(e) = self.update(irc_msg).await {
|
|
|
|
|
+ warn!("[CLIENT {}] Connection error: {}", self.address, e);
|
|
|
|
|
+ return Err(Error::ChannelStopped)
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn update(&mut self, line: String) -> Result<()> {
|
|
|
|
|
+ if line.len() > settings::MAXIMUM_LENGTH_OF_MESSAGE {
|
|
|
|
|
+ return Err(Error::MalformedPacket)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let (command, value) = parse_line(&line)?;
|
|
|
|
|
+ let (command, value) = (command.as_str(), value.as_str());
|
|
|
|
|
+
|
|
|
|
|
+ match command {
|
|
|
|
|
+ "PASS" => self.on_receive_pass(value).await?,
|
|
|
|
|
+ "USER" => self.on_receive_user().await?,
|
|
|
|
|
+ "NAMES" => self.on_receive_names(value.split(',').map(String::from).collect()).await?,
|
|
|
|
|
+ "NICK" => self.on_receive_nick(value).await?,
|
|
|
|
|
+ "JOIN" => self.on_receive_join(value.split(',').map(String::from).collect()).await?,
|
|
|
|
|
+ "PART" => self.on_receive_part(value.split(',').map(String::from).collect()).await?,
|
|
|
|
|
+ "TOPIC" => self.on_receive_topic(&line, value).await?,
|
|
|
|
|
+ "PING" => self.on_ping(value).await?,
|
|
|
|
|
+ "PRIVMSG" => self.on_receive_privmsg(&line, value).await?,
|
|
|
|
|
+ "CAP" => self.on_receive_cap(&line, &value.to_uppercase()).await?,
|
|
|
|
|
+ "QUIT" => self.on_quit()?,
|
|
|
|
|
+ _ => warn!("[CLIENT {}] Unimplemented `{}` command", self.address, command),
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.registre().await?;
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn registre(&mut self) -> Result<()> {
|
|
|
|
|
+ if !self.irc_config.is_pass_init && self.irc_config.password.is_empty() {
|
|
|
|
|
+ self.irc_config.is_pass_init = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if !self.irc_config.is_registered &&
|
|
|
|
|
+ self.irc_config.is_cap_end &&
|
|
|
|
|
+ self.irc_config.is_nick_init &&
|
|
|
|
|
+ self.irc_config.is_user_init
|
|
|
|
|
+ {
|
|
|
|
|
+ debug!("Initializing peer connection");
|
|
|
|
|
+ let register_reply =
|
|
|
|
|
+ format!(":darkfi 001 {} :Let there be dark\r\n", self.irc_config.nickname);
|
|
|
|
|
+ self.reply(®ister_reply).await?;
|
|
|
|
|
+ self.irc_config.is_registered = true;
|
|
|
|
|
+
|
|
|
|
|
+ // join all channels
|
|
|
|
|
+ self.on_receive_join(self.irc_config.channels.keys().cloned().collect()).await?;
|
|
|
|
|
+
|
|
|
|
|
+ if *self.irc_config.capabilities.get("no-history").unwrap() {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn reply(&mut self, message: &str) -> Result<()> {
|
|
|
|
|
+ self.write_stream.write_all(message.as_bytes()).await?;
|
|
|
|
|
+ debug!("Sent {}", message.trim_end());
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fn on_quit(&self) -> Result<()> {
|
|
|
|
|
+ // Close the connection
|
|
|
|
|
+ Err(Error::NetworkServiceStopped)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_user(&mut self) -> Result<()> {
|
|
|
|
|
+ // We can stuff any extra things like public keys in here.
|
|
|
|
|
+ // Ignore it for now.
|
|
|
|
|
+ if self.irc_config.is_pass_init {
|
|
|
|
|
+ self.irc_config.is_user_init = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Close the connection
|
|
|
|
|
+ warn!("[CLIENT {}] Password is required", self.address);
|
|
|
|
|
+ return self.on_quit()
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_pass(&mut self, password: &str) -> Result<()> {
|
|
|
|
|
+ if self.irc_config.password == password {
|
|
|
|
|
+ self.irc_config.is_pass_init = true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Close the connection
|
|
|
|
|
+ warn!("[CLIENT {}] Password is not correct!", self.address);
|
|
|
|
|
+ return self.on_quit()
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_nick(&mut self, nickname: &str) -> Result<()> {
|
|
|
|
|
+ if nickname.len() > settings::MAXIMUM_LENGTH_OF_NICKNAME {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.irc_config.is_nick_init = true;
|
|
|
|
|
+ let old_nick = std::mem::replace(&mut self.irc_config.nickname, nickname.to_string());
|
|
|
|
|
+
|
|
|
|
|
+ let nick_reply =
|
|
|
|
|
+ format!(":{}!anon@dark.fi NICK {}\r\n", old_nick, self.irc_config.nickname);
|
|
|
|
|
+ self.reply(&nick_reply).await
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_part(&mut self, channels: Vec<String>) -> Result<()> {
|
|
|
|
|
+ for chan in channels.iter() {
|
|
|
|
|
+ let part_reply =
|
|
|
|
|
+ format!(":{}!anon@dark.fi PART {}\r\n", self.irc_config.nickname, chan);
|
|
|
|
|
+ self.reply(&part_reply).await?;
|
|
|
|
|
+ if self.irc_config.channels.contains_key(chan) {
|
|
|
|
|
+ let chan_info = self.irc_config.channels.get_mut(chan).unwrap();
|
|
|
|
|
+ chan_info.joined = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_topic(&mut self, line: &str, channel: &str) -> Result<()> {
|
|
|
|
|
+ if let Some(substr_idx) = line.find(':') {
|
|
|
|
|
+ // Client is setting the topic
|
|
|
|
|
+ if substr_idx >= line.len() {
|
|
|
|
|
+ return Err(Error::MalformedPacket)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let topic = &line[substr_idx + 1..];
|
|
|
|
|
+ let chan_info = self.irc_config.channels.get_mut(channel).unwrap();
|
|
|
|
|
+ chan_info.topic = Some(topic.to_string());
|
|
|
|
|
+
|
|
|
|
|
+ let topic_reply = format!(
|
|
|
|
|
+ ":{}!anon@dark.fi TOPIC {} :{}\r\n",
|
|
|
|
|
+ self.irc_config.nickname, channel, topic
|
|
|
|
|
+ );
|
|
|
|
|
+ self.reply(&topic_reply).await?;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Client is asking or the topic
|
|
|
|
|
+ let chan_info = self.irc_config.channels.get(channel).unwrap();
|
|
|
|
|
+ let topic_reply = if let Some(topic) = &chan_info.topic {
|
|
|
|
|
+ format!(
|
|
|
|
|
+ "{} {} {} :{}\r\n",
|
|
|
|
|
+ RPL::Topic as u32,
|
|
|
|
|
+ self.irc_config.nickname,
|
|
|
|
|
+ channel,
|
|
|
|
|
+ topic
|
|
|
|
|
+ )
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const TOPIC: &str = "No topic is set";
|
|
|
|
|
+ format!(
|
|
|
|
|
+ "{} {} {} :{}\r\n",
|
|
|
|
|
+ RPL::NoTopic as u32,
|
|
|
|
|
+ self.irc_config.nickname,
|
|
|
|
|
+ channel,
|
|
|
|
|
+ TOPIC
|
|
|
|
|
+ )
|
|
|
|
|
+ };
|
|
|
|
|
+ self.reply(&topic_reply).await?;
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_ping(&mut self, value: &str) -> Result<()> {
|
|
|
|
|
+ let pong = format!("PONG {}\r\n", value);
|
|
|
|
|
+ self.reply(&pong).await
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_cap(&mut self, line: &str, subcommand: &str) -> Result<()> {
|
|
|
|
|
+ self.irc_config.is_cap_end = false;
|
|
|
|
|
+
|
|
|
|
|
+ let capabilities_keys: Vec<String> = self.irc_config.capabilities.keys().cloned().collect();
|
|
|
|
|
+
|
|
|
|
|
+ match subcommand {
|
|
|
|
|
+ "LS" => {
|
|
|
|
|
+ let cap_ls_reply = format!(
|
|
|
|
|
+ ":{}!anon@dark.fi CAP * LS :{}\r\n",
|
|
|
|
|
+ self.irc_config.nickname,
|
|
|
|
|
+ capabilities_keys.join(" ")
|
|
|
|
|
+ );
|
|
|
|
|
+ self.reply(&cap_ls_reply).await?;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ "REQ" => {
|
|
|
|
|
+ let substr_idx = line.find(':').ok_or(Error::MalformedPacket)?;
|
|
|
|
|
+
|
|
|
|
|
+ if substr_idx >= line.len() {
|
|
|
|
|
+ return Err(Error::MalformedPacket)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let cap: Vec<&str> = line[substr_idx + 1..].split(' ').collect();
|
|
|
|
|
+
|
|
|
|
|
+ let mut ack_list = vec![];
|
|
|
|
|
+ let mut nak_list = vec![];
|
|
|
|
|
+
|
|
|
|
|
+ for c in cap {
|
|
|
|
|
+ if self.irc_config.capabilities.contains_key(c) {
|
|
|
|
|
+ self.irc_config.capabilities.insert(c.to_string(), true);
|
|
|
|
|
+ ack_list.push(c);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ nak_list.push(c);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let cap_ack_reply = format!(
|
|
|
|
|
+ ":{}!anon@dark.fi CAP * ACK :{}\r\n",
|
|
|
|
|
+ self.irc_config.nickname,
|
|
|
|
|
+ ack_list.join(" ")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ let cap_nak_reply = format!(
|
|
|
|
|
+ ":{}!anon@dark.fi CAP * NAK :{}\r\n",
|
|
|
|
|
+ self.irc_config.nickname,
|
|
|
|
|
+ nak_list.join(" ")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ self.reply(&cap_ack_reply).await?;
|
|
|
|
|
+ self.reply(&cap_nak_reply).await?;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ "LIST" => {
|
|
|
|
|
+ let enabled_capabilities: Vec<String> = self
|
|
|
|
|
+ .irc_config
|
|
|
|
|
+ .capabilities
|
|
|
|
|
+ .clone()
|
|
|
|
|
+ .into_iter()
|
|
|
|
|
+ .filter(|(_, v)| *v)
|
|
|
|
|
+ .map(|(k, _)| k)
|
|
|
|
|
+ .collect();
|
|
|
|
|
+
|
|
|
|
|
+ let cap_list_reply = format!(
|
|
|
|
|
+ ":{}!anon@dark.fi CAP * LIST :{}\r\n",
|
|
|
|
|
+ self.irc_config.nickname,
|
|
|
|
|
+ enabled_capabilities.join(" ")
|
|
|
|
|
+ );
|
|
|
|
|
+ self.reply(&cap_list_reply).await?;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ "END" => {
|
|
|
|
|
+ self.irc_config.is_cap_end = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ _ => {}
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_names(&mut self, channels: Vec<String>) -> Result<()> {
|
|
|
|
|
+ for chan in channels.iter() {
|
|
|
|
|
+ if !chan.starts_with('#') {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ if self.irc_config.channels.contains_key(chan) {
|
|
|
|
|
+ let chan_info = self.irc_config.channels.get(chan).unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ if chan_info.names.is_empty() {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let names_reply = format!(
|
|
|
|
|
+ ":{}!anon@dark.fi {} = {} : {}\r\n",
|
|
|
|
|
+ self.irc_config.nickname,
|
|
|
|
|
+ RPL::NameReply as u32,
|
|
|
|
|
+ chan,
|
|
|
|
|
+ chan_info.names.join(" ")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ self.reply(&names_reply).await?;
|
|
|
|
|
+
|
|
|
|
|
+ let end_of_names = format!(
|
|
|
|
|
+ ":DarkFi {:03} {} {} :End of NAMES list\r\n",
|
|
|
|
|
+ RPL::EndOfNames as u32,
|
|
|
|
|
+ self.irc_config.nickname,
|
|
|
|
|
+ chan
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ self.reply(&end_of_names).await?;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_privmsg(&mut self, line: &str, target: &str) -> Result<()> {
|
|
|
|
|
+ let substr_idx = line.find(':').ok_or(Error::MalformedPacket)?;
|
|
|
|
|
+
|
|
|
|
|
+ if substr_idx >= line.len() {
|
|
|
|
|
+ return Err(Error::MalformedPacket)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let message = line[substr_idx + 1..].to_string();
|
|
|
|
|
+
|
|
|
|
|
+ info!("[CLIENT {}] (Plain) PRIVMSG {} :{}", self.address, target, message,);
|
|
|
|
|
+
|
|
|
|
|
+ let mut privmsg = PrivMsgEvent {
|
|
|
|
|
+ nick: self.irc_config.nickname.clone(),
|
|
|
|
|
+ target: target.to_string(),
|
|
|
|
|
+ msg: message,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if target.starts_with('#') {
|
|
|
|
|
+ if !self.irc_config.channels.contains_key(target) {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let channel_info = self.irc_config.channels.get(target).unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ if !channel_info.joined {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if let Some(salt_box) = &channel_info.salt_box(target) {
|
|
|
|
|
+ encrypt_privmsg(salt_box, &mut privmsg);
|
|
|
|
|
+ info!("[CLIENT {}] (Encrypted) PRIVMSG: {:?}", self.address, privmsg.to_string());
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if self.irc_config.private_key.is_some() {
|
|
|
|
|
+ if let Some(contact_info) = self.irc_config.contacts.get(target) {
|
|
|
|
|
+ if let Some(salt_box) =
|
|
|
|
|
+ &contact_info.salt_box(self.irc_config.private_key.as_ref().unwrap(), target)
|
|
|
|
|
+ {
|
|
|
|
|
+ encrypt_privmsg(salt_box, &mut privmsg);
|
|
|
|
|
+ info!(
|
|
|
|
|
+ "[CLIENT {}] (Encrypted) PRIVMSG: {:?}",
|
|
|
|
|
+ self.address,
|
|
|
|
|
+ privmsg.to_string()
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.server_notifier
|
|
|
|
|
+ .send((NotifierMsg::Privmsg(privmsg), self.subscription.get_id()))
|
|
|
|
|
+ .await?;
|
|
|
|
|
+
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn on_receive_join(&mut self, channels: Vec<String>) -> Result<()> {
|
|
|
|
|
+ for chan in channels.iter() {
|
|
|
|
|
+ if !chan.starts_with('#') {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ if !self.irc_config.channels.contains_key(chan) {
|
|
|
|
|
+ let mut chan_info = ChannelInfo::new();
|
|
|
|
|
+ chan_info.topic = Some("n/a".to_string());
|
|
|
|
|
+ self.irc_config.channels.insert(chan.to_string(), chan_info);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let chan_info = self.irc_config.channels.get_mut(chan).unwrap();
|
|
|
|
|
+ if chan_info.joined {
|
|
|
|
|
+ return Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+ chan_info.joined = true;
|
|
|
|
|
+
|
|
|
|
|
+ let topic =
|
|
|
|
|
+ if let Some(topic) = chan_info.topic.clone() { topic } else { "n/a".to_string() };
|
|
|
|
|
+ chan_info.topic = Some(topic.to_string());
|
|
|
|
|
+
|
|
|
|
|
+ {
|
|
|
|
|
+ let j = format!(":{}!anon@dark.fi JOIN {}\r\n", self.irc_config.nickname, chan);
|
|
|
|
|
+ let t = format!(":DarkFi TOPIC {} :{}\r\n", chan, topic);
|
|
|
|
|
+ self.reply(&j).await?;
|
|
|
|
|
+ self.reply(&t).await?;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let unread_events = self.unread_events.lock().await.events.clone();
|
|
|
|
|
+
|
|
|
|
|
+ let chan_info = self.irc_config.channels.get_mut(chan).unwrap();
|
|
|
|
|
+ if chan_info.joined {
|
|
|
|
|
+ let mut hash_vec: Vec<Event> = unread_events.values().cloned().collect();
|
|
|
|
|
+ hash_vec.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
|
|
|
|
|
+
|
|
|
|
|
+ for event in hash_vec {
|
|
|
|
|
+ match event.action {
|
|
|
|
|
+ EventAction::PrivMsg(mut m) => {
|
|
|
|
|
+ if let Err(e) = self.process_msg(&mut m).await {
|
|
|
|
|
+ error!("[CLIENT {}] Process msg: {}", self.address, e);
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//
|
|
|
|
|
+// Helper functions
|
|
|
|
|
+//
|
|
|
|
|
+fn clean_input_line(mut line: String) -> Result<String> {
|
|
|
|
|
+ if line.is_empty() {
|
|
|
|
|
+ return Err(Error::ChannelStopped)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if line == "\n" || line == "\r\n" {
|
|
|
|
|
+ return Err(Error::ChannelStopped)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if &line[(line.len() - 2)..] == "\r\n" {
|
|
|
|
|
+ // Remove CRLF
|
|
|
|
|
+ line.pop();
|
|
|
|
|
+ line.pop();
|
|
|
|
|
+ } else if &line[(line.len() - 1)..] == "\n" {
|
|
|
|
|
+ line.pop();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return Err(Error::ChannelStopped)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Ok(line.clone())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn parse_line(line: &str) -> Result<(String, String)> {
|
|
|
|
|
+ let mut tokens = line.split_ascii_whitespace();
|
|
|
|
|
+ // Commands can begin with :garbage but we will reject clients doing
|
|
|
|
|
+ // that for now to keep the protocol simple and focused.
|
|
|
|
|
+ let command = tokens.next().ok_or(Error::MalformedPacket)?.to_uppercase();
|
|
|
|
|
+ let value = tokens.next().ok_or(Error::MalformedPacket)?;
|
|
|
|
|
+ Ok((command, value.to_owned()))
|
|
|
|
|
+}
|