diff --git a/conf/Cargo.toml b/conf/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..681eccd40c2987be1246b3c99f09894116cd37c5 --- /dev/null +++ b/conf/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "duniter-conf" +version = "0.1.0" +authors = ["librelois <elois@ifee.fr>"] +description = "Configuration module for the Duniter project." +license = "AGPL-3.0" + +[lib] +path = "lib.rs" + +[dependencies] +rand = "0.4.2" +serde = "1.0.24" +serde_derive = "1.0.24" +serde_json = "1.0.9" +duniter-crypto = { path = "../crypto" } +duniter-module = { path = "../module" } \ No newline at end of file diff --git a/conf/lib.rs b/conf/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..482946a32899753e710460d055b7a676b09adf9a --- /dev/null +++ b/conf/lib.rs @@ -0,0 +1,381 @@ +// Copyright (C) 2018 The Duniter Project Developers. +// +// 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/>. + +//! Defined the few global types used by all modules, +//! as well as the DuniterModule trait that all modules must implement. + +#![cfg_attr(feature = "strict", deny(warnings))] +#![deny( + missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, + trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, + unused_qualifications +)] + +#[macro_use] +extern crate serde_json; + +extern crate duniter_crypto; +extern crate duniter_module; +extern crate rand; +extern crate serde; +use duniter_crypto::keys::{ed25519, KeyPair, PrivateKey, PublicKey}; +use duniter_module::{Currency, DuniterConf, DuniterConfV1, RequiredKeys, RequiredKeysContent}; +use rand::Rng; +use serde::ser::{Serialize, SerializeStruct, Serializer}; +use std::env; +use std::fs; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; + +static USER_DATAS_FOLDER: &'static str = "durs-dev"; + +/// If no currency is specified by the user, is the currency will be chosen by default +pub static DEFAULT_CURRRENCY: &'static str = "g1"; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// Keypairs filled in by the user (via a file or by direct entry in the terminal). +pub struct DuniterKeyPairs { + /// Keypair used by the node to sign its communications with other nodes. This keypair is mandatory, if it's not filled in, a random keypair is generated. + pub network_keypair: ed25519::KeyPair, + /// Keypair used to sign the blocks forged by this node. If this keypair is'nt filled in, the node will not calculate blocks. + pub member_keypair: Option<ed25519::KeyPair>, +} + +impl Serialize for DuniterKeyPairs { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let member_sec = if let Some(member_keypair) = self.member_keypair { + member_keypair.private_key().to_string() + } else { + String::from("") + }; + let member_pub = if let Some(member_keypair) = self.member_keypair { + member_keypair.pubkey.to_string() + } else { + String::from("") + }; + let mut state = serializer.serialize_struct("DuniterKeyPairs", 4)?; + state.serialize_field( + "network_sec", + &self.network_keypair.private_key().to_string().as_str(), + )?; + state.serialize_field( + "network_pub", + &self.network_keypair.pubkey.to_string().as_str(), + )?; + state.serialize_field("member_sec", member_sec.as_str())?; + state.serialize_field("member_pub", member_pub.as_str())?; + state.end() + } +} + +impl DuniterKeyPairs { + /// Returns only the keys indicated as required + pub fn get_required_keys_content( + required_keys: RequiredKeys, + keypairs: DuniterKeyPairs, + ) -> RequiredKeysContent<ed25519::PublicKey, ed25519::KeyPair> { + match required_keys { + RequiredKeys::MemberKeyPair() => { + RequiredKeysContent::MemberKeyPair(keypairs.member_keypair) + } + RequiredKeys::MemberPublicKey() => { + RequiredKeysContent::MemberPublicKey(if let Some(keys) = keypairs.member_keypair { + Some(keys.pubkey) + } else { + None + }) + } + RequiredKeys::NetworkKeyPair() => { + RequiredKeysContent::NetworkKeyPair(keypairs.network_keypair) + } + RequiredKeys::NetworkPublicKey() => { + RequiredKeysContent::NetworkPublicKey(keypairs.network_keypair.pubkey) + } + RequiredKeys::None() => RequiredKeysContent::None(), + } + } +} + +fn _use_json_macro() -> serde_json::Value { + json!({}) +} + +fn generate_random_keypair() -> ed25519::KeyPair { + let mut rng = rand::thread_rng(); + let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters(); + generator.generate(&[rng.gen::<u8>(); 8], &[rng.gen::<u8>(); 8]) +} + +fn generate_random_node_id() -> u32 { + let mut rng = rand::thread_rng(); + rng.gen::<u32>() +} + +/// Return the user datas folder name +pub fn get_user_datas_folder() -> &'static str { + USER_DATAS_FOLDER +} + +/// Returns the path to the folder containing the user data of the running profile +pub fn datas_path(profile: &str, currency: &Currency) -> PathBuf { + let mut datas_path = match env::home_dir() { + Some(path) => path, + None => panic!("Impossible to get your home dir!"), + }; + datas_path.push(".config/"); + datas_path.push(USER_DATAS_FOLDER); + datas_path.push(profile); + datas_path.push(currency.to_string()); + datas_path +} + +/// Load configuration. +pub fn load_conf(profile: &str) -> (DuniterConf, DuniterKeyPairs) { + // Define and create datas directory if not exist + let mut profile_path = match env::home_dir() { + Some(path) => path, + None => panic!("Impossible to get your home dir !"), + }; + profile_path.push(".config/"); + if !profile_path.as_path().exists() { + fs::create_dir(profile_path.as_path()).expect("Impossible to create ~/.config dir !"); + } + profile_path.push(USER_DATAS_FOLDER); + if !profile_path.as_path().exists() { + fs::create_dir(profile_path.as_path()).expect(&format!( + "Impossible to create ~/.config/{} dir !", + USER_DATAS_FOLDER + )); + } + profile_path.push(profile); + if !profile_path.as_path().exists() { + fs::create_dir(profile_path.as_path()).expect("Impossible to create your profile dir !"); + } + + // Load conf + let (conf, keypairs) = load_conf_at_path(profile, &profile_path); + + // Create currency dir + profile_path.push(conf.currency().to_string()); + if !profile_path.as_path().exists() { + fs::create_dir(profile_path.as_path()).expect("Impossible to create currency dir !"); + } + + // Return conf and keypairs + (conf, keypairs) +} + +/// Load configuration. at specified path +pub fn load_conf_at_path(profile: &str, profile_path: &PathBuf) -> (DuniterConf, DuniterKeyPairs) { + // Default conf + let mut conf = DuniterConfV1 { + profile: String::from(profile), + currency: Currency::Str(DEFAULT_CURRRENCY.to_string()), + my_node_id: generate_random_node_id(), + modules: serde_json::Value::Null, + }; + + // Get KeyPairs + let mut keypairs_path = profile_path.clone(); + keypairs_path.push("keypairs.json"); + let keypairs = if keypairs_path.as_path().exists() { + if let Ok(mut f) = File::open(keypairs_path.as_path()) { + let mut contents = String::new(); + if f.read_to_string(&mut contents).is_ok() { + let json_conf: serde_json::Value = + serde_json::from_str(&contents).expect("Conf: Fail to parse keypairs file !"); + if let Some(network_sec) = json_conf.get("network_sec") { + if let Some(network_pub) = json_conf.get("network_pub") { + let network_sec = network_sec + .as_str() + .expect("Conf: Fail to parse keypairs file !"); + let network_pub = network_pub + .as_str() + .expect("Conf: Fail to parse keypairs file !"); + DuniterKeyPairs { + network_keypair: ed25519::KeyPair { + privkey: PrivateKey::from_base58(network_sec) + .expect("conf : keypairs file : fail to parse network_sec !"), + pubkey: PublicKey::from_base58(network_pub) + .expect("conf : keypairs file : fail to parse network_pub !"), + }, + member_keypair: None, + } + } else { + panic!("Fatal error : keypairs file wrong format : no field salt !") + } + } else { + panic!("Fatal error : keypairs file wrong format : no field password !") + } + } else { + panic!("Fail to read keypairs file !"); + } + } else { + panic!("Fail to open keypairs file !"); + } + } else { + // Create keypairs file with random keypair + let keypairs = DuniterKeyPairs { + network_keypair: generate_random_keypair(), + member_keypair: None, + }; + write_keypairs_file(&keypairs_path, &keypairs) + .expect("Fatal error : fail to write default keypairs file !"); + keypairs + }; + + // Open conf file + let mut conf_path = profile_path.clone(); + conf_path.push("conf.json"); + if conf_path.as_path().exists() { + if let Ok(mut f) = File::open(conf_path.as_path()) { + let mut contents = String::new(); + if f.read_to_string(&mut contents).is_ok() { + let json_conf: serde_json::Value = + serde_json::from_str(&contents).expect("Conf: Fail to parse conf file !"); + if let Some(currency) = json_conf.get("currency") { + conf.currency = Currency::Str( + currency + .as_str() + .expect("Conf: fail to parse currency field !") + .to_string(), + ); + }; + if let Some(node_id) = json_conf.get("node_id") { + conf.my_node_id = + u32::from_str_radix( + node_id + .as_str() + .expect("Conf : fail to parse node_id field !"), + 16, + ).expect("Fail to load conf: node_id must be an hexadecimal number !"); + }; + if let Some(modules_conf) = json_conf.get("modules") { + conf.modules = modules_conf.clone(); + }; + } + } else { + panic!("Fail to open conf file !"); + } + } else { + // Create conf file with default conf + write_conf_file(&conf_path, &DuniterConf::V1(conf.clone())) + .expect("Fatal error : fail to write default conf file !"); + } + + // Return conf and keypairs + (DuniterConf::V1(conf), keypairs) +} + +/// Save keypairs in profile folder +pub fn write_keypairs_file( + file_path: &PathBuf, + keypairs: &DuniterKeyPairs, +) -> Result<(), std::io::Error> { + let mut f = try!(File::create(file_path.as_path())); + try!( + f.write_all( + serde_json::to_string_pretty(keypairs) + .expect("Fatal error : fail to write default keypairs file !") + .as_bytes() + ) + ); + try!(f.sync_all()); + Ok(()) +} + +/// Save configuration in profile folder +pub fn write_conf_file(file_path: &PathBuf, conf: &DuniterConf) -> Result<(), std::io::Error> { + match conf { + &DuniterConf::V1(ref conf_v1) => { + let mut f = try!(File::create(file_path.as_path())); + try!( + f.write_all( + serde_json::to_string_pretty(conf_v1) + .expect("Fatal error : fail to write default conf file !") + .as_bytes() + ) + ); + try!(f.sync_all()); + } + &_ => { + panic!("Fatal error : Conf version is not supported !"); + } + } + Ok(()) +} + +/// Returns the path to the database containing the blockchain +pub fn get_db_path(profile: &str, currency: &Currency, sync: bool) -> PathBuf { + if sync { + let mut db_path = PathBuf::new(); + let mut db_name = String::from(profile); + db_name.push_str("_durs.db"); + db_path.push("/dev/shm"); + db_path.push(db_name); + db_path + } else { + let mut db_path = datas_path(profile, ¤cy); + db_path.push("blockchain.db"); + db_path + } +} + +/// Returns the path to the binary file containing the state of the web of trust +pub fn get_wot_path(profile: String, currency: &Currency) -> PathBuf { + let mut wot_path = match env::home_dir() { + Some(path) => path, + None => panic!("Impossible to get your home dir!"), + }; + wot_path.push(".config/"); + wot_path.push(USER_DATAS_FOLDER); + wot_path.push(profile); + wot_path.push(currency.to_string()); + wot_path.push("wot.bin"); + wot_path +} + +#[cfg(test)] +mod tests { + use super::*; + use duniter_module::ModuleId; + + #[test] + fn load_conf_file() { + let (conf, _keys) = load_conf_at_path("test", &PathBuf::from("./test/")); + assert_eq!( + conf.modules() + .get("ws2p") + .expect("Not found ws2p conf") + .clone(), + json!({ + "sync_peers": [{ + "pubkey": "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx", + "ws2p_endpoints": ["WS2P c1c39a0a i3.ifee.fr 80 /ws2p"] + },{ + "pubkey": "BoZP6aqtErHjiKLosLrQxBafi4ATciyDZQ6XRQkNefqG", + "ws2p_endpoints": ["WS2P 15af24db g1.ifee.fr 80 /ws2p"] + },{ + "pubkey": "7v2J4badvfWQ6qwRdCwhhJfAsmKwoxRUNpJHiJHj7zef", + "ws2p_endpoints": ["WS2P b48824f0 g1.monnaielibreoccitanie.org 80 /ws2p"] + }] + }) + ); + } +}