diff --git a/.env b/.env index 4b481b6..152f4ce 100644 --- a/.env +++ b/.env @@ -1,13 +1,34 @@ +## Bitwarden_RS Configuration File +## Uncomment any of the following lines to change the defaults + +## Main data folder +# DATA_FOLDER=data + +## Individual folders, these override %DATA_FOLDER% # DATABASE_URL=data/db.sqlite3 -# PRIVATE_RSA_KEY=data/private_rsa_key.der -# PUBLIC_RSA_KEY=data/public_rsa_key.der +# RSA_KEY_FILENAME=data/rsa_key # ICON_CACHE_FOLDER=data/icon_cache # ATTACHMENTS_FOLDER=data/attachments -# true for yes, anything else for no -SIGNUPS_ALLOWED=true +## Web vault settings +# WEB_VAULT_FOLDER=web-vault/ +# WEB_VAULT_ENABLED=true -# ROCKET_ENV=production +## Controls if new users can register +# SIGNUPS_ALLOWED=true + +## Use a local favicon extractor +## Set to false to use bitwarden's official icon servers +## Set to true to use the local version, which is not as smart, +## but it doesn't send the cipher domains to bitwarden's servers +# LOCAL_ICON_EXTRACTOR=false + +## Controls the PBBKDF password iterations to apply on the server +## The change only applies when the password is changed +# PASSWORD_ITERATIONS=100000 + +## Rocket specific settings, check Rocket documentation to learn more +# ROCKET_ENV=staging # ROCKET_ADDRESS=0.0.0.0 # Enable this to test mobile app # ROCKET_PORT=8000 # ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} diff --git a/src/api/icons.rs b/src/api/icons.rs index 2ca29fa..6783d32 100644 --- a/src/api/icons.rs +++ b/src/api/icons.rs @@ -1,4 +1,3 @@ -use std::io; use std::io::prelude::*; use std::fs::{create_dir_all, File}; @@ -23,24 +22,56 @@ fn icon(domain: String) -> Content> { return Content(icon_type, get_fallback_icon()); } - let url = format!("https://icons.bitwarden.com/{}/icon.png", domain); - - // Get the icon, or fallback in case of error - let icon = match get_icon_cached(&domain, &url) { - Ok(icon) => icon, - Err(_) => return Content(icon_type, get_fallback_icon()) - }; + let icon = get_icon(&domain); Content(icon_type, icon) } -fn get_icon(url: &str) -> Result, reqwest::Error> { +fn get_icon (domain: &str) -> Vec { + let path = format!("{}/{}.png", CONFIG.icon_cache_folder, domain); + + if let Some(icon) = get_cached_icon(&path) { + return icon; + } + + let url = get_icon_url(&domain); + + // Get the icon, or fallback in case of error + match download_icon(&url) { + Ok(icon) => { + save_icon(&path, &icon); + icon + }, + Err(_) => get_fallback_icon() + } +} + +fn get_cached_icon(path: &str) -> Option> { + // Try to read the cached icon, and return it if it exists + if let Ok(mut f) = File::open(path) { + let mut buffer = Vec::new(); + + if f.read_to_end(&mut buffer).is_ok() { + return Some(buffer); + } + } + + None +} + +fn get_icon_url(domain: &str) -> String { + if CONFIG.local_icon_extractor { + format!("http://{}/favicon.ico", domain) + } else { + format!("https://icons.bitwarden.com/{}/icon.png", domain) + } +} + +fn download_icon(url: &str) -> Result, reqwest::Error> { + println!("Downloading icon for {}...", url); let mut res = reqwest::get(url)?; - res = match res.error_for_status() { - Err(e) => return Err(e), - Ok(res) => res - }; + res = res.error_for_status()?; let mut buffer: Vec = vec![]; res.copy_to(&mut buffer)?; @@ -48,35 +79,28 @@ fn get_icon(url: &str) -> Result, reqwest::Error> { Ok(buffer) } -fn get_icon_cached(key: &str, url: &str) -> io::Result> { - create_dir_all(&CONFIG.icon_cache_folder)?; - let path = &format!("{}/{}.png", CONFIG.icon_cache_folder, key); +fn save_icon(path: &str, icon: &[u8]) { + create_dir_all(&CONFIG.icon_cache_folder).expect("Error creating icon cache"); - // Try to read the cached icon, and return it if it exists - if let Ok(mut f) = File::open(path) { - let mut buffer = Vec::new(); - - if f.read_to_end(&mut buffer).is_ok() { - return Ok(buffer); - } - /* If error reading file continue */ - } - - println!("Downloading icon for {}...", key); - let icon = match get_icon(url) { - Ok(icon) => icon, - Err(_) => return Err(io::Error::new(io::ErrorKind::NotFound, "")) - }; - - // Save the currently downloaded icon if let Ok(mut f) = File::create(path) { - f.write_all(&icon).expect("Error writing icon file"); + f.write_all(icon).expect("Error writing icon file"); }; - - Ok(icon) } +const FALLBACK_ICON_URL: &str = "https://raw.githubusercontent.com/bitwarden/web/master/src/images/fa-globe.png"; + fn get_fallback_icon() -> Vec { - let fallback_icon = "https://raw.githubusercontent.com/bitwarden/web/master/src/images/fa-globe.png"; - get_icon_cached("default", fallback_icon).unwrap() + let path = format!("{}/default.png", CONFIG.icon_cache_folder); + + if let Some(icon) = get_cached_icon(&path) { + return icon; + } + + match download_icon(FALLBACK_ICON_URL) { + Ok(icon) => { + save_icon(&path, &icon); + icon + }, + Err(_) => vec![] + } } diff --git a/src/api/web.rs b/src/api/web.rs index 8e39b3a..d65452d 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -8,19 +8,23 @@ use rocket_contrib::Json; use CONFIG; pub fn routes() -> Vec { - routes![index, files, attachments, alive] + if CONFIG.web_vault_enabled { + routes![web_index, web_files, attachments, alive] + } else { + routes![attachments, alive] + } } // TODO: Might want to use in memory cache: https://github.com/hgzimmerman/rocket-file-cache #[get("/")] -fn index() -> io::Result { +fn web_index() -> io::Result { NamedFile::open( Path::new(&CONFIG.web_vault_folder) .join("index.html")) } #[get("/", rank = 1)] // Only match this if the other routes don't match -fn files(p: PathBuf) -> io::Result { +fn web_files(p: PathBuf) -> io::Result { NamedFile::open( Path::new(&CONFIG.web_vault_folder) .join(p)) diff --git a/src/main.rs b/src/main.rs index 3e2dd3d..9994173 100644 --- a/src/main.rs +++ b/src/main.rs @@ -127,6 +127,10 @@ fn check_rsa_keys() { } fn check_web_vault() { + if !CONFIG.web_vault_enabled { + return; + } + let index_path = Path::new(&CONFIG.web_vault_folder).join("index.html"); if !index_path.exists() { @@ -151,7 +155,9 @@ pub struct Config { public_rsa_key: String, web_vault_folder: String, + web_vault_enabled: bool, + local_icon_extractor: bool, signups_allowed: bool, password_iterations: i32, } @@ -161,20 +167,22 @@ impl Config { dotenv::dotenv().ok(); let df = env::var("DATA_FOLDER").unwrap_or("data".into()); - let key = env::var("RSA_KEY_NAME").unwrap_or("rsa_key".into()); + let key = env::var("RSA_KEY_FILENAME").unwrap_or(format!("{}/{}", &df, "rsa_key")); Config { database_url: env::var("DATABASE_URL").unwrap_or(format!("{}/{}", &df, "db.sqlite3")), icon_cache_folder: env::var("ICON_CACHE_FOLDER").unwrap_or(format!("{}/{}", &df, "icon_cache")), attachments_folder: env::var("ATTACHMENTS_FOLDER").unwrap_or(format!("{}/{}", &df, "attachments")), - private_rsa_key: format!("{}/{}.der", &df, &key), - private_rsa_key_pem: format!("{}/{}.pem", &df, &key), - public_rsa_key: format!("{}/{}.pub.der", &df, &key), + private_rsa_key: format!("{}.der", &key), + private_rsa_key_pem: format!("{}.pem", &key), + public_rsa_key: format!("{}.pub.der", &key), web_vault_folder: env::var("WEB_VAULT_FOLDER").unwrap_or("web-vault/".into()), + web_vault_enabled: util::parse_option_string(env::var("WEB_VAULT_ENABLED").ok()).unwrap_or(true), - signups_allowed: util::parse_option_string(env::var("SIGNUPS_ALLOWED").ok()).unwrap_or(false), + local_icon_extractor: util::parse_option_string(env::var("LOCAL_ICON_EXTRACTOR").ok()).unwrap_or(false), + signups_allowed: util::parse_option_string(env::var("SIGNUPS_ALLOWED").ok()).unwrap_or(true), password_iterations: util::parse_option_string(env::var("PASSWORD_ITERATIONS").ok()).unwrap_or(100_000), } }