Explorar el Código

ircd2: Split IRC server and client, add nickserv idea.

parazyd hace 3 años
padre
commit
ff77ab444e
Se han modificado 6 ficheros con 441 adiciones y 275 borrados
  1. 1 0
      Cargo.lock
  2. 3 0
      bin/ircd2/Cargo.toml
  3. 8 0
      bin/ircd2/README.md
  4. 5 275
      bin/ircd2/src/irc/mod.rs
  5. 320 0
      bin/ircd2/src/irc/server.rs
  6. 104 0
      bin/ircd2/src/irc/server/nickserv.rs

+ 1 - 0
Cargo.lock

@@ -2497,6 +2497,7 @@ dependencies = [
  "serde",
  "serde_json",
  "simplelog",
+ "sled",
  "smol",
  "structopt",
  "structopt-toml",

+ 3 - 0
bin/ircd2/Cargo.toml

@@ -25,6 +25,9 @@ easy-parallel = "3.2.0"
 crypto_box = "0.8.2"
 rand = "0.8.5"
 
+# db
+sled = "0.34.7"
+
 # Misc
 clap = {version = "4.0.32", features = ["derive"]}
 log = "0.4.17"

+ 8 - 0
bin/ircd2/README.md

@@ -2,3 +2,11 @@
 
 see [Darkfi Book](https://darkrenaissance.github.io/darkfi/misc/ircd.html) for the installation guide.
 
+
+## Services
+
+To operate with ircd using IRC clients, we can implement special
+namespaces which we are then able to query and use that as the
+client's interactive communication with the server/daemon:
+
+* `nickserv` - Account management

+ 5 - 275
bin/ircd2/src/irc/mod.rs

@@ -16,28 +16,9 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{collections::HashMap, fs::File, net::SocketAddr};
+use std::collections::HashMap;
 
-use async_std::{
-    net::TcpListener,
-    sync::{Arc, Mutex},
-};
-use futures::{io::BufReader, AsyncRead, AsyncReadExt, AsyncWrite};
-use futures_rustls::{rustls, TlsAcceptor};
-use log::{error, info};
-
-use darkfi::{
-    event_graph::{
-        get_current_time,
-        model::{Event, EventId, ModelPtr},
-        protocol_event::{Seen, SeenPtr, UnreadEventsPtr},
-        view::ViewPtr,
-    },
-    net::P2pPtr,
-    system::SubscriberPtr,
-    util::path::expand_path,
-    Error, Result,
-};
+use darkfi::Result;
 
 use crate::{
     settings::{Args, ChannelInfo, ContactInfo},
@@ -45,9 +26,11 @@ use crate::{
 };
 
 mod client;
-
 pub use client::IrcClient;
 
+mod server;
+pub use server::IrcServer;
+
 #[derive(Clone)]
 pub struct IrcConfig {
     // init bool
@@ -112,256 +95,3 @@ pub enum NotifierMsg {
     Privmsg(PrivMsgEvent),
     UpdateConfig,
 }
-
-pub struct IrcServer {
-    settings: Args,
-    p2p: P2pPtr,
-    model: ModelPtr<PrivMsgEvent>,
-    view: ViewPtr<PrivMsgEvent>,
-    unread_events: UnreadEventsPtr<PrivMsgEvent>,
-    clients_subscriptions: SubscriberPtr<ClientSubMsg>,
-    seen: SeenPtr<EventId>,
-    missed_events: Arc<Mutex<Vec<Event<PrivMsgEvent>>>>,
-}
-
-impl IrcServer {
-    pub async fn new(
-        settings: Args,
-        p2p: P2pPtr,
-        model: ModelPtr<PrivMsgEvent>,
-        view: ViewPtr<PrivMsgEvent>,
-        unread_events: UnreadEventsPtr<PrivMsgEvent>,
-        clients_subscriptions: SubscriberPtr<ClientSubMsg>,
-    ) -> Result<Self> {
-        let seen = Seen::new();
-        let missed_events = Arc::new(Mutex::new(vec![]));
-        Ok(Self {
-            settings,
-            p2p,
-            model,
-            view,
-            unread_events,
-            clients_subscriptions,
-            seen,
-            missed_events,
-        })
-    }
-    pub async fn start(&self, executor: Arc<smol::Executor<'_>>) -> Result<()> {
-        let (msg_notifier, msg_recv) = smol::channel::unbounded();
-
-        // Listen to msgs from clients
-        executor
-            .clone()
-            .spawn(Self::listen_to_msgs(
-                self.p2p.clone(),
-                self.model.clone(),
-                self.seen.clone(),
-                self.unread_events.clone(),
-                msg_recv,
-                self.clients_subscriptions.clone(),
-            ))
-            .detach();
-
-        executor
-            .clone()
-            .spawn(Self::listen_to_view(
-                self.view.clone(),
-                self.seen.clone(),
-                self.missed_events.clone(),
-                self.clients_subscriptions.clone(),
-            ))
-            .detach();
-
-        // Start listening for new connections
-        self.listen(msg_notifier, executor.clone()).await?;
-
-        Ok(())
-    }
-
-    async fn listen_to_view(
-        view: ViewPtr<PrivMsgEvent>,
-        seen: SeenPtr<EventId>,
-        missed_events: Arc<Mutex<Vec<Event<PrivMsgEvent>>>>,
-        clients_subscriptions: SubscriberPtr<ClientSubMsg>,
-    ) -> Result<()> {
-        loop {
-            let event = view.lock().await.process().await?;
-            if !seen.push(&event.hash()).await {
-                continue
-            }
-
-            missed_events.lock().await.push(event.clone());
-
-            let msg = event.action.clone();
-
-            clients_subscriptions.notify(ClientSubMsg::Privmsg(msg)).await;
-        }
-    }
-
-    /// Start listening to msgs from irc clients
-    pub async fn listen_to_msgs(
-        p2p: P2pPtr,
-        model: ModelPtr<PrivMsgEvent>,
-        seen: SeenPtr<EventId>,
-        unread_events: UnreadEventsPtr<PrivMsgEvent>,
-        recv: smol::channel::Receiver<(NotifierMsg, u64)>,
-        clients_subscriptions: SubscriberPtr<ClientSubMsg>,
-    ) -> Result<()> {
-        loop {
-            let (msg, subscription_id) = recv.recv().await?;
-
-            match msg {
-                NotifierMsg::Privmsg(msg) => {
-                    let event = Event {
-                        previous_event_hash: model.lock().await.get_head_hash(),
-                        action: msg.clone(),
-                        timestamp: get_current_time(),
-                        read_confirms: 0,
-                    };
-
-                    // Since this will be added to the View directly, other clients connected to irc
-                    // server must get informed about this new msg
-                    clients_subscriptions
-                        .notify_with_exclude(ClientSubMsg::Privmsg(msg), &[subscription_id])
-                        .await;
-
-                    if !seen.push(&event.hash()).await {
-                        continue
-                    }
-                    unread_events.lock().await.insert(&event);
-
-                    p2p.broadcast(event).await?;
-                }
-
-                NotifierMsg::UpdateConfig => {
-                    //
-                    // load and parse the new settings from configuration file and pass it to all
-                    // irc clients
-                    //
-                    // let new_config = IrcConfig::new()?;
-                    // clients_subscriptions.notify(ClientSubMsg::Config(new_config)).await;
-                }
-            }
-        }
-    }
-
-    /// Start listening to new connections from irc clients
-    pub async fn listen(
-        &self,
-        notifier: smol::channel::Sender<(NotifierMsg, u64)>,
-        executor: Arc<smol::Executor<'_>>,
-    ) -> Result<()> {
-        let (listener, acceptor) = self.setup_listener().await?;
-        info!("[IRC SERVER] listening on {}", self.settings.irc_listen);
-
-        loop {
-            let (stream, peer_addr) = match listener.accept().await {
-                Ok((s, a)) => (s, a),
-                Err(e) => {
-                    error!("[IRC SERVER] Failed accepting new connections: {}", e);
-                    continue
-                }
-            };
-
-            let result = if let Some(acceptor) = acceptor.clone() {
-                // TLS connection
-                let stream = match acceptor.accept(stream).await {
-                    Ok(s) => s,
-                    Err(e) => {
-                        error!("[IRC SERVER] Failed accepting TLS connection: {}", e);
-                        continue
-                    }
-                };
-                self.process_connection(stream, peer_addr, notifier.clone(), executor.clone()).await
-            } else {
-                // TCP connection
-                self.process_connection(stream, peer_addr, notifier.clone(), executor.clone()).await
-            };
-
-            if let Err(e) = result {
-                error!("[IRC SERVER] Failed processing connection {}: {}", peer_addr, e);
-                continue
-            };
-
-            info!("[IRC SERVER] Accept new connection: {}", peer_addr);
-        }
-    }
-
-    /// On every new connection create new IrcClient
-    async fn process_connection<C: AsyncRead + AsyncWrite + Send + Unpin + 'static>(
-        &self,
-        stream: C,
-        peer_addr: SocketAddr,
-        notifier: smol::channel::Sender<(NotifierMsg, u64)>,
-        executor: Arc<smol::Executor<'_>>,
-    ) -> Result<()> {
-        let (reader, writer) = stream.split();
-        let reader = BufReader::new(reader);
-
-        // Subscription for the new client
-        let client_subscription = self.clients_subscriptions.clone().subscribe().await;
-
-        // new irc configuration
-        let irc_config = IrcConfig::new(&self.settings)?;
-
-        // New irc client
-        let mut client = IrcClient::new(
-            writer,
-            reader,
-            peer_addr,
-            irc_config,
-            notifier,
-            client_subscription,
-            self.missed_events.clone(),
-        );
-
-        // Start listening and detach
-        executor
-            .spawn(async move {
-                client.listen().await;
-            })
-            .detach();
-
-        Ok(())
-    }
-
-    /// Setup a listener for irc server
-    async fn setup_listener(&self) -> Result<(TcpListener, Option<TlsAcceptor>)> {
-        let listenaddr = self.settings.irc_listen.socket_addrs(|| None)?[0];
-        let listener = TcpListener::bind(listenaddr).await?;
-
-        let acceptor = match self.settings.irc_listen.scheme() {
-            "tls" => {
-                // openssl genpkey -algorithm ED25519 > example.com.key
-                // openssl req -new -out example.com.csr -key example.com.key
-                // openssl x509 -req -days 700 -in example.com.csr -signkey example.com.key -out example.com.crt
-
-                if self.settings.irc_tls_secret.is_none() || self.settings.irc_tls_cert.is_none() {
-                    error!("[IRC SERVER] To listen using TLS, please set irc_tls_secret and irc_tls_cert in your config file.");
-                    return Err(Error::KeypairPathNotFound)
-                }
-
-                let file =
-                    File::open(expand_path(self.settings.irc_tls_secret.as_ref().unwrap())?)?;
-                let mut reader = std::io::BufReader::new(file);
-                let secret = &rustls_pemfile::pkcs8_private_keys(&mut reader)?[0];
-                let secret = rustls::PrivateKey(secret.clone());
-
-                let file = File::open(expand_path(self.settings.irc_tls_cert.as_ref().unwrap())?)?;
-                let mut reader = std::io::BufReader::new(file);
-                let certificate = &rustls_pemfile::certs(&mut reader)?[0];
-                let certificate = rustls::Certificate(certificate.clone());
-
-                let config = rustls::ServerConfig::builder()
-                    .with_safe_defaults()
-                    .with_no_client_auth()
-                    .with_single_cert(vec![certificate], secret)?;
-
-                let acceptor = TlsAcceptor::from(Arc::new(config));
-                Some(acceptor)
-            }
-            _ => None,
-        };
-        Ok((listener, acceptor))
-    }
-}

+ 320 - 0
bin/ircd2/src/irc/server.rs

@@ -0,0 +1,320 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{fs::File, net::SocketAddr};
+
+use async_std::{
+    net::TcpListener,
+    sync::{Arc, Mutex},
+};
+use futures::{io::BufReader, AsyncRead, AsyncReadExt, AsyncWrite};
+use futures_rustls::{rustls, TlsAcceptor};
+use log::{error, info};
+
+use darkfi::{
+    event_graph::{
+        get_current_time,
+        model::{Event, EventId, ModelPtr},
+        protocol_event::{Seen, SeenPtr, UnreadEventsPtr},
+        view::ViewPtr,
+    },
+    net::P2pPtr,
+    system::SubscriberPtr,
+    util::path::expand_path,
+    Error, Result,
+};
+
+use super::{ClientSubMsg, IrcClient, IrcConfig, NotifierMsg};
+
+use crate::{settings::Args, PrivMsgEvent};
+
+mod nickserv;
+use nickserv::NickServ;
+
+const NICK_NICKSERV: &str = "nickserv";
+
+pub struct IrcServer {
+    settings: Args,
+    p2p: P2pPtr,
+    model: ModelPtr<PrivMsgEvent>,
+    view: ViewPtr<PrivMsgEvent>,
+    unread_events: UnreadEventsPtr<PrivMsgEvent>,
+    clients_subscriptions: SubscriberPtr<ClientSubMsg>,
+    seen: SeenPtr<EventId>,
+    missed_events: Arc<Mutex<Vec<Event<PrivMsgEvent>>>>,
+    /// nickserv service
+    pub nickserv: NickServ,
+}
+
+impl IrcServer {
+    pub async fn new(
+        settings: Args,
+        p2p: P2pPtr,
+        model: ModelPtr<PrivMsgEvent>,
+        view: ViewPtr<PrivMsgEvent>,
+        unread_events: UnreadEventsPtr<PrivMsgEvent>,
+        clients_subscriptions: SubscriberPtr<ClientSubMsg>,
+    ) -> Result<Self> {
+        let seen = Seen::new();
+        let missed_events = Arc::new(Mutex::new(vec![]));
+        Ok(Self {
+            settings,
+            p2p,
+            model,
+            view,
+            unread_events,
+            clients_subscriptions,
+            seen,
+            missed_events,
+            nickserv: NickServ::default(),
+        })
+    }
+
+    pub async fn start(&self, executor: Arc<smol::Executor<'_>>) -> Result<()> {
+        let (msg_notifier, msg_recv) = smol::channel::unbounded();
+
+        // Listen to msgs from clients
+        executor
+            .clone()
+            .spawn(Self::listen_to_msgs(
+                self.p2p.clone(),
+                self.model.clone(),
+                self.seen.clone(),
+                self.unread_events.clone(),
+                msg_recv,
+                self.clients_subscriptions.clone(),
+            ))
+            .detach();
+
+        executor
+            .clone()
+            .spawn(Self::listen_to_view(
+                self.view.clone(),
+                self.seen.clone(),
+                self.missed_events.clone(),
+                self.clients_subscriptions.clone(),
+            ))
+            .detach();
+
+        // Start listening for new connections
+        self.listen(msg_notifier, executor.clone()).await?;
+
+        Ok(())
+    }
+
+    async fn listen_to_view(
+        view: ViewPtr<PrivMsgEvent>,
+        seen: SeenPtr<EventId>,
+        missed_events: Arc<Mutex<Vec<Event<PrivMsgEvent>>>>,
+        clients_subscriptions: SubscriberPtr<ClientSubMsg>,
+    ) -> Result<()> {
+        loop {
+            let event = view.lock().await.process().await?;
+            if !seen.push(&event.hash()).await {
+                continue
+            }
+
+            missed_events.lock().await.push(event.clone());
+
+            let msg = event.action.clone();
+
+            clients_subscriptions.notify(ClientSubMsg::Privmsg(msg)).await;
+        }
+    }
+
+    /// Start listening to msgs from irc clients
+    pub async fn listen_to_msgs(
+        p2p: P2pPtr,
+        model: ModelPtr<PrivMsgEvent>,
+        seen: SeenPtr<EventId>,
+        unread_events: UnreadEventsPtr<PrivMsgEvent>,
+        recv: smol::channel::Receiver<(NotifierMsg, u64)>,
+        clients_subscriptions: SubscriberPtr<ClientSubMsg>,
+    ) -> Result<()> {
+        loop {
+            let (msg, subscription_id) = recv.recv().await?;
+
+            match msg {
+                NotifierMsg::Privmsg(msg) => {
+                    // First check if we're communicating with any services.
+                    // If not, then we proceed with behaving like it's a normal
+                    // message.
+                    // TODO: This needs to be protected from adversaries doing
+                    //       remote execution.
+                    match msg.target.to_lowercase().as_str() {
+                        NICK_NICKSERV => {
+                            //self.nickserv.act(msg);
+                            continue
+                        }
+
+                        _ => {} // pass
+                    }
+
+                    let event = Event {
+                        previous_event_hash: model.lock().await.get_head_hash(),
+                        action: msg.clone(),
+                        timestamp: get_current_time(),
+                        read_confirms: 0,
+                    };
+
+                    // Since this will be added to the View directly, other clients connected to irc
+                    // server must get informed about this new msg
+                    clients_subscriptions
+                        .notify_with_exclude(ClientSubMsg::Privmsg(msg), &[subscription_id])
+                        .await;
+
+                    if !seen.push(&event.hash()).await {
+                        continue
+                    }
+                    unread_events.lock().await.insert(&event);
+
+                    p2p.broadcast(event).await?;
+                }
+
+                NotifierMsg::UpdateConfig => {
+                    //
+                    // load and parse the new settings from configuration file and pass it to all
+                    // irc clients
+                    //
+                    // let new_config = IrcConfig::new()?;
+                    // clients_subscriptions.notify(ClientSubMsg::Config(new_config)).await;
+                }
+            }
+        }
+    }
+
+    /// Start listening to new connections from irc clients
+    pub async fn listen(
+        &self,
+        notifier: smol::channel::Sender<(NotifierMsg, u64)>,
+        executor: Arc<smol::Executor<'_>>,
+    ) -> Result<()> {
+        let (listener, acceptor) = self.setup_listener().await?;
+        info!("[IRC SERVER] listening on {}", self.settings.irc_listen);
+
+        loop {
+            let (stream, peer_addr) = match listener.accept().await {
+                Ok((s, a)) => (s, a),
+                Err(e) => {
+                    error!("[IRC SERVER] Failed accepting new connections: {}", e);
+                    continue
+                }
+            };
+
+            let result = if let Some(acceptor) = acceptor.clone() {
+                // TLS connection
+                let stream = match acceptor.accept(stream).await {
+                    Ok(s) => s,
+                    Err(e) => {
+                        error!("[IRC SERVER] Failed accepting TLS connection: {}", e);
+                        continue
+                    }
+                };
+                self.process_connection(stream, peer_addr, notifier.clone(), executor.clone()).await
+            } else {
+                // TCP connection
+                self.process_connection(stream, peer_addr, notifier.clone(), executor.clone()).await
+            };
+
+            if let Err(e) = result {
+                error!("[IRC SERVER] Failed processing connection {}: {}", peer_addr, e);
+                continue
+            };
+
+            info!("[IRC SERVER] Accept new connection: {}", peer_addr);
+        }
+    }
+
+    /// On every new connection create new IrcClient
+    async fn process_connection<C: AsyncRead + AsyncWrite + Send + Unpin + 'static>(
+        &self,
+        stream: C,
+        peer_addr: SocketAddr,
+        notifier: smol::channel::Sender<(NotifierMsg, u64)>,
+        executor: Arc<smol::Executor<'_>>,
+    ) -> Result<()> {
+        let (reader, writer) = stream.split();
+        let reader = BufReader::new(reader);
+
+        // Subscription for the new client
+        let client_subscription = self.clients_subscriptions.clone().subscribe().await;
+
+        // new irc configuration
+        let irc_config = IrcConfig::new(&self.settings)?;
+
+        // New irc client
+        let mut client = IrcClient::new(
+            writer,
+            reader,
+            peer_addr,
+            irc_config,
+            notifier,
+            client_subscription,
+            self.missed_events.clone(),
+        );
+
+        // Start listening and detach
+        executor
+            .spawn(async move {
+                client.listen().await;
+            })
+            .detach();
+
+        Ok(())
+    }
+
+    /// Setup a listener for irc server
+    async fn setup_listener(&self) -> Result<(TcpListener, Option<TlsAcceptor>)> {
+        let listenaddr = self.settings.irc_listen.socket_addrs(|| None)?[0];
+        let listener = TcpListener::bind(listenaddr).await?;
+
+        let acceptor = match self.settings.irc_listen.scheme() {
+            "tls" => {
+                // openssl genpkey -algorithm ED25519 > example.com.key
+                // openssl req -new -out example.com.csr -key example.com.key
+                // openssl x509 -req -days 700 -in example.com.csr -signkey example.com.key -out example.com.crt
+
+                if self.settings.irc_tls_secret.is_none() || self.settings.irc_tls_cert.is_none() {
+                    error!("[IRC SERVER] To listen using TLS, please set irc_tls_secret and irc_tls_cert in your config file.");
+                    return Err(Error::KeypairPathNotFound)
+                }
+
+                let file =
+                    File::open(expand_path(self.settings.irc_tls_secret.as_ref().unwrap())?)?;
+                let mut reader = std::io::BufReader::new(file);
+                let secret = &rustls_pemfile::pkcs8_private_keys(&mut reader)?[0];
+                let secret = rustls::PrivateKey(secret.clone());
+
+                let file = File::open(expand_path(self.settings.irc_tls_cert.as_ref().unwrap())?)?;
+                let mut reader = std::io::BufReader::new(file);
+                let certificate = &rustls_pemfile::certs(&mut reader)?[0];
+                let certificate = rustls::Certificate(certificate.clone());
+
+                let config = rustls::ServerConfig::builder()
+                    .with_safe_defaults()
+                    .with_no_client_auth()
+                    .with_single_cert(vec![certificate], secret)?;
+
+                let acceptor = TlsAcceptor::from(Arc::new(config));
+                Some(acceptor)
+            }
+            _ => None,
+        };
+        Ok((listener, acceptor))
+    }
+}

+ 104 - 0
bin/ircd2/src/irc/server/nickserv.rs

@@ -0,0 +1,104 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::collections::BTreeMap;
+
+use sled::IVec;
+
+use crate::PrivMsgEvent;
+
+#[derive(Debug, Clone, Default)]
+pub struct NickServ {
+    db: BTreeMap<IVec, IVec>,
+}
+
+impl NickServ {
+    fn usage() -> Vec<String> {
+        let r = vec![
+            "***** nickserv help *****",
+            "",
+            "nickserv allows clients to 'register' an account. An account",
+            "registration is necessary to be able to join and send messages",
+            "to the p2p network.",
+            "",
+            "The following commands are available:",
+            "",
+            "    CREATE      Create a new account",
+            "    LIST        List available accounts",
+            "    REGISTER    Register a new account",
+            "    IDENTIFY    Identify and pick an account to use",
+            "",
+            "***** end of help *****",
+        ];
+
+        r.iter().map(|x| x.to_string()).collect()
+    }
+
+    // Here because we might consider returning the actual full protocol PRIVMSG
+    // in a vec. So we can use the result of this and feed it directly to the
+    // client as messages. Dunno if necessary, just a thought.
+    fn reply(msg: String) -> Vec<String> {
+        vec![msg]
+    }
+
+    /// Parse an incoming nickserv message
+    pub fn act(&mut self, ev: PrivMsgEvent) -> Result<Vec<String>, Vec<String>> {
+        assert_eq!(ev.target.to_lowercase().as_str(), super::NICK_NICKSERV);
+
+        let parts: Vec<String> = ev.msg.split(' ').map(|x| x.to_string()).collect();
+
+        match parts[0].to_uppercase().as_str() {
+            "CREATE" => self.create(),
+
+            "LIST" => self.list(),
+
+            "REGISTER" => self.register(),
+
+            "IDENTIFY" => self.identify(),
+
+            "HELP" => return Ok(Self::usage()),
+
+            c => {
+                return Err(vec![
+                    format!("Invalid command {}", c),
+                    "Type HELP to get help".to_string(),
+                ])
+            }
+        }
+    }
+
+    /// Create a new account
+    fn create(&mut self) -> Result<Vec<String>, Vec<String>> {
+        Ok(Self::reply("Account created successfully.".to_string()))
+    }
+
+    /// List available accounts
+    fn list(&self) -> Result<Vec<String>, Vec<String>> {
+        Ok(Self::reply("Accounts: ...".to_string()))
+    }
+
+    /// Register a created but unregistered account
+    fn register(&mut self) -> Result<Vec<String>, Vec<String>> {
+        Ok(Self::reply("Account created successfully.".to_string()))
+    }
+
+    /// Pick an account to use
+    fn identify(&mut self) -> Result<Vec<String>, Vec<String>> {
+        Ok(Self::reply("Using account 0.".to_string()))
+    }
+}