diff --git a/Cargo.toml b/Cargo.toml index e22bd7b7..6ddab26a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,12 @@ bcder = "^0.1" bytes = "^0.4" chrono = { version = "^0.4", features = ["serde"] } clap = "^2.32" -futures = "0.1" failure = "^0.1" +fern = "^0.5" +futures = "0.1" hex = "^0.3" lazy_static = "^1.1" +log = "^0.4" openssl = "^0.10" pretty = "0.5.2" rand = "^0.5" @@ -23,6 +25,7 @@ rpki = { version = "^0.2", features = ["softkeys"] } serde = { version = "^1.0", features = ["rc"] } serde_derive = "^1.0" serde_json = "^1.0" +syslog = "^4.0" toml = "^0.4" tokio = "^0.1" xml-rs = "0.8.0" diff --git a/defaults/server.toml b/defaults/pubserver.conf similarity index 62% rename from defaults/server.toml rename to defaults/pubserver.conf index ff058fd4..2b94fa9d 100644 --- a/defaults/server.toml +++ b/defaults/pubserver.conf @@ -26,4 +26,31 @@ rsync_base = "rsync://127.0.0.1/repo/" rrdp_base_uri = "http://127.0.0.1:3000/rrdp/" # Specify the base service URI where publishers can connect. -service_uri = "http://127.0.0.1:3000/rfc8181/" \ No newline at end of file +service_uri = "http://127.0.0.1:3000/rfc8181/" + +# Log level +# +# The maximum log level ("off", "error", "warn", "info", or "debug") for +# which to log messages. +# +# Defaults to "warn" +log_level = "debug" + +# Log type +# +# Where to log to. One of "stderr" for stderr, "syslog" for syslog, or "file" +# for a file. If "file" is given, the "log_file" field needs to be given, too. +# +# Defaults to "syslog". +log_type = "stderr" + +# Syslog facility +# +# The syslog facility to log to if syslog logging is used. Defaults to "daemon". +syslog_facility = "daemon" + +# Log file +# +# The path to the file to log to if file logging is used. If the path is +# relative, it is relative to the directory this config file lives in. +log_file = "myfile.log" \ No newline at end of file diff --git a/src/ext_serde.rs b/src/ext_serde.rs index 62af920f..5c88b0b5 100644 --- a/src/ext_serde.rs +++ b/src/ext_serde.rs @@ -7,8 +7,12 @@ use rpki::remote::idcert::IdCert; use rpki::signing::signer::KeyId; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::de; +use log::LevelFilter; +use syslog::Facility; +//------------ Bytes --------------------------------------------------------- + pub fn de_bytes<'de, D>(d: D) -> Result where D: Deserializer<'de> { @@ -23,6 +27,9 @@ where S: Serializer base64::encode(b).serialize(s) } + +//------------ uri::Rsync ---------------------------------------------------- + pub fn de_rsync_uri<'de, D>(d: D) -> Result where D: Deserializer<'de> { @@ -36,6 +43,9 @@ where S: Serializer uri.to_string().serialize(s) } + +//------------ uri::Http ----------------------------------------------------- + pub fn de_http_uri<'de, D>(d: D) -> Result where D: Deserializer<'de> { @@ -49,6 +59,9 @@ where S: Serializer uri.to_string().serialize(s) } + +//------------ IdCert -------------------------------------------------------- + pub fn de_id_cert<'de, D>(d: D) -> Result where D: Deserializer<'de> { @@ -66,6 +79,8 @@ where S: Serializer str.serialize(s) } +//------------ KeyId --------------------------------------------------------- + pub fn de_key_id<'de, D>(d: D) -> Result where D: Deserializer<'de> { @@ -77,4 +92,28 @@ pub fn ser_key_id(key_id: &KeyId, s: S) -> Result where S: Serializer { key_id.as_str().serialize(s) +} + + +//------------ LevelFilter --------------------------------------------------- + +pub fn de_level_filter<'de, D>(d: D) -> Result +where D: Deserializer<'de> +{ + use std::str::FromStr; + let string = String::deserialize(d)?; + LevelFilter::from_str(&string).map_err(de::Error::custom) +} + + +//------------ Facility ------------------------------------------------------ + +pub fn de_facility<'de, D>(d: D) -> Result + where D: Deserializer<'de> +{ + use std::str::FromStr; + let string = String::deserialize(d)?; + Facility::from_str(&string).map_err( + |_| { de::Error::custom( + format!("Unsupported syslog_facility: \"{}\"", string))}) } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 5f91ed76..5f51aa28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,15 +10,17 @@ extern crate core; extern crate futures; extern crate hex; extern crate openssl; +#[macro_use] extern crate log; extern crate rand; +extern crate reqwest; extern crate rpki; #[macro_use] extern crate serde_derive; extern crate serde; extern crate serde_json; +extern crate syslog; extern crate tokio; extern crate toml; extern crate xml; -extern crate reqwest; pub mod file; pub mod provisioning; diff --git a/src/pubc/config.rs b/src/pubc/config.rs index 4d3b526e..8fb1f36c 100644 --- a/src/pubc/config.rs +++ b/src/pubc/config.rs @@ -1,15 +1,11 @@ use std::io; use std::path::PathBuf; -use clap::{App, Arg}; +use clap::{App, Arg, SubCommand}; use toml; -use clap::SubCommand; -/// Global configuration for the RRDP Server. -/// -/// This will parse a default config file ('./defaults/server.toml') unless -/// another file is explicitly specified. Command line arguments may be used -/// to override any of the settings in the config file. +//------------ Config -------------------------------------------------------- + #[derive(Debug, Deserialize)] pub struct Config { name: String, diff --git a/src/pubd/config.rs b/src/pubd/config.rs index a531488e..03f21529 100644 --- a/src/pubd/config.rs +++ b/src/pubd/config.rs @@ -1,19 +1,39 @@ -use std::fs; use std::fs::File; use std::io; use std::io::Read; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; -use std::str::FromStr; use clap::{App, Arg}; use ext_serde; -use toml; +use log::LevelFilter; use rpki::uri; +use syslog::Facility; +use serde::de; +use serde::{Deserialize, Deserializer}; +use toml; + + +const SERVER_NAME: &'static str = "Publication Server"; + + +pub struct ConfigDefaults; + +impl ConfigDefaults { + fn log_level() -> LevelFilter { + LevelFilter::Warn + } + fn log_type() -> LogType { + LogType::Syslog + } + fn syslog_facility() -> Facility { + Facility::LOG_DAEMON + } +} /// Global configuration for the RRDP Server. /// -/// This will parse a default config file ('./defaults/server.toml') unless +/// This will parse a default config file ('./defaults/pubserver.conf') unless /// another file is explicitly specified. Command line arguments may be used /// to override any of the settings in the config file. #[derive(Debug, Deserialize)] @@ -31,6 +51,23 @@ pub struct Config { #[serde(deserialize_with = "ext_serde::de_http_uri")] service_uri: uri::Http, + + #[serde( + default = "ConfigDefaults::log_level", + deserialize_with = "ext_serde::de_level_filter" + )] + log_level: LevelFilter, + + #[serde(default = "ConfigDefaults::log_type")] + log_type: LogType, + + #[serde( + default = "ConfigDefaults::syslog_facility", + deserialize_with = "ext_serde::de_facility" + )] + syslog_facility: Facility, + + log_file: Option } /// # Accessors @@ -39,23 +76,15 @@ impl Config { SocketAddr::new(self.ip, self.port) } - pub fn data_dir(&self) -> &PathBuf { - &self.data_dir - } + pub fn data_dir(&self) -> &PathBuf { &self.data_dir } - pub fn pub_xml_dir(&self) -> &PathBuf { - &self.pub_xml_dir - } + pub fn pub_xml_dir(&self) -> &PathBuf { &self.pub_xml_dir } pub fn rsync_base(&self) -> &uri::Rsync { &self.rsync_base } - pub fn service_uri(&self) -> &uri::Http { - &self.service_uri - } + pub fn service_uri(&self) -> &uri::Http { &self.service_uri } - pub fn rrdp_base_uri(&self) -> &uri::Http { - &self.rrdp_base_uri - } + pub fn rrdp_base_uri(&self) -> &uri::Http { &self.rrdp_base_uri } } /// # Create @@ -78,6 +107,10 @@ impl Config { "http://127.0.0.1:3000/rrdp/").unwrap(); let service_uri = uri::Http::from_str( "http://127.0.0.1:3000/rfc8181/").unwrap(); + let log_level = ConfigDefaults::log_level(); + let log_type = ConfigDefaults::log_type(); + let log_file = None; + let syslog_facility = ConfigDefaults::syslog_facility(); Config { ip, @@ -86,7 +119,11 @@ impl Config { pub_xml_dir, rsync_base, rrdp_base_uri, - service_uri + service_uri, + log_level, + log_type, + log_file, + syslog_facility } } @@ -99,108 +136,18 @@ impl Config { .long("config") .value_name("FILE") .help("Specify non-default config file. If no file is \ - specified './defaults/server.toml' will be used to \ + specified './defaults/pubserver.conf' will be used to \ determine default values for all settings. Note that you \ can use any of the following options to override any of \ these values..") .required(false)) - .arg(Arg::with_name("ip") - .short("i") - .long("ip") - .value_name("IP Address") - .help("Override the IP address.") - .required(false)) - .arg(Arg::with_name("port") - .short("p") - .long("port") - .value_name("Port number") - .help("Override the port number.") - .required(false)) - .arg(Arg::with_name("pub_xml_dir") - .short("x") - .long("pub_xml_dir") - .value_name("DIR") - .help("Override the directory with publisher XML files.") - .required(false)) - .arg(Arg::with_name("rsync_base") - .short("r") - .long("rsync_base") - .value_name("URI") - .help("Override rsync base URI.") - .required(false)) - .arg(Arg::with_name("rrdp_base_uri") - .short("n") - .long("rrdp_base_uri") - .value_name("URI") - .help("Override the RRDP base URI.") - .required(false)) - .arg(Arg::with_name("service_uri") - .short("u") - .long("service_uri") - .value_name("URI") - .help("Override the service URI.") - .required(false)) .get_matches(); let config_file = matches.value_of("config") - .unwrap_or("./defaults/server.toml"); - - let mut c = Self::read_config(config_file.as_ref())?; - - if ! fs::metadata(&c.data_dir)?.is_dir() { - return Err( - ConfigError::Other( - format!( - "Invalid data_dir: {}", - c.data_dir.to_string_lossy().as_ref() - ) - ) - ) - } - - - if let Some(ip_arg) = matches.value_of("ip") { - match IpAddr::from_str(ip_arg) { - Ok(ip) => c.ip = ip, - Err(_) => return Err( - ConfigError::Other( - format!("Invalid IP Address: {}", ip_arg) - )) - } - } - - if let Some(port_arg) = matches.value_of("port") { - match u16::from_str(port_arg) { - Ok(p) => { - if p < 1024 { - return Err( - ConfigError::Other( - "Port number must be between 1024 and \ - 65535".to_string() - ) - ) - } - c.port = p; - } - Err(_) => return Err( - ConfigError::Other( - format!("Invalid port: {}", port_arg))) - } - - } - - if let Some(xml_arg) = matches.value_of("pub_xml_dir") { - c.pub_xml_dir = PathBuf::from(xml_arg) - } - - if let Some(rsync_base) = matches.value_of("rsync_base") { - c.rsync_base = uri::Rsync::from_str(rsync_base)?; - } - - if let Some(rrdp_base_uri) = matches.value_of("rrdp_base_uri") { - c.rrdp_base_uri = uri::Http::from_str(rrdp_base_uri)?; - } + .unwrap_or("./defaults/pubserver.conf"); + let c = Self::read_config(config_file.as_ref())?; + c.init_logging()?; Ok(c) } @@ -212,11 +159,61 @@ impl Config { let c: Config = toml::from_slice(v.as_slice())?; if c.port < 1024 { - Err(ConfigError::Other("Port number must be >1024".to_string())) - } else { - Ok(c) + return Err(ConfigError::from_str("Port number must be >1024")) } + if c.log_type == LogType::File && c.log_file == None { + return Err(ConfigError::from_str( + "Must specify log_file if log_type is 'file'." + )) + } + + Ok(c) + } + + pub fn init_logging(&self) -> Result<(), ConfigError> { + match self.log_type { + + LogType::File => { + let file = fern::log_file(self.log_file.as_ref().unwrap())?; + + let dispatch = fern::Dispatch::new() + .level(self.log_level) + .chain(file); + + dispatch.apply().map_err(|e| { + ConfigError::Other( + format!("Failed to init file logging: {}", e) + ) + })?; + }, + + LogType::Syslog => { + syslog::init( + self.syslog_facility, + self.log_level, + Some(SERVER_NAME) + ).map_err(|e| { + ConfigError::Other( + format!("Failed to init syslog: {}", e) + ) + })?; + }, + + LogType::Stderr => { + let dispatch = fern::Dispatch::new() + .level(self.log_level) + .chain(io::stderr()); + + dispatch.apply().map_err(|e| { + ConfigError::Other( + format!("Failed to init stderr logging: {}", e) + ) + })?; + } + } + + Ok(()) } } @@ -236,6 +233,12 @@ pub enum ConfigError { Other(String) } +impl ConfigError { + pub fn from_str(s: &str) -> ConfigError { + ConfigError::Other(s.to_string()) + } +} + impl From for ConfigError { fn from(e: io::Error) -> Self { ConfigError::IoError(e) @@ -255,6 +258,48 @@ impl From for ConfigError { } +//------------ LogType ------------------------------------------------------- + +/// The target to log to. +#[derive(Clone, Debug)] +pub enum LogType { + Syslog, + Stderr, + File +} + + +//--- PartialEq and Eq + +impl PartialEq for LogType { + fn eq(&self, other: &LogType) -> bool { + match (self, other) { + (&LogType::Syslog, &LogType::Syslog) => true, + (&LogType::Stderr, &LogType::Stderr) => true, + (&LogType::File, &LogType::File) => true, + _ => false + } + } +} + +impl Eq for LogType { } + +impl<'de> Deserialize<'de> for LogType { + fn deserialize(d: D) -> Result + where D: Deserializer<'de> { + let string = String::deserialize(d)?; + match string.as_str() { + "stderr" => Ok(LogType::Stderr), + "syslog" => Ok(LogType::Syslog), + "file" => Ok(LogType::Stderr), + _ => Err( + de::Error::custom( + format!("Unsupported log type: {}", string))) + } + } +} + + //------------ Tests --------------------------------------------------------- #[cfg(test)] @@ -264,7 +309,7 @@ mod tests { #[test] fn should_parse_default_config_file() { - let c = Config::read_config("./defaults/server.toml").unwrap(); + let c = Config::read_config("./defaults/pubserver.conf").unwrap(); let expected_socket_addr = ([127, 0, 0, 1], 3000).into(); assert_eq!(c.socket_addr(), expected_socket_addr); }