command.rs 40 KB

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