| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 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/>.
- */
- //! IRC command implemenatations
- //!
- //! These try to follow the RFCs, modified in order for our P2P stack.
- //! Copied from <https://simple.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands>
- //!
- //! Unimplemented commands:
- //! * `AWAY`
- //! * `CONNECT`
- //! * `DIE`
- //! * `ERROR`
- //! * `INVITE`
- //! * `ISON`
- //! * `KICK`
- //! * `KILL`
- //! * `NOTICE`
- //! * `OPER`
- //! * `RESTART`
- //! * `SERVICE`
- //! * `SERVLIST`
- //! * `SERVER`
- //! * `SQUERY`
- //! * `SQUIT`
- //! * `SUMMON`
- //! * `TRACE`
- //! * `USERHOST`
- //! * `WALLOPS`
- //! * `WHO`
- //! * `WHOIS`
- //! * `WHOWAS`
- //!
- //! Some of the above commands could actually be implemented and could
- //! work in respect to the P2P network.
- use std::{collections::HashSet, sync::atomic::Ordering::SeqCst};
- use darkfi::Result;
- use darkfi_serial::deserialize_async_partial;
- use log::{error, info};
- use super::{
- client::{Client, ReplyType},
- rpl::*,
- server::MAX_NICK_LEN,
- IrcChannel, SERVER_NAME,
- };
- use crate::crypto::bcrypt::bcrypt_hash_password;
- impl Client {
- /// `ADMIN [<server>]`
- ///
- /// Asks the server for information about the administrator of the server.
- pub async fn handle_cmd_admin(&self, _args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let nick = self.nickname.read().await.to_string();
- let replies = vec![
- ReplyType::Server((
- RPL_ADMINME,
- format!("{} {} :Administrative info", nick, SERVER_NAME),
- )),
- ReplyType::Server((RPL_ADMINLOC1, format!("{} :", nick))),
- ReplyType::Server((RPL_ADMINLOC2, format!("{} :", nick))),
- ReplyType::Server((RPL_ADMINEMAIL, format!("{} :anon@darkirc", nick))),
- ];
- Ok(replies)
- }
- /// `CAP <args>`
- pub async fn handle_cmd_cap(&self, args: &str) -> Result<Vec<ReplyType>> {
- let mut tokens = args.split_ascii_whitespace();
- let Some(subcommand) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} CAP :{}", self.nickname.read().await, INVALID_SYNTAX),
- ))])
- };
- let caps_keys: Vec<String> = self.caps.read().await.keys().cloned().collect();
- let nick = self.nickname.read().await.to_string();
- match subcommand.to_uppercase().as_str() {
- "LS" => {
- /*
- let Some(_version) = tokens.next() else {
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} CAP :{}", self.nickname.read().await, INVALID_SYNTAX),
- ))])
- };
- */
- self.reg_paused.store(true, SeqCst);
- return Ok(vec![ReplyType::Cap(format!("CAP * LS :{}", caps_keys.join(" ")))])
- }
- "REQ" => {
- let Some(substr_idx) = args.find(':') else {
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} CAP :{}", nick, INVALID_SYNTAX),
- ))])
- };
- if substr_idx >= args.len() {
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} CAP :{}", nick, INVALID_SYNTAX),
- ))])
- }
- let cap_reqs: Vec<&str> = args[substr_idx + 1..].split(' ').collect();
- let mut ack_list = vec![];
- let mut nak_list = vec![];
- let mut available_caps = self.caps.write().await;
- for cap in cap_reqs {
- if available_caps.contains_key(cap) {
- available_caps.insert(cap.to_string(), true);
- ack_list.push(cap);
- } else {
- nak_list.push(cap);
- }
- }
- let mut replies = vec![];
- if !ack_list.is_empty() {
- replies.push(ReplyType::Cap(format!(
- "CAP {} ACK :{}",
- nick,
- ack_list.join(" ")
- )));
- }
- if !nak_list.is_empty() {
- replies.push(ReplyType::Cap(format!(
- "CAP {} NAK :{}",
- nick,
- nak_list.join(" ")
- )));
- }
- return Ok(replies)
- }
- "LIST" => {
- let enabled_caps: Vec<String> = self
- .caps
- .read()
- .await
- .clone()
- .into_iter()
- .filter(|(_, v)| *v)
- .map(|(k, _)| k)
- .collect();
- return Ok(vec![ReplyType::Cap(format!(
- "CAP {} LIST :{}",
- nick,
- enabled_caps.join(" ")
- ))])
- }
- "END" => {
- // At CAP END, if we have USER and NICK, we can welcome them.
- self.reg_paused.store(false, SeqCst);
- if self.registered.load(SeqCst) && !self.is_cap_end.load(SeqCst) {
- self.is_cap_end.store(true, SeqCst);
- return Ok(self.welcome().await)
- }
- return Ok(vec![])
- }
- _ => {}
- }
- self.penalty.fetch_add(1, SeqCst);
- Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} CAP :{}", nick, INVALID_SYNTAX),
- ))])
- }
- /// `INFO [<target>]`
- ///
- /// Gives information about the `<target>` server, or the current server if
- /// `<target>` is not used. The information includes the server's version,
- /// when it was compiled, the patch level, when it was started, and any
- /// other information which might be relevant.
- pub async fn handle_cmd_info(&self, _args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let nick = self.nickname.read().await.clone();
- let replies = vec![
- ReplyType::Server((
- RPL_INFO,
- format!("{} :DarkIRC {}", nick, env!("CARGO_PKG_VERSION")),
- )),
- ReplyType::Server((RPL_ENDOFINFO, format!("{} :End of INFO list", nick))),
- ];
- Ok(replies)
- }
- /// `JOIN <channels> [<keys>]`
- ///
- /// Makes the client join the channels in the list `<channels>`.
- /// Passwords can be used in the list `<keys>`. If the channels do not
- /// exist, they will be created.
- pub async fn handle_cmd_join(&self, args: &str, hist: bool) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- // Client's (already) active channels
- let mut active_channels = self.channels.write().await;
- // Here we'll hold valid channel names.
- let mut channels = HashSet::new();
- // Let's scan through our channels. For now we'll only support
- // channel names starting with a single '#' character.
- let nick = self.nickname.read().await.to_string();
- let tokens = args.split_ascii_whitespace();
- for channel in tokens {
- if !channel.starts_with('#') {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} JOIN :{}", nick, INVALID_SYNTAX),
- ))])
- }
- if !active_channels.contains(channel) {
- channels.insert(channel.to_string());
- }
- }
- // We need at least one channel.
- if channels.is_empty() {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} JOIN :{}", nick, INVALID_SYNTAX),
- ))])
- }
- // Weechat sends channels as `#chan1,#chan2,#chan3`. Handle it.
- if channels.len() == 1 {
- let list = channels.iter().next().unwrap().clone();
- channels.remove(list.as_str());
- for channel in list.split(',') {
- if !channel.starts_with('#') || channel.as_bytes().len() > MAX_NICK_LEN {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} JOIN :{}", nick, INVALID_SYNTAX),
- ))])
- }
- if !active_channels.contains(channel) {
- channels.insert(channel.to_string());
- }
- }
- }
- // Create new channels for this client and construct replies.
- let mut server_channels = self.server.channels.write().await;
- let mut replies = vec![];
- for channel in channels.iter() {
- // Insert the channel name into the set of client's active channels
- active_channels.insert(channel.clone());
- // Create or update the channel on the server side.
- if let Some(server_chan) = server_channels.get_mut(channel) {
- server_chan.nicks.insert(nick.clone());
- } else {
- let chan = IrcChannel {
- topic: String::new(),
- nicks: HashSet::from([nick.clone()]),
- saltbox: None,
- };
- server_channels.insert(channel.clone(), chan);
- }
- // Create the replies
- replies.push(ReplyType::Client((nick.clone(), format!("JOIN :{}", channel))));
- if let Some(chan) = server_channels.get(channel) {
- if !chan.topic.is_empty() {
- replies.push(ReplyType::Client((
- nick.clone(),
- format!("TOPIC {} :{}", channel, chan.topic),
- )));
- }
- }
- }
- // Drop the locks as they're used in get_history()
- drop(active_channels);
- drop(server_channels);
- if hist {
- // Potentially extend the replies with channel history
- replies.extend(self.get_history(&channels).await.unwrap());
- }
- Ok(replies)
- }
- /// `LIST [<channels> [<server>]]`
- ///
- /// List all channels on the server. If the list `<channels>` is given, it
- /// will return the channel topics. If `<server>` is given, the command will
- /// be sent to `<server>` for evaluation.
- pub async fn handle_cmd_list(&self, _args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let nick = self.nickname.read().await.to_string();
- let mut list = vec![];
- for (name, channel) in self.server.channels.read().await.iter() {
- list.push(format!("{} {} {} :{}", nick, name, channel.nicks.len(), channel.topic));
- }
- let mut replies = vec![];
- replies.push(ReplyType::Server((RPL_LISTSTART, format!("{} Channel :Users Name", nick))));
- for chan in list {
- replies.push(ReplyType::Server((RPL_LIST, chan)));
- }
- replies.push(ReplyType::Server((RPL_LISTEND, format!("{} :End of /LIST", nick))));
- Ok(replies)
- }
- /// `MODE <nickname> <flags>`
- /// `MODE <channel> <flags>`
- ///
- /// The MODE command has two uses. It can be used to set both user and
- /// channel modes.
- pub async fn handle_cmd_mode(&self, args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let nick = self.nickname.read().await.to_string();
- let mut tokens = args.split_ascii_whitespace();
- let Some(target) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} MODE :{}", nick, INVALID_SYNTAX),
- ))])
- };
- if target == nick {
- return Ok(vec![ReplyType::Server((RPL_UMODEIS, format!("{} +", nick)))])
- }
- if !target.starts_with('#') {
- return Ok(vec![ReplyType::Server((
- ERR_USERSDONTMATCH,
- format!("{} :Can't set/get mode for other users", nick),
- ))])
- }
- if !self.server.channels.read().await.contains_key(target) {
- return Ok(vec![ReplyType::Server((
- ERR_NOSUCHNICK,
- format!("{} {} :No such nick or channel name", nick, target),
- ))])
- }
- Ok(vec![ReplyType::Server((RPL_CHANNELMODEIS, format!("{} {} +", nick, target)))])
- }
- /// `MOTD [<server>]`
- ///
- /// Returns the message of the day on `<server>` or the current server if
- /// it is not stated.
- pub async fn handle_cmd_motd(&self, _args: &str) -> Result<Vec<ReplyType>> {
- let nick = self.nickname.read().await.to_string();
- Ok(vec![
- ReplyType::Server((
- RPL_MOTDSTART,
- format!("{} :- {} message of the day", nick, SERVER_NAME),
- )),
- ReplyType::Server((RPL_MOTD, format!("{} :Let there be dark!", nick))),
- ReplyType::Server((RPL_ENDOFMOTD, format!("{} :End of /MOTD command.", nick))),
- ])
- }
- /// `NAMES [<channel>]`
- ///
- /// Returns a list of who is on the list of `<channel>`, by channel name.
- /// If `<channel>` is not used, all users are shown. They are grouped by
- /// channel name with all users who are not on a channel being shown as
- /// part of channel "*".
- pub async fn handle_cmd_names(&self, args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let nick = self.nickname.read().await.to_string();
- let mut tokens = args.split_ascii_whitespace();
- let mut replies = vec![];
- // If a channel was requested, reply only with that one.
- // Otherwise, return info for all known channels.
- if let Some(req_chan) = tokens.next() {
- if let Some(chan) = self.server.channels.read().await.get(req_chan) {
- let nicks: Vec<String> = chan.nicks.iter().cloned().collect();
- replies.push(ReplyType::Server((
- RPL_NAMREPLY,
- format!("{} = {} :{}", nick, req_chan, nicks.join(" ")),
- )));
- }
- replies.push(ReplyType::Server((
- RPL_ENDOFNAMES,
- format!("{} {} :End of NAMES list", nick, req_chan),
- )));
- Ok(replies)
- } else {
- for (name, chan) in self.server.channels.read().await.iter() {
- let nicks: Vec<String> = chan.nicks.iter().cloned().collect();
- replies.push(ReplyType::Server((
- RPL_NAMREPLY,
- format!("{} = {} :{}", nick, name, nicks.join(" ")),
- )));
- }
- replies.push(ReplyType::Server((
- RPL_ENDOFNAMES,
- format!("{} * :End of NAMES list", nick),
- )));
- Ok(replies)
- }
- }
- /// `NICK <nickname>`
- ///
- /// Allows a client to change their IRC nickname.
- pub async fn handle_cmd_nick(&self, args: &str) -> Result<Vec<ReplyType>> {
- // Parse the line
- let mut tokens = args.split_ascii_whitespace();
- // Reference the current nickname
- let old_nick = self.nickname.read().await.to_string();
- let Some(nickname) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} NICK :{}", old_nick, INVALID_SYNTAX),
- ))])
- };
- // Forbid disallowed characters.
- // The next() call is done to check for ASCII whitespace in the nick.
- if tokens.next().is_some() || nickname.starts_with(':') || nickname.starts_with('#') {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_ERRONEOUSNICKNAME,
- format!("{} {} :Erroneous nickname", old_nick, nickname),
- ))])
- }
- // Disallow too long nicks
- if nickname.as_bytes().len() > MAX_NICK_LEN {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_ERRONEOUSNICKNAME,
- format!("{} {} :Nickname too long", old_nick, nickname),
- ))])
- }
- // Set the new nickname
- *self.nickname.write().await = nickname.to_string();
- // If the username is set, we can complete the registration
- if *self.username.read().await != "*" &&
- !self.registered.load(SeqCst) &&
- self.is_pass_set.load(SeqCst)
- {
- self.registered.store(true, SeqCst);
- if self.reg_paused.load(SeqCst) {
- return Ok(vec![])
- } else {
- return Ok(self.welcome().await)
- }
- }
- // If we were registered, we send a client reply about it.
- if self.registered.load(SeqCst) {
- Ok(vec![ReplyType::Client((old_nick, format!("NICK :{}", nickname)))])
- } else {
- // Otherwise, we don't reply.
- Ok(vec![])
- }
- }
- /// `PART <channel>`
- ///
- /// Causes a user to leave the channel `<channel>`.
- pub async fn handle_cmd_part(&self, args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let nick = self.nickname.read().await.to_string();
- let mut tokens = args.split_ascii_whitespace();
- let Some(channel) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} PART :{}", nick, INVALID_SYNTAX),
- ))])
- };
- if !channel.starts_with('#') {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} PART :{}", nick, INVALID_SYNTAX),
- ))])
- }
- let mut active_channels = self.channels.write().await;
- if !active_channels.contains(channel) {
- return Ok(vec![ReplyType::Server((
- ERR_NOSUCHCHANNEL,
- format!("{} {} :No such channel", nick, channel),
- ))])
- }
- // Remove the channel from the client's channel list
- active_channels.remove(channel);
- let replies = vec![ReplyType::Client((nick, format!("PART {} :Bye", channel)))];
- Ok(replies)
- }
- /// `PASS <password>`
- ///
- /// Used to set a ‘connection password’. If set, the password must
- /// be set before USER/NICK commands.
- pub async fn handle_cmd_pass(&self, args: &str) -> Result<Vec<ReplyType>> {
- let mut tokens = args.split_ascii_whitespace();
- let nick = self.nickname.read().await.to_string();
- let Some(password) = tokens.next() else {
- // self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} PASS :{}", nick, INVALID_SYNTAX),
- ))])
- };
- if self.server.password == bcrypt_hash_password(password) {
- self.is_pass_set.store(true, SeqCst);
- } else {
- error!("[IRC CLIENT] Password is not correct!");
- return Ok(vec![ReplyType::Server((
- ERR_PASSWDMISMATCH,
- format!("{} PASS :{}", nick, PASSWORD_MISMATCH),
- ))])
- }
- Ok(vec![])
- }
- /// `PING <server1>`
- ///
- /// Tests a connection. A PING message results in a PONG reply.
- pub async fn handle_cmd_ping(&self, args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let mut tokens = args.split_ascii_whitespace();
- let Some(origin) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOORIGIN,
- format!("{} :No origin specified", self.nickname.read().await),
- ))])
- };
- Ok(vec![ReplyType::Pong(origin.to_string())])
- }
- /// `PRIVMSG <msgtarget> <message>`
- ///
- /// Sends `<message>` to `<msgtarget>`. The target is usually a user or
- /// a channel.
- pub async fn handle_cmd_privmsg(&self, args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let nick = self.nickname.read().await.to_string();
- let mut tokens = args.split_ascii_whitespace();
- let Some(target) = tokens.next() else {
- return Ok(vec![ReplyType::Server((
- ERR_NORECIPIENT,
- format!("{} :No recipient given (PRIVMSG)", nick),
- ))])
- };
- let Some(message) = tokens.next() else {
- return Ok(vec![ReplyType::Server((
- ERR_NOTEXTTOSEND,
- format!("{} :No text to send", nick),
- ))])
- };
- if !message.starts_with(':') {
- return Ok(vec![ReplyType::Server((
- ERR_NOTEXTTOSEND,
- format!("{} :No text to send", nick),
- ))])
- }
- // We only send a client reply if the message is for ourself or if
- // we're trying to communicate with IRC services.
- // Anything else is rendered by the IRC client and not supposed
- // to be echoed by the IRC serer.
- if target == nick {
- return Ok(vec![ReplyType::Client((
- target.to_string(),
- format!("PRIVMSG {} {}", target, message),
- ))])
- }
- // Handle queries to NickServ
- if target.to_lowercase().as_str() == "nickserv" {
- return self.nickserv.handle_query(message.strip_prefix(':').unwrap()).await
- }
- // If it's a DM and we don't have an encryption key, we will
- // refuse to send it. Send ERR_NORECIPIENT to the client.
- if !target.starts_with('#') && !self.server.contacts.read().await.contains_key(target) {
- return Ok(vec![ReplyType::Server((ERR_NOSUCHNICK, format!("{} :{}", nick, target)))])
- }
- Ok(vec![])
- }
- /// `REHASH`
- ///
- /// Causes the server to re-read and re-process its configuration file(s).
- pub async fn handle_cmd_rehash(&self, _args: &str) -> Result<Vec<ReplyType>> {
- info!("Attempting to rehash server...");
- if let Err(e) = self.server.rehash().await {
- error!("Failed to rehash server: {}", e);
- }
- Ok(vec![])
- }
- /// `TOPIC <channel> [<topic>]`
- ///
- /// Used to get the channel topic on `<channel>`. If `<topic>` is given, it
- /// sets the channel topic to `<topic>`.
- pub async fn handle_cmd_topic(&self, args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let nick = self.nickname.read().await.to_string();
- let mut tokens = args.split_ascii_whitespace();
- let Some(channel) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} TOPIC :{}", nick, INVALID_SYNTAX),
- ))])
- };
- if !self.server.channels.read().await.contains_key(channel) {
- return Ok(vec![ReplyType::Server((
- ERR_NOSUCHCHANNEL,
- format!("{} {} :No such channel", nick, channel),
- ))])
- }
- // If there's a topic, we'll set it, otherwise return the set topic.
- let Some(topic) = tokens.next() else {
- let topic = self.server.channels.read().await.get(channel).unwrap().topic.clone();
- if topic.is_empty() {
- return Ok(vec![ReplyType::Server((
- RPL_NOTOPIC,
- format!("{} {} :No topic is set", nick, channel),
- ))])
- } else {
- return Ok(vec![ReplyType::Server((
- RPL_TOPIC,
- format!("{} {} :{}", nick, channel, topic),
- ))])
- }
- };
- // Set the new topic
- self.server.channels.write().await.get_mut(channel).unwrap().topic =
- topic.strip_prefix(':').unwrap().to_string();
- // Send reply
- let replies = vec![ReplyType::Client((nick, format!("TOPIC {} {}", channel, topic)))];
- Ok(replies)
- }
- /// `USER <user> <mode> <unused> <realname>`
- ///
- /// This command is used at the beginning of a connection to specify the
- /// username, hostname, real name, and the initial user modes of the
- /// connecting client. `<realname>` may contain spaces, and thus must be
- /// prefixed with a colon.
- pub async fn handle_cmd_user(&self, args: &str) -> Result<Vec<ReplyType>> {
- if self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_ALREADYREGISTERED,
- format!("{} :{}", self.nickname.read().await, ALREADY_REGISTERED),
- ))])
- }
- // If password is not set register user normally
- if self.server.password.is_empty() {
- self.is_pass_set.store(true, SeqCst);
- }
- // Parse the line
- let nick = self.nickname.read().await.to_string();
- let mut tokens = args.split_ascii_whitespace();
- let Some(username) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} USER :{}", nick, INVALID_SYNTAX),
- ))])
- };
- // Mode syntax is currently ignored, but should be part of the command
- let Some(_mode) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} USER :{}", nick, INVALID_SYNTAX),
- ))])
- };
- // Next token is unused per RFC, but should be part of the command
- let Some(_unused) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} USER :{}", nick, INVALID_SYNTAX),
- ))])
- };
- // The final token should be realname and should start with a colon
- let Some(realname) = tokens.next() else {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} USER :{}", nick, INVALID_SYNTAX),
- ))])
- };
- if !realname.starts_with(':') {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NEEDMOREPARAMS,
- format!("{} USER :{}", nick, INVALID_SYNTAX),
- ))])
- }
- *self.username.write().await = username.to_string();
- *self.realname.write().await = realname.to_string();
- // If the nickname is set, we can complete the registration
- if nick != "*" {
- if !self.is_pass_set.load(SeqCst) {
- return Ok(vec![ReplyType::Server((
- ERR_PASSWDMISMATCH,
- format!("{} PASS :{}", nick, PASSWORD_MISMATCH),
- ))])
- }
- self.registered.store(true, SeqCst);
- if self.reg_paused.load(SeqCst) {
- return Ok(vec![])
- } else {
- return Ok(self.welcome().await)
- }
- }
- // Otherwise, we don't have to reply.
- Ok(vec![])
- }
- /// `VERSION`
- ///
- /// Returns the version of the server.
- pub async fn handle_cmd_version(&self, _args: &str) -> Result<Vec<ReplyType>> {
- if !self.registered.load(SeqCst) {
- self.penalty.fetch_add(1, SeqCst);
- return Ok(vec![ReplyType::Server((
- ERR_NOTREGISTERED,
- format!("* :{}", NOT_REGISTERED),
- ))])
- }
- let replies = vec![ReplyType::Server((
- RPL_VERSION,
- format!(
- "{} {} {} :Let there be dark!",
- self.nickname.read().await,
- env!("CARGO_PKG_VERSION"),
- SERVER_NAME
- ),
- ))];
- Ok(replies)
- }
- /// Internal function that constructs the welcome message.
- async fn welcome(&self) -> Vec<ReplyType> {
- let nick = self.nickname.read().await.to_string();
- let mut replies = vec![
- ReplyType::Server((RPL_WELCOME, format!("{} :{}", nick, WELCOME))),
- ReplyType::Server((
- RPL_YOURHOST,
- format!(
- "{} :Your host is irc.dark.fi, running version {}",
- nick,
- env!("CARGO_PKG_VERSION")
- ),
- )),
- ];
- // Append the MOTD
- replies.append(&mut self.handle_cmd_motd("").await.unwrap());
- let mut channels = HashSet::new();
- // If we have any configured autojoin channels, let's join the user
- // and set their topics, if any.
- if !*self.caps.read().await.get("no-autojoin").unwrap() {
- for channel in self.server.autojoin.read().await.iter() {
- replies.extend(self.handle_cmd_join(channel, false).await.unwrap());
- channels.insert(channel.to_string());
- }
- }
- // Potentially extend the replies with channel history
- replies.extend(self.get_history(&channels).await.unwrap());
- // And request NAMES list.
- if !*self.caps.read().await.get("no-autojoin").unwrap() {
- for channel in self.server.autojoin.read().await.iter() {
- if let Some(chan) = self.server.channels.read().await.get(channel) {
- let nicks: Vec<String> = chan.nicks.iter().cloned().collect();
- replies.push(ReplyType::Server((
- RPL_NAMREPLY,
- format!("{} = {} :{}", nick, channel, nicks.join(" ")),
- )));
- }
- replies.push(ReplyType::Server((
- RPL_ENDOFNAMES,
- format!("{} {} :End of NAMES list", nick, channel),
- )));
- }
- }
- replies
- }
- /// Internal function that scans the DAG and returns events for
- /// given channels. Will return empty if no_history CAP is requested.
- async fn get_history(&self, channels: &HashSet<String>) -> Result<Vec<ReplyType>> {
- if channels.is_empty() || *self.caps.read().await.get("no-history").unwrap() {
- return Ok(vec![])
- }
- // Fetch and order all the events from the DAG
- let dag_events = self.server.darkirc.event_graph.order_events().await;
- // Here we'll hold the events in order we'll push to the client
- let mut replies = vec![];
- for event_id in dag_events.iter() {
- // If it was seen, skip
- match self.is_seen(event_id).await {
- Ok(true) => continue,
- Ok(false) => {}
- Err(e) => {
- error!("[IRC CLIENT] (get_history) self.is_seen({}) failed: {}", event_id, e);
- return Err(e)
- }
- }
- // Get the event from the DAG
- let event = self.server.darkirc.event_graph.dag_get(event_id).await.unwrap().unwrap();
- // Try to deserialize it. (Here we skip errors)
- let Ok((mut privmsg, _)) = deserialize_async_partial(event.content()).await else {
- continue
- };
- // Potentially decrypt the privmsg
- self.server.try_decrypt(&mut privmsg).await;
- // If the privmsg is intented for any of the given
- // channels, contacts or oursleves, add it as a reply and
- // mark it as seen in the seen_events tree.
- let contacts = self.server.contacts.read().await;
- if !channels.contains(&privmsg.channel) &&
- !contacts.contains_key(&privmsg.channel) &&
- !contacts.contains_key(&privmsg.nick)
- {
- continue
- }
- // Insert nicks into channels
- if let Some(chan) = self.server.channels.write().await.get_mut(&privmsg.channel) {
- chan.nicks.insert(privmsg.nick.clone());
- }
- let msg = format!("PRIVMSG {} :{}", privmsg.channel, privmsg.msg);
- replies.push(ReplyType::Client((privmsg.nick, msg)));
- if let Err(e) = self.mark_seen(event_id).await {
- error!("[IRC CLIENT] (get_history) self.mark_seen({}) failed: {}", event_id, e);
- return Err(e)
- }
- }
- Ok(replies)
- }
- }
|