1 //! Managing the process Routinator runs in.
2 
3 use std::{fs, io};
4 use std::future::Future;
5 use std::path::Path;
6 use std::sync::mpsc;
7 use std::sync::{Mutex, RwLock};
8 use bytes::Bytes;
9 use chrono::{DateTime, Utc};
10 use log::{error, LevelFilter};
11 use tokio::runtime::Runtime;
12 use crate::config::{Config, LogTarget};
13 use crate::error::Failed;
14 
15 
16 //------------ Process -------------------------------------------------------
17 
18 /// A representation of the process Routinator runs in.
19 ///
20 /// This type provides access to the configuration and the environment in a
21 /// platform independent way.
22 pub struct Process {
23     config: Config,
24     service: Option<ServiceImpl>,
25 }
26 
27 impl Process {
init() -> Result<(), Failed>28     pub fn init() -> Result<(), Failed> {
29         Self::init_logging()?;
30 
31         Ok(())
32     }
33 
34     /// Creates a new process object.
35     ///
new(config: Config) -> Self36     pub fn new(config: Config) -> Self {
37         Process {
38             service: Some(ServiceImpl::new(&config)),
39             config
40         }
41     }
42 
43     /// Returns a reference to the config.
config(&self) -> &Config44     pub fn config(&self) -> &Config {
45         &self.config
46     }
47 
48     /// Returns an exclusive reference to the config.
config_mut(&mut self) -> &mut Config49     pub fn config_mut(&mut self) -> &mut Config {
50         &mut self.config
51     }
52 }
53 
54 /// # Logging
55 ///
56 impl Process {
57     /// Initialize logging.
58     ///
59     /// All diagnostic output of Routinator is done via logging, never to
60     /// stderr directly. Thus, it is important to initalize logging before
61     /// doing anything else that may result in such output. This function
62     /// does exactly that. It sets a maximum log level of `warn`, leading
63     /// only printing important information, and directs all logging to
64     /// stderr.
init_logging() -> Result<(), Failed>65     fn init_logging() -> Result<(), Failed> {
66         log::set_max_level(LevelFilter::Warn);
67         if let Err(err) = log_reroute::init() {
68             eprintln!("Failed to initialize logger: {}.\nAborting.", err);
69             return Err(Failed)
70         };
71         let dispatch = fern::Dispatch::new()
72             .level(LevelFilter::Error)
73             .chain(io::stderr())
74             .into_log().1;
75         log_reroute::reroute_boxed(dispatch);
76         Ok(())
77     }
78 
79     /// Switches logging to the configured target.
80     ///
81     /// Once the configuration has been successfully loaded, logging should
82     /// be switched to whatever the user asked for via this method.
83     #[allow(unused_variables)] // for cfg(not(unix))
switch_logging( &self, daemon: bool, with_output: bool ) -> Result<Option<LogOutput>, Failed>84     pub fn switch_logging(
85         &self,
86         daemon: bool,
87         with_output: bool
88     ) -> Result<Option<LogOutput>, Failed> {
89         let logger = match self.config.log_target {
90             #[cfg(unix)]
91             LogTarget::Default(fac) => {
92                 if daemon {
93                     self.syslog_logger(fac)?
94                 }
95                 else {
96                     self.stderr_logger(false)
97                 }
98             }
99             #[cfg(unix)]
100             LogTarget::Syslog(fac) => {
101                 self.syslog_logger(fac)?
102             }
103             LogTarget::Stderr => {
104                 self.stderr_logger(daemon)
105             }
106             LogTarget::File(ref path) => {
107                 self.file_logger(path)?
108             }
109         };
110         let (logger, res) = if with_output {
111             let (tx, res) = LogOutput::new();
112             let logger = logger.chain(tx);
113             (logger, Some(res))
114         }
115         else {
116             (logger, None)
117         };
118 
119         log_reroute::reroute_boxed(logger.into_log().1);
120         log::set_max_level(self.config.log_level);
121         Ok(res)
122     }
123 
124     /// Creates a syslog logger and configures correctly.
125     #[cfg(unix)]
syslog_logger( &self, facility: syslog::Facility ) -> Result<fern::Dispatch, Failed>126     fn syslog_logger(
127         &self,
128         facility: syslog::Facility
129     ) -> Result<fern::Dispatch, Failed> {
130         let process = std::env::current_exe().ok().and_then(|path|
131             path.file_name()
132                 .and_then(std::ffi::OsStr::to_str)
133                 .map(ToString::to_string)
134         ).unwrap_or_else(|| String::from("routinator"));
135         let formatter = syslog::Formatter3164 {
136             facility,
137             hostname: None,
138             process,
139             pid: nix::unistd::getpid().as_raw()
140         };
141         let logger = syslog::unix(formatter.clone()).or_else(|_| {
142             syslog::tcp(formatter.clone(), ("127.0.0.1", 601))
143         }).or_else(|_| {
144             syslog::udp(formatter, ("127.0.0.1", 0), ("127.0.0.1", 514))
145         });
146         match logger {
147             Ok(logger) => {
148                 Ok(self.fern_logger(false).chain(
149                     Box::new(syslog::BasicLogger::new(logger))
150                     as Box::<dyn log::Log>
151                 ))
152             }
153             Err(err) => {
154                 error!("Cannot connect to syslog: {}", err);
155                 Err(Failed)
156             }
157         }
158     }
159 
160     /// Creates a stderr logger.
161     ///
162     /// If we are in daemon mode, we add a timestamp to the output.
stderr_logger(&self, daemon: bool) -> fern::Dispatch163     fn stderr_logger(&self, daemon: bool) -> fern::Dispatch {
164         self.fern_logger(daemon).chain(io::stderr())
165     }
166 
167     /// Creates a file logger using the file provided by `path`.
file_logger(&self, path: &Path) -> Result<fern::Dispatch, Failed>168     fn file_logger(&self, path: &Path) -> Result<fern::Dispatch, Failed> {
169         let file = match fern::log_file(path) {
170             Ok(file) => file,
171             Err(err) => {
172                 error!(
173                     "Failed to open log file '{}': {}",
174                     path.display(), err
175                 );
176                 return Err(Failed)
177             }
178         };
179         Ok(self.fern_logger(true).chain(file))
180     }
181 
182     /// Creates and returns a fern logger.
fern_logger(&self, timestamp: bool) -> fern::Dispatch183     fn fern_logger(&self, timestamp: bool) -> fern::Dispatch {
184         let mut res = fern::Dispatch::new();
185         if timestamp {
186             res = res.format(|out, message, _record| {
187                 out.finish(format_args!(
188                     "{} {} {}",
189                     chrono::Local::now().format("[%Y-%m-%d %H:%M:%S]"),
190                     _record.module_path().unwrap_or(""),
191                     message
192                 ))
193             });
194         }
195         res = res
196             .level(self.config.log_level)
197             .level_for("rustls", LevelFilter::Error);
198         if self.config.log_level == LevelFilter::Debug {
199             res = res
200                 .level_for("tokio_reactor", LevelFilter::Info)
201                 .level_for("hyper", LevelFilter::Info)
202                 .level_for("reqwest", LevelFilter::Info)
203                 .level_for("h2", LevelFilter::Info)
204                 .level_for("sled", LevelFilter::Info);
205         }
206         res
207     }
208 }
209 
210 
211 /// # System Service
212 ///
213 impl Process {
214     /// Sets up the system service.
215     ///
216     /// If `detach` is `true`, the service will detach from the current
217     /// process and keep running in the background.
218     ///
219     /// After the method returns, we will be running in the final process
220     /// but still have the same privileges as when we were initially started.
221     /// Whether there is still a terminal and standard stream available
222     /// depends on the config.
223     ///
224     /// This method may encounter and log errors after detaching. You should
225     /// therefore call `switch_logging` before this method.
setup_service(&mut self, detach: bool) -> Result<(), Failed>226     pub fn setup_service(&mut self, detach: bool) -> Result<(), Failed> {
227         self.service.as_mut().unwrap().setup_service(&self.config, detach)
228     }
229 
230     /// Drops privileges.
231     ///
232     /// If requested via the config, this method will drop all potentially
233     /// elevated privileges. This may include loosing root or system
234     /// administrator permissions and change the file system root.
drop_privileges(&mut self) -> Result<(), Failed>235     pub fn drop_privileges(&mut self) -> Result<(), Failed> {
236         self.service.take().unwrap().drop_privileges(&mut self.config)
237     }
238 }
239 
240 
241 /// # Directory Management
242 ///
243 impl Process {
244     /// Creates the cache directory.
245     ///
246     /// This will also change ownership of the directory if necessary.
create_cache_dir(&self) -> Result<(), Failed>247     pub fn create_cache_dir(&self) -> Result<(), Failed> {
248         if let Err(err) = fs::create_dir_all(&self.config.cache_dir) {
249             error!("Fatal: failed to create cache directory {}: {}",
250                 self.config.cache_dir.display(), err
251             );
252             return Err(Failed)
253         }
254         ServiceImpl::prepare_cache_dir(&self.config)
255     }
256 }
257 
258 
259 /// # Tokio Runtime
260 ///
261 impl Process {
262     /// Returns a Tokio runtime based on the configuration.
runtime(&self) -> Result<Runtime, Failed>263     pub fn runtime(&self) -> Result<Runtime, Failed> {
264         Runtime::new().map_err(|err| {
265             error!("Failed to create runtime: {}", err);
266             Failed
267         })
268     }
269 
270     /// Runs a future to completion atop a Tokio runtime.
block_on<F: Future>(&self, future: F) -> Result<F::Output, Failed>271     pub fn block_on<F: Future>(&self, future: F) -> Result<F::Output, Failed> {
272         Ok(self.runtime()?.block_on(future))
273     }
274 }
275 
276 
277 //------------ LogOutput -----------------------------------------------------
278 
279 #[derive(Debug)]
280 pub struct LogOutput {
281     queue: Mutex<mpsc::Receiver<String>>,
282     current: RwLock<(Bytes, DateTime<Utc>)>,
283 }
284 
285 impl LogOutput {
new() -> (mpsc::Sender<String>, Self)286     fn new() -> (mpsc::Sender<String>, Self) {
287         let (tx, rx) = mpsc::channel();
288         let res = LogOutput {
289             queue: Mutex::new(rx),
290             current: RwLock::new((
291                 "Initial validation ongoing. Please wait.".into(),
292                 Utc::now()
293             ))
294         };
295         (tx, res)
296     }
297 
start(&self)298     pub fn start(&self) {
299         self.current.write().expect("Log lock got poisoned").1 = Utc::now();
300     }
301 
flush(&self)302     pub fn flush(&self) {
303         let queue = self.queue.lock().expect("Log queue lock got poisoned");
304         let started = self.current.read().expect("Log lock got poisoned").1;
305 
306         let mut content = format!(
307             "Log from validation run started at {}\n\n", started
308         );
309         for item in queue.try_iter() {
310             content.push_str(&item)
311         }
312         self.current.write().expect("Log lock got poisoned").0 = content.into();
313     }
314 
get_output(&self) -> Bytes315     pub fn get_output(&self) -> Bytes {
316         self.current.read().expect("Log lock got poisoned").0.clone()
317     }
318 }
319 
320 
321 //------------ Platform-dependent Service Implementation ---------------------
322 
323 #[cfg(unix)]
324 use self::unix::ServiceImpl;
325 
326 #[cfg(not(unix))]
327 use self::noop::ServiceImpl;
328 
329 
330 /// Unix “Service.”
331 ///
332 /// This implementation is based on the
333 /// [daemonize](https://github.com/knsd/daemonize) crate.
334 ///
335 #[cfg(unix)]
336 mod unix {
337     use std::env::set_current_dir;
338     use std::ffi::CString;
339     use std::os::unix::io::RawFd;
340     use std::path::Path;
341     use log::error;
342     use nix::libc;
343     use nix::fcntl::{flock, open, FlockArg, OFlag};
344     use nix::unistd::{
345         chown, chroot, fork, getpid, setgid, setuid, write, Gid, Uid
346     };
347     use nix::sys::stat::Mode;
348     use crate::config::Config;
349     use crate::error::Failed;
350 
351     #[derive(Debug, Default)]
352     pub struct ServiceImpl {
353         pid_file: Option<RawFd>,
354         uid: Option<Uid>,
355         gid: Option<Gid>,
356     }
357 
358     impl ServiceImpl {
new(_config: &Config) -> Self359         pub fn new(_config: &Config) -> Self {
360             ServiceImpl::default()
361         }
362 
setup_service( &mut self, config: &Config, detach: bool ) -> Result<(), Failed>363         pub fn setup_service(
364             &mut self, config: &Config, detach: bool
365         ) -> Result<(), Failed> {
366             if let Some(pid_file) = config.pid_file.as_ref() {
367                 self.create_pid_file(pid_file)?
368             }
369             if detach {
370                 self.perform_fork()?
371             }
372             if let Some(path) = config.working_dir.as_ref() {
373                 if let Err(err) = set_current_dir(&path) {
374                     error!("Fatal: failed to set working directory {}: {}",
375                         path.display(), err
376                     );
377                     return Err(Failed)
378                 }
379             }
380             // set_sid
381             // umask
382             if detach {
383                 self.perform_fork()?
384             }
385             // redirect_standard_streams
386             self.uid = Self::get_user(config)?;
387             self.gid = Self::get_group(config)?;
388             // chown_pid_file
389 
390             Ok(())
391         }
392 
drop_privileges( self, config: &mut Config ) -> Result<(), Failed>393         pub fn drop_privileges(
394             self, config: &mut Config
395         ) -> Result<(), Failed> {
396             config.adjust_chroot_paths()?;
397             if let Some(path) = config.chroot.as_ref() {
398                 if let Err(err) = chroot(path) {
399                     error!("Fatal: cannot chroot to '{}': {}'",
400                         path.display(), err
401                     );
402                     return Err(Failed)
403                 }
404             }
405             if let Some(gid) = self.gid {
406                 if let Err(err) = setgid(gid) {
407                     error!("Fatal: failed to set group: {}", err);
408                     return Err(Failed)
409                 }
410             }
411             if let Some(uid) = self.uid {
412                 if let Err(err) = setuid(uid) {
413                     error!("Fatal: failed to set user: {}", err);
414                     return Err(Failed)
415                 }
416             }
417             self.write_pid_file()?;
418 
419             Ok(())
420         }
421 
create_pid_file(&mut self, path: &Path) -> Result<(), Failed>422         fn create_pid_file(&mut self, path: &Path) -> Result<(), Failed> {
423             let fd = match open(
424                 path,
425                 OFlag::O_WRONLY | OFlag::O_CREAT,
426                 Mode::from_bits_truncate(0o666)
427             ) {
428                 Ok(fd) => fd,
429                 Err(err) => {
430                     error!("Fatal: failed to create PID file {}: {}",
431                         path.display(), err
432                     );
433                     return Err(Failed)
434                 }
435             };
436             if let Err(err) = flock(fd, FlockArg::LockExclusiveNonblock) {
437                 error!("Fatal: cannot lock PID file {}: {}",
438                     path.display(), err
439                 );
440                 return Err(Failed)
441             }
442             self.pid_file = Some(fd);
443             Ok(())
444         }
445 
write_pid_file(&self) -> Result<(), Failed>446         fn write_pid_file(&self) -> Result<(), Failed> {
447             if let Some(pid_file) = self.pid_file {
448                 let pid = format!("{}", getpid());
449                 match write(pid_file, pid.as_bytes()) {
450                     Ok(len) if len == pid.len() => {}
451                     Ok(_) => {
452                         error!(
453                             "Fatal: failed to write PID to PID file: \
454                              short write"
455                         );
456                         return Err(Failed)
457                     }
458                     Err(err) => {
459                         error!(
460                             "Fatal: failed to write PID to PID file: {}", err
461                         );
462                         return Err(Failed)
463                     }
464                 }
465             }
466             Ok(())
467         }
468 
perform_fork(&self) -> Result<(), Failed>469         fn perform_fork(&self) -> Result<(), Failed> {
470             match unsafe { fork() } {
471                 Ok(res) => {
472                     if res.is_parent() {
473                         std::process::exit(0)
474                     }
475                     Ok(())
476                 }
477                 Err(err) => {
478                     error!("Fatal: failed to detach: {}", err);
479                     Err(Failed)
480                 }
481             }
482         }
483 
get_user(config: &Config) -> Result<Option<Uid>, Failed>484         fn get_user(config: &Config) -> Result<Option<Uid>, Failed> {
485             let name = match config.user.as_ref() {
486                 Some(name) => name,
487                 None => return Ok(None)
488             };
489             let cname = match CString::new(name.clone()) {
490                 Ok(name) => name,
491                 Err(_) => {
492                     error!("Fatal: invalid user ID '{}'", name);
493                     return Err(Failed)
494                 }
495             };
496 
497             let uid = unsafe {
498                 let ptr = libc::getpwnam(cname.as_ptr() as *const libc::c_char);
499                 if ptr.is_null() {
500                     None
501                 }
502                 else {
503                     let s = &*ptr;
504                     Some(s.pw_uid)
505                 }
506             };
507             match uid {
508                 Some(uid) => Ok(Some(Uid::from_raw(uid))),
509                 None => {
510                     error!("Fatal: unknown user ID '{}'", name);
511                     Err(Failed)
512                 }
513             }
514         }
515 
get_group(config: &Config) -> Result<Option<Gid>, Failed>516         fn get_group(config: &Config) -> Result<Option<Gid>, Failed> {
517             let name = match config.group.as_ref() {
518                 Some(name) => name,
519                 None => return Ok(None)
520             };
521             let cname = match CString::new(name.clone()) {
522                 Ok(name) => name,
523                 Err(_) => {
524                     error!("Fatal: invalid user ID '{}'", name);
525                     return Err(Failed)
526                 }
527             };
528 
529             let gid = unsafe {
530                 let ptr = libc::getgrnam(cname.as_ptr() as *const libc::c_char);
531                 if ptr.is_null() {
532                     None
533                 }
534                 else {
535                     let s = &*ptr;
536                     Some(s.gr_gid)
537                 }
538             };
539             match gid {
540                 Some(gid) => Ok(Some(Gid::from_raw(gid))),
541                 None => {
542                     error!("Fatal: unknown group ID '{}'", name);
543                     Err(Failed)
544                 }
545             }
546         }
547 
prepare_cache_dir(config: &Config) -> Result<(), Failed>548         pub fn prepare_cache_dir(config: &Config) -> Result<(), Failed> {
549             let uid = Self::get_user(config)?;
550             let gid = Self::get_group(config)?;
551             if uid.is_some() || gid.is_some() {
552                 if let Err(err) = chown(&config.cache_dir, uid, gid) {
553                     error!(
554                         "Fatal: failed to change ownership of cache dir \
555                          {}: {}",
556                         config.cache_dir.display(),
557                         err
558                     );
559                     return Err(Failed)
560                 }
561             }
562             Ok(())
563         }
564     }
565 }
566 
567 #[cfg(not(unix))]
568 mod noop {
569     use crate::error::Failed;
570     use crate::config::Config;
571 
572     pub struct ServiceImpl;
573 
574     impl ServiceImpl {
new(_config: &Config) -> Self575         pub fn new(_config: &Config) -> Self {
576             ServiceImpl
577         }
578 
setup_service( &mut self, _config: &Config, _detach: bool ) -> Result<(), Failed>579         pub fn setup_service(
580             &mut self, _config: &Config, _detach: bool
581         ) -> Result<(), Failed> {
582             Ok(())
583         }
584 
drop_privileges( self, _config: &mut Config ) -> Result<(), Failed>585         pub fn drop_privileges(
586             self, _config: &mut Config
587         ) -> Result<(), Failed> {
588             Ok(())
589         }
590 
prepare_cache_dir(_config: &Config) -> Result<(), Failed>591         pub fn prepare_cache_dir(_config: &Config) -> Result<(), Failed> {
592             Ok(())
593         }
594     }
595 }
596