/ bin / darkirc / src / irc / command.rs
command.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 Dyne.org foundation
  4   *
  5   * This program is free software: you can redistribute it and/or modify
  6   * it under the terms of the GNU Affero General Public License as
  7   * published by the Free Software Foundation, either version 3 of the
  8   * License, or (at your option) any later version.
  9   *
 10   * This program is distributed in the hope that it will be useful,
 11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   * GNU Affero General Public License for more details.
 14   *
 15   * You should have received a copy of the GNU Affero General Public License
 16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17   */
 18  
 19  //! IRC command implemenatations
 20  //!
 21  //! These try to follow the RFCs, modified in order for our P2P stack.
 22  //! Copied from <https://simple.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands>
 23  //!
 24  //! Unimplemented commands:
 25  //! * `AWAY`
 26  //! * `CONNECT`
 27  //! * `DIE`
 28  //! * `ERROR`
 29  //! * `INVITE`
 30  //! * `ISON`
 31  //! * `KICK`
 32  //! * `KILL`
 33  //! * `NOTICE`
 34  //! * `OPER`
 35  //! * `RESTART`
 36  //! * `SERVICE`
 37  //! * `SERVLIST`
 38  //! * `SERVER`
 39  //! * `SQUERY`
 40  //! * `SQUIT`
 41  //! * `SUMMON`
 42  //! * `TRACE`
 43  //! * `USERHOST`
 44  //! * `WALLOPS`
 45  //! * `WHO`
 46  //! * `WHOIS`
 47  //! * `WHOWAS`
 48  //!
 49  //! Some of the above commands could actually be implemented and could
 50  //! work in respect to the P2P network.
 51  
 52  use std::{collections::HashSet, sync::atomic::Ordering::SeqCst};
 53  
 54  use darkfi::Result;
 55  use tracing::{error, info};
 56  
 57  use super::{
 58      client::{Client, ReplyType},
 59      rpl::*,
 60      server::MAX_NICK_LEN,
 61      IrcChannel, Msg, SERVER_NAME,
 62  };
 63  use crate::crypto::bcrypt::bcrypt_hash_password;
 64  
 65  impl Client {
 66      /// `ADMIN [<server>]`
 67      ///
 68      /// Asks the server for information about the administrator of the server.
 69      pub async fn handle_cmd_admin(&self, _args: &str) -> Result<Vec<ReplyType>> {
 70          if !self.registered.load(SeqCst) {
 71              self.penalty.fetch_add(1, SeqCst);
 72              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
 73          }
 74  
 75          let nick = self.nickname.read().await.to_string();
 76  
 77          let replies = vec![
 78              ReplyType::Server((RPL_ADMINME, format!("{nick} {SERVER_NAME} :Administrative info"))),
 79              ReplyType::Server((RPL_ADMINLOC1, format!("{nick} :"))),
 80              ReplyType::Server((RPL_ADMINLOC2, format!("{nick} :"))),
 81              ReplyType::Server((RPL_ADMINEMAIL, format!("{nick} :anon@darkirc"))),
 82          ];
 83  
 84          Ok(replies)
 85      }
 86  
 87      /// `CAP <args>`
 88      pub async fn handle_cmd_cap(&self, args: &str) -> Result<Vec<ReplyType>> {
 89          let mut tokens = args.split_ascii_whitespace();
 90  
 91          let Some(subcommand) = tokens.next() else {
 92              self.penalty.fetch_add(1, SeqCst);
 93              return Ok(vec![ReplyType::Server((
 94                  ERR_NEEDMOREPARAMS,
 95                  format!("{} CAP :{INVALID_SYNTAX}", self.nickname.read().await),
 96              ))])
 97          };
 98  
 99          let caps_keys: Vec<String> = self.caps.read().await.keys().cloned().collect();
100          let nick = self.nickname.read().await.to_string();
101  
102          match subcommand.to_uppercase().as_str() {
103              "LS" => {
104                  /*
105                  let Some(_version) = tokens.next() else {
106                      return Ok(vec![ReplyType::Server((
107                          ERR_NEEDMOREPARAMS,
108                          format!("{} CAP :{INVALID_SYNTAX}", self.nickname.read().await),
109                      ))])
110                  };
111                  */
112  
113                  self.reg_paused.store(true, SeqCst);
114                  return Ok(vec![ReplyType::Cap(format!("CAP * LS :{}", caps_keys.join(" ")))])
115              }
116  
117              "REQ" => {
118                  let Some(substr_idx) = args.find(':') else {
119                      return Ok(vec![ReplyType::Server((
120                          ERR_NEEDMOREPARAMS,
121                          format!("{nick} CAP :{INVALID_SYNTAX}"),
122                      ))])
123                  };
124  
125                  if substr_idx >= args.len() {
126                      return Ok(vec![ReplyType::Server((
127                          ERR_NEEDMOREPARAMS,
128                          format!("{nick} CAP :{INVALID_SYNTAX}"),
129                      ))])
130                  }
131  
132                  let cap_reqs: Vec<&str> = args[substr_idx + 1..].split(' ').collect();
133  
134                  let mut ack_list = vec![];
135                  let mut nak_list = vec![];
136  
137                  let mut available_caps = self.caps.write().await;
138                  for cap in cap_reqs {
139                      if available_caps.contains_key(cap) {
140                          available_caps.insert(cap.to_string(), true);
141                          ack_list.push(cap);
142                      } else {
143                          nak_list.push(cap);
144                      }
145                  }
146  
147                  let mut replies = vec![];
148  
149                  if !ack_list.is_empty() {
150                      replies.push(ReplyType::Cap(format!("CAP {nick} ACK :{}", ack_list.join(" "))));
151                  }
152  
153                  if !nak_list.is_empty() {
154                      replies.push(ReplyType::Cap(format!("CAP {nick} NAK :{}", nak_list.join(" "))));
155                  }
156  
157                  return Ok(replies)
158              }
159  
160              "LIST" => {
161                  let enabled_caps: Vec<String> = self
162                      .caps
163                      .read()
164                      .await
165                      .clone()
166                      .into_iter()
167                      .filter(|(_, v)| *v)
168                      .map(|(k, _)| k)
169                      .collect();
170  
171                  return Ok(vec![ReplyType::Cap(format!(
172                      "CAP {nick} LIST :{}",
173                      enabled_caps.join(" ")
174                  ))])
175              }
176  
177              "END" => {
178                  // At CAP END, if we have USER and NICK, we can welcome them.
179                  self.reg_paused.store(false, SeqCst);
180                  if self.registered.load(SeqCst) && !self.is_cap_end.load(SeqCst) {
181                      self.is_cap_end.store(true, SeqCst);
182                      return Ok(self.welcome().await)
183                  }
184  
185                  return Ok(vec![])
186              }
187  
188              _ => {}
189          }
190  
191          self.penalty.fetch_add(1, SeqCst);
192          Ok(vec![ReplyType::Server((ERR_NEEDMOREPARAMS, format!("{nick} CAP :{INVALID_SYNTAX}")))])
193      }
194  
195      /// `INFO [<target>]`
196      ///
197      /// Gives information about the `<target>` server, or the current server if
198      /// `<target>` is not used. The information includes the server's version,
199      /// when it was compiled, the patch level, when it was started, and any
200      /// other information which might be relevant.
201      pub async fn handle_cmd_info(&self, _args: &str) -> Result<Vec<ReplyType>> {
202          if !self.registered.load(SeqCst) {
203              self.penalty.fetch_add(1, SeqCst);
204              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
205          }
206  
207          let nick = self.nickname.read().await.clone();
208          let replies = vec![
209              ReplyType::Server((RPL_INFO, format!("{nick} :DarkIRC {}", env!("CARGO_PKG_VERSION")))),
210              ReplyType::Server((RPL_ENDOFINFO, format!("{nick} :End of INFO list"))),
211          ];
212  
213          Ok(replies)
214      }
215  
216      /// `JOIN <channels> [<keys>]`
217      ///
218      /// Makes the client join the channels in the list `<channels>`.
219      /// Passwords can be used in the list `<keys>`. If the channels do not
220      /// exist, they will be created.
221      pub async fn handle_cmd_join(&self, args: &str, hist: bool) -> Result<Vec<ReplyType>> {
222          if !self.registered.load(SeqCst) {
223              self.penalty.fetch_add(1, SeqCst);
224              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
225          }
226  
227          // Client's (already) active channels
228          let mut active_channels = self.channels.write().await;
229          // Here we'll hold valid channel names.
230          let mut channels = HashSet::new();
231  
232          // Let's scan through our channels. For now we'll only support
233          // channel names starting with a single '#' character.
234          let nick = self.nickname.read().await.to_string();
235          let tokens = args.split_ascii_whitespace();
236          for channel in tokens {
237              if !channel.starts_with('#') {
238                  self.penalty.fetch_add(1, SeqCst);
239                  return Ok(vec![ReplyType::Server((
240                      ERR_NEEDMOREPARAMS,
241                      format!("{nick} JOIN :{INVALID_SYNTAX}"),
242                  ))])
243              }
244  
245              if !active_channels.contains(channel) {
246                  channels.insert(channel.to_string());
247              }
248          }
249  
250          // Weechat sends channels as `#chan1,#chan2,#chan3`. Handle it.
251          if channels.len() == 1 {
252              let list = channels.iter().next().unwrap().clone();
253              channels.remove(list.as_str());
254  
255              for channel in list.split(',') {
256                  if !channel.starts_with('#') || channel.len() > MAX_NICK_LEN {
257                      self.penalty.fetch_add(1, SeqCst);
258                      return Ok(vec![ReplyType::Server((
259                          ERR_NEEDMOREPARAMS,
260                          format!("{nick} JOIN :{INVALID_SYNTAX}"),
261                      ))])
262                  }
263                  if !active_channels.contains(channel) {
264                      channels.insert(channel.to_string());
265                  }
266              }
267          }
268  
269          // Create new channels for this client and construct replies.
270          let mut server_channels = self.server.channels.write().await;
271          let mut replies = vec![];
272  
273          for channel in channels.iter() {
274              // Insert the channel name into the set of client's active channels
275              active_channels.insert(channel.clone());
276              // Create or update the channel on the server side.
277              if let Some(server_chan) = server_channels.get_mut(channel) {
278                  server_chan.nicks.insert(nick.clone());
279              } else {
280                  let chan = IrcChannel {
281                      topic: String::new(),
282                      nicks: HashSet::from([nick.clone()]),
283                      saltbox: None,
284                  };
285                  server_channels.insert(channel.clone(), chan);
286              }
287  
288              // Create the replies
289              replies.push(ReplyType::Client((nick.clone(), format!("JOIN :{channel}"))));
290  
291              if let Some(chan) = server_channels.get(channel) {
292                  if !chan.topic.is_empty() {
293                      replies.push(ReplyType::Client((
294                          nick.clone(),
295                          format!("TOPIC {channel} :{}", chan.topic),
296                      )));
297                  }
298              }
299          }
300  
301          // Drop the locks as they're used in get_history()
302          drop(active_channels);
303          drop(server_channels);
304  
305          if hist {
306              // Potentially extend the replies with channel history
307              replies.extend(self.get_history(&channels).await.unwrap());
308          }
309  
310          Ok(replies)
311      }
312  
313      /// `LIST [<channels> [<server>]]`
314      ///
315      /// List all channels on the server. If the list `<channels>` is given, it
316      /// will return the channel topics. If `<server>` is given, the command will
317      /// be sent to `<server>` for evaluation.
318      pub async fn handle_cmd_list(&self, _args: &str) -> Result<Vec<ReplyType>> {
319          if !self.registered.load(SeqCst) {
320              self.penalty.fetch_add(1, SeqCst);
321              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
322          }
323  
324          let nick = self.nickname.read().await.to_string();
325  
326          let mut list = vec![];
327          for (name, channel) in self.server.channels.read().await.iter() {
328              list.push(format!("{nick} {name} {} :{}", channel.nicks.len(), channel.topic));
329          }
330  
331          let mut replies = vec![];
332          replies.push(ReplyType::Server((RPL_LISTSTART, format!("{nick} Channel :Users  Name"))));
333          for chan in list {
334              replies.push(ReplyType::Server((RPL_LIST, chan)));
335          }
336          replies.push(ReplyType::Server((RPL_LISTEND, format!("{nick} :End of /LIST"))));
337  
338          Ok(replies)
339      }
340  
341      /// `MODE <nickname> <flags>`
342      /// `MODE <channel> <flags>`
343      ///
344      /// The MODE command has two uses. It can be used to set both user and
345      /// channel modes.
346      pub async fn handle_cmd_mode(&self, args: &str) -> Result<Vec<ReplyType>> {
347          if !self.registered.load(SeqCst) {
348              self.penalty.fetch_add(1, SeqCst);
349              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
350          }
351  
352          let nick = self.nickname.read().await.to_string();
353  
354          let mut tokens = args.split_ascii_whitespace();
355  
356          let Some(target) = tokens.next() else {
357              self.penalty.fetch_add(1, SeqCst);
358              return Ok(vec![ReplyType::Server((
359                  ERR_NEEDMOREPARAMS,
360                  format!("{nick} MODE :{INVALID_SYNTAX}"),
361              ))])
362          };
363  
364          if target == nick {
365              return Ok(vec![ReplyType::Server((RPL_UMODEIS, format!("{nick} +")))])
366          }
367  
368          if !target.starts_with('#') {
369              return Ok(vec![ReplyType::Server((
370                  ERR_USERSDONTMATCH,
371                  format!("{nick} :Can't set/get mode for other users"),
372              ))])
373          }
374  
375          if !self.server.channels.read().await.contains_key(target) {
376              return Ok(vec![ReplyType::Server((
377                  ERR_NOSUCHNICK,
378                  format!("{nick} {target} :No such nick or channel name"),
379              ))])
380          }
381  
382          Ok(vec![ReplyType::Server((RPL_CHANNELMODEIS, format!("{nick} {target} +")))])
383      }
384  
385      /// `MOTD [<server>]`
386      ///
387      /// Returns the message of the day on `<server>` or the current server if
388      /// it is not stated.
389      pub async fn handle_cmd_motd(&self, _args: &str) -> Result<Vec<ReplyType>> {
390          let nick = self.nickname.read().await.to_string();
391  
392          Ok(vec![
393              ReplyType::Server((
394                  RPL_MOTDSTART,
395                  format!("{nick} :- {SERVER_NAME} message of the day"),
396              )),
397              ReplyType::Server((RPL_MOTD, format!("{nick} :Let there be dark!"))),
398              ReplyType::Server((RPL_ENDOFMOTD, format!("{nick} :End of /MOTD command."))),
399          ])
400      }
401  
402      /// `NAMES [<channel>]`
403      ///
404      /// Returns a list of who is on the list of `<channel>`, by channel name.
405      /// If `<channel>` is not used, all users are shown. They are grouped by
406      /// channel name with all users who are not on a channel being shown as
407      /// part of channel "*".
408      pub async fn handle_cmd_names(&self, args: &str) -> Result<Vec<ReplyType>> {
409          if !self.registered.load(SeqCst) {
410              self.penalty.fetch_add(1, SeqCst);
411              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
412          }
413  
414          let nick = self.nickname.read().await.to_string();
415          let mut tokens = args.split_ascii_whitespace();
416          let mut replies = vec![];
417  
418          // If a channel was requested, reply only with that one.
419          // Otherwise, return info for all known channels.
420          if let Some(req_chan) = tokens.next() {
421              if let Some(chan) = self.server.channels.read().await.get(req_chan) {
422                  let nicks: Vec<String> = chan.nicks.iter().cloned().collect();
423  
424                  replies.push(ReplyType::Server((
425                      RPL_NAMREPLY,
426                      format!("{nick} = {req_chan} :{}", nicks.join(" ")),
427                  )));
428              }
429  
430              replies.push(ReplyType::Server((
431                  RPL_ENDOFNAMES,
432                  format!("{nick} {req_chan} :End of NAMES list"),
433              )));
434  
435              Ok(replies)
436          } else {
437              for (name, chan) in self.server.channels.read().await.iter() {
438                  let nicks: Vec<String> = chan.nicks.iter().cloned().collect();
439  
440                  replies.push(ReplyType::Server((
441                      RPL_NAMREPLY,
442                      format!("{nick} = {name} :{}", nicks.join(" ")),
443                  )));
444              }
445  
446              replies
447                  .push(ReplyType::Server((RPL_ENDOFNAMES, format!("{nick} * :End of NAMES list"))));
448  
449              Ok(replies)
450          }
451      }
452  
453      /// `NICK <nickname>`
454      ///
455      /// Allows a client to change their IRC nickname.
456      pub async fn handle_cmd_nick(&self, args: &str) -> Result<Vec<ReplyType>> {
457          // Parse the line
458          let mut tokens = args.split_ascii_whitespace();
459  
460          // Reference the current nickname
461          let old_nick = self.nickname.read().await.to_string();
462  
463          let Some(nickname) = tokens.next() else {
464              self.penalty.fetch_add(1, SeqCst);
465              return Ok(vec![ReplyType::Server((
466                  ERR_NEEDMOREPARAMS,
467                  format!("{old_nick} NICK :{INVALID_SYNTAX}"),
468              ))])
469          };
470  
471          // Forbid disallowed characters.
472          // The next() call is done to check for ASCII whitespace in the nick.
473          if tokens.next().is_some() || nickname.starts_with(':') || nickname.starts_with('#') {
474              self.penalty.fetch_add(1, SeqCst);
475              return Ok(vec![ReplyType::Server((
476                  ERR_ERRONEOUSNICKNAME,
477                  format!("{old_nick} {nickname} :Erroneous nickname"),
478              ))])
479          }
480  
481          // Disallow too long nicks
482          if nickname.len() > MAX_NICK_LEN {
483              self.penalty.fetch_add(1, SeqCst);
484              return Ok(vec![ReplyType::Server((
485                  ERR_ERRONEOUSNICKNAME,
486                  format!("{old_nick} {nickname} :Nickname too long"),
487              ))])
488          }
489  
490          // Set the new nickname
491          *self.nickname.write().await = nickname.to_string();
492  
493          // If the username is set, we can complete the registration
494          if *self.username.read().await != "*" &&
495              !self.registered.load(SeqCst) &&
496              self.is_pass_set.load(SeqCst)
497          {
498              self.registered.store(true, SeqCst);
499              if self.reg_paused.load(SeqCst) {
500                  return Ok(vec![])
501              } else {
502                  return Ok(self.welcome().await)
503              }
504          }
505  
506          // If we were registered, we send a client reply about it.
507          if self.registered.load(SeqCst) {
508              Ok(vec![ReplyType::Client((old_nick, format!("NICK :{nickname}")))])
509          } else {
510              // Otherwise, we don't reply.
511              Ok(vec![])
512          }
513      }
514  
515      /// `PART <channel>`
516      ///
517      /// Causes a user to leave the channel `<channel>`.
518      pub async fn handle_cmd_part(&self, args: &str) -> Result<Vec<ReplyType>> {
519          if !self.registered.load(SeqCst) {
520              self.penalty.fetch_add(1, SeqCst);
521              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
522          }
523  
524          let nick = self.nickname.read().await.to_string();
525          let mut tokens = args.split_ascii_whitespace();
526  
527          let Some(channel) = tokens.next() else {
528              self.penalty.fetch_add(1, SeqCst);
529              return Ok(vec![ReplyType::Server((
530                  ERR_NEEDMOREPARAMS,
531                  format!("{nick} PART :{INVALID_SYNTAX}"),
532              ))])
533          };
534  
535          if !channel.starts_with('#') {
536              self.penalty.fetch_add(1, SeqCst);
537              return Ok(vec![ReplyType::Server((
538                  ERR_NEEDMOREPARAMS,
539                  format!("{nick} PART :{INVALID_SYNTAX}"),
540              ))])
541          }
542  
543          let mut active_channels = self.channels.write().await;
544          if !active_channels.contains(channel) {
545              return Ok(vec![ReplyType::Server((
546                  ERR_NOSUCHCHANNEL,
547                  format!("{nick} {channel} :No such channel"),
548              ))])
549          }
550  
551          // Remove the channel from the client's channel list
552          active_channels.remove(channel);
553  
554          let replies = vec![ReplyType::Client((nick, format!("PART {channel} :Bye")))];
555  
556          Ok(replies)
557      }
558  
559      /// `PASS <password>`
560      ///
561      /// Used to set a connection `<password>`. If set, the password must
562      /// be set before USER/NICK commands.
563      pub async fn handle_cmd_pass(&self, args: &str) -> Result<Vec<ReplyType>> {
564          let nick = self.nickname.read().await.to_string();
565  
566          // "the final parameter can be prepended with a (':', 0x3A) character"
567          // <https://modern.ircdocs.horse/#parameters>
568          let Some(i) = args.find(' ') else {
569              // self.penalty.fetch_add(1, SeqCst);
570              return Ok(vec![ReplyType::Server((
571                  ERR_NEEDMOREPARAMS,
572                  format!("{nick} PASS :{INVALID_SYNTAX}"),
573              ))])
574          };
575          let mut password = &args[i + 1..];
576          if password.starts_with(':') {
577              password = &password[1..];
578          }
579  
580          if self.server.password == bcrypt_hash_password(password) {
581              self.is_pass_set.store(true, SeqCst);
582          } else {
583              error!("[IRC CLIENT] Password is not correct!");
584              return Ok(vec![ReplyType::Server((
585                  ERR_PASSWDMISMATCH,
586                  format!("{nick} PASS :{PASSWORD_MISMATCH}"),
587              ))])
588          }
589  
590          Ok(vec![])
591      }
592  
593      /// `PING <server1>`
594      ///
595      /// Tests a connection. A PING message results in a PONG reply.
596      pub async fn handle_cmd_ping(&self, args: &str) -> Result<Vec<ReplyType>> {
597          if !self.registered.load(SeqCst) {
598              self.penalty.fetch_add(1, SeqCst);
599              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
600          }
601  
602          let mut tokens = args.split_ascii_whitespace();
603  
604          let Some(origin) = tokens.next() else {
605              self.penalty.fetch_add(1, SeqCst);
606              return Ok(vec![ReplyType::Server((
607                  ERR_NOORIGIN,
608                  format!("{} :No origin specified", self.nickname.read().await),
609              ))])
610          };
611  
612          Ok(vec![ReplyType::Pong(origin.to_string())])
613      }
614  
615      /// `PRIVMSG <msgtarget> <message>`
616      ///
617      /// Sends `<message>` to `<msgtarget>`. The target is usually a user or
618      /// a channel.
619      pub async fn handle_cmd_privmsg(&self, args: &str) -> Result<Vec<ReplyType>> {
620          if !self.registered.load(SeqCst) {
621              self.penalty.fetch_add(1, SeqCst);
622              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
623          }
624  
625          let nick = self.nickname.read().await.to_string();
626          let mut tokens = args.split_ascii_whitespace();
627  
628          let Some(target) = tokens.next() else {
629              return Ok(vec![ReplyType::Server((
630                  ERR_NORECIPIENT,
631                  format!("{nick} :No recipient given (PRIVMSG)"),
632              ))])
633          };
634  
635          let Some(message) = tokens.next() else {
636              return Ok(vec![ReplyType::Server((
637                  ERR_NOTEXTTOSEND,
638                  format!("{nick} :No text to send"),
639              ))])
640          };
641  
642          if !message.starts_with(':') || (message.trim() == ":" && tokens.next().is_none()) {
643              return Ok(vec![ReplyType::Server((
644                  ERR_NOTEXTTOSEND,
645                  format!("{nick} :No text to send"),
646              ))])
647          }
648  
649          // We only send a client reply if the message is for ourself or if
650          // we're trying to communicate with IRC services.
651          // Anything else is rendered by the IRC client and not supposed
652          // to be echoed by the IRC serer.
653          if target == nick {
654              return Ok(vec![ReplyType::Client((
655                  target.to_string(),
656                  format!("PRIVMSG {target} {message}"),
657              ))])
658          }
659  
660          // Handle queries to NickServ
661          if target.to_lowercase().as_str() == "nickserv" {
662              return self.nickserv.handle_query(message.strip_prefix(':').unwrap()).await
663          }
664  
665          // If it's a DM and we don't have an encryption key, we will
666          // refuse to send it. Send ERR_NORECIPIENT to the client.
667          if !target.starts_with('#') && !self.server.contacts.read().await.contains_key(target) {
668              return Ok(vec![ReplyType::Server((ERR_NOSUCHNICK, format!("{nick} :{target}")))])
669          }
670  
671          Ok(vec![])
672      }
673  
674      /// `REHASH`
675      ///
676      /// Causes the server to re-read and re-process its configuration file(s).
677      pub async fn handle_cmd_rehash(&self, _args: &str) -> Result<Vec<ReplyType>> {
678          info!("Attempting to rehash server...");
679          if let Err(e) = self.server.rehash().await {
680              error!("Failed to rehash server: {e}");
681          }
682  
683          Ok(vec![ReplyType::Server((RPL_REHASHING, "Config reloaded!".to_string()))])
684      }
685  
686      /// `TOPIC <channel> [<topic>]`
687      ///
688      /// Used to get the channel topic on `<channel>`. If `<topic>` is given, it
689      /// sets the channel topic to `<topic>`.
690      pub async fn handle_cmd_topic(&self, args: &str) -> Result<Vec<ReplyType>> {
691          if !self.registered.load(SeqCst) {
692              self.penalty.fetch_add(1, SeqCst);
693              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
694          }
695  
696          let nick = self.nickname.read().await.to_string();
697          let mut tokens = args.split_ascii_whitespace();
698  
699          let Some(channel) = tokens.next() else {
700              self.penalty.fetch_add(1, SeqCst);
701              return Ok(vec![ReplyType::Server((
702                  ERR_NEEDMOREPARAMS,
703                  format!("{nick} TOPIC :{INVALID_SYNTAX}"),
704              ))])
705          };
706  
707          if !self.server.channels.read().await.contains_key(channel) {
708              return Ok(vec![ReplyType::Server((
709                  ERR_NOSUCHCHANNEL,
710                  format!("{nick} {channel} :No such channel"),
711              ))])
712          }
713  
714          // If there's a topic, we'll set it, otherwise return the set topic.
715          let Some(topic) = tokens.next() else {
716              let topic = self.server.channels.read().await.get(channel).unwrap().topic.clone();
717              if topic.is_empty() {
718                  return Ok(vec![ReplyType::Server((
719                      RPL_NOTOPIC,
720                      format!("{nick} {channel} :No topic is set"),
721                  ))])
722              } else {
723                  return Ok(vec![ReplyType::Server((
724                      RPL_TOPIC,
725                      format!("{nick} {channel} :{topic}"),
726                  ))])
727              }
728          };
729  
730          // Set the new topic
731          self.server.channels.write().await.get_mut(channel).unwrap().topic =
732              topic.strip_prefix(':').unwrap().to_string();
733  
734          // Send reply
735          let replies = vec![ReplyType::Client((nick, format!("TOPIC {channel} {topic}")))];
736  
737          Ok(replies)
738      }
739  
740      /// `USER <user> <mode> <unused> <realname>`
741      ///
742      /// This command is used at the beginning of a connection to specify the
743      /// username, hostname, real name, and the initial user modes of the
744      /// connecting client. `<realname>` may contain spaces, and thus must be
745      /// prefixed with a colon.
746      pub async fn handle_cmd_user(&self, args: &str) -> Result<Vec<ReplyType>> {
747          if self.registered.load(SeqCst) {
748              self.penalty.fetch_add(1, SeqCst);
749              return Ok(vec![ReplyType::Server((
750                  ERR_ALREADYREGISTERED,
751                  format!("{} :{ALREADY_REGISTERED}", self.nickname.read().await),
752              ))])
753          }
754  
755          // If password is not set register user normally
756          if self.server.password.is_empty() {
757              self.is_pass_set.store(true, SeqCst);
758          }
759  
760          // Parse the line
761          let nick = self.nickname.read().await.to_string();
762          let mut tokens = args.split_ascii_whitespace();
763  
764          let Some(username) = tokens.next() else {
765              self.penalty.fetch_add(1, SeqCst);
766              return Ok(vec![ReplyType::Server((
767                  ERR_NEEDMOREPARAMS,
768                  format!("{nick} USER :{INVALID_SYNTAX}"),
769              ))])
770          };
771  
772          // Mode syntax is currently ignored, but should be part of the command
773          let Some(_mode) = tokens.next() else {
774              self.penalty.fetch_add(1, SeqCst);
775              return Ok(vec![ReplyType::Server((
776                  ERR_NEEDMOREPARAMS,
777                  format!("{nick} USER :{INVALID_SYNTAX}"),
778              ))])
779          };
780  
781          // Next token is unused per RFC, but should be part of the command
782          let Some(_unused) = tokens.next() else {
783              self.penalty.fetch_add(1, SeqCst);
784              return Ok(vec![ReplyType::Server((
785                  ERR_NEEDMOREPARAMS,
786                  format!("{nick} USER :{INVALID_SYNTAX}"),
787              ))])
788          };
789  
790          // The final token should be realname and should start with a colon
791          let Some(realname) = tokens.next() else {
792              self.penalty.fetch_add(1, SeqCst);
793              return Ok(vec![ReplyType::Server((
794                  ERR_NEEDMOREPARAMS,
795                  format!("{nick} USER :{INVALID_SYNTAX}"),
796              ))])
797          };
798  
799          if !realname.starts_with(':') {
800              self.penalty.fetch_add(1, SeqCst);
801              return Ok(vec![ReplyType::Server((
802                  ERR_NEEDMOREPARAMS,
803                  format!("{nick} USER :{INVALID_SYNTAX}"),
804              ))])
805          }
806  
807          *self.username.write().await = username.to_string();
808          *self.realname.write().await = realname.to_string();
809  
810          // If the nickname is set, we can complete the registration
811          if nick != "*" {
812              if !self.is_pass_set.load(SeqCst) {
813                  return Ok(vec![ReplyType::Server((
814                      ERR_PASSWDMISMATCH,
815                      format!("{nick} PASS :{PASSWORD_MISMATCH}"),
816                  ))])
817              }
818              self.registered.store(true, SeqCst);
819              if self.reg_paused.load(SeqCst) {
820                  return Ok(vec![])
821              } else {
822                  return Ok(self.welcome().await)
823              }
824          }
825  
826          // Otherwise, we don't have to reply.
827          Ok(vec![])
828      }
829  
830      /// `VERSION`
831      ///
832      /// Returns the version of the server.
833      pub async fn handle_cmd_version(&self, _args: &str) -> Result<Vec<ReplyType>> {
834          if !self.registered.load(SeqCst) {
835              self.penalty.fetch_add(1, SeqCst);
836              return Ok(vec![ReplyType::Server((ERR_NOTREGISTERED, format!("* :{NOT_REGISTERED}")))])
837          }
838  
839          let replies = vec![ReplyType::Server((
840              RPL_VERSION,
841              format!(
842                  "{} {} {SERVER_NAME} :Let there be dark!",
843                  self.nickname.read().await,
844                  env!("CARGO_PKG_VERSION")
845              ),
846          ))];
847  
848          Ok(replies)
849      }
850  
851      /// Internal function that constructs the welcome message.
852      async fn welcome(&self) -> Vec<ReplyType> {
853          let nick = self.nickname.read().await.to_string();
854  
855          let mut replies = vec![
856              ReplyType::Server((RPL_WELCOME, format!("{nick} :{WELCOME}"))),
857              ReplyType::Server((
858                  RPL_YOURHOST,
859                  format!(
860                      "{nick} :Your host is irc.dark.fi, running version {}",
861                      env!("CARGO_PKG_VERSION")
862                  ),
863              )),
864          ];
865  
866          // Append the MOTD
867          replies.append(&mut self.handle_cmd_motd("").await.unwrap());
868  
869          let mut channels = HashSet::new();
870  
871          // If we have any configured autojoin channels, let's join the user
872          // and set their topics, if any.
873          if !*self.caps.read().await.get("no-autojoin").unwrap() {
874              for channel in self.server.autojoin.read().await.iter() {
875                  replies.extend(self.handle_cmd_join(channel, false).await.unwrap());
876                  channels.insert(channel.to_string());
877              }
878          }
879  
880          // Potentially extend the replies with channel history
881          replies.extend(self.get_history(&channels).await.unwrap());
882  
883          // And request NAMES list.
884          if !*self.caps.read().await.get("no-autojoin").unwrap() {
885              for channel in self.server.autojoin.read().await.iter() {
886                  if let Some(chan) = self.server.channels.read().await.get(channel) {
887                      let nicks: Vec<String> = chan.nicks.iter().cloned().collect();
888  
889                      replies.push(ReplyType::Server((
890                          RPL_NAMREPLY,
891                          format!("{nick} = {channel} :{}", nicks.join(" ")),
892                      )));
893                  }
894  
895                  replies.push(ReplyType::Server((
896                      RPL_ENDOFNAMES,
897                      format!("{nick} {channel} :End of NAMES list"),
898                  )));
899              }
900          }
901  
902          replies
903      }
904  
905      /// Internal function that scans the DAG and returns events for
906      /// given channels. Will return empty if no_history CAP is requested.
907      // N.b. the handling of "live messages" is implemented
908      // <file:./client.rs::r = self.incoming.receive().fuse() => {>
909      // for which the logic for delivery should be kept in sync
910      async fn get_history(&self, channels: &HashSet<String>) -> Result<Vec<ReplyType>> {
911          if channels.is_empty() || *self.caps.read().await.get("no-history").unwrap() {
912              return Ok(vec![])
913          }
914  
915          // Fetch and order all the events from the DAG
916          let dag_events = self.server.darkirc.event_graph.order_events().await;
917  
918          // Here we'll hold the events in order we'll push to the client
919          let mut replies = vec![];
920  
921          for event in dag_events.iter() {
922              let event_id = event.id();
923              // If it was seen, skip
924              match self.is_seen(&event_id).await {
925                  Ok(true) => continue,
926                  Ok(false) => {}
927                  Err(e) => {
928                      error!("[IRC CLIENT] (get_history) self.is_seen({event_id}) failed: {e}");
929                      return Err(e)
930                  }
931              }
932  
933              // Try to deserialize it. (Here we skip errors)
934              let mut privmsg = match Msg::deserialize(event.content()).await {
935                  Ok(Msg::V1(old_msg)) => old_msg.into_new(),
936                  Ok(Msg::V2(new_msg)) => new_msg,
937                  Err(_) => continue,
938              };
939  
940              // Potentially decrypt the privmsg
941              self.server.try_decrypt(&mut privmsg, self.nickname.read().await.as_ref()).await;
942  
943              // We should skip any attempts to contact services from the network.
944              if ["nickserv", "chanserv"].contains(&privmsg.nick.to_lowercase().as_str()) {
945                  continue
946              }
947  
948              // If the PRIVMSG is intended for any of the given
949              // channels or contacts, add it as a reply and
950              // mark it as seen in the seen_events tree.
951              let contacts = self.server.contacts.read().await;
952              if !channels.contains(&privmsg.channel) && !contacts.contains_key(&privmsg.channel) {
953                  continue
954              }
955  
956              // Insert nicks into channels
957              if let Some(chan) = self.server.channels.write().await.get_mut(&privmsg.channel) {
958                  chan.nicks.insert(privmsg.nick.clone());
959              }
960  
961              // Handle message lines individually
962              for line in privmsg.msg.lines() {
963                  // Skip empty lines
964                  if line.is_empty() {
965                      continue;
966                  }
967  
968                  // Format the message
969                  let msg = format!("PRIVMSG {} :{line}", privmsg.channel);
970  
971                  // Send it to the client
972                  replies.push(ReplyType::Client((privmsg.nick.clone(), msg)));
973              }
974  
975              // Mark the message as seen for this USER
976              if let Err(e) = self.mark_seen(&event_id).await {
977                  error!("[IRC CLIENT] (get_history) self.mark_seen({event_id}) failed: {e}");
978                  return Err(e)
979              }
980          }
981  
982          Ok(replies)
983      }
984  }