From 398cda4af68ecf2e460c74dfb23e74dc464584cf Mon Sep 17 00:00:00 2001 From: Aaron Weiss <aaronweiss74@gmail.com> Date: Tue, 9 Dec 2014 17:01:47 -0500 Subject: [PATCH] Implemented password support for nickservs as per #9. --- src/data/command.rs | 90 +++++++++++++++++++++++++++++++++++++++++++-- src/data/config.rs | 11 ++++++ src/server/utils.rs | 52 ++++++++++++++++++++++++-- 3 files changed, 147 insertions(+), 6 deletions(-) diff --git a/src/data/command.rs b/src/data/command.rs index 607c9d7..122b560 100644 --- a/src/data/command.rs +++ b/src/data/command.rs @@ -4,9 +4,10 @@ use std::io::{InvalidInput, IoError, IoResult}; use std::str::FromStr; use data::message::Message; -/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). -/// This also includes commands from the +/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This +/// also includes commands from the /// [capabilities extension](https://tools.ietf.org/html/draft-mitchell-irc-capabilities-01). +/// Additionally, this includes some common additional commands from popular IRCds. #[stable] #[deriving(Show, PartialEq)] pub enum Command<'a> { @@ -131,6 +132,18 @@ pub enum Command<'a> { SAPART(&'a str, &'a str), /// SAQUIT nickname reason SAQUIT(&'a str, &'a str), + /// NICKSERV :message + NICKSERV(&'a str), + /// CHANSERV :message + CHANSERV(&'a str), + /// OPERSERV :message + OPERSERV(&'a str), + /// BOTSERV :message + BOTSERV(&'a str), + /// HOSTSERV :message + HOSTSERV(&'a str), + /// MEMOSERV :message + MEMOSERV(&'a str), // Capabilities extension to IRCv3 /// CAP COMMAND [param] @@ -239,7 +252,12 @@ impl<'a> Command<'a> { Command::SANICK(o, n) => Message::new(None, "SANICK", Some(vec![o, n]), None), Command::SAPART(c, r) => Message::new(None, "SAPART", Some(vec![c]), Some(r)), Command::SAQUIT(c, r) => Message::new(None, "SAQUIT", Some(vec![c]), Some(r)), - + Command::NICKSERV(m) => Message::new(None, "NICKSERV", None, Some(m)), + Command::CHANSERV(m) => Message::new(None, "CHANSERV", None, Some(m)), + Command::OPERSERV(m) => Message::new(None, "OPERSERV", None, Some(m)), + Command::BOTSERV(m) => Message::new(None, "BOTSERV", None, Some(m)), + Command::HOSTSERV(m) => Message::new(None, "HOSTSERV", None, Some(m)), + Command::MEMOSERV(m) => Message::new(None, "MEMOSERV", None, Some(m)), Command::CAP(s, p) => Message::new(None, "CAP", Some(vec![s.to_str()]), p), } } @@ -846,6 +864,72 @@ impl<'a> Command<'a> { Command::SAQUIT(m.args[0][], m.args[1][]) } } + } else if let "NICKSERV" = m.command[] { + match m.suffix { + Some(ref suffix) => { + if m.args.len() != 0 { return Err(invalid_input()) } + Command::NICKSERV(suffix[]) + }, + None => { + if m.args.len() != 1 { return Err(invalid_input()) } + Command::NICKSERV(m.args[0][]) + } + } + } else if let "CHANSERV" = m.command[] { + match m.suffix { + Some(ref suffix) => { + if m.args.len() != 0 { return Err(invalid_input()) } + Command::CHANSERV(suffix[]) + }, + None => { + if m.args.len() != 1 { return Err(invalid_input()) } + Command::CHANSERV(m.args[0][]) + } + } + } else if let "OPERSERV" = m.command[] { + match m.suffix { + Some(ref suffix) => { + if m.args.len() != 0 { return Err(invalid_input()) } + Command::OPERSERV(suffix[]) + }, + None => { + if m.args.len() != 1 { return Err(invalid_input()) } + Command::OPERSERV(m.args[0][]) + } + } + } else if let "BOTSERV" = m.command[] { + match m.suffix { + Some(ref suffix) => { + if m.args.len() != 0 { return Err(invalid_input()) } + Command::BOTSERV(suffix[]) + }, + None => { + if m.args.len() != 1 { return Err(invalid_input()) } + Command::BOTSERV(m.args[0][]) + } + } + } else if let "HOSTSERV" = m.command[] { + match m.suffix { + Some(ref suffix) => { + if m.args.len() != 0 { return Err(invalid_input()) } + Command::HOSTSERV(suffix[]) + }, + None => { + if m.args.len() != 1 { return Err(invalid_input()) } + Command::HOSTSERV(m.args[0][]) + } + } + } else if let "MEMOSERV" = m.command[] { + match m.suffix { + Some(ref suffix) => { + if m.args.len() != 0 { return Err(invalid_input()) } + Command::MEMOSERV(suffix[]) + }, + None => { + if m.args.len() != 1 { return Err(invalid_input()) } + Command::MEMOSERV(m.args[0][]) + } + } } else if let "CAP" = m.command[] { if m.args.len() != 1 { return Err(invalid_input()) } if let Some(cmd) = from_str(m.args[0][]) { diff --git a/src/data/config.rs b/src/data/config.rs index 71a5434..f62e45f 100644 --- a/src/data/config.rs +++ b/src/data/config.rs @@ -13,6 +13,8 @@ pub struct Config { pub owners: Option<Vec<String>>, /// The bot's nickname. pub nickname: Option<String>, + /// The bot's NICKSERV password. + pub nick_password: Option<String>, /// Alternative nicknames for the bots, if the default is taken. pub alt_nicks: Option<Vec<String>>, /// The bot's username. @@ -69,6 +71,13 @@ impl Config { self.nickname.as_ref().map(|s| s[]).unwrap() } + /// Gets the bot's nickserv password specified in the configuration. + /// This defaults to an empty string when not specified. + #[experimental] + pub fn nick_password(&self) -> &str { + self.nick_password.as_ref().map(|s| s[]).unwrap_or("") + } + /// Gets the alternate nicknames specified in the configuration. /// This defaults to an empty vector when not specified. #[experimental] @@ -157,6 +166,7 @@ mod test { let cfg = Config { owners: Some(vec![format!("test")]), nickname: Some(format!("test")), + nick_password: None, alt_nicks: None, username: Some(format!("test")), realname: Some(format!("test")), @@ -176,6 +186,7 @@ mod test { let cfg = Config { owners: Some(vec![format!("test")]), nickname: Some(format!("test")), + nick_password: None, alt_nicks: None, username: Some(format!("test")), realname: Some(format!("test")), diff --git a/src/server/utils.rs b/src/server/utils.rs index 1629e99..9035a16 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -3,7 +3,7 @@ use std::io::IoResult; use data::{Command, Config, User}; -use data::Command::{CAP, INVITE, JOIN, KICK, KILL, MODE, NICK, NOTICE}; +use data::Command::{CAP, INVITE, JOIN, KICK, KILL, MODE, NICK, NICKSERV, NOTICE}; use data::Command::{OPER, PASS, PONG, PRIVMSG, SAMODE, SANICK, TOPIC, USER}; use data::command::CapSubCommand::{END, REQ}; use data::kinds::{IrcReader, IrcWriter}; @@ -51,8 +51,14 @@ impl<'a, T: IrcReader, U: IrcWriter> Wrapper<'a, T, U> { try!(self.server.send(PASS(self.server.config().password()))); } try!(self.server.send(NICK(self.server.config().nickname()))); - self.server.send(USER(self.server.config().username(), "0", - self.server.config().real_name())) + try!(self.server.send(USER(self.server.config().username(), "0", + self.server.config().real_name()))); + if self.server.config().nick_password() != "" { + try!(self.server.send(NICKSERV( + format!("IDENTIFY {}", self.server.config().nick_password())[] + ))); + } + Ok(()) } /// Sends a PONG with the specified message. @@ -157,9 +163,11 @@ impl<'a, T: IrcReader, U: IrcWriter> Wrapper<'a, T, U> { #[cfg(test)] mod test { use super::Wrapper; + use std::default::Default; use std::io::MemWriter; use std::io::util::NullReader; use conn::Connection; + use data::Config; use server::IrcServer; use server::test::{get_server_value, test_config}; @@ -175,6 +183,44 @@ mod test { "CAP REQ :multi-prefix\r\nCAP END\r\nNICK :test\r\nUSER test 0 * :test\r\n"); } + #[test] + fn identify_with_password() { + let server = IrcServer::from_connection(Config { + owners: Some(vec![format!("test")]), + nickname: Some(format!("test")), + alt_nicks: Some(vec![format!("test2")]), + server: Some(format!("irc.test.net")), + password: Some(format!("password")), + channels: Some(vec![format!("#test"), format!("#test2")]), + .. Default::default() + }, Connection::new(NullReader, MemWriter::new())); + { + let wrapper = Wrapper::new(&server); + wrapper.identify().unwrap(); + } + assert_eq!(get_server_value(server)[], "CAP REQ :multi-prefix\r\nCAP END\r\n\ + PASS :password\r\nNICK :test\r\nUSER test 0 * :test\r\n"); + } + + #[test] + fn identify_with_nick_password() { + let server = IrcServer::from_config(Config { + owners: Some(vec![format!("test")]), + nickname: Some(format!("test")), + alt_nicks: Some(vec![format!("test2")]), + server: Some(format!("irc.test.net")), + nick_password: Some(format!("password")), + channels: Some(vec![format!("#test"), format!("#test2")]), + .. Default::default() + }, Connection::new(NullReader, MemWriter::new())); + { + let wrapper = Wrapper::new(&server); + wrapper.identify().unwrap(); + } + assert_eq!(get_server_value(server)[], "CAP REQ :multi-prefix\r\nCAP END\r\nNICK :test\r\n\ + USER test 0 * :test\r\nNICKSERV :IDENTIFY password\r\n"); + } + #[test] fn send_pong() { let server = IrcServer::from_connection(test_config(),