| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- use std::sync::atomic::Ordering;
- use async_std::net::TcpStream;
- use futures::{io::WriteHalf, AsyncWriteExt};
- use fxhash::FxHashMap;
- use log::{debug, info, warn};
- use rand::{rngs::OsRng, RngCore};
- use darkfi::{Error, Result};
- use crate::{crypto::encrypt_message, privmsg::Privmsg, ChannelInfo, SeenMsgIds};
- const RPL_NOTOPIC: u32 = 331;
- const RPL_TOPIC: u32 = 332;
- pub struct IrcServerConnection {
- write_stream: WriteHalf<TcpStream>,
- is_nick_init: bool,
- is_user_init: bool,
- is_registered: bool,
- nickname: String,
- seen_msg_id: SeenMsgIds,
- p2p_sender: async_channel::Sender<Privmsg>,
- auto_channels: Vec<String>,
- pub configured_chans: FxHashMap<String, ChannelInfo>,
- }
- impl IrcServerConnection {
- pub fn new(
- write_stream: WriteHalf<TcpStream>,
- seen_msg_id: SeenMsgIds,
- p2p_sender: async_channel::Sender<Privmsg>,
- auto_channels: Vec<String>,
- configured_chans: FxHashMap<String, ChannelInfo>,
- ) -> Self {
- Self {
- write_stream,
- is_nick_init: false,
- is_user_init: false,
- is_registered: false,
- nickname: "anon".to_string(),
- seen_msg_id,
- p2p_sender,
- auto_channels,
- configured_chans,
- }
- }
- pub async fn update(&mut self, line: String) -> Result<()> {
- 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)?;
- info!("IRC server received command: {}", command.to_uppercase());
- match command.to_uppercase().as_str() {
- "USER" => {
- // We can stuff any extra things like public keys in here.
- // Ignore it for now.
- self.is_user_init = true;
- }
- "NICK" => {
- let nickname = tokens.next().ok_or(Error::MalformedPacket)?;
- self.is_nick_init = true;
- let old_nick = std::mem::replace(&mut self.nickname, nickname.to_string());
- let nick_reply = format!(":{}!anon@dark.fi NICK {}\r\n", old_nick, self.nickname);
- self.reply(&nick_reply).await?;
- }
- "JOIN" => {
- let channels = tokens.next().ok_or(Error::MalformedPacket)?;
- for chan in channels.split(',') {
- if !chan.starts_with('#') {
- warn!("{} is not a valid name for channel", chan);
- continue
- }
- let join_reply = format!(":{}!anon@dark.fi JOIN {}\r\n", self.nickname, chan);
- self.reply(&join_reply).await?;
- if !self.configured_chans.contains_key(chan) {
- self.configured_chans.insert(chan.to_string(), ChannelInfo::new()?);
- } else {
- let chan_info = self.configured_chans.get_mut(chan).unwrap();
- chan_info.joined.store(true, Ordering::Relaxed);
- }
- }
- }
- "PART" => {
- let channels = tokens.next().ok_or(Error::MalformedPacket)?;
- for chan in channels.split(',') {
- let part_reply = format!(":{}!anon@dark.fi PART {}\r\n", self.nickname, chan);
- self.reply(&part_reply).await?;
- if self.configured_chans.contains_key(chan) {
- let chan_info = self.configured_chans.get_mut(chan).unwrap();
- chan_info.joined.store(false, Ordering::Relaxed);
- }
- }
- }
- "TOPIC" => {
- let channel = tokens.next().ok_or(Error::MalformedPacket)?;
- 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.configured_chans.get_mut(channel).unwrap();
- chan_info.topic = Some(topic.to_string());
- let topic_reply =
- format!(":{}!anon@dark.fi TOPIC {} :{}\r\n", self.nickname, channel, topic);
- self.reply(&topic_reply).await?;
- } else {
- // Client is asking or the topic
- let chan_info = self.configured_chans.get(channel).unwrap();
- let topic_reply = if let Some(topic) = &chan_info.topic {
- format!("{} {} {} :{}\r\n", RPL_TOPIC, self.nickname, channel, topic)
- } else {
- const TOPIC: &str = "No topic is set";
- format!("{} {} {} :{}\r\n", RPL_NOTOPIC, self.nickname, channel, TOPIC)
- };
- self.reply(&topic_reply).await?;
- }
- }
- "PING" => {
- let line_clone = line.clone();
- let split_line: Vec<&str> = line_clone.split_whitespace().collect();
- if split_line.len() > 1 {
- let pong = format!("PONG {}\r\n", split_line[1]);
- self.reply(&pong).await?;
- }
- }
- "PRIVMSG" => {
- let channel = tokens.next().ok_or(Error::MalformedPacket)?;
- let substr_idx = line.find(':').ok_or(Error::MalformedPacket)?;
- if substr_idx >= line.len() {
- return Err(Error::MalformedPacket)
- }
- let message = &line[substr_idx + 1..];
- info!("(Plain) PRIVMSG {} :{}", channel, message);
- if self.configured_chans.contains_key(channel) {
- let channel_info = self.configured_chans.get(channel).unwrap();
- if channel_info.joined.load(Ordering::Relaxed) {
- let message = if let Some(salt_box) = &channel_info.salt_box {
- let encrypted = encrypt_message(salt_box, message);
- info!("(Encrypted) PRIVMSG {} :{}", channel, encrypted);
- encrypted
- } else {
- message.to_string()
- };
- let random_id = OsRng.next_u32();
- let protocol_msg = Privmsg {
- id: random_id,
- nickname: self.nickname.clone(),
- channel: channel.to_string(),
- message,
- };
- let mut smi = self.seen_msg_id.lock().await;
- smi.push(random_id);
- drop(smi);
- debug!(target: "ircd", "PRIVMSG to be sent: {:?}", protocol_msg);
- self.p2p_sender.send(protocol_msg).await?;
- }
- }
- }
- "QUIT" => {
- // Close the connection
- return Err(Error::NetworkServiceStopped)
- }
- _ => {
- warn!("Unimplemented `{}` command", command);
- }
- }
- if !self.is_registered && self.is_nick_init && self.is_user_init {
- debug!("Initializing peer connection");
- let register_reply = format!(":darkfi 001 {} :Let there be dark\r\n", self.nickname);
- self.reply(®ister_reply).await?;
- self.is_registered = true;
- // Auto-joins
- macro_rules! autojoin {
- ($channel:expr,$topic:expr) => {
- let j = format!(":{}!anon@dark.fi JOIN {}\r\n", self.nickname, $channel);
- let t = format!(":DarkFi TOPIC {} :{}\r\n", $channel, $topic);
- self.reply(&j).await?;
- self.reply(&t).await?;
- };
- }
- for chan in self.auto_channels.clone() {
- if self.configured_chans.contains_key(&chan) {
- let chan_info = self.configured_chans.get_mut(&chan).unwrap();
- let topic = if let Some(topic) = chan_info.topic.clone() {
- topic
- } else {
- "n/a".to_string()
- };
- chan_info.topic = Some(topic.to_string());
- autojoin!(chan, topic);
- } else {
- let mut chan_info = ChannelInfo::new()?;
- chan_info.topic = Some("n/a".to_string());
- self.configured_chans.insert(chan.clone(), chan_info);
- autojoin!(chan, "n/a");
- }
- }
- }
- Ok(())
- }
- pub async fn reply(&mut self, message: &str) -> Result<()> {
- self.write_stream.write_all(message.as_bytes()).await?;
- debug!("Sent {}", message);
- Ok(())
- }
- }
|