1use std::collections::HashSet;
2use std::convert::{TryFrom, TryInto};
3use std::fs::File;
4use std::io::Read;
5use std::path::Path;
6
7use crypt4gh::error::Crypt4GHError;
8use crypt4gh::Keys;
9use fuser::MountOption;
10use itertools::Itertools;
11use rpassword::prompt_password;
12use serde::Deserialize;
13
14use crate::error::Crypt4GHFSError;
15
16const PASSPHRASE: &str = "C4GH_PASSPHRASE";
17
18#[derive(Deserialize, Debug, Copy, Clone)]
19#[serde(rename_all = "UPPERCASE")]
20pub enum LogLevel {
21 #[serde(alias = "CRITICAL")]
22 Critical,
23 Warn,
24 Info,
25 Debug,
26 #[serde(alias = "NOTSET")]
27 Trace,
28}
29
30#[derive(Deserialize, Debug, Copy, Clone)]
31#[serde(rename_all = "snake_case")]
32pub enum Facility {
33 Kern,
34 User,
35 Mail,
36 Daemon,
37 Auth,
38 Syslog,
39 Lpr,
40 News,
41 Uucp,
42 Cron,
43 Authpriv,
44 Ftp,
45 Local0,
46 Local1,
47 Local2,
48 Local3,
49 Local4,
50 Local5,
51 Local6,
52 Local7,
53}
54
55#[derive(Deserialize, Debug)]
56pub struct Default {
57 extensions: Vec<String>,
58}
59
60#[derive(Deserialize, Debug)]
61pub struct Fuse {
62 options: Option<Vec<String>>,
63 rootdir: String,
64 cache_directories: Option<bool>,
65}
66
67#[derive(Deserialize, Debug)]
68pub struct Crypt4GH {
69 #[serde(rename = "seckey")]
70 seckey_path: Option<String>,
71 recipients: Option<Vec<String>>,
72 include_myself_as_recipient: Option<bool>,
73}
74
75#[derive(Deserialize, Debug)]
76pub struct LoggerConfig {
77 pub log_level: LogLevel,
78 pub use_syslog: bool,
79 pub log_facility: Option<Facility>,
80}
81
82#[derive(Deserialize, Debug)]
83#[serde(rename_all = "UPPERCASE")]
84pub struct Config {
85 default: Default,
86 pub logger: LoggerConfig,
87 fuse: Fuse,
88 crypt4gh: Crypt4GH,
89}
90
91impl Config {
92 pub fn new_with_defaults(rootdir: String, seckey_path: Option<String>) -> Self {
93 Self {
94 default: Default { extensions: vec![] },
95 fuse: Fuse {
96 rootdir,
97 options: Some(vec!["ro".into(), "default_permissions".into(), "auto_unmount".into()]),
98 cache_directories: Some(true),
99 },
100 crypt4gh: Crypt4GH {
101 seckey_path,
102 recipients: Some(vec![]),
103 include_myself_as_recipient: Some(true),
104 },
105 logger: LoggerConfig {
106 log_level: LogLevel::Info,
107 use_syslog: false,
108 log_facility: None,
109 },
110 }
111 }
112
113 #[must_use]
114 pub fn with_extensions(mut self, extensions: Vec<String>) -> Self {
115 self.default.extensions = extensions;
116 self
117 }
118
119 #[must_use]
120 pub const fn with_log_level(mut self, log_level: LogLevel) -> Self {
121 self.logger.log_level = log_level;
122 self
123 }
124
125 pub fn get_options(&self) -> Vec<MountOption> {
126 self.fuse.options.clone().map_or_else(
127 || vec![MountOption::RW, MountOption::DefaultPermissions],
128 |options| {
129 options
130 .iter()
131 .map(String::as_str)
132 .map(str_to_mount_option)
133 .inspect(|option| {
134 log::info!("+ fuse option: {:?}", option);
135 })
136 .collect()
137 },
138 )
139 }
140
141 pub const fn get_cache(&self) -> bool {
142 if let Some(cache_directories) = self.fuse.cache_directories {
143 return cache_directories;
144 }
145 true
146 }
147
148 pub fn get_extensions(&self) -> Vec<String> {
149 self.default.extensions.clone()
150 }
151
152 pub fn get_secret_key(&self) -> Result<Option<Vec<u8>>, Crypt4GHFSError> {
153 match &self.crypt4gh.seckey_path {
154 Some(seckey_path_str) => {
155 let seckey_path = Path::new(&seckey_path_str);
156 log::info!("Loading secret key from {}", seckey_path.display());
157
158 if !seckey_path.is_file() {
159 return Err(Crypt4GHFSError::SecretNotFound(seckey_path.into()));
160 }
161
162 let callback: Box<dyn Fn() -> Result<String, Crypt4GHError>> = match std::env::var(PASSPHRASE) {
163 Ok(_) => {
164 log::warn!("Warning: Using a passphrase in an environment variable is insecure");
165 Box::new(|| std::env::var(PASSPHRASE).map_err(|e| Crypt4GHError::NoPassphrase(e.into())))
166 },
167 Err(_) => Box::new(|| {
168 prompt_password(format!("Passphrase for {}: ", seckey_path.display()))
169 .map_err(|e| Crypt4GHError::NoPassphrase(e.into()))
170 }),
171 };
172
173 let key = crypt4gh::keys::get_private_key(seckey_path, callback)
174 .map_err(|e| Crypt4GHFSError::SecretKeyError(e.to_string()))?;
175
176 Ok(Some(key))
177 },
178 None => Ok(None),
179 }
180 }
181
182 pub fn get_recipients(&self, seckey: &[u8]) -> HashSet<Keys> {
183 let recipient_paths = &self.crypt4gh.recipients.clone().unwrap_or_default();
184
185 let mut recipient_pubkeys: HashSet<_> = recipient_paths
186 .iter()
187 .map(Path::new)
188 .filter(|path| path.exists())
189 .filter_map(|path| {
190 log::debug!("Recipient pubkey path: {}", path.display());
191 crypt4gh::keys::get_public_key(path).ok()
192 })
193 .unique()
194 .map(|key| Keys {
195 method: 0,
196 privkey: seckey.to_vec(),
197 recipient_pubkey: key,
198 })
199 .collect();
200
201 if self.crypt4gh.include_myself_as_recipient.unwrap_or(true) {
202 let k = crypt4gh::keys::get_public_key_from_private_key(seckey)
203 .expect("Unable to extract public key from seckey");
204 recipient_pubkeys.insert(Keys {
205 method: 0,
206 privkey: seckey.to_vec(),
207 recipient_pubkey: k,
208 });
209 }
210
211 recipient_pubkeys
212 }
213
214 pub const fn get_log_level(&self) -> LogLevel {
215 self.logger.log_level
216 }
217
218 pub fn get_rootdir(&self) -> String {
219 self.fuse.rootdir.to_string()
220 }
221
222 pub fn from_file(mut config_file: File) -> Result<Self, Crypt4GHFSError> {
223 let mut config_string = String::new();
224 config_file
225 .read_to_string(&mut config_string)
226 .map_err(|e| Crypt4GHFSError::BadConfig(e.to_string()))?;
227 let config_toml = toml::from_str(config_string.as_str()).map_err(|e| Crypt4GHFSError::BadConfig(e.to_string()));
228 config_toml
229 }
230
231 pub fn get_facility(&self) -> syslog::Facility {
232 match self.logger.log_facility.unwrap_or(Facility::User) {
233 Facility::Kern => syslog::Facility::LOG_KERN,
234 Facility::User => syslog::Facility::LOG_USER,
235 Facility::Mail => syslog::Facility::LOG_MAIL,
236 Facility::Daemon => syslog::Facility::LOG_DAEMON,
237 Facility::Auth => syslog::Facility::LOG_AUTH,
238 Facility::Syslog => syslog::Facility::LOG_SYSLOG,
239 Facility::Lpr => syslog::Facility::LOG_LPR,
240 Facility::News => syslog::Facility::LOG_NEWS,
241 Facility::Uucp => syslog::Facility::LOG_UUCP,
242 Facility::Cron => syslog::Facility::LOG_CRON,
243 Facility::Authpriv => syslog::Facility::LOG_AUTHPRIV,
244 Facility::Ftp => syslog::Facility::LOG_FTP,
245 Facility::Local0 => syslog::Facility::LOG_LOCAL0,
246 Facility::Local1 => syslog::Facility::LOG_LOCAL1,
247 Facility::Local2 => syslog::Facility::LOG_LOCAL2,
248 Facility::Local3 => syslog::Facility::LOG_LOCAL3,
249 Facility::Local4 => syslog::Facility::LOG_LOCAL4,
250 Facility::Local5 => syslog::Facility::LOG_LOCAL5,
251 Facility::Local6 => syslog::Facility::LOG_LOCAL6,
252 Facility::Local7 => syslog::Facility::LOG_LOCAL7,
253 }
254 }
255
256 pub fn setup_logger(&self) -> Result<(), Crypt4GHFSError> {
257 let log_level: LogLevel = if let Ok(log_level_str) = std::env::var("RUST_LOG") {
258 log_level_str
259 .as_str()
260 .try_into()
261 .expect("Unable to parse RUST_LOG environment variable")
262 }
263 else {
264 let log_level = self.get_log_level();
265 let log_level_str = match log_level {
266 LogLevel::Critical => "error",
267 LogLevel::Warn => "warn",
268 LogLevel::Info => "info",
269 LogLevel::Debug => "debug",
270 LogLevel::Trace => "trace",
271 };
272 std::env::set_var("RUST_LOG", log_level_str);
273 log_level
274 };
275
276 if self.logger.use_syslog {
278 syslog::init(self.get_facility(), log_level.into(), None)?;
279 }
280 else {
281 let _ = pretty_env_logger::try_init(); }
283
284 Ok(())
285 }
286}
287
288impl TryFrom<&str> for LogLevel {
289 type Error = Crypt4GHFSError;
290
291 fn try_from(level: &str) -> Result<Self, Self::Error> {
292 match level {
293 "error" => Ok(Self::Critical),
294 "warn" => Ok(Self::Warn),
295 "info" => Ok(Self::Info),
296 "debug" => Ok(Self::Debug),
297 "trace" => Ok(Self::Trace),
298 _ => Err(Crypt4GHFSError::BadConfig("Wrong log level".into())),
299 }
300 }
301}
302
303impl From<LogLevel> for log::LevelFilter {
304 fn from(val: LogLevel) -> Self {
305 match val {
306 LogLevel::Critical => Self::Error,
307 LogLevel::Warn => Self::Warn,
308 LogLevel::Info => Self::Info,
309 LogLevel::Debug => Self::Debug,
310 LogLevel::Trace => Self::Trace,
311 }
312 }
313}
314
315fn str_to_mount_option(s: &str) -> MountOption {
316 match s {
317 "auto_unmount" => MountOption::AutoUnmount,
318 "allow_other" => MountOption::AllowOther,
319 "allow_root" => MountOption::AllowRoot,
320 "default_permissions" => MountOption::DefaultPermissions,
321 "dev" => MountOption::Dev,
322 "nodev" => MountOption::NoDev,
323 "suid" => MountOption::Suid,
324 "nosuid" => MountOption::NoSuid,
325 "ro" => MountOption::RO,
326 "rw" => MountOption::RW,
327 "exec" => MountOption::Exec,
328 "noexec" => MountOption::NoExec,
329 "atime" => MountOption::Atime,
330 "noatime" => MountOption::NoAtime,
331 "dirsync" => MountOption::DirSync,
332 "sync" => MountOption::Sync,
333 "async" => MountOption::Async,
334 x if x.starts_with("fsname=") => MountOption::FSName(x[7..].into()),
335 x if x.starts_with("subtype=") => MountOption::Subtype(x[8..].into()),
336 x => MountOption::CUSTOM(x.into()),
337 }
338}