1 use std::process::exit;
2 use std::sync::RwLock;
3 
4 use once_cell::sync::Lazy;
5 use reqwest::Url;
6 
7 use crate::{
8     db::DbConnType,
9     error::Error,
10     util::{get_env, get_env_bool},
11 };
12 
13 static CONFIG_FILE: Lazy<String> = Lazy::new(|| {
14     let data_folder = get_env("DATA_FOLDER").unwrap_or_else(|| String::from("data"));
15     get_env("CONFIG_FILE").unwrap_or_else(|| format!("{}/config.json", data_folder))
16 });
17 
18 pub static CONFIG: Lazy<Config> = Lazy::new(|| {
19     Config::load().unwrap_or_else(|e| {
20         println!("Error loading config:\n\t{:?}\n", e);
21         exit(12)
22     })
23 });
24 
25 pub type Pass = String;
26 
27 macro_rules! make_config {
28     ($(
29         $(#[doc = $groupdoc:literal])?
30         $group:ident $(: $group_enabled:ident)? {
31         $(
32             $(#[doc = $doc:literal])+
33             $name:ident : $ty:ident, $editable:literal, $none_action:ident $(, $default:expr)?;
34         )+},
35     )+) => {
36         pub struct Config { inner: RwLock<Inner> }
37 
38         struct Inner {
39             templates: Handlebars<'static>,
40             config: ConfigItems,
41 
42             _env: ConfigBuilder,
43             _usr: ConfigBuilder,
44 
45             _overrides: Vec<String>,
46         }
47 
48         #[derive(Clone, Default, Deserialize, Serialize)]
49         pub struct ConfigBuilder {
50             $($(
51                 #[serde(skip_serializing_if = "Option::is_none")]
52                 $name: Option<$ty>,
53             )+)+
54         }
55 
56         impl ConfigBuilder {
57             #[allow(clippy::field_reassign_with_default)]
58             fn from_env() -> Self {
59                 match dotenv::from_path(".env") {
60                     Ok(_) => (),
61                     Err(e) => match e {
62                         dotenv::Error::LineParse(msg, pos) => {
63                             panic!("Error loading the .env file:\nNear {:?} on position {}\nPlease fix and restart!\n", msg, pos);
64                         },
65                         dotenv::Error::Io(ioerr) => match ioerr.kind() {
66                             std::io::ErrorKind::NotFound => {
67                                 println!("[INFO] No .env file found.\n");
68                             },
69                             std::io::ErrorKind::PermissionDenied => {
70                                 println!("[WARNING] Permission Denied while trying to read the .env file!\n");
71                             },
72                             _ => {
73                                 println!("[WARNING] Reading the .env file failed:\n{:?}\n", ioerr);
74                             }
75                         },
76                         _ => {
77                             println!("[WARNING] Reading the .env file failed:\n{:?}\n", e);
78                         }
79                     }
80                 };
81 
82                 let mut builder = ConfigBuilder::default();
83                 $($(
84                     builder.$name = make_config! { @getenv &stringify!($name).to_uppercase(), $ty };
85                 )+)+
86 
87                 builder
88             }
89 
90             fn from_file(path: &str) -> Result<Self, Error> {
91                 use crate::util::read_file_string;
92                 let config_str = read_file_string(path)?;
93                 serde_json::from_str(&config_str).map_err(Into::into)
94             }
95 
96             /// Merges the values of both builders into a new builder.
97             /// If both have the same element, `other` wins.
98             fn merge(&self, other: &Self, show_overrides: bool, overrides: &mut Vec<String>) -> Self {
99                 let mut builder = self.clone();
100                 $($(
101                     if let v @Some(_) = &other.$name {
102                         builder.$name = v.clone();
103 
104                         if self.$name.is_some() {
105                             overrides.push(stringify!($name).to_uppercase());
106                         }
107                     }
108                 )+)+
109 
110                 if show_overrides && !overrides.is_empty() {
111                     // We can't use warn! here because logging isn't setup yet.
112                     println!("[WARNING] The following environment variables are being overriden by the config file,");
113                     println!("[WARNING] please use the admin panel to make changes to them:");
114                     println!("[WARNING] {}\n", overrides.join(", "));
115                 }
116 
117                 builder
118             }
119 
120             fn build(&self) -> ConfigItems {
121                 let mut config = ConfigItems::default();
122                 let _domain_set = self.domain.is_some();
123                 $($(
124                     config.$name = make_config!{ @build self.$name.clone(), &config, $none_action, $($default)? };
125                 )+)+
126                 config.domain_set = _domain_set;
127 
128                 config.signups_domains_whitelist = config.signups_domains_whitelist.trim().to_lowercase();
129                 config.org_creation_users = config.org_creation_users.trim().to_lowercase();
130 
131                 config
132             }
133         }
134 
135         #[derive(Clone, Default)]
136         struct ConfigItems { $($( $name: make_config!{@type $ty, $none_action}, )+)+ }
137 
138         #[allow(unused)]
139         impl Config {
140             $($(
141                 $(#[doc = $doc])+
142                 pub fn $name(&self) -> make_config!{@type $ty, $none_action} {
143                     self.inner.read().unwrap().config.$name.clone()
144                 }
145             )+)+
146 
147             pub fn prepare_json(&self) -> serde_json::Value {
148                 let (def, cfg, overriden) = {
149                     let inner = &self.inner.read().unwrap();
150                     (inner._env.build(), inner.config.clone(), inner._overrides.clone())
151                 };
152 
153                 fn _get_form_type(rust_type: &str) -> &'static str {
154                     match rust_type {
155                         "Pass" => "password",
156                         "String" => "text",
157                         "bool" => "checkbox",
158                         _ => "number"
159                     }
160                 }
161 
162                 fn _get_doc(doc: &str) -> serde_json::Value {
163                     let mut split = doc.split("|>").map(str::trim);
164 
165                     // We do not use the json!() macro here since that causes a lot of macro recursion.
166                     // This slows down compile time and it also causes issues with rust-analyzer
167                     serde_json::Value::Object({
168                         let mut doc_json = serde_json::Map::new();
169                         doc_json.insert("name".into(), serde_json::to_value(split.next()).unwrap());
170                         doc_json.insert("description".into(), serde_json::to_value(split.next()).unwrap());
171                         doc_json
172                     })
173                 }
174 
175                 // We do not use the json!() macro here since that causes a lot of macro recursion.
176                 // This slows down compile time and it also causes issues with rust-analyzer
177                 serde_json::Value::Array(<[_]>::into_vec(Box::new([
178                 $(
179                     serde_json::Value::Object({
180                         let mut group = serde_json::Map::new();
181                         group.insert("group".into(), (stringify!($group)).into());
182                         group.insert("grouptoggle".into(), (stringify!($($group_enabled)?)).into());
183                         group.insert("groupdoc".into(), (make_config!{ @show $($groupdoc)? }).into());
184 
185                         group.insert("elements".into(), serde_json::Value::Array(<[_]>::into_vec(Box::new([
186                         $(
187                             serde_json::Value::Object({
188                                 let mut element = serde_json::Map::new();
189                                 element.insert("editable".into(), ($editable).into());
190                                 element.insert("name".into(), (stringify!($name)).into());
191                                 element.insert("value".into(), serde_json::to_value(cfg.$name).unwrap());
192                                 element.insert("default".into(), serde_json::to_value(def.$name).unwrap());
193                                 element.insert("type".into(), (_get_form_type(stringify!($ty))).into());
194                                 element.insert("doc".into(), (_get_doc(concat!($($doc),+))).into());
195                                 element.insert("overridden".into(), (overriden.contains(&stringify!($name).to_uppercase())).into());
196                                 element
197                             }),
198                         )+
199                         ]))));
200                         group
201                     }),
202                 )+
203                 ])))
204             }
205 
206             pub fn get_support_json(&self) -> serde_json::Value {
207                 // Define which config keys need to be masked.
208                 // Pass types will always be masked and no need to put them in the list.
209                 // Besides Pass, only String types will be masked via _privacy_mask.
210                 const PRIVACY_CONFIG: &[&str] = &[
211                     "allowed_iframe_ancestors",
212                     "database_url",
213                     "domain_origin",
214                     "domain_path",
215                     "domain",
216                     "helo_name",
217                     "org_creation_users",
218                     "signups_domains_whitelist",
219                     "smtp_from",
220                     "smtp_host",
221                     "smtp_username",
222                 ];
223 
224                 let cfg = {
225                     let inner = &self.inner.read().unwrap();
226                     inner.config.clone()
227                 };
228 
229                 /// We map over the string and remove all alphanumeric, _ and - characters.
230                 /// This is the fastest way (within micro-seconds) instead of using a regex (which takes mili-seconds)
231                 fn _privacy_mask(value: &str) -> String {
232                     value.chars().map(|c|
233                         match c {
234                             c if c.is_alphanumeric() => '*',
235                             '_' => '*',
236                             '-' => '*',
237                             _ => c
238                         }
239                     ).collect::<String>()
240                 }
241 
242                 serde_json::Value::Object({
243                     let mut json = serde_json::Map::new();
244                     $($(
245                         json.insert(stringify!($name).into(), make_config!{ @supportstr $name, cfg.$name, $ty, $none_action });
246                     )+)+;
247                     json
248                 })
249             }
250 
251             pub fn get_overrides(&self) -> Vec<String> {
252                 let overrides = {
253                     let inner = &self.inner.read().unwrap();
254                     inner._overrides.clone()
255                 };
256                 overrides
257             }
258         }
259     };
260 
261     // Support string print
262     ( @supportstr $name:ident, $value:expr, Pass, option ) => { serde_json::to_value($value.as_ref().map(|_| String::from("***"))).unwrap() }; // Optional pass, we map to an Option<String> with "***"
263     ( @supportstr $name:ident, $value:expr, Pass, $none_action:ident ) => { "***".into() }; // Required pass, we return "***"
264     ( @supportstr $name:ident, $value:expr, String, option ) => { // Optional other value, we return as is or convert to string to apply the privacy config
265         if PRIVACY_CONFIG.contains(&stringify!($name)) {
266             serde_json::to_value($value.as_ref().map(|x| _privacy_mask(x) )).unwrap()
267         } else {
268             serde_json::to_value($value).unwrap()
269         }
270     };
271     ( @supportstr $name:ident, $value:expr, String, $none_action:ident ) => { // Required other value, we return as is or convert to string to apply the privacy config
272         if PRIVACY_CONFIG.contains(&stringify!($name)) {
273             _privacy_mask(&$value).into()
274         } else {
275             ($value).into()
276         }
277     };
278     ( @supportstr $name:ident, $value:expr, $ty:ty, option ) => { serde_json::to_value($value).unwrap() }; // Optional other value, we return as is or convert to string to apply the privacy config
279     ( @supportstr $name:ident, $value:expr, $ty:ty, $none_action:ident ) => { ($value).into() }; // Required other value, we return as is or convert to string to apply the privacy config
280 
281     // Group or empty string
282     ( @show ) => { "" };
283     ( @show $lit:literal ) => { $lit };
284 
285     // Wrap the optionals in an Option type
286     ( @type $ty:ty, option) => { Option<$ty> };
287     ( @type $ty:ty, $id:ident) => { $ty };
288 
289     // Generate the values depending on none_action
290     ( @build $value:expr, $config:expr, option, ) => { $value };
291     ( @build $value:expr, $config:expr, def, $default:expr ) => { $value.unwrap_or($default) };
292     ( @build $value:expr, $config:expr, auto, $default_fn:expr ) => {{
293         match $value {
294             Some(v) => v,
295             None => {
296                 let f: &dyn Fn(&ConfigItems) -> _ = &$default_fn;
297                 f($config)
298             }
299         }
300     }};
301     ( @build $value:expr, $config:expr, gen, $default_fn:expr ) => {{
302         let f: &dyn Fn(&ConfigItems) -> _ = &$default_fn;
303         f($config)
304     }};
305 
306     ( @getenv $name:expr, bool ) => { get_env_bool($name) };
307     ( @getenv $name:expr, $ty:ident ) => { get_env($name) };
308 
309 }
310 
311 //STRUCTURE:
312 // /// Short description (without this they won't appear on the list)
313 // group {
314 //   /// Friendly Name |> Description (Optional)
315 //   name: type, is_editable, action, <default_value (Optional)>
316 // }
317 //
318 // Where action applied when the value wasn't provided and can be:
319 //  def:    Use a default value
320 //  auto:   Value is auto generated based on other values
321 //  option: Value is optional
322 //  gen:    Value is always autogenerated and it's original value ignored
323 make_config! {
324     folders {
325         ///  Data folder |> Main data folder
326         data_folder:            String, false,  def,    "data".to_string();
327         /// Database URL
328         database_url:           String, false,  auto,   |c| format!("{}/{}", c.data_folder, "db.sqlite3");
329         /// Icon cache folder
330         icon_cache_folder:      String, false,  auto,   |c| format!("{}/{}", c.data_folder, "icon_cache");
331         /// Attachments folder
332         attachments_folder:     String, false,  auto,   |c| format!("{}/{}", c.data_folder, "attachments");
333         /// Sends folder
334         sends_folder:           String, false,  auto,   |c| format!("{}/{}", c.data_folder, "sends");
335         /// Templates folder
336         templates_folder:       String, false,  auto,   |c| format!("{}/{}", c.data_folder, "templates");
337         /// Session JWT key
338         rsa_key_filename:       String, false,  auto,   |c| format!("{}/{}", c.data_folder, "rsa_key");
339         /// Web vault folder
340         web_vault_folder:       String, false,  def,    "web-vault/".to_string();
341     },
342     ws {
343         /// Enable websocket notifications
344         websocket_enabled:      bool,   false,  def,    false;
345         /// Websocket address
346         websocket_address:      String, false,  def,    "0.0.0.0".to_string();
347         /// Websocket port
348         websocket_port:         u16,    false,  def,    3012;
349     },
350     jobs {
351         /// Job scheduler poll interval |> How often the job scheduler thread checks for jobs to run.
352         /// Set to 0 to globally disable scheduled jobs.
353         job_poll_interval_ms:   u64,    false,  def,    30_000;
354         /// Send purge schedule |> Cron schedule of the job that checks for Sends past their deletion date.
355         /// Defaults to hourly. Set blank to disable this job.
356         send_purge_schedule:    String, false,  def,    "0 5 * * * *".to_string();
357         /// Trash purge schedule |> Cron schedule of the job that checks for trashed items to delete permanently.
358         /// Defaults to daily. Set blank to disable this job.
359         trash_purge_schedule:   String, false,  def,    "0 5 0 * * *".to_string();
360         /// Incomplete 2FA login schedule |> Cron schedule of the job that checks for incomplete 2FA logins.
361         /// Defaults to once every minute. Set blank to disable this job.
362         incomplete_2fa_schedule: String, false,  def,   "30 * * * * *".to_string();
363         /// Emergency notification reminder schedule |> Cron schedule of the job that sends expiration reminders to emergency access grantors.
364         /// Defaults to hourly. Set blank to disable this job.
365         emergency_notification_reminder_schedule:   String, false,  def,    "0 5 * * * *".to_string();
366         /// Emergency request timeout schedule |> Cron schedule of the job that grants emergency access requests that have met the required wait time.
367         /// Defaults to hourly. Set blank to disable this job.
368         emergency_request_timeout_schedule:   String, false,  def,    "0 5 * * * *".to_string();
369     },
370 
371     /// General settings
372     settings {
373         /// Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://'
374         /// and port, if it's different than the default. Some server functions don't work correctly without this value
375         domain:                 String, true,   def,    "http://localhost".to_string();
376         /// Domain Set |> Indicates if the domain is set by the admin. Otherwise the default will be used.
377         domain_set:             bool,   false,  def,    false;
378         /// Domain origin |> Domain URL origin (in https://example.com:8443/path, https://example.com:8443 is the origin)
379         domain_origin:          String, false,  auto,   |c| extract_url_origin(&c.domain);
380         /// Domain path |> Domain URL path (in https://example.com:8443/path, /path is the path)
381         domain_path:            String, false,  auto,   |c| extract_url_path(&c.domain);
382         /// Enable web vault
383         web_vault_enabled:      bool,   false,  def,    true;
384 
385         /// Allow Sends |> Controls whether users are allowed to create Bitwarden Sends.
386         /// This setting applies globally to all users. To control this on a per-org basis instead, use the "Disable Send" org policy.
387         sends_allowed:          bool,   true,   def,    true;
388 
389         /// HIBP Api Key |> HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
390         hibp_api_key:           Pass,   true,   option;
391 
392         /// Per-user attachment storage limit (KB) |> Max kilobytes of attachment storage allowed per user. When this limit is reached, the user will not be allowed to upload further attachments.
393         user_attachment_limit:  i64,    true,   option;
394         /// Per-organization attachment storage limit (KB) |> Max kilobytes of attachment storage allowed per org. When this limit is reached, org members will not be allowed to upload further attachments for ciphers owned by that org.
395         org_attachment_limit:   i64,    true,   option;
396 
397         /// Trash auto-delete days |> Number of days to wait before auto-deleting a trashed item.
398         /// If unset, trashed items are not auto-deleted. This setting applies globally, so make
399         /// sure to inform all users of any changes to this setting.
400         trash_auto_delete_days: i64,    true,   option;
401 
402         /// Incomplete 2FA time limit |> Number of minutes to wait before a 2FA-enabled login is
403         /// considered incomplete, resulting in an email notification. An incomplete 2FA login is one
404         /// where the correct master password was provided but the required 2FA step was not completed,
405         /// which potentially indicates a master password compromise. Set to 0 to disable this check.
406         /// This setting applies globally to all users.
407         incomplete_2fa_time_limit: i64, true,   def,    3;
408 
409         /// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from
410         /// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
411         /// otherwise it will delete them and they won't be downloaded again.
412         disable_icon_download:  bool,   true,   def,    false;
413         /// Allow new signups |> Controls whether new users can register. Users can be invited by the vaultwarden admin even if this is disabled
414         signups_allowed:        bool,   true,   def,    true;
415         /// Require email verification on signups. This will prevent logins from succeeding until the address has been verified
416         signups_verify:         bool,   true,   def,    false;
417         /// If signups require email verification, automatically re-send verification email if it hasn't been sent for a while (in seconds)
418         signups_verify_resend_time: u64, true,  def,    3_600;
419         /// If signups require email verification, limit how many emails are automatically sent when login is attempted (0 means no limit)
420         signups_verify_resend_limit: u32, true, def,    6;
421         /// Email domain whitelist |> Allow signups only from this list of comma-separated domains, even when signups are otherwise disabled
422         signups_domains_whitelist: String, true, def,   "".to_string();
423         /// Org creation users |> Allow org creation only by this list of comma-separated user emails.
424         /// Blank or 'all' means all users can create orgs; 'none' means no users can create orgs.
425         org_creation_users:     String, true,   def,    "".to_string();
426         /// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are otherwise disabled
427         invitations_allowed:    bool,   true,   def,    true;
428         /// Allow emergency access |> Controls whether users can enable emergency access to their accounts. This setting applies globally to all users.
429         emergency_access_allowed:    bool,   true,   def,    true;
430         /// Password iterations |> Number of server-side passwords hashing iterations.
431         /// The changes only apply when a user changes their password. Not recommended to lower the value
432         password_iterations:    i32,    true,   def,    100_000;
433         /// Show password hint |> Controls whether a password hint should be shown directly in the web page
434         /// if SMTP service is not configured. Not recommended for publicly-accessible instances as this
435         /// provides unauthenticated access to potentially sensitive data.
436         show_password_hint:     bool,   true,   def,    false;
437 
438         /// Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session
439         admin_token:            Pass,   true,   option;
440 
441         /// Invitation organization name |> Name shown in the invitation emails that don't come from a specific organization
442         invitation_org_name:    String, true,   def,    "Vaultwarden".to_string();
443     },
444 
445     /// Advanced settings
446     advanced {
447         /// Client IP header |> If not present, the remote IP is used.
448         /// Set to the string "none" (without quotes), to disable any headers and just use the remote IP
449         ip_header:              String, true,   def,    "X-Real-IP".to_string();
450         /// Internal IP header property, used to avoid recomputing each time
451         _ip_header_enabled:     bool,   false,  gen,    |c| &c.ip_header.trim().to_lowercase() != "none";
452         /// Positive icon cache expiry |> Number of seconds to consider that an already cached icon is fresh. After this period, the icon will be redownloaded
453         icon_cache_ttl:         u64,    true,   def,    2_592_000;
454         /// Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again.
455         icon_cache_negttl:      u64,    true,   def,    259_200;
456         /// Icon download timeout |> Number of seconds when to stop attempting to download an icon.
457         icon_download_timeout:  u64,    true,   def,    10;
458         /// Icon blacklist Regex |> Any domains or IPs that match this regex won't be fetched by the icon service.
459         /// Useful to hide other servers in the local network. Check the WIKI for more details
460         icon_blacklist_regex:   String, true,   option;
461         /// Icon blacklist non global IPs |> Any IP which is not defined as a global IP will be blacklisted.
462         /// Usefull to secure your internal environment: See https://en.wikipedia.org/wiki/Reserved_IP_addresses for a list of IPs which it will block
463         icon_blacklist_non_global_ips:  bool,   true,   def,    true;
464 
465         /// Disable Two-Factor remember |> Enabling this would force the users to use a second factor to login every time.
466         /// Note that the checkbox would still be present, but ignored.
467         disable_2fa_remember:   bool,   true,   def,    false;
468 
469         /// Disable authenticator time drifted codes to be valid |> Enabling this only allows the current TOTP code to be valid
470         /// TOTP codes of the previous and next 30 seconds will be invalid.
471         authenticator_disable_time_drift: bool, true, def, false;
472 
473         /// Require new device emails |> When a user logs in an email is required to be sent.
474         /// If sending the email fails the login attempt will fail.
475         require_device_email:   bool,   true,   def,     false;
476 
477         /// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request.
478         /// ONLY use this during development, as it can slow down the server
479         reload_templates:       bool,   true,   def,    false;
480         /// Enable extended logging
481         extended_logging:       bool,   false,  def,    true;
482         /// Log timestamp format
483         log_timestamp_format:   String, true,   def,    "%Y-%m-%d %H:%M:%S.%3f".to_string();
484         /// Enable the log to output to Syslog
485         use_syslog:             bool,   false,  def,    false;
486         /// Log file path
487         log_file:               String, false,  option;
488         /// Log level
489         log_level:              String, false,  def,    "Info".to_string();
490 
491         /// Enable DB WAL |> Turning this off might lead to worse performance, but might help if using vaultwarden on some exotic filesystems,
492         /// that do not support WAL. Please make sure you read project wiki on the topic before changing this setting.
493         enable_db_wal:          bool,   false,  def,    true;
494 
495         /// Max database connection retries |> Number of times to retry the database connection during startup, with 1 second between each retry, set to 0 to retry indefinitely
496         db_connection_retries:  u32,    false,  def,    15;
497 
498         /// Database connection pool size
499         database_max_conns:     u32,    false,  def,    10;
500 
501         /// Bypass admin page security (Know the risks!) |> Disables the Admin Token for the admin page so you may use your own auth in-front
502         disable_admin_token:    bool,   true,   def,    false;
503 
504         /// Allowed iframe ancestors (Know the risks!) |> Allows other domains to embed the web vault into an iframe, useful for embedding into secure intranets
505         allowed_iframe_ancestors: String, true, def,    String::new();
506     },
507 
508     /// Yubikey settings
509     yubico: _enable_yubico {
510         /// Enabled
511         _enable_yubico:         bool,   true,   def,     true;
512         /// Client ID
513         yubico_client_id:       String, true,   option;
514         /// Secret Key
515         yubico_secret_key:      Pass,   true,   option;
516         /// Server
517         yubico_server:          String, true,   option;
518     },
519 
520     /// Global Duo settings (Note that users can override them)
521     duo: _enable_duo {
522         /// Enabled
523         _enable_duo:            bool,   true,   def,     false;
524         /// Integration Key
525         duo_ikey:               String, true,   option;
526         /// Secret Key
527         duo_skey:               Pass,   true,   option;
528         /// Host
529         duo_host:               String, true,   option;
530         /// Application Key (generated automatically)
531         _duo_akey:              Pass,   false,  option;
532     },
533 
534     /// SMTP Email Settings
535     smtp: _enable_smtp {
536         /// Enabled
537         _enable_smtp:                  bool,   true,   def,     true;
538         /// Host
539         smtp_host:                     String, true,   option;
540         /// Enable Secure SMTP |> (Explicit) - Enabling this by default would use STARTTLS (Standard ports 587 or 25)
541         smtp_ssl:                      bool,   true,   def,     true;
542         /// Force TLS |> (Implicit) - Enabling this would force the use of an SSL/TLS connection, instead of upgrading an insecure one with STARTTLS (Standard port 465)
543         smtp_explicit_tls:             bool,   true,   def,     false;
544         /// Port
545         smtp_port:                     u16,    true,   auto,    |c| if c.smtp_explicit_tls {465} else if c.smtp_ssl {587} else {25};
546         /// From Address
547         smtp_from:                     String, true,   def,     String::new();
548         /// From Name
549         smtp_from_name:                String, true,   def,     "Vaultwarden".to_string();
550         /// Username
551         smtp_username:                 String, true,   option;
552         /// Password
553         smtp_password:                 Pass,   true,   option;
554         /// SMTP Auth mechanism |> Defaults for SSL is "Plain" and "Login" and nothing for Non-SSL connections. Possible values: ["Plain", "Login", "Xoauth2"]. Multiple options need to be separated by a comma ','.
555         smtp_auth_mechanism:           String, true,   option;
556         /// SMTP connection timeout |> Number of seconds when to stop trying to connect to the SMTP server
557         smtp_timeout:                  u64,    true,   def,     15;
558         /// Server name sent during HELO |> By default this value should be is on the machine's hostname, but might need to be changed in case it trips some anti-spam filters
559         helo_name:                     String, true,   option;
560         /// Enable SMTP debugging (Know the risks!) |> DANGEROUS: Enabling this will output very detailed SMTP messages. This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting!
561         smtp_debug:                    bool,   false,  def,     false;
562         /// Accept Invalid Certs (Know the risks!) |> DANGEROUS: Allow invalid certificates. This option introduces significant vulnerabilities to man-in-the-middle attacks!
563         smtp_accept_invalid_certs:     bool,   true,   def,     false;
564         /// Accept Invalid Hostnames (Know the risks!) |> DANGEROUS: Allow invalid hostnames. This option introduces significant vulnerabilities to man-in-the-middle attacks!
565         smtp_accept_invalid_hostnames: bool,   true,   def,     false;
566     },
567 
568     /// Email 2FA Settings
569     email_2fa: _enable_email_2fa {
570         /// Enabled |> Disabling will prevent users from setting up new email 2FA and using existing email 2FA configured
571         _enable_email_2fa:      bool,   true,   auto,    |c| c._enable_smtp && c.smtp_host.is_some();
572         /// Email token size |> Number of digits in an email token (min: 6, max: 19). Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting.
573         email_token_size:       u32,    true,   def,      6;
574         /// Token expiration time |> Maximum time in seconds a token is valid. The time the user has to open email client and copy token.
575         email_expiration_time:  u64,    true,   def,      600;
576         /// Maximum attempts |> Maximum attempts before an email token is reset and a new email will need to be sent
577         email_attempts_limit:   u64,    true,   def,      3;
578     },
579 }
580 
validate_config(cfg: &ConfigItems) -> Result<(), Error>581 fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
582     // Validate connection URL is valid and DB feature is enabled
583     DbConnType::from_url(&cfg.database_url)?;
584 
585     let limit = 256;
586     if cfg.database_max_conns < 1 || cfg.database_max_conns > limit {
587         err!(format!("`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {}.", limit,));
588     }
589 
590     let dom = cfg.domain.to_lowercase();
591     if !dom.starts_with("http://") && !dom.starts_with("https://") {
592         err!(
593             "DOMAIN variable needs to contain the protocol (http, https). Use 'http[s]://bw.example.com' instead of 'bw.example.com'"
594         );
595     }
596 
597     let whitelist = &cfg.signups_domains_whitelist;
598     if !whitelist.is_empty() && whitelist.split(',').any(|d| d.trim().is_empty()) {
599         err!("`SIGNUPS_DOMAINS_WHITELIST` contains empty tokens");
600     }
601 
602     let org_creation_users = cfg.org_creation_users.trim().to_lowercase();
603     if !(org_creation_users.is_empty() || org_creation_users == "all" || org_creation_users == "none")
604         && org_creation_users.split(',').any(|u| !u.contains('@'))
605     {
606         err!("`ORG_CREATION_USERS` contains invalid email addresses");
607     }
608 
609     if let Some(ref token) = cfg.admin_token {
610         if token.trim().is_empty() && !cfg.disable_admin_token {
611             println!("[WARNING] `ADMIN_TOKEN` is enabled but has an empty value, so the admin page will be disabled.");
612             println!("[WARNING] To enable the admin page without a token, use `DISABLE_ADMIN_TOKEN`.");
613         }
614     }
615 
616     if cfg._enable_duo
617         && (cfg.duo_host.is_some() || cfg.duo_ikey.is_some() || cfg.duo_skey.is_some())
618         && !(cfg.duo_host.is_some() && cfg.duo_ikey.is_some() && cfg.duo_skey.is_some())
619     {
620         err!("All Duo options need to be set for global Duo support")
621     }
622 
623     if cfg._enable_yubico && cfg.yubico_client_id.is_some() != cfg.yubico_secret_key.is_some() {
624         err!("Both `YUBICO_CLIENT_ID` and `YUBICO_SECRET_KEY` need to be set for Yubikey OTP support")
625     }
626 
627     if cfg._enable_smtp {
628         if cfg.smtp_host.is_some() == cfg.smtp_from.is_empty() {
629             err!("Both `SMTP_HOST` and `SMTP_FROM` need to be set for email support")
630         }
631 
632         if cfg.smtp_host.is_some() && !cfg.smtp_from.contains('@') {
633             err!("SMTP_FROM does not contain a mandatory @ sign")
634         }
635 
636         if cfg.smtp_username.is_some() != cfg.smtp_password.is_some() {
637             err!("Both `SMTP_USERNAME` and `SMTP_PASSWORD` need to be set to enable email authentication")
638         }
639 
640         if cfg._enable_email_2fa && (!cfg._enable_smtp || cfg.smtp_host.is_none()) {
641             err!("To enable email 2FA, SMTP must be configured")
642         }
643 
644         if cfg._enable_email_2fa && cfg.email_token_size < 6 {
645             err!("`EMAIL_TOKEN_SIZE` has a minimum size of 6")
646         }
647 
648         if cfg._enable_email_2fa && cfg.email_token_size > 19 {
649             err!("`EMAIL_TOKEN_SIZE` has a maximum size of 19")
650         }
651     }
652 
653     // Check if the icon blacklist regex is valid
654     if let Some(ref r) = cfg.icon_blacklist_regex {
655         let validate_regex = regex::Regex::new(r);
656         match validate_regex {
657             Ok(_) => (),
658             Err(e) => err!(format!("`ICON_BLACKLIST_REGEX` is invalid: {:#?}", e)),
659         }
660     }
661 
662     Ok(())
663 }
664 
665 /// Extracts an RFC 6454 web origin from a URL.
extract_url_origin(url: &str) -> String666 fn extract_url_origin(url: &str) -> String {
667     match Url::parse(url) {
668         Ok(u) => u.origin().ascii_serialization(),
669         Err(e) => {
670             println!("Error validating domain: {}", e);
671             String::new()
672         }
673     }
674 }
675 
676 /// Extracts the path from a URL.
677 /// All trailing '/' chars are trimmed, even if the path is a lone '/'.
extract_url_path(url: &str) -> String678 fn extract_url_path(url: &str) -> String {
679     match Url::parse(url) {
680         Ok(u) => u.path().trim_end_matches('/').to_string(),
681         Err(_) => {
682             // We already print it in the method above, no need to do it again
683             String::new()
684         }
685     }
686 }
687 
688 impl Config {
load() -> Result<Self, Error>689     pub fn load() -> Result<Self, Error> {
690         // Loading from env and file
691         let _env = ConfigBuilder::from_env();
692         let _usr = ConfigBuilder::from_file(&CONFIG_FILE).unwrap_or_default();
693 
694         // Create merged config, config file overwrites env
695         let mut _overrides = Vec::new();
696         let builder = _env.merge(&_usr, true, &mut _overrides);
697 
698         // Fill any missing with defaults
699         let config = builder.build();
700         validate_config(&config)?;
701 
702         Ok(Config {
703             inner: RwLock::new(Inner {
704                 templates: load_templates(&config.templates_folder),
705                 config,
706                 _env,
707                 _usr,
708                 _overrides,
709             }),
710         })
711     }
712 
update_config(&self, other: ConfigBuilder) -> Result<(), Error>713     pub fn update_config(&self, other: ConfigBuilder) -> Result<(), Error> {
714         // Remove default values
715         //let builder = other.remove(&self.inner.read().unwrap()._env);
716 
717         // TODO: Remove values that are defaults, above only checks those set by env and not the defaults
718         let builder = other;
719 
720         // Serialize now before we consume the builder
721         let config_str = serde_json::to_string_pretty(&builder)?;
722 
723         // Prepare the combined config
724         let mut overrides = Vec::new();
725         let config = {
726             let env = &self.inner.read().unwrap()._env;
727             env.merge(&builder, false, &mut overrides).build()
728         };
729         validate_config(&config)?;
730 
731         // Save both the user and the combined config
732         {
733             let mut writer = self.inner.write().unwrap();
734             writer.config = config;
735             writer._usr = builder;
736             writer._overrides = overrides;
737         }
738 
739         //Save to file
740         use std::{fs::File, io::Write};
741         let mut file = File::create(&*CONFIG_FILE)?;
742         file.write_all(config_str.as_bytes())?;
743 
744         Ok(())
745     }
746 
update_config_partial(&self, other: ConfigBuilder) -> Result<(), Error>747     fn update_config_partial(&self, other: ConfigBuilder) -> Result<(), Error> {
748         let builder = {
749             let usr = &self.inner.read().unwrap()._usr;
750             let mut _overrides = Vec::new();
751             usr.merge(&other, false, &mut _overrides)
752         };
753         self.update_config(builder)
754     }
755 
756     /// Tests whether an email's domain is allowed. A domain is allowed if it
757     /// is in signups_domains_whitelist, or if no whitelist is set (so there
758     /// are no domain restrictions in effect).
is_email_domain_allowed(&self, email: &str) -> bool759     pub fn is_email_domain_allowed(&self, email: &str) -> bool {
760         let e: Vec<&str> = email.rsplitn(2, '@').collect();
761         if e.len() != 2 || e[0].is_empty() || e[1].is_empty() {
762             warn!("Failed to parse email address '{}'", email);
763             return false;
764         }
765         let email_domain = e[0].to_lowercase();
766         let whitelist = self.signups_domains_whitelist();
767 
768         whitelist.is_empty() || whitelist.split(',').any(|d| d.trim() == email_domain)
769     }
770 
771     /// Tests whether signup is allowed for an email address, taking into
772     /// account the signups_allowed and signups_domains_whitelist settings.
is_signup_allowed(&self, email: &str) -> bool773     pub fn is_signup_allowed(&self, email: &str) -> bool {
774         if !self.signups_domains_whitelist().is_empty() {
775             // The whitelist setting overrides the signups_allowed setting.
776             self.is_email_domain_allowed(email)
777         } else {
778             self.signups_allowed()
779         }
780     }
781 
782     /// Tests whether the specified user is allowed to create an organization.
is_org_creation_allowed(&self, email: &str) -> bool783     pub fn is_org_creation_allowed(&self, email: &str) -> bool {
784         let users = self.org_creation_users();
785         if users.is_empty() || users == "all" {
786             true
787         } else if users == "none" {
788             false
789         } else {
790             let email = email.to_lowercase();
791             users.split(',').any(|u| u.trim() == email)
792         }
793     }
794 
delete_user_config(&self) -> Result<(), Error>795     pub fn delete_user_config(&self) -> Result<(), Error> {
796         crate::util::delete_file(&CONFIG_FILE)?;
797 
798         // Empty user config
799         let usr = ConfigBuilder::default();
800 
801         // Config now is env + defaults
802         let config = {
803             let env = &self.inner.read().unwrap()._env;
804             env.build()
805         };
806 
807         // Save configs
808         {
809             let mut writer = self.inner.write().unwrap();
810             writer.config = config;
811             writer._usr = usr;
812             writer._overrides = Vec::new();
813         }
814 
815         Ok(())
816     }
817 
private_rsa_key(&self) -> String818     pub fn private_rsa_key(&self) -> String {
819         format!("{}.pem", CONFIG.rsa_key_filename())
820     }
public_rsa_key(&self) -> String821     pub fn public_rsa_key(&self) -> String {
822         format!("{}.pub.pem", CONFIG.rsa_key_filename())
823     }
mail_enabled(&self) -> bool824     pub fn mail_enabled(&self) -> bool {
825         let inner = &self.inner.read().unwrap().config;
826         inner._enable_smtp && inner.smtp_host.is_some()
827     }
828 
get_duo_akey(&self) -> String829     pub fn get_duo_akey(&self) -> String {
830         if let Some(akey) = self._duo_akey() {
831             akey
832         } else {
833             let akey = crate::crypto::get_random_64();
834             let akey_s = data_encoding::BASE64.encode(&akey);
835 
836             // Save the new value
837             let builder = ConfigBuilder {
838                 _duo_akey: Some(akey_s.clone()),
839                 ..Default::default()
840             };
841             self.update_config_partial(builder).ok();
842 
843             akey_s
844         }
845     }
846 
847     /// Tests whether the admin token is set to a non-empty value.
is_admin_token_set(&self) -> bool848     pub fn is_admin_token_set(&self) -> bool {
849         let token = self.admin_token();
850 
851         token.is_some() && !token.unwrap().trim().is_empty()
852     }
853 
render_template<T: serde::ser::Serialize>( &self, name: &str, data: &T, ) -> Result<String, crate::error::Error>854     pub fn render_template<T: serde::ser::Serialize>(
855         &self,
856         name: &str,
857         data: &T,
858     ) -> Result<String, crate::error::Error> {
859         if CONFIG.reload_templates() {
860             warn!("RELOADING TEMPLATES");
861             let hb = load_templates(CONFIG.templates_folder());
862             hb.render(name, data).map_err(Into::into)
863         } else {
864             let hb = &CONFIG.inner.read().unwrap().templates;
865             hb.render(name, data).map_err(Into::into)
866         }
867     }
868 }
869 
870 use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, Renderable};
871 
load_templates<P>(path: P) -> Handlebars<'static> where P: AsRef<std::path::Path>,872 fn load_templates<P>(path: P) -> Handlebars<'static>
873 where
874     P: AsRef<std::path::Path>,
875 {
876     let mut hb = Handlebars::new();
877     // Error on missing params
878     hb.set_strict_mode(true);
879     // Register helpers
880     hb.register_helper("case", Box::new(case_helper));
881     hb.register_helper("jsesc", Box::new(js_escape_helper));
882 
883     macro_rules! reg {
884         ($name:expr) => {{
885             let template = include_str!(concat!("static/templates/", $name, ".hbs"));
886             hb.register_template_string($name, template).unwrap();
887         }};
888         ($name:expr, $ext:expr) => {{
889             reg!($name);
890             reg!(concat!($name, $ext));
891         }};
892     }
893 
894     // First register default templates here
895     reg!("email/email_header");
896     reg!("email/email_footer");
897     reg!("email/email_footer_text");
898 
899     reg!("email/change_email", ".html");
900     reg!("email/delete_account", ".html");
901     reg!("email/emergency_access_invite_accepted", ".html");
902     reg!("email/emergency_access_invite_confirmed", ".html");
903     reg!("email/emergency_access_recovery_approved", ".html");
904     reg!("email/emergency_access_recovery_initiated", ".html");
905     reg!("email/emergency_access_recovery_rejected", ".html");
906     reg!("email/emergency_access_recovery_reminder", ".html");
907     reg!("email/emergency_access_recovery_timed_out", ".html");
908     reg!("email/incomplete_2fa_login", ".html");
909     reg!("email/invite_accepted", ".html");
910     reg!("email/invite_confirmed", ".html");
911     reg!("email/new_device_logged_in", ".html");
912     reg!("email/pw_hint_none", ".html");
913     reg!("email/pw_hint_some", ".html");
914     reg!("email/send_2fa_removed_from_org", ".html");
915     reg!("email/send_single_org_removed_from_org", ".html");
916     reg!("email/send_org_invite", ".html");
917     reg!("email/send_emergency_access_invite", ".html");
918     reg!("email/twofactor_email", ".html");
919     reg!("email/verify_email", ".html");
920     reg!("email/welcome", ".html");
921     reg!("email/welcome_must_verify", ".html");
922     reg!("email/smtp_test", ".html");
923 
924     reg!("admin/base");
925     reg!("admin/login");
926     reg!("admin/settings");
927     reg!("admin/users");
928     reg!("admin/organizations");
929     reg!("admin/diagnostics");
930 
931     // And then load user templates to overwrite the defaults
932     // Use .hbs extension for the files
933     // Templates get registered with their relative name
934     hb.register_templates_directory(".hbs", path).unwrap();
935 
936     hb
937 }
938 
case_helper<'reg, 'rc>( h: &Helper<'reg, 'rc>, r: &'reg Handlebars, ctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> HelperResult939 fn case_helper<'reg, 'rc>(
940     h: &Helper<'reg, 'rc>,
941     r: &'reg Handlebars,
942     ctx: &'rc Context,
943     rc: &mut RenderContext<'reg, 'rc>,
944     out: &mut dyn Output,
945 ) -> HelperResult {
946     let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"case\""))?;
947     let value = param.value().clone();
948 
949     if h.params().iter().skip(1).any(|x| x.value() == &value) {
950         h.template().map(|t| t.render(r, ctx, rc, out)).unwrap_or(Ok(()))
951     } else {
952         Ok(())
953     }
954 }
955 
js_escape_helper<'reg, 'rc>( h: &Helper<'reg, 'rc>, _r: &'reg Handlebars, _ctx: &'rc Context, _rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> HelperResult956 fn js_escape_helper<'reg, 'rc>(
957     h: &Helper<'reg, 'rc>,
958     _r: &'reg Handlebars,
959     _ctx: &'rc Context,
960     _rc: &mut RenderContext<'reg, 'rc>,
961     out: &mut dyn Output,
962 ) -> HelperResult {
963     let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?;
964 
965     let no_quote = h.param(1).is_some();
966 
967     let value =
968         param.value().as_str().ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?;
969 
970     let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27");
971     if !no_quote {
972         escaped_value = format!("&quot;{}&quot;", escaped_value);
973     }
974 
975     out.write(&escaped_value)?;
976     Ok(())
977 }
978