Explorar o código

bin/darkirc: added support for private channels and DMs

Dastan-glitch %!s(int64=3) %!d(string=hai) anos
pai
achega
40b8a71d7b

+ 0 - 0
bin/darkirc/ircd_config.toml → bin/darkirc/darkirc_config.toml


+ 25 - 17
bin/darkirc/src/crypto.rs

@@ -80,40 +80,48 @@ pub fn encrypt(salt_box: &SalsaBox, plaintext: &str) -> String {
     bs58::encode(concat).into_string()
 }
 
-/// Decrypt PrivMsg target
 pub fn decrypt_target(
+    contact: &mut String,
     privmsg: &mut PrivMsgEvent,
-    configured_chans: &HashMap<String, ChannelInfo>,
-    configured_contacts: &HashMap<String, ContactInfo>,
-    private_key: &Option<String>,
+    configured_chans: HashMap<String, ChannelInfo>,
+    configured_contacts: HashMap<String, ContactInfo>,
 ) {
-    for (name, chan_info) in configured_chans {
+    for chan_name in configured_chans.keys() {
+        let chan_info = configured_chans.get(chan_name).unwrap();
         if !chan_info.joined {
             continue
         }
 
-        let salt_box = chan_info.salt_box(name).clone();
+        let salt_box = chan_info.salt_box.clone();
 
         if let Some(salt_box) = salt_box {
-            if try_decrypt(&salt_box, &privmsg.target).is_some() {
-                privmsg.target = name.clone();
+            let decrypted_target = try_decrypt(&salt_box, &privmsg.target);
+            if decrypted_target.is_none() {
+                continue
+            }
+
+            let target = decrypted_target.unwrap();
+            if *chan_name == target {
+                privmsg.target = target;
                 return
             }
         }
     }
 
-    if private_key.is_none() {
-        return
-    }
-
-    for (name, contact_info) in configured_contacts {
-        let salt_box = contact_info.salt_box(private_key.as_ref().unwrap(), name).clone();
+    for cnt_name in configured_contacts.keys() {
+        let cnt_info = configured_contacts.get(cnt_name).unwrap();
 
+        let salt_box = cnt_info.salt_box.clone();
         if let Some(salt_box) = salt_box {
-            if try_decrypt(&salt_box, &privmsg.target).is_some() {
-                privmsg.target = name.clone();
-                return
+            let decrypted_target = try_decrypt(&salt_box, &privmsg.target);
+            if decrypted_target.is_none() {
+                continue
             }
+
+            let target = decrypted_target.unwrap();
+            privmsg.target = target;
+            *contact = cnt_name.into();
+            return
         }
     }
 }

+ 48 - 51
bin/darkirc/src/irc/client.rs

@@ -26,7 +26,11 @@ use futures::{
 
 use log::{debug, error, info, warn};
 
-use darkfi::{event_graph::model::Event, system::Subscription, Error, Result};
+use darkfi::{
+    event_graph::{model::Event, EventMsg},
+    system::Subscription,
+    Error, Result,
+};
 
 use crate::{
     crypto::{decrypt_privmsg, decrypt_target, encrypt_privmsg},
@@ -115,23 +119,27 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcClient<C> {
 
         self.irc_config.channels.extend(new_config.channels);
         self.irc_config.contacts.extend(new_config.contacts);
-        self.irc_config.nickname = new_config.nickname;
-        self.irc_config.private_key = new_config.private_key;
+        // self.irc_config.private_key = new_config.private_key;
         self.irc_config.password = new_config.password;
 
         if self.on_receive_join(self.irc_config.channels.keys().cloned().collect()).await.is_err() {
             warn!("Error to join updated channels");
+        } else {
+            info!("[CLIENT {}] Config updated", self.address);
         }
     }
 
     pub async fn process_msg(&mut self, msg: &mut PrivMsgEvent) -> Result<()> {
         info!("[CLIENT {}] msg from View: {:?}", self.address, msg.to_string());
 
+        let mut msg = msg.clone();
+        let mut contact = String::new();
+
         decrypt_target(
-            msg,
-            &self.irc_config.channels,
-            &self.irc_config.contacts,
-            &self.irc_config.private_key,
+            &mut contact,
+            &mut msg,
+            self.irc_config.channels.clone(),
+            self.irc_config.contacts.clone(),
         );
 
         if msg.target.starts_with('#') {
@@ -145,13 +153,9 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcClient<C> {
                 return Ok(())
             }
 
-            if let Some(salt_box) = &chan_info.salt_box(&msg.target) {
-                decrypt_privmsg(salt_box, msg);
-                info!(
-                    "[CLIENT {}] Decrypted received message: {:?}",
-                    self.address,
-                    msg.to_string()
-                );
+            if let Some(salt_box) = &chan_info.salt_box {
+                decrypt_privmsg(salt_box, &mut msg);
+                info!("Decrypted received message: {:?}", msg);
             }
 
             // add the nickname to the channel's names
@@ -160,25 +164,20 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcClient<C> {
             }
 
             self.reply(&msg.to_string()).await?;
-        } else if self.irc_config.private_key.is_some() {
-            if let Some(contact_info) = self.irc_config.contacts.get(&msg.target) {
-                let salt_box = &contact_info
-                    .salt_box(self.irc_config.private_key.as_ref().unwrap(), &msg.target);
-
-                if salt_box.is_none() {
-                    return Ok(())
-                }
-
-                decrypt_privmsg(salt_box.as_ref().unwrap(), msg);
-
-                info!(
-                    "[CLIENT {}] Decrypted received message: {:?}",
-                    self.address,
-                    msg.to_string()
-                );
+        } else if self.irc_config.is_cap_end && self.irc_config.is_nick_init {
+            if !self.irc_config.contacts.contains_key(&contact) {
+                return Ok(())
+            }
 
-                self.reply(&msg.to_string()).await?;
+            let contact_info = self.irc_config.contacts.get(&contact).unwrap();
+            if let Some(salt_box) = &contact_info.salt_box {
+                decrypt_privmsg(salt_box, &mut msg);
+                // This is for /query
+                msg.nick = contact;
+                info!("[P2P] Decrypted received message: {:?}", msg);
             }
+
+            self.reply(&msg.to_string()).await?;
         }
 
         Ok(())
@@ -246,6 +245,7 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcClient<C> {
             self.irc_config.is_registered = true;
 
             // join all channels
+            self.on_receive_join(self.irc_config.auto_channels.clone()).await?;
             self.on_receive_join(self.irc_config.channels.keys().cloned().collect()).await?;
 
             if *self.irc_config.capabilities.get("no-history").unwrap() {
@@ -487,11 +487,11 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcClient<C> {
 
         info!("[CLIENT {}] (Plain) PRIVMSG {} :{}", self.address, target, message,);
 
-        let mut privmsg = PrivMsgEvent {
-            nick: self.irc_config.nickname.clone(),
-            target: target.to_string(),
-            msg: message,
-        };
+        let mut privmsg = PrivMsgEvent::new();
+
+        privmsg.nick = self.irc_config.nickname.clone();
+        privmsg.target = target.to_string();
+        privmsg.msg = message.clone();
 
         if target.starts_with('#') {
             if !self.irc_config.channels.contains_key(target) {
@@ -504,22 +504,19 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcClient<C> {
                 return Ok(())
             }
 
-            if let Some(salt_box) = &channel_info.salt_box(target) {
+            if let Some(salt_box) = &channel_info.salt_box {
                 encrypt_privmsg(salt_box, &mut privmsg);
-                info!("[CLIENT {}] (Encrypted) PRIVMSG: {:?}", self.address, privmsg.to_string());
+                info!("[CLIENT {}] (Encrypted) PRIVMSG: {:?}", self.address, privmsg);
             }
-        } else if self.irc_config.private_key.is_some() {
-            if let Some(contact_info) = self.irc_config.contacts.get(target) {
-                if let Some(salt_box) =
-                    &contact_info.salt_box(self.irc_config.private_key.as_ref().unwrap(), target)
-                {
-                    encrypt_privmsg(salt_box, &mut privmsg);
-                    info!(
-                        "[CLIENT {}] (Encrypted) PRIVMSG: {:?}",
-                        self.address,
-                        privmsg.to_string()
-                    );
-                }
+        } else {
+            if !self.irc_config.contacts.contains_key(target) {
+                return Ok(())
+            }
+
+            let contact_info = self.irc_config.contacts.get(target).unwrap();
+            if let Some(salt_box) = &contact_info.salt_box {
+                encrypt_privmsg(salt_box, &mut privmsg);
+                info!("[CLIENT {}] (Encrypted) PRIVMSG: {:?}", self.address, privmsg);
             }
         }
 
@@ -536,7 +533,7 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcClient<C> {
                 continue
             }
             if !self.irc_config.channels.contains_key(chan) {
-                let mut chan_info = ChannelInfo::new();
+                let mut chan_info = ChannelInfo::new()?;
                 chan_info.topic = Some("n/a".to_string());
                 self.irc_config.channels.insert(chan.to_string(), chan_info);
             }

+ 15 - 13
bin/darkirc/src/irc/mod.rs

@@ -18,10 +18,13 @@
 
 use std::collections::HashMap;
 
-use darkfi::Result;
+use darkfi::{util::path::get_config_path, Result};
 
 use crate::{
-    settings::{Args, ChannelInfo, ContactInfo},
+    settings::{
+        parse_configured_channels, parse_configured_contacts, Args, ChannelInfo, ContactInfo,
+        CONFIG_FILE,
+    },
     PrivMsgEvent,
 };
 
@@ -43,10 +46,11 @@ pub struct IrcConfig {
     // user config
     pub nickname: String,
     pub password: String,
-    pub private_key: Option<String>,
+    // pub private_key: Option<String>,
     pub capabilities: HashMap<String, bool>,
 
     // channels and contacts
+    pub auto_channels: Vec<String>,
     pub channels: HashMap<String, ChannelInfo>,
     pub contacts: HashMap<String, ContactInfo>,
 }
@@ -54,17 +58,15 @@ pub struct IrcConfig {
 impl IrcConfig {
     pub fn new(settings: &Args) -> Result<Self> {
         let password = settings.password.as_ref().unwrap_or(&String::new()).clone();
-        let private_key = settings.private_key.clone();
+        // let private_key = settings.private_key.clone();
 
-        let mut channels = settings.channels.clone();
+        let auto_channels = settings.autojoin.clone();
 
-        for chan in settings.autojoin.iter() {
-            if !channels.contains_key(chan) {
-                channels.insert(chan.clone(), ChannelInfo::new());
-            }
-        }
-
-        let contacts = settings.contacts.clone();
+        // Pick up channel settings from the TOML configuration
+        let cfg_path = get_config_path(settings.config.clone(), CONFIG_FILE)?;
+        let toml_contents = std::fs::read_to_string(cfg_path)?;
+        let channels = parse_configured_channels(&toml_contents)?;
+        let contacts = parse_configured_contacts(&toml_contents)?;
 
         let mut capabilities = HashMap::new();
         capabilities.insert("no-history".to_string(), false);
@@ -77,9 +79,9 @@ impl IrcConfig {
             is_pass_init: false,
             nickname: "anon".to_string(),
             password,
+            auto_channels,
             channels,
             contacts,
-            private_key,
             capabilities,
         })
     }

+ 1 - 0
bin/darkirc/src/irc/server.rs

@@ -96,6 +96,7 @@ impl IrcServer {
             ))
             .detach();
 
+        // Listen to msgs from View
         executor
             .clone()
             .spawn(Self::listen_to_view(

+ 204 - 60
bin/darkirc/src/settings.rs

@@ -17,18 +17,19 @@
  */
 
 use crypto_box::SalsaBox;
-use log::error;
-use serde::{self, Deserialize, Serialize};
+use log::{info, warn};
+use serde::{self, Deserialize};
 use std::collections::HashMap;
 use structopt::StructOpt;
 use structopt_toml::StructOptToml;
+use toml::Value;
 use url::Url;
 
 use darkfi::{net::settings::SettingsOpt, Result};
 
 // Location for config file
-pub const CONFIG_FILE: &str = "ircd_config.toml";
-pub const CONFIG_FILE_CONTENTS: &str = include_str!("../ircd_config.toml");
+pub const CONFIG_FILE: &str = "darkirc_config.toml";
+pub const CONFIG_FILE_CONTENTS: &str = include_str!("../darkirc_config.toml");
 
 // Msg config
 pub const MAXIMUM_LENGTH_OF_MESSAGE: usize = 1024;
@@ -45,7 +46,7 @@ pub enum RPL {
 /// ircd cli
 #[derive(Clone, Deserialize, StructOpt, StructOptToml)]
 #[serde(default)]
-#[structopt(name = "ircd")]
+#[structopt(name = "darkirc")]
 pub struct Args {
     /// Sets a custom config file
     #[structopt(long)]
@@ -56,7 +57,7 @@ pub struct Args {
     pub rpc_listen: Url,
 
     /// IRC listen URL
-    #[structopt(long = "irc", default_value = "tcp://127.0.0.1:7776")]
+    #[structopt(long = "irc", default_value = "tcp://127.0.0.1:6667")]
     pub irc_listen: Url,
 
     /// Optional TLS certificate file path if `irc_listen` uses TLS
@@ -81,18 +82,10 @@ pub struct Args {
     #[structopt(long)]
     pub password: Option<String>,
 
-    /// Channels
-    #[structopt(skip)]
-    pub channels: HashMap<String, ChannelInfo>,
-
-    /// Channels
-    #[structopt(skip)]
-    pub contacts: HashMap<String, ContactInfo>,
-
-    /// Private key
-    #[structopt(skip)]
-    pub private_key: Option<String>,
-
+    // /// Private key
+    // #[structopt(skip)]
+    // pub private_key: Option<String>,
+    /// Network settings
     #[structopt(flatten)]
     pub net: SettingsOpt,
 
@@ -108,30 +101,15 @@ pub struct Args {
 /// [contact."nick"]
 /// pubkey = "7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"
 /// ```
-#[derive(Default, Clone, Debug, Deserialize, Serialize)]
+#[derive(Clone)]
 pub struct ContactInfo {
-    pub pubkey: Option<String>,
+    /// Optional NaCl box for the channel, used for {en,de}cryption.
+    pub salt_box: Option<SalsaBox>,
 }
 
 impl ContactInfo {
-    pub fn new() -> Self {
-        Self { pubkey: None }
-    }
-
-    pub fn salt_box(&self, private_key: &str, contact_name: &str) -> Option<SalsaBox> {
-        if let Ok(private) = parse_priv(private_key) {
-            if let Some(p) = &self.pubkey {
-                if let Ok(public) = parse_pub(p) {
-                    return Some(SalsaBox::new(&public, &private))
-                } else {
-                    error!("Uncorrect public key in for contact {}", contact_name);
-                }
-            }
-        } else {
-            error!("Uncorrect Private key in config",);
-        }
-
-        None
+    pub fn new() -> Result<Self> {
+        Ok(Self { salt_box: None })
     }
 }
 
@@ -148,48 +126,214 @@ impl ContactInfo {
 /// Having a topic set is useful if one wants to have a topic in the
 /// configured channel. It is not shared with others, but it is useful
 /// for personal reference.
-#[derive(Default, Clone, Debug, Serialize, Deserialize)]
+#[derive(Default, Clone)]
 pub struct ChannelInfo {
     /// Optional topic for the channel
     pub topic: Option<String>,
     /// Optional NaCl box for the channel, used for {en,de}cryption.
-    pub secret: Option<String>,
+    pub salt_box: Option<SalsaBox>,
     /// Flag indicates whether the user has joined the channel or not
-    #[serde(default, skip_serializing)]
     pub joined: bool,
     /// All nicknames which are visible on the channel
-    #[serde(default, skip_serializing)]
     pub names: Vec<String>,
 }
 
 impl ChannelInfo {
-    pub fn new() -> Self {
-        Self { topic: None, secret: None, joined: false, names: vec![] }
+    pub fn new() -> Result<Self> {
+        Ok(Self { topic: None, salt_box: None, joined: false, names: vec![] })
     }
+}
 
-    pub fn salt_box(&self, channel_name: &str) -> Option<SalsaBox> {
-        if let Some(s) = &self.secret {
-            let secret = parse_priv(s);
+/// Parse a TOML string for any configured channels and return
+/// a map containing said configurations.
+///
+/// ```toml
+/// [channel."#memes"]
+/// secret = "7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"
+/// topic = "Dank Memes"
+/// ```
+pub fn parse_configured_channels(data: &str) -> Result<HashMap<String, ChannelInfo>> {
+    let mut ret = HashMap::new();
 
-            if secret.is_err() {
-                error!("Uncorrect secret key for the channel {}", channel_name);
-                return None
+    let map = match toml::from_str(data)? {
+        Value::Table(m) => m,
+        _ => return Ok(ret),
+    };
+
+    if !map.contains_key("channel") {
+        return Ok(ret)
+    }
+
+    if !map["channel"].is_table() {
+        return Ok(ret)
+    }
+
+    for chan in map["channel"].as_table().unwrap() {
+        info!("Found configuration for channel {}", chan.0);
+        let mut channel_info = ChannelInfo::new()?;
+
+        if chan.1.as_table().unwrap().contains_key("topic") {
+            let topic = chan.1["topic"].as_str().unwrap().to_string();
+            info!("Found topic for channel {}: {}", chan.0, topic);
+            channel_info.topic = Some(topic);
+        }
+
+        if chan.1.as_table().unwrap().contains_key("secret") {
+            // Build the NaCl box
+            if let Some(s) = chan.1["secret"].as_str() {
+                let salt_box = salt_box_from_shared_secret(s)?;
+                channel_info.salt_box = Some(salt_box);
+                info!("Instantiated NaCl box for channel {}", chan.0);
             }
+        }
+
+        ret.insert(chan.0.to_string(), channel_info);
+    }
+
+    Ok(ret)
+}
 
-            let secret = secret.unwrap();
-            let public = secret.public_key();
-            return Some(SalsaBox::new(&public, &secret))
+/// Parse a TOML string for any configured contact list and return
+/// a map containing said configurations.
+///
+/// ```toml
+/// [contact."nick"]
+/// contact_pubkey = "7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"
+/// ```
+pub fn parse_configured_contacts(data: &str) -> Result<HashMap<String, ContactInfo>> {
+    let mut ret = HashMap::new();
+
+    let map = match toml::from_str(data) {
+        Ok(Value::Table(m)) => m,
+        _ => {
+            warn!("Invalid TOML string passed as argument to parse_configured_contacts()");
+            return Ok(ret)
         }
-        None
+    };
+
+    if !map.contains_key("contact") {
+        return Ok(ret)
+    }
+
+    if !map["contact"].is_table() {
+        warn!("TOML configuration contains a \"contact\" field, but it is not a table.");
+        return Ok(ret)
     }
+
+    let contacts = map["contact"].as_table().unwrap();
+
+    // Our secret key for NaCl boxes.
+    let found_priv = match parse_priv_key(data) {
+        Ok(v) => v,
+        Err(_) => {
+            info!("Did not find private key in config, skipping contact configuration.");
+            return Ok(ret)
+        }
+    };
+
+    let bytes: [u8; 32] = match bs58::decode(found_priv).into_vec() {
+        Ok(v) => {
+            if v.len() != 32 {
+                warn!("Decoded base58 secret key string is not 32 bytes");
+                warn!("Skipping private contact configuration");
+                return Ok(ret)
+            }
+            v.try_into().unwrap()
+        }
+        Err(e) => {
+            warn!("Failed to decode base58 secret key from string: {}", e);
+            warn!("Skipping private contact configuration");
+            return Ok(ret)
+        }
+    };
+
+    let secret = crypto_box::SecretKey::from(bytes);
+
+    for cnt in contacts {
+        info!("Found configuration for contact {}", cnt.0);
+        let mut contact_info = ContactInfo::new()?;
+
+        if !cnt.1.is_table() {
+            warn!("Config for contact {} isn't a TOML table", cnt.0);
+            continue
+        }
+
+        let table = cnt.1.as_table().unwrap();
+        if table.is_empty() {
+            warn!("Configuration for contact {} is empty.", cnt.0);
+            continue
+        }
+
+        // Build the NaCl box
+        if !table.contains_key("contact_pubkey") || !table["contact_pubkey"].is_str() {
+            warn!("Contact {} doesn't have `contact_pubkey` set or is not a string.", cnt.0);
+            continue
+        }
+
+        let pub_str = table["contact_pubkey"].as_str().unwrap();
+        let bytes: [u8; 32] = match bs58::decode(pub_str).into_vec() {
+            Ok(v) => {
+                if v.len() != 32 {
+                    warn!("Decoded base58 string is not 32 bytes");
+                    continue
+                }
+
+                v.try_into().unwrap()
+            }
+            Err(e) => {
+                warn!("Failed to decode base58 pubkey from string: {}", e);
+                continue
+            }
+        };
+
+        let public = crypto_box::PublicKey::from(bytes);
+        contact_info.salt_box = Some(SalsaBox::new(&public, &secret));
+        ret.insert(cnt.0.to_string(), contact_info);
+        info!("Instantiated NaCl box for contact {}", cnt.0);
+    }
+
+    Ok(ret)
 }
 
-fn parse_priv(key: &str) -> Result<crypto_box::SecretKey> {
-    let bytes: [u8; 32] = bs58::decode(key).into_vec()?.try_into().unwrap();
-    Ok(crypto_box::SecretKey::from(bytes))
+fn salt_box_from_shared_secret(s: &str) -> Result<SalsaBox> {
+    let bytes: [u8; 32] = bs58::decode(s).into_vec()?.try_into().unwrap();
+    let secret = crypto_box::SecretKey::from(bytes);
+    let public = secret.public_key();
+    Ok(SalsaBox::new(&public, &secret))
 }
 
-fn parse_pub(key: &str) -> Result<crypto_box::PublicKey> {
-    let bytes: [u8; 32] = bs58::decode(key).into_vec()?.try_into().unwrap();
-    Ok(crypto_box::PublicKey::from(bytes))
+fn parse_priv_key(data: &str) -> Result<String> {
+    let mut pk = String::new();
+
+    let map = match toml::from_str(data)? {
+        Value::Table(m) => m,
+        _ => return Ok(pk),
+    };
+
+    if !map.contains_key("private_key") {
+        return Ok(pk)
+    }
+
+    if !map["private_key"].is_table() {
+        return Ok(pk)
+    }
+
+    let private_keys = map["private_key"].as_table().unwrap();
+
+    for prv_key in private_keys {
+        pk = prv_key.0.into();
+    }
+
+    info!("Found secret key in config, noted it down.");
+    Ok(pk)
 }
+
+// fn parse_priv(key: &str) -> Result<crypto_box::SecretKey> {
+//     let bytes: [u8; 32] = bs58::decode(key).into_vec()?.try_into().unwrap();
+//     Ok(crypto_box::SecretKey::from(bytes))
+// }
+
+// fn parse_pub(key: &str) -> Result<crypto_box::PublicKey> {
+//     let bytes: [u8; 32] = bs58::decode(key).into_vec()?.try_into().unwrap();
+//     Ok(crypto_box::PublicKey::from(bytes))
+// }