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 }