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(),