diff --git a/examples/bffh.dhall b/examples/bffh.dhall index 4a0f025..2c90eed 100644 --- a/examples/bffh.dhall +++ b/examples/bffh.dhall @@ -9,9 +9,13 @@ , init_connections = [] : List { _1 : Text, _2 : Text } , initiators = ./initiators.dhall , listens = - [ { address = "127.0.0.1", port = Some 59661 } - , { address = "::1", port = Some 59661 } - , { address = "192.168.0.114", port = Some 59661 } + [ "127.0.0.1" + , "::1" + , "[::1]:1235" + , "localhost:1234" + , "localhost" + , "notahost:541" + , "notahostandnoport" ] , machines = ./machines.dhall , db_path = "/tmp/bffh" diff --git a/src/bin/bffhd.rs b/src/bin/bffhd.rs index a8c37b1..27250dd 100644 --- a/src/bin/bffhd.rs +++ b/src/bin/bffhd.rs @@ -2,12 +2,14 @@ use std::{ io, io::Write, path::PathBuf, - sync::Arc, }; use clap::{App, Arg, crate_version, crate_description, crate_name}; use std::str::FromStr; +use diflouroborane::{config, error::Error}; +use std::net::ToSocketAddrs; +use std::error::Error as _; -fn main_res() -> Result<(), dyn std::error::Error> { +fn main_res() -> Result<(), Error> { // Argument parsing // values for the name, description and version are pulled from `Cargo.toml`. let matches = App::new(crate_name!()) @@ -56,7 +58,7 @@ fn main_res() -> Result<(), dyn std::error::Error> { } else if matches.is_present("check config") { let configpath = matches.value_of("config").unwrap_or("/etc/diflouroborane.dhall"); match config::read(&PathBuf::from_str(configpath).unwrap()) { - Ok(cfg) => { + Ok(_) => { //TODO: print a normalized version of the supplied config println!("config is valid"); std::process::exit(0); @@ -71,8 +73,23 @@ fn main_res() -> Result<(), dyn std::error::Error> { // If no `config` option is given use a preset default. let configpath = matches.value_of("config").unwrap_or("/etc/diflouroborane.dhall"); let config = config::read(&PathBuf::from_str(configpath).unwrap())?; - tracing::debug!("Loaded Config: {:?}", config); + println!("{:#?}", config); + let mut sockaddrs = Vec::new(); + for listen in config.listens { + match listen.to_socket_addrs() { + Ok(addrs) => { + sockaddrs.extend(addrs) + }, + Err(e) => { + tracing::error!("Invalid listen \"{}\" {}", listen, e); + } + } + } + + println!("Final listens: {:?}", sockaddrs); + + /* if matches.is_present("dump") { let db = db::Databases::new(&log, &config)?; let v = db.access.dump_roles().unwrap(); @@ -131,6 +148,9 @@ fn main_res() -> Result<(), dyn std::error::Error> { server::serve_api_connections(log.clone(), config, db, network, ex) } + */ + + Ok(()) } fn main() { diff --git a/src/config.rs b/src/config.rs index 730f297..f37d315 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,10 +2,14 @@ use std::default::Default; use std::path::{Path, PathBuf}; use std::collections::HashMap; -use serde::{Serialize, Deserialize}; +use serde::{Serialize, Deserialize, Deserializer, Serializer}; use crate::error::Result; -use crate::machine::MachineDescription; +use std::fmt::Formatter; +use std::net::{SocketAddr, IpAddr, ToSocketAddrs}; +use std::str::FromStr; +use crate::db::access::PermRule; +use serde::de::Error; pub fn read(path: &Path) -> Result { serde_dhall::from_file(path) @@ -19,10 +23,10 @@ pub struct Config { // TODO: This should really be a variant type; that is something that can figure out itself if // it contains enough information to open a socket (i.e. it checks if it's a valid path (=> // Unix socket) or IPv4/v6 address) - pub listens: Box<[Listen]>, + pub listens: Vec, /// Machine descriptions to load - pub machines: HashMap, + //pub machines: HashMap, /// Actors to load and their configuration options pub actors: HashMap, @@ -48,23 +52,107 @@ pub struct RoleConfig { pub permissions: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Listen { - pub address: String, - pub port: Option, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModuleConfig { pub module: String, pub params: HashMap } +#[derive(Debug, Clone)] +pub struct Listen { + address: String, + port: Option, +} + +impl std::fmt::Display for Listen { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", &self.address, self.port.unwrap_or(DEFAULT_PORT)) + } +} + +impl ToSocketAddrs for Listen { + type Iter = <(String, u16) as ToSocketAddrs>::Iter; + + fn to_socket_addrs(&self) -> std::io::Result { + if let Some(port) = self.port { + (self.address.as_str(), port).to_socket_addrs() + } else { + (self.address.as_str(), DEFAULT_PORT).to_socket_addrs() + } + } +} + +impl<'de> serde::Deserialize<'de> for Listen { + fn deserialize(deserializer: D) -> std::result::Result + where D: Deserializer<'de> + { + deserializer.deserialize_str(ListenVisitor) + } +} +impl serde::Serialize for Listen { + fn serialize(&self, serializer: S) -> std::result::Result + where S: Serializer + { + if let Some(port) = self.port { + serializer.serialize_str(&format!("{}:{}", self.address, port)) + } else { + serializer.serialize_str(&self.address) + } + } +} + +struct ListenVisitor; +impl<'de> serde::de::Visitor<'de> for ListenVisitor { + type Value = Listen; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + write!(formatter, "A string encoding a valid IP or Hostname (e.g. 127.0.0.1 or [::1]) with\ + or without a defined port") + } + + fn visit_str(self, v: &str) -> std::result::Result + where E: Error + { + let sockaddr = SocketAddr::from_str(v); + if let Ok(address) = sockaddr { + return Ok(Listen { + address: address.ip().to_string(), + port: Some(address.port()), + }) + } + + let ipaddr = IpAddr::from_str(v); + if let Ok(address) = ipaddr { + return Ok(Listen { + address: address.to_string(), + port: None, + }) + } + + let mut split = v.split(':'); + let address = split.next() + .expect("str::split should always return at least one element") + .to_string(); + let port = if let Some(port) = split.next() { + let port: u16 = port.parse() + .map_err(|_| { + E::custom(&format!("Expected valid ip address or hostname with or without \ + port. Failed to parse \"{}\".", v)) + })?; + + Some(port) + } else { + None + }; + + Ok(Listen { address, port }) + } +} + impl Default for Config { fn default() -> Self { let mut actors: HashMap:: = HashMap::new(); let mut initiators: HashMap:: = HashMap::new(); - let mut machines = HashMap::new(); actors.insert("Actor".to_string(), ModuleConfig { module: "Shelly".to_string(), @@ -75,25 +163,13 @@ impl Default for Config { params: HashMap::new(), }); - machines.insert("Testmachine".to_string(), MachineDescription { - name: "Testmachine".to_string(), - description: Some("A test machine".to_string()), - privs: PrivilegesBuf { - disclose: PermissionBuf::from_string("lab.test.read".to_string()), - read: PermissionBuf::from_string("lab.test.read".to_string()), - write: PermissionBuf::from_string("lab.test.write".to_string()), - manage: PermissionBuf::from_string("lab.test.admin".to_string()), - }, - }); - Config { - listens: Box::new([ + listens: vec![ Listen { - address: "localhost".to_string(), - port: Some(DEFAULT_PORT), + address: "127.0.0.1".to_string(), + port: None, } - ]), - machines, + ], actors, initiators, mqtt_url: "tcp://localhost:1883".to_string(), diff --git a/src/db/access.rs b/src/db/access.rs index 8106f68..a074c30 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -7,20 +7,13 @@ use std::sync::Arc; use std::fmt; use std::collections::HashMap; use std::cmp::Ordering; -use std::path::Path; -use std::fs; -use std::iter::FromIterator; use std::convert::{TryFrom, Into}; use serde::{Serialize, Deserialize}; use crate::error::Result; -pub mod internal; - use crate::config::Config; -use crate::db::user::UserData; -pub use internal::Internal; pub struct AccessControl { internal: HashMap, @@ -49,25 +42,6 @@ impl AccessControl { } } - pub fn check>(&self, user: &UserData, perm: P) -> Result { - let mut roles = HashMap::new(); - // Check all user roles by.. - Ok(user.roles.iter().any(|role| { - // 1. Getting the whole tree down to a list of Roles applied - self.internal.tally_role(&mut roles, role)?; - - // 2. Checking if any of the roles the user has give any permission granting the - // requested one. - roles.drain().any(|(rid, role)| { - role.permissions.iter().any(|rule| rule.match_perm(perm)) - }) - })) - } - - pub fn collect_permrules(&self, user: &UserData) -> Result> { - self.internal.collect_permrules(user) - } - pub fn dump_roles(&self) -> Result> { Ok(self.internal.iter().map(|(k,v)| (k.clone(), v.clone())).collect()) } @@ -106,21 +80,6 @@ pub trait RoleDB { Ok(()) } - fn collect_permrules(&self, user: &UserData) -> Result> { - let mut roleset = HashMap::new(); - for role_id in user.roles.iter() { - self.tally_role(&mut roleset, role_id)?; - } - - let mut output = Vec::new(); - - // Iter all unique role->permissions we've found and early return on match. - for (_roleid, role) in roleset.iter() { - output.extend(role.permissions.iter().cloned()) - } - - return Ok(output); - } } impl RoleDB for HashMap { @@ -162,15 +121,6 @@ pub struct Role { } impl Role { - fn load_file>(path: P) -> Result> { - let content = fs::read(path)?; - let file_roles: HashMap = toml::from_slice(&content[..])?; - - Ok(HashMap::from_iter(file_roles.into_iter().map(|(key, value)| { - (RoleIdentifier::local_from_str("lmdb".to_string(), key), value) - }))) - } - pub fn new(parents: Vec, permissions: Vec) -> Self { Self { parents, permissions } } diff --git a/src/db/access/internal.rs b/src/db/access/internal.rs index d3748e3..fa6d2ce 100644 --- a/src/db/access/internal.rs +++ b/src/db/access/internal.rs @@ -3,8 +3,6 @@ use std::collections::HashMap; use std::path::Path; use std::sync::Arc; -use flexbuffers; - use slog::Logger; use lmdb::{Environment, Transaction, RwTransaction, Cursor}; @@ -12,7 +10,6 @@ use crate::config::Config; use crate::error::Result; use crate::db::access::{Permission, Role, RoleIdentifier, RoleDB}; -use crate::db::user::UserData; #[derive(Clone, Debug)] pub struct Internal { @@ -31,28 +28,28 @@ impl Internal { pub fn _check>(&self, txn: &T, user: &UserData, perm: &P) -> Result { - debug!(self.log, "Checking user {:?} for permission {:?}", user, perm.as_ref()); + tracing::debug!("Checking user {:?} for permission {:?}", user, perm.as_ref()); // Tally all roles. Makes dependent roles easier let mut roles = HashMap::new(); for role_id in user.roles.iter() { - debug!(self.log, "Tallying role {} for its parents", role_id); + tracing::debug!("Tallying role {} for its parents", role_id); self._tally_role(txn, &mut roles, role_id)?; } // Iter all unique role->permissions we've found and early return on match. // TODO: Change this for negative permissions? for (roleid, role) in roles.iter() { - debug!(self.log, " checking role {}", roleid); + tracing::debug!(" checking role {}", roleid); for perm_rule in role.permissions.iter() { if perm_rule.match_perm(perm) { - debug!(self.log, " matches permission rule {}", perm_rule); + tracing::debug!(" matches permission rule {}", perm_rule); return Ok(true); } - trace!(self.log, " rejecting permission rule {}", perm_rule); + tracing::trace!(" rejecting permission rule {}", perm_rule); } } - debug!(self.log, "Checked all roles, rejecting access"); + tracing::debug!("Checked all roles, rejecting access"); return Ok(false); } @@ -69,14 +66,14 @@ impl Internal { roles.insert(role_id.clone(), role); } } else { - warn!(self.log, "Did not find role {} while trying to tally", role_id); + tracing::warn!("Did not find role {} while trying to tally", role_id); } Ok(()) } pub fn _get_role<'txn, T: Transaction>(&self, txn: &'txn T, role_id: &RoleIdentifier) -> Result> { - debug!(self.log, "Reading role '{}'", role_id.name); + tracing::debug!("Reading role '{}'", role_id.name); match txn.get(self.roledb, &role_id.name.as_bytes()) { Ok(bytes) => { Ok(Some(flexbuffers::from_slice(bytes)?)) @@ -109,7 +106,8 @@ impl Internal { let role_id = RoleIdentifier::local_from_str("lmdb".to_string(), role_name_str.to_string()); match flexbuffers::from_slice(v) { Ok(role) => vec.push((role_id, role)), - Err(e) => error!(self.log, "Bad format for roleid {}: {}", role_id, e), + Err(e) => tracing::error!("Bad format for roleid {}: {}", role_id, + e), } }, Err(e) => return Err(e.into()), @@ -134,7 +132,7 @@ impl Internal { self.put_role(txn, k, v.clone())?; } - debug!(self.log, "Loaded roles: {:?}", roles); + tracing::debug!("Loaded roles: {:?}", roles); Ok(()) } @@ -154,20 +152,4 @@ impl RoleDB for Internal { let txn = self.env.begin_ro_txn()?; self._tally_role(&txn, roles, role_id) } -} - - - -/// Initialize the access db by loading all the lmdb databases -pub fn init(log: Logger, _config: &Config, env: Arc) - -> std::result::Result -{ - let mut flags = lmdb::DatabaseFlags::empty(); - flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); - let roledb = env.create_db(Some("role"), flags)?; - debug!(&log, "Opened access database '{}' successfully.", "role"); - //let permdb = env.create_db(Some("perm"), flags)?; - //debug!(&log, "Opened access database '{}' successfully.", "perm"); - - Ok(Internal::new(log, env, roledb)) -} +} \ No newline at end of file diff --git a/src/db/mod.rs b/src/db/mod.rs index 146b276..894c6a3 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -48,6 +48,9 @@ mod resources; pub use resources::{ ResourceDB, }; + +pub mod access; + use lmdb::Error; use rkyv::ser::serializers::AlignedSerializer; diff --git a/src/lib.rs b/src/lib.rs index f452f90..f0323fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,8 @@ mod db; mod network; pub mod oid; mod varint; -mod error; +pub mod error; +pub mod config; /* diff --git a/src/oid.rs b/src/oid.rs index c5a30ab..720fdc2 100644 --- a/src/oid.rs +++ b/src/oid.rs @@ -297,7 +297,7 @@ fn parse_string_child_node( node_str: &str, out: &mut Vec ) -> Result<(), ObjectIdentifierError> { - let mut node: Node = node_str.parse() + let node: Node = node_str.parse() .map_err(|_| ObjectIdentifierError::IllegalChildNodeValue)?; // TODO bench against !*node &= 0x80, compiler may already optimize better if node <= 127 {