diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1879fbf88..c5f42b529 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,7 @@ +# 0.8.1 (TBD) + +* Allow setting `--cookie-file` path via configuration (@Kixunil) + # 0.8.0 (28 Oct 2019) * Use `configure_me` instead of `clap` to support config files, environment variables and man pages (@Kixunil) diff --git a/config_spec.toml b/config_spec.toml index c364336e0..a0c4b0077 100644 --- a/config_spec.toml +++ b/config_spec.toml @@ -32,11 +32,17 @@ default = "crate::config::default_daemon_dir()" [[param]] name = "cookie" type = "String" -doc = "JSONRPC authentication cookie ('USER:PASSWORD', default: read from ~/.bitcoin/.cookie)" +doc = "JSONRPC authentication cookie ('USER:PASSWORD', default: read from cookie file)" # Force the user to use config file in order to avoid password leaks argument = false env_var = false +[[param]] +name = "cookie_file" +type = "std::path::PathBuf" +doc = "JSONRPC authentication cookie file (default: ~/.bitcoin/.cookie)" +# This is safe to configure on command line. + [[param]] name = "network" type = "crate::config::BitcoinNetwork" diff --git a/src/config.rs b/src/config.rs index 5010cc425..9ea230f34 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,7 +7,7 @@ use std::fmt; use std::fs; use std::net::SocketAddr; use std::net::ToSocketAddrs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; use stderrlog; @@ -121,7 +121,6 @@ impl Into for BitcoinNetwork { } /// Parsed and post-processed configuration -#[derive(Debug)] pub struct Config { // See below for the documentation of each field: pub log: stderrlog::StdErrLog, @@ -129,7 +128,6 @@ pub struct Config { pub db_path: PathBuf, pub daemon_dir: PathBuf, pub daemon_rpc_addr: SocketAddr, - pub cookie: Option, pub electrum_rpc_addr: SocketAddr, pub monitoring_addr: SocketAddr, pub jsonrpc_import: bool, @@ -139,6 +137,7 @@ pub struct Config { pub txid_limit: usize, pub server_banner: String, pub blocktxids_cache_size: usize, + pub cookie_getter: Arc, } /// Returns default daemon directory @@ -151,6 +150,22 @@ fn default_daemon_dir() -> PathBuf { home } +fn create_cookie_getter( + cookie: Option, + cookie_file: Option, + daemon_dir: &Path, +) -> Arc { + match (cookie, cookie_file) { + (None, None) => Arc::new(CookieFile::from_daemon_dir(daemon_dir)), + (None, Some(file)) => Arc::new(CookieFile::from_file(file)), + (Some(cookie), None) => Arc::new(StaticCookie::from_string(cookie)), + (Some(_), Some(_)) => { + eprintln!("Error: ambigous configuration - cookie and cookie_file can't be specified at the same time"); + std::process::exit(1); + } + } +} + impl Config { /// Parses args, env vars, config files and post-processes them pub fn from_args() -> Config { @@ -169,6 +184,9 @@ impl Config { let (mut config, _) = internal::Config::including_optional_config_files(configs).unwrap_or_exit(); + let cookie_getter = + create_cookie_getter(config.cookie, config.cookie_file, &config.daemon_dir); + let db_subdir = match config.network { // We must keep the name "mainnet" due to backwards compatibility Network::Bitcoin => "mainnet", @@ -241,7 +259,6 @@ impl Config { db_path: config.db_dir, daemon_dir: config.daemon_dir, daemon_rpc_addr, - cookie: config.cookie, electrum_rpc_addr, monitoring_addr, jsonrpc_import: config.jsonrpc_import, @@ -251,28 +268,61 @@ impl Config { blocktxids_cache_size: (config.blocktxids_cache_size_mb * MB) as usize, txid_limit: config.txid_limit, server_banner: config.server_banner, + cookie_getter, }; eprintln!("{:?}", config); config } pub fn cookie_getter(&self) -> Arc { - if let Some(ref value) = self.cookie { - Arc::new(StaticCookie { - value: value.as_bytes().to_vec(), - }) - } else { - Arc::new(CookieFile { - daemon_dir: self.daemon_dir.clone(), - }) + Arc::clone(&self.cookie_getter) + } +} + +// CookieGetter + Debug isn't implemented in Rust, so we have to skip cookie_getter +macro_rules! debug_struct { + ($name:ty, $($field:ident,)*) => { + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct(stringify!($name)) + $( + .field(stringify!($field), &self.$field) + )* + .finish() + } } } } +debug_struct! { Config, + log, + network_type, + db_path, + daemon_dir, + daemon_rpc_addr, + electrum_rpc_addr, + monitoring_addr, + jsonrpc_import, + index_batch_size, + bulk_index_threads, + tx_cache_size, + txid_limit, + server_banner, + blocktxids_cache_size, +} + struct StaticCookie { value: Vec, } +impl StaticCookie { + fn from_string(value: String) -> Self { + StaticCookie { + value: value.into(), + } + } +} + impl CookieGetter for StaticCookie { fn get(&self) -> Result> { Ok(self.value.clone()) @@ -280,14 +330,28 @@ impl CookieGetter for StaticCookie { } struct CookieFile { - daemon_dir: PathBuf, + cookie_file: PathBuf, +} + +impl CookieFile { + fn from_daemon_dir(daemon_dir: &Path) -> Self { + CookieFile { + cookie_file: daemon_dir.join(".cookie"), + } + } + + fn from_file(cookie_file: PathBuf) -> Self { + CookieFile { cookie_file } + } } impl CookieGetter for CookieFile { fn get(&self) -> Result> { - let path = self.daemon_dir.join(".cookie"); - let contents = fs::read(&path).chain_err(|| { - ErrorKind::Connection(format!("failed to read cookie from {:?}", path)) + let contents = fs::read(&self.cookie_file).chain_err(|| { + ErrorKind::Connection(format!( + "failed to read cookie from {}", + self.cookie_file.display() + )) })?; Ok(contents) }