#![feature(proc_macro_hygiene, decl_macro, vec_remove_item, try_trait)] #![recursion_limit = "256"] #[macro_use] extern crate rocket; #[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_json; #[macro_use] extern crate log; #[macro_use] extern crate diesel; #[macro_use] extern crate diesel_migrations; #[macro_use] extern crate lazy_static; #[macro_use] extern crate derive_more; #[macro_use] extern crate num_derive; use rocket::{fairing::AdHoc, Rocket}; use std::{ path::Path, process::{exit, Command}, }; #[macro_use] mod error; mod api; mod auth; mod config; mod crypto; mod db; mod mail; mod util; pub use config::CONFIG; pub use error::{Error, MapResult}; fn launch_rocket() { // Create Rocket object, this stores current log level and sets it's own let rocket = rocket::ignite(); // If we aren't logging the mounts, we force the logging level down if !CONFIG.log_mounts() { log::set_max_level(log::LevelFilter::Warn); } let rocket = rocket .mount("/", api::web_routes()) .mount("/api", api::core_routes()) .mount("/admin", api::admin_routes()) .mount("/identity", api::identity_routes()) .mount("/icons", api::icons_routes()) .mount("/notifications", api::notifications_routes()); // Force the level up for the fairings, managed state and lauch if !CONFIG.log_mounts() { log::set_max_level(log::LevelFilter::max()); } let rocket = rocket .manage(db::init_pool()) .manage(api::start_notification_server()) .attach(util::AppHeaders()) .attach(AdHoc::on_launch("Launch Info", launch_info)); // Launch and print error if there is one // The launch will restore the original logging level error!("Launch error {:#?}", rocket.launch()); } // Embed the migrations from the migrations folder into the application // This way, the program automatically migrates the database to the latest version // https://docs.rs/diesel_migrations/*/diesel_migrations/macro.embed_migrations.html #[allow(unused_imports)] mod migrations { embed_migrations!(); pub fn run_migrations() { // Make sure the database is up to date (create if it doesn't exist, or run the migrations) let connection = crate::db::get_connection().expect("Can't conect to DB"); use std::io::stdout; embedded_migrations::run_with_output(&connection, &mut stdout()).expect("Can't run migrations"); } } fn main() { if CONFIG.extended_logging() { init_logging().ok(); } check_db(); check_rsa_keys(); check_web_vault(); migrations::run_migrations(); launch_rocket(); } fn init_logging() -> Result<(), fern::InitError> { let mut logger = fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( "{}[{}][{}] {}", chrono::Local::now().format("[%Y-%m-%d %H:%M:%S]"), record.target(), record.level(), message )) }) .level(log::LevelFilter::Debug) .level_for("hyper", log::LevelFilter::Warn) .level_for("rustls", log::LevelFilter::Warn) .level_for("handlebars", log::LevelFilter::Warn) .level_for("ws", log::LevelFilter::Info) .level_for("multipart", log::LevelFilter::Info) .level_for("html5ever", log::LevelFilter::Info) .chain(std::io::stdout()); if let Some(log_file) = CONFIG.log_file() { logger = logger.chain(fern::log_file(log_file)?); } logger = chain_syslog(logger); logger.apply()?; Ok(()) } #[cfg(not(feature = "enable_syslog"))] fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch { logger } #[cfg(feature = "enable_syslog")] fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch { let syslog_fmt = syslog::Formatter3164 { facility: syslog::Facility::LOG_USER, hostname: None, process: "bitwarden_rs".into(), pid: 0, }; match syslog::unix(syslog_fmt) { Ok(sl) => logger.chain(sl), Err(e) => { error!("Unable to connect to syslog: {:?}", e); logger } } } fn check_db() { let url = CONFIG.database_url(); let path = Path::new(&url); if let Some(parent) = path.parent() { use std::fs; if fs::create_dir_all(parent).is_err() { error!("Error creating database directory"); exit(1); } } // Turn on WAL in SQLite use diesel::RunQueryDsl; let connection = db::get_connection().expect("Can't conect to DB"); diesel::sql_query("PRAGMA journal_mode=wal") .execute(&connection) .expect("Failed to turn on WAL"); } fn check_rsa_keys() { // If the RSA keys don't exist, try to create them if !util::file_exists(&CONFIG.private_rsa_key()) || !util::file_exists(&CONFIG.public_rsa_key()) { info!("JWT keys don't exist, checking if OpenSSL is available..."); Command::new("openssl").arg("version").output().unwrap_or_else(|_| { info!("Can't create keys because OpenSSL is not available, make sure it's installed and available on the PATH"); exit(1); }); info!("OpenSSL detected, creating keys..."); let mut success = Command::new("openssl") .arg("genrsa") .arg("-out") .arg(&CONFIG.private_rsa_key_pem()) .output() .expect("Failed to create private pem file") .status .success(); success &= Command::new("openssl") .arg("rsa") .arg("-in") .arg(&CONFIG.private_rsa_key_pem()) .arg("-outform") .arg("DER") .arg("-out") .arg(&CONFIG.private_rsa_key()) .output() .expect("Failed to create private der file") .status .success(); success &= Command::new("openssl") .arg("rsa") .arg("-in") .arg(&CONFIG.private_rsa_key()) .arg("-inform") .arg("DER") .arg("-RSAPublicKey_out") .arg("-outform") .arg("DER") .arg("-out") .arg(&CONFIG.public_rsa_key()) .output() .expect("Failed to create public der file") .status .success(); if success { info!("Keys created correctly."); } else { error!("Error creating keys, exiting..."); exit(1); } } } 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() { error!("Web vault is not found. To install it, please follow the steps in https://github.com/dani-garcia/bitwarden_rs/wiki/Building-binary#install-the-web-vault"); exit(1); } } fn launch_info(_: &Rocket) { // Remove the target to keep the message more centered macro_rules! w {( $l:literal $(,$e:expr)* ) => {warn!(target: "", $l, $($e),* )}} w!("/--------------------------------------------------------------------\\"); w!("| Starting Bitwarden_RS |"); if let Some(version) = option_env!("GIT_VERSION") { w!("|{:^68}|", format!("Version {}", version)); } w!("|--------------------------------------------------------------------|"); w!("| This is an *unofficial* Bitwarden implementation, DO NOT use the |"); w!("| official channels to report bugs/features, regardless of client. |"); w!("| Report URL: https://github.com/dani-garcia/bitwarden_rs/issues/new |"); w!("\\--------------------------------------------------------------------/"); }