1 //! Configuration.
2 //!
3 //! This module primarily contains the type [`Config`] that holds all the
4 //! configuration used by Routinator. It can be loaded both from a TOML
5 //! formatted config file and command line options.
6 //!
7 //! [`Config`]: struct.Config.html
8 
9 use std::{env, fmt, fs};
10 use std::collections::HashMap;
11 use std::convert::{TryFrom, TryInto};
12 use std::io::Read;
13 use std::net::{IpAddr, SocketAddr};
14 use std::path::{Path, PathBuf};
15 use std::str::FromStr;
16 use std::time::Duration;
17 use clap::{App, Arg, ArgMatches, crate_version};
18 use dirs::home_dir;
19 use log::{LevelFilter, error};
20 #[cfg(unix)] use syslog::Facility;
21 use crate::error::Failed;
22 
23 
24 //------------ Defaults for Some Values --------------------------------------
25 
26 /// Are we doing strict validation by default?
27 const DEFAULT_STRICT: bool = false;
28 
29 /// The default timeout for running rsync commands in seconds.
30 const DEFAULT_RSYNC_TIMEOUT: u64 = 300;
31 
32 /// Are we leaving the repository dirty by default?
33 const DEFAULT_DIRTY_REPOSITORY: bool = false;
34 
35 /// The default refresh interval in seconds.
36 const DEFAULT_REFRESH: u64 = 600;
37 
38 /// The default RTR retry interval in seconds.
39 const DEFAULT_RETRY: u64 = 600;
40 
41 /// The default RTR expire interval in seconds.
42 const DEFAULT_EXPIRE: u64 = 7200;
43 
44 /// The default number of VRP diffs to keep.
45 const DEFAULT_HISTORY_SIZE: usize = 10;
46 
47 /// The default for the RRDP timeout.
48 const DEFAULT_RRDP_TIMEOUT: Duration = Duration::from_secs(300);
49 
50 /// The default for the RRDP fallback time.
51 const DEFAULT_RRDP_FALLBACK_TIME: Duration = Duration::from_secs(3600);
52 
53 /// The default for the maximum number of deltas.
54 const DEFAULT_RRDP_MAX_DELTA_COUNT: usize = 100;
55 
56 /// The default RRDP HTTP User Agent header value to send.
57 const DEFAULT_RRDP_USER_AGENT: &str = concat!("Routinator/", crate_version!());
58 
59 /// The default RTR TCP keepalive.
60 const DEFAULT_RTR_TCP_KEEPALIVE: Option<Duration>
61     = Some(Duration::from_secs(60));
62 
63 /// The default stale policy.
64 const DEFAULT_STALE_POLICY: FilterPolicy = FilterPolicy::Reject;
65 
66 /// The default unsafe-vrps policy.
67 const DEFAULT_UNSAFE_VRPS_POLICY: FilterPolicy = FilterPolicy::Warn;
68 
69 /// The default unknown-objects policy.
70 const DEFAULT_UNKNOWN_OBJECTS_POLICY: FilterPolicy = FilterPolicy::Warn;
71 
72 /// The default maximum object size.
73 const DEFAULT_MAX_OBJECT_SIZE: u64 = 20_000_000;
74 
75 /// The default maximum CA depth.
76 const DEFAULT_MAX_CA_DEPTH: usize = 32;
77 
78 
79 //------------ Config --------------------------------------------------------
80 
81 /// Routinator configuration.
82 ///
83 /// This type contains both the basic configuration of Routinator, such as
84 /// where to keep the repository and how to update it, as well as the
85 /// configuration for server mode.
86 ///
87 /// All values are public and can be accessed directly.
88 ///
89 /// The two functions [`config_args`] and [`server_args`] can be used to
90 /// create the clap application. Its matches can then be used to create the
91 /// basic config via [`from_arg_matches`]. If the RTR server configuration is
92 /// necessary, it can be added via [`apply_server_arg_matches`] from the
93 /// server subcommand matches.
94 ///
95 /// The methods [`init_logging`] and [`switch_logging`] can be used to
96 /// configure logging according to the strategy provided by the configuration.
97 /// On Unix systems only, the method [`daemonize`] creates a correctly
98 /// configured `Daemonizer`. Finally, [`to_toml`] can be used to produce a
99 /// TOML value that contains a configuration file content representing the
100 /// current configuration.
101 ///
102 /// [`config_args`]: #method.config_args
103 /// [`server_args`]: #method.server_args
104 /// [`from_arg_matches`]: #method.from_arg_matches
105 /// [`apply_server_arg_matches`]: #method.apply_server_arg_matches
106 /// [`init_logging`]: #method.init_logging
107 /// [`switch_logging`]: #method.switch_logging
108 /// [`daemonize`]: #method.daemonize
109 /// [`to_toml`]: #method.to_toml
110 #[derive(Clone, Debug, Eq, PartialEq)]
111 pub struct Config {
112     /// Path to the directory that contains the repository cache.
113     pub cache_dir: PathBuf,
114 
115     /// Path to the directory that contains the trust anchor locators.
116     pub tal_dir: PathBuf,
117 
118     /// Paths to the local exceptions files.
119     pub exceptions: Vec<PathBuf>,
120 
121     /// Should we do strict validation?
122     ///
123     /// See [the relevant RPKI crate documentation](https://github.com/NLnetLabs/rpki-rs/blob/master/doc/relaxed-validation.md)
124     /// for more information.
125     pub strict: bool,
126 
127     /// How should we deal with stale objects?
128     ///
129     /// Stale objects are manifests and CRLs that have a `next_update` field
130     /// in the past. The current version of the protocol leaves the decision
131     /// how to interpret stale objects to local policy. This configuration
132     /// value configures this policy.
133     ///
134     /// Since the upcoming version of the protocol clarifies that these
135     /// objects should be rejected, this is the default policy.
136     pub stale: FilterPolicy,
137 
138     /// How should we deal with unsafe VRPs?
139     ///
140     /// Unsafe VRPs have their prefix intersect with a prefix held by a
141     /// rejected CA. Allowing such VRPs may lead to legitimate routes being
142     /// flagged as RPKI invalid. To avoid this, these can VRPs can be
143     /// filtered.
144     ///
145     /// The default for now is to warn about them.
146     pub unsafe_vrps: FilterPolicy,
147 
148     /// How to deal with unknown RPKI object types.
149     pub unknown_objects: FilterPolicy,
150 
151     /// Allow dubious host names.
152     pub allow_dubious_hosts: bool,
153 
154     /// Should we wipe the cache before starting?
155     ///
156     /// (This option is only available on command line.)
157     pub fresh: bool,
158 
159     /// Whether to disable rsync.
160     pub disable_rsync: bool,
161 
162     /// The command to run for rsync.
163     pub rsync_command: String,
164 
165     /// Optional arguments passed to rsync.
166     ///
167     /// If these are present, they overide the arguments automatically
168     /// determined otherwise. Thus, `Some<Vec::new()>` will supress all
169     /// arguments.
170     pub rsync_args: Option<Vec<String>>,
171 
172     /// Timeout for rsync commands.
173     pub rsync_timeout: Duration,
174 
175     /// Whether to disable RRDP.
176     pub disable_rrdp: bool,
177 
178     /// Time since last update of an RRDP repository before fallback to rsync.
179     pub rrdp_fallback_time: Duration,
180 
181     /// The maxmimm number of deltas we allow before using snapshot.
182     pub rrdp_max_delta_count: usize,
183 
184     /// RRDP timeout in seconds.
185     ///
186     /// If this is None, no timeout is set.
187     pub rrdp_timeout: Option<Duration>,
188 
189     /// Optional RRDP connect timeout in seconds.
190     pub rrdp_connect_timeout: Option<Duration>,
191 
192     /// Optional RRDP local address to bind to when doing requests.
193     pub rrdp_local_addr: Option<IpAddr>,
194 
195     /// RRDP additional root certificates for HTTPS.
196     ///
197     /// These do not overide the default system root certififcates.
198     pub rrdp_root_certs: Vec<PathBuf>,
199 
200     /// RRDP HTTP proxies.
201     pub rrdp_proxies: Vec<String>,
202 
203     /// RRDP HTTP User Agent.
204     pub rrdp_user_agent: String,
205 
206     /// Should we keep RRDP responses and if so where?
207     pub rrdp_keep_responses: Option<PathBuf>,
208 
209     /// Optional size limit for objects.
210     pub max_object_size: Option<u64>,
211 
212     /// Maxium length of the CA chain.
213     pub max_ca_depth: usize,
214 
215     /// Whether to not cleanup the repository directory after a validation run.
216     ///
217     /// If this is `false` and update has not been disabled otherwise, all
218     /// data for rsync modules (if rsync is enabled) and RRDP servers (if
219     /// RRDP is enabled) that have not been used during validation will be
220     /// deleted.
221     pub dirty_repository: bool,
222 
223     /// Number of threads used during validation.
224     pub validation_threads: usize,
225 
226     /// The refresh interval for repository validation.
227     pub refresh: Duration,
228 
229     /// The RTR retry inverval to be announced to a client.
230     pub retry: Duration,
231 
232     /// The RTR expire time to be announced to a client.
233     pub expire: Duration,
234 
235     /// How many diffs to keep in the history.
236     pub history_size: usize,
237 
238     /// Addresses to listen on for RTR TCP transport connections.
239     pub rtr_listen: Vec<SocketAddr>,
240 
241     /// Addresses to listen on for HTTP monitoring connectsion.
242     pub http_listen: Vec<SocketAddr>,
243 
244     /// Whether to get the listening sockets from systemd.
245     pub systemd_listen: bool,
246 
247     /// The length of the TCP keep-alive timeout for RTR TCP sockets.
248     ///
249     /// If this is `None`, TCP keep-alive will not be enabled.
250     pub rtr_tcp_keepalive: Option<Duration>,
251 
252     /// Should we publish detailed RTR client statistics?
253     pub rtr_client_metrics: bool,
254 
255     /// The log levels to be logged.
256     pub log_level: LevelFilter,
257 
258     /// The target to log to.
259     pub log_target: LogTarget,
260 
261     /// The optional PID file for server mode.
262     pub pid_file: Option<PathBuf>,
263 
264     /// The optional working directory for server mode.
265     pub working_dir: Option<PathBuf>,
266 
267     /// The optional directory to chroot to in server mode.
268     pub chroot: Option<PathBuf>,
269 
270     /// The name of the user to change to in server mode.
271     pub user: Option<String>,
272 
273     /// The name of the group to change to in server mode.
274     pub group: Option<String>,
275 
276     /// A mapping of TAL file names to TAL labels.
277     pub tal_labels: HashMap<String, String>,
278 }
279 
280 
281 impl Config {
282     /// Adds the basic arguments to a clapp app.
283     ///
284     /// The function follows clap’s builder pattern: it takes an app,
285     /// adds a bunch of arguments to it and returns it at the end.
config_args<'a: 'b, 'b>(app: App<'a, 'b>) -> App<'a, 'b>286     pub fn config_args<'a: 'b, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
287         app
288         .arg(Arg::with_name("config")
289              .short("c")
290              .long("config")
291              .takes_value(true)
292              .value_name("PATH")
293              .help("Read base configuration from this file")
294         )
295         .arg(Arg::with_name("base-dir")
296              .short("b")
297              .long("base-dir")
298              .value_name("DIR")
299              .help("Sets the base directory for cache and TALs")
300              .takes_value(true)
301         )
302         .arg(Arg::with_name("repository-dir")
303              .short("r")
304              .long("repository-dir")
305              .value_name("DIR")
306              .help("Sets the repository cache directory")
307              .takes_value(true)
308         )
309         .arg(Arg::with_name("tal-dir")
310              .short("t")
311              .long("tal-dir")
312              .value_name("DIR")
313              .help("Sets the TAL directory")
314              .takes_value(true)
315         )
316         .arg(Arg::with_name("exceptions")
317              .short("x")
318              .long("exceptions")
319              .value_name("FILE")
320              .help("File with local exceptions (see RFC 8416 for format)")
321              .takes_value(true)
322              .multiple(true)
323              .number_of_values(1)
324         )
325         .arg(Arg::with_name("strict")
326              .long("strict")
327              .help("Parse RPKI data in strict mode")
328         )
329         .arg(Arg::with_name("stale")
330              .long("stale")
331              .value_name("POLICY")
332              .help("The policy for handling stale objects")
333              .takes_value(true)
334         )
335         .arg(Arg::with_name("unsafe-vrps")
336             .long("unsafe-vrps")
337             .value_name("POLICY")
338             .help("The policy for handling unsafe VRPs")
339             .takes_value(true)
340         )
341         .arg(Arg::with_name("unknown-objects")
342             .long("unknown-objects")
343             .value_name("POLICY")
344             .help("The policy for handling unknown object types")
345             .takes_value(true)
346         )
347         .arg(Arg::with_name("allow-dubious-hosts")
348              .long("allow-dubious-hosts")
349              .help("Allow dubious host names in rsync and HTTPS URIs")
350         )
351         .arg(Arg::with_name("fresh")
352             .long("fresh")
353             .help("Delete cached data, download everything again")
354         )
355         .arg(Arg::with_name("disable-rsync")
356             .long("disable-rsync")
357             .help("Disable rsync and only use RRDP")
358         )
359         .arg(Arg::with_name("rsync-command")
360              .long("rsync-command")
361              .value_name("COMMAND")
362              .help("The command to run for rsync")
363              .takes_value(true)
364         )
365         .arg(Arg::with_name("rsync-timeout")
366             .long("rsync-timeout")
367             .value_name("SECONDS")
368             .help("Timeout for rsync commands")
369             .takes_value(true)
370         )
371         .arg(Arg::with_name("disable-rrdp")
372             .long("disable-rrdp")
373             .help("Disable RRDP and only use rsync")
374         )
375         .arg(Arg::with_name("rrdp-max-delta-count")
376             .long("rrdp-max-delta-count")
377             .takes_value(true)
378             .value_name("COUNT")
379             .help("Maximum number of RRDP deltas before using snapshot")
380         )
381         .arg(Arg::with_name("rrdp-fallback-time")
382             .long("rrdp-fallback-time")
383             .takes_value(true)
384             .value_name("SECONDS")
385             .help("Maximum time since last update before fallback to rsync")
386         )
387         .arg(Arg::with_name("rrdp-timeout")
388             .long("rrdp-timeout")
389             .value_name("SECONDS")
390             .help("Timeout of network operation for RRDP (0 for none)")
391             .takes_value(true)
392         )
393         .arg(Arg::with_name("rrdp-connect-timeout")
394             .long("rrdp-connect-timeout")
395             .value_name("SECONDS")
396             .help("Timeout for connecting to an RRDP server")
397             .takes_value(true)
398         )
399         .arg(Arg::with_name("rrdp-local-addr")
400             .long("rrdp-local-addr")
401             .value_name("ADDR")
402             .help("Local address for outgoing RRDP connections")
403             .takes_value(true)
404         )
405         .arg(Arg::with_name("rrdp-root-cert")
406             .long("rrdp-root-cert")
407             .value_name("PATH")
408             .help("Path to trusted PEM certificate for RRDP HTTPS")
409             .takes_value(true)
410             .multiple(true)
411             .number_of_values(1)
412         )
413         .arg(Arg::with_name("rrdp-proxy")
414             .long("rrdp-proxy")
415             .value_name("URI")
416             .help("Proxy server for RRDP (HTTP or SOCKS5)")
417             .takes_value(true)
418             .multiple(true)
419             .number_of_values(1)
420         )
421         .arg(Arg::with_name("rrdp-keep-responses")
422             .long("rrdp-keep-responses")
423             .value_name("DIR")
424             .help("Keep RRDP responses in the given directory")
425             .takes_value(true)
426         )
427         // XXX Remove in next breaking release
428         .arg(Arg::with_name("rrdp-disable-gzip")
429             .long("rrdp-disable-gzip")
430             .hidden(true)
431         )
432         .arg(Arg::with_name("max-object-size")
433             .long("max-object-size")
434             .value_name("BYTES")
435             .help("Maximum size of downloaded objects (0 for no limit)")
436             .takes_value(true)
437         )
438         .arg(Arg::with_name("dirty-repository")
439             .long("dirty")
440             .help("Do not clean up repository directory after validation")
441         )
442         .arg(Arg::with_name("validation-threads")
443              .long("validation-threads")
444              .value_name("COUNT")
445              .help("Number of threads for validation")
446              .takes_value(true)
447         )
448         .arg(Arg::with_name("verbose")
449              .short("v")
450              .long("verbose")
451              .multiple(true)
452              .help("Log more information, twice for even more")
453         )
454         .arg(Arg::with_name("quiet")
455              .short("q")
456              .long("quiet")
457              .multiple(true)
458              .conflicts_with("verbose")
459              .help("Log less information, twice for no information")
460         )
461         .arg(Arg::with_name("syslog")
462              .long("syslog")
463              .help("Log to syslog")
464         )
465         .arg(Arg::with_name("syslog-facility")
466              .long("syslog-facility")
467              .takes_value(true)
468              .default_value("daemon")
469              .help("Facility to use for syslog logging")
470         )
471         .arg(Arg::with_name("logfile")
472              .long("logfile")
473              .takes_value(true)
474              .value_name("PATH")
475              .help("Log to this file")
476         )
477     }
478 
479     /// Adds the relevant config args to the server subcommand.
480     ///
481     /// Some of the options in the config only make sense for the
482     /// RTR server. Having them in the global part of the clap command line
483     /// is confusing, so we stick to defaults unless we actually run the
484     /// server. This function adds the relevant arguments to the subcommand
485     /// provided via `app`.
486     ///
487     /// It follows clap’s builder pattern and returns the app with all
488     /// arguments added.
server_args<'a: 'b, 'b>(app: App<'a, 'b>) -> App<'a, 'b>489     pub fn server_args<'a: 'b, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
490         app
491         .arg(Arg::with_name("refresh")
492             .long("refresh")
493             .value_name("SECONDS")
494             .help("Refresh interval in seconds [default 3600]")
495         )
496         .arg(Arg::with_name("retry")
497             .long("retry")
498             .value_name("SECONDS")
499             .help("RTR retry interval in seconds [default 600]")
500         )
501         .arg(Arg::with_name("expire")
502             .long("expire")
503             .value_name("SECONDS")
504             .help("RTR expire interval in seconds [default 600]")
505         )
506         .arg(Arg::with_name("history")
507             .long("history")
508             .value_name("COUNT")
509             .help("Number of history items to keep [default 10]")
510         )
511         .arg(Arg::with_name("rtr-listen")
512             .long("rtr")
513             .value_name("ADDR:PORT")
514             .help("Listen on address/port for RTR")
515             .takes_value(true)
516             .multiple(true)
517             .number_of_values(1)
518         )
519         .arg(Arg::with_name("http-listen")
520             .long("http")
521             .value_name("ADDR:PORT")
522             .help("Listen on address/port for HTTP")
523             .takes_value(true)
524             .multiple(true)
525             .number_of_values(1)
526         )
527         .arg(Arg::with_name("systemd-listen")
528             .long("systemd-listen")
529             .help("Acquire listening sockets from systemd")
530         )
531         .arg(Arg::with_name("rtr-tcp-keepalive")
532             .long("rtr-tcp-keepalive")
533             .value_name("SECONDS")
534             .help("The TCP keep-alive timeout on RTR. [default 60, 0 for off]")
535             .takes_value(true)
536         )
537         .arg(Arg::with_name("rtr-client-metrics")
538             .long("rtr-client-metrics")
539             .help("Include RTR client information in metrics")
540         )
541         .arg(Arg::with_name("pid-file")
542             .long("pid-file")
543             .value_name("PATH")
544             .help("The file for keep the daemon process's PID in")
545             .takes_value(true)
546         )
547         .arg(Arg::with_name("working-dir")
548             .long("working-dir")
549             .value_name("PATH")
550             .help("The working directory of the daemon process")
551             .takes_value(true)
552         )
553         .arg(Arg::with_name("chroot")
554             .long("chroot")
555             .value_name("PATH")
556             .help("Root directory for the daemon process")
557             .takes_value(true)
558         )
559         .arg(Arg::with_name("user")
560             .long("user")
561             .value_name("USER")
562             .help("User for the daemon process")
563             .takes_value(true)
564         )
565         .arg(Arg::with_name("group")
566             .long("group")
567             .value_name("GROUP")
568             .help("Group for the daemon process")
569             .takes_value(true)
570         )
571     }
572 
573     /// Creates a configuration from command line matches.
574     ///
575     /// The function attempts to create configuration from the command line
576     /// arguments provided via `matches`. It will try to read a config file
577     /// if provided via the config file option (`-c` or `--config`) or a
578     /// file in `$HOME/.routinator.conf` otherwise. If the latter doesn’t
579     /// exist either, starts with a default configuration.
580     ///
581     /// All relative paths given in command line arguments will be interpreted
582     /// relative to `cur_dir`. Conversely, paths in the config file are
583     /// treated as relative to the config file’s directory.
584     ///
585     /// If you are runming in server mode, you need to also apply the server
586     /// arguments via [`apply_server_arg_matches`].
587     ///
588     /// [`apply_server_arg_matches`]: #method.apply_server_arg_matches
from_arg_matches( matches: &ArgMatches, cur_dir: &Path, ) -> Result<Self, Failed>589     pub fn from_arg_matches(
590         matches: &ArgMatches,
591         cur_dir: &Path,
592     ) -> Result<Self, Failed> {
593         let mut res = Self::create_base_config(
594             Self::path_value_of(matches, "config", cur_dir)
595                 .as_ref().map(AsRef::as_ref)
596         )?;
597 
598         res.apply_arg_matches(matches, cur_dir)?;
599 
600         Ok(res)
601     }
602 
603     /// Applies the basic command line arguments to a configuration.
604     ///
605     /// The path arguments in `matches` will be interpreted relative to
606     /// `cur_dir`.
607     #[allow(clippy::cognitive_complexity)]
apply_arg_matches( &mut self, matches: &ArgMatches, cur_dir: &Path, ) -> Result<(), Failed>608     fn apply_arg_matches(
609         &mut self,
610         matches: &ArgMatches,
611         cur_dir: &Path,
612     ) -> Result<(), Failed> {
613         // cache_dir
614         if let Some(dir) = matches.value_of("repository-dir") {
615             self.cache_dir = cur_dir.join(dir)
616         }
617         else if let Some(dir) = matches.value_of("base-dir") {
618             self.cache_dir = cur_dir.join(dir).join("repository")
619         }
620         if self.cache_dir == Path::new("") {
621             error!(
622                 "Couldn’t determine default repository directory: \
623                  no home directory.\n\
624                  Please specify the repository directory with the -r option."
625             );
626             return Err(Failed)
627         }
628 
629         // tal_dir
630         if let Some(dir) = matches.value_of("tal-dir") {
631             self.tal_dir = cur_dir.join(dir)
632         }
633         else if let Some(dir) = matches.value_of("base-dir") {
634             self.tal_dir = cur_dir.join(dir).join("tals")
635         }
636         if self.tal_dir == Path::new("") {
637             error!(
638                 "Couldn’t determine default TAL directory: \
639                  no home directory.\n\
640                  Please specify the repository directory with the -t option."
641             );
642             return Err(Failed)
643         }
644 
645         // expceptions
646         if let Some(list) = matches.values_of("exceptions") {
647             self.exceptions = list.map(|path| cur_dir.join(path)).collect()
648         }
649 
650         // strict
651         if matches.is_present("strict") {
652             self.strict = true
653         }
654 
655         // stale
656         if let Some(value) = from_str_value_of(matches, "stale")? {
657             self.stale = value
658         }
659 
660         // unsafe_vrps
661         if let Some(value) = from_str_value_of(matches, "unsafe-vrps")? {
662             self.unsafe_vrps = value
663         }
664 
665         // unknown_objects
666         if let Some(value) = from_str_value_of(matches, "unknown-objects")? {
667             self.unknown_objects = value
668         }
669 
670         // allow_dubious_hosts
671         if matches.is_present("allow-dubious-hosts") {
672             self.allow_dubious_hosts = true
673         }
674 
675         // fresh
676         if matches.is_present("fresh") {
677             self.fresh = true
678         }
679 
680         // disable_rsync
681         if matches.is_present("disable-rsync") {
682             self.disable_rsync = true
683         }
684 
685         // rsync_command
686         if let Some(value) = matches.value_of("rsync-command") {
687             self.rsync_command = value.into()
688         }
689 
690         // rsync_timeout
691         if let Some(value) = from_str_value_of(matches, "rsync-timeout")? {
692             self.rsync_timeout = Duration::from_secs(value)
693         }
694 
695         // disable_rrdp
696         if matches.is_present("disable-rrdp") {
697             self.disable_rrdp = true
698         }
699 
700         // rrdp_fallback_time
701         if let Some(value) = from_str_value_of(
702             matches, "rrdp-fallback-time"
703         )? {
704             self.rrdp_fallback_time = Duration::from_secs(value)
705         }
706 
707         // rrdp_max_delta_count
708         if let Some(value) = from_str_value_of(
709             matches, "rrdp-max-delta-count"
710         )? {
711             self.rrdp_max_delta_count = value
712         }
713 
714         // rrdp_timeout
715         if let Some(value) = from_str_value_of(matches, "rrdp-timeout")? {
716             self.rrdp_timeout = if value == 0 {
717                 None
718             }
719             else {
720                 Some(Duration::from_secs(value))
721             };
722         }
723 
724         // rrdp_connect_timeout
725         if let Some(value) = from_str_value_of(
726             matches, "rrdp-connect-timeout"
727         )? {
728             self.rrdp_connect_timeout = Some(Duration::from_secs(value))
729         }
730 
731         // rrdp_local_addr
732         if let Some(value) = from_str_value_of(matches, "rrdp-local-addr")? {
733             self.rrdp_local_addr = Some(value)
734         }
735 
736         // rrdp_root_certs
737         if let Some(list) = matches.values_of("rrdp-root-cert") {
738             self.rrdp_root_certs = Vec::new();
739             for value in list {
740                 match PathBuf::from_str(value) {
741                     Ok(path) => self.rrdp_root_certs.push(path),
742                     Err(_) => {
743                         error!("Invalid path for rrdp-root-cert '{}'", value);
744                         return Err(Failed)
745                     }
746                 };
747             }
748         }
749 
750         // rrdp_proxies
751         if let Some(list) = matches.values_of("rrdp-proxy") {
752             self.rrdp_proxies = list.map(Into::into).collect();
753         }
754 
755         // rrdp_keep_responses
756         if let Some(path) = matches.value_of("rrdp-keep-responses") {
757             self.rrdp_keep_responses = Some(path.into())
758         }
759 
760         // max_object_size
761         if let Some(value) = from_str_value_of(matches, "max-object-size")? {
762             if value == 0 {
763                 self.max_object_size = None
764             }
765             else {
766                 self.max_object_size = Some(value)
767             }
768         }
769 
770         // max_ca_depth
771         if let Some(value) = from_str_value_of(matches, "max-ca-depth")? {
772             self.max_ca_depth = value;
773         }
774 
775         // dirty_repository
776         if matches.is_present("dirty-repository") {
777             self.dirty_repository = true
778         }
779 
780         // validation_threads
781         if let Some(value) = from_str_value_of(matches, "validation-threads")? {
782             self.validation_threads = value
783         }
784 
785         // log_level
786         match (matches.occurrences_of("verbose"),
787                                             matches.occurrences_of("quiet")) {
788             // This assumes that -v and -q are conflicting.
789             (0, 0) => { }
790             (1, 0) => self.log_level = LevelFilter::Info,
791             (_, 0) => self.log_level = LevelFilter::Debug,
792             (0, 1) => self.log_level = LevelFilter::Error,
793             (0, _) => self.log_level = LevelFilter::Off,
794             _ => { }
795         }
796 
797         // log_target
798         self.apply_log_matches(matches, cur_dir)?;
799 
800         Ok(())
801     }
802 
803     /// Applies the logging-specific command line arguments to the config.
804     ///
805     /// This is the Unix version that also considers syslog as a valid
806     /// target.
807     #[cfg(unix)]
apply_log_matches( &mut self, matches: &ArgMatches, cur_dir: &Path, ) -> Result<(), Failed>808     fn apply_log_matches(
809         &mut self,
810         matches: &ArgMatches,
811         cur_dir: &Path,
812     ) -> Result<(), Failed> {
813         if matches.is_present("syslog") {
814             self.log_target = LogTarget::Syslog(
815                 match Facility::from_str(
816                                matches.value_of("syslog-facility").unwrap()) {
817                     Ok(value) => value,
818                     Err(_) => {
819                         error!("Invalid value for syslog-facility.");
820                         return Err(Failed);
821                     }
822                 }
823             )
824         }
825         else if let Some(file) = matches.value_of("logfile") {
826             if file == "-" {
827                 self.log_target = LogTarget::Stderr
828             }
829             else {
830                 self.log_target = LogTarget::File(cur_dir.join(file))
831             }
832         }
833         Ok(())
834     }
835 
836     /// Applies the logging-specific command line arguments to the config.
837     ///
838     /// This is the non-Unix version that does not use syslog.
839     #[cfg(not(unix))]
840     #[allow(clippy::unnecessary_wraps)]
apply_log_matches( &mut self, matches: &ArgMatches, cur_dir: &Path, ) -> Result<(), Failed>841     fn apply_log_matches(
842         &mut self,
843         matches: &ArgMatches,
844         cur_dir: &Path,
845     ) -> Result<(), Failed> {
846         if let Some(file) = matches.value_of("logfile") {
847             if file == "-" {
848                 self.log_target = LogTarget::Stderr
849             }
850             else {
851                 self.log_target = LogTarget::File(cur_dir.join(file))
852             }
853         }
854         Ok(())
855     }
856 
857 
858     /// Applies the RTR server command line arguments to an existing config.
859     ///
860     /// All paths used in arguments are interpreted relative to `cur_dir`.
apply_server_arg_matches( &mut self, matches: &ArgMatches, cur_dir: &Path, ) -> Result<(), Failed>861     pub fn apply_server_arg_matches(
862         &mut self,
863         matches: &ArgMatches,
864         cur_dir: &Path,
865     ) -> Result<(), Failed> {
866         // refresh
867         if let Some(value) = from_str_value_of(matches, "refresh")? {
868             self.refresh = Duration::from_secs(value)
869         }
870 
871         // retry
872         if let Some(value) = from_str_value_of(matches, "retry")? {
873             self.retry = Duration::from_secs(value)
874         }
875 
876         // expire
877         if let Some(value) = from_str_value_of(matches, "expire")? {
878             self.expire = Duration::from_secs(value)
879         }
880 
881         // history_size
882         if let Some(value) = from_str_value_of(matches, "history")? {
883             self.history_size = value
884         }
885 
886         // rtr_listen
887         if let Some(list) = matches.values_of("rtr-listen") {
888             self.rtr_listen = Vec::new();
889             for value in list {
890                 match SocketAddr::from_str(value) {
891                     Ok(some) => self.rtr_listen.push(some),
892                     Err(_) => {
893                         error!("Invalid value for rtr: {}", value);
894                         return Err(Failed);
895                     }
896                 }
897             }
898         }
899 
900         // http_listen
901         if let Some(list) = matches.values_of("http-listen") {
902             self.http_listen = Vec::new();
903             for value in list {
904                 match SocketAddr::from_str(value) {
905                     Ok(some) => self.http_listen.push(some),
906                     Err(_) => {
907                         error!("Invalid value for http: {}", value);
908                         return Err(Failed);
909                     }
910                 }
911             }
912         }
913 
914         // systemd_listen
915         if matches.is_present("systemd-listen") {
916             self.systemd_listen = true
917         }
918 
919         // rtr_tcp_keepalive
920         if let Some(keep) = from_str_value_of(matches, "rtr-tcp-keepalive")? {
921             self.rtr_tcp_keepalive = if keep == 0 {
922                 None
923             }
924             else {
925                 Some(Duration::from_secs(keep))
926             }
927         }
928 
929         // rtr_client_metrics
930         if matches.is_present("rtr-client-metrics") {
931             self.rtr_client_metrics = true
932         }
933 
934         // pid_file
935         if let Some(pid_file) = matches.value_of("pid-file") {
936             self.pid_file = Some(cur_dir.join(pid_file))
937         }
938 
939         // working_dir
940         if let Some(working_dir) = matches.value_of("working-dir") {
941             self.working_dir = Some(cur_dir.join(working_dir))
942         }
943 
944         // chroot
945         if let Some(chroot) = matches.value_of("chroot") {
946             self.chroot = Some(cur_dir.join(chroot))
947         }
948 
949         // user
950         if let Some(user) = matches.value_of("user") {
951             self.user = Some(user.into())
952         }
953 
954         // group
955         if let Some(group) = matches.value_of("group") {
956             self.group = Some(group.into())
957         }
958 
959         Ok(())
960     }
961 
962     /// Returns a path value in arg matches.
963     ///
964     /// This expands a relative path based on the given directory.
path_value_of( matches: &ArgMatches, key: &str, dir: &Path ) -> Option<PathBuf>965     fn path_value_of(
966         matches: &ArgMatches,
967         key: &str,
968         dir: &Path
969     ) -> Option<PathBuf> {
970         matches.value_of(key).map(|path| dir.join(path))
971     }
972 
973     /// Creates the correct base configuration for the given config file path.
974     ///
975     /// If no config path is given, tries to read the default config in
976     /// `$HOME/.routinator.conf`. If that doesn’t exist, creates a default
977     /// config.
create_base_config(path: Option<&Path>) -> Result<Self, Failed>978     fn create_base_config(path: Option<&Path>) -> Result<Self, Failed> {
979         let file = match path {
980             Some(path) => {
981                 match ConfigFile::read(path)? {
982                     Some(file) => file,
983                     None => {
984                         error!("Cannot read config file {}", path.display());
985                         return Err(Failed);
986                     }
987                 }
988             }
989             None => {
990                 match home_dir() {
991                     Some(dir) => match ConfigFile::read(
992                                             &dir.join(".routinator.conf"))? {
993                         Some(file) => file,
994                         None => return Ok(Self::default()),
995                     }
996                     None => return Ok(Self::default())
997                 }
998             }
999         };
1000         Self::from_config_file(file)
1001     }
1002 
1003     /// Creates a base config from a config file.
from_config_file(mut file: ConfigFile) -> Result<Self, Failed>1004     fn from_config_file(mut file: ConfigFile) -> Result<Self, Failed> {
1005         let log_target = Self::log_target_from_config_file(&mut file)?;
1006         let res = Config {
1007             cache_dir: file.take_mandatory_path("repository-dir")?,
1008             tal_dir: file.take_mandatory_path("tal-dir")?,
1009             exceptions: {
1010                 file.take_path_array("exceptions")?.unwrap_or_else(Vec::new)
1011             },
1012             strict: file.take_bool("strict")?.unwrap_or(false),
1013             stale: {
1014                 file.take_from_str("stale")?.unwrap_or(DEFAULT_STALE_POLICY)
1015             },
1016             unsafe_vrps: {
1017                 file.take_from_str("unsafe-vrps")?
1018                     .unwrap_or(DEFAULT_UNSAFE_VRPS_POLICY)
1019             },
1020             unknown_objects: {
1021                 file.take_from_str("unknown-objects")?
1022                     .unwrap_or(DEFAULT_UNKNOWN_OBJECTS_POLICY)
1023             },
1024             allow_dubious_hosts:
1025                 file.take_bool("allow-dubious-hosts")?.unwrap_or(false),
1026             fresh: false,
1027             disable_rsync: file.take_bool("disable-rsync")?.unwrap_or(false),
1028             rsync_command: {
1029                 file.take_string("rsync-command")?
1030                     .unwrap_or_else(|| "rsync".into())
1031             },
1032             rsync_args: file.take_string_array("rsync-args")?,
1033             rsync_timeout: {
1034                 Duration::from_secs(
1035                     file.take_u64("rsync-timeout")?
1036                         .unwrap_or(DEFAULT_RSYNC_TIMEOUT)
1037                 )
1038             },
1039             disable_rrdp: file.take_bool("disable-rrdp")?.unwrap_or(false),
1040             rrdp_fallback_time: {
1041                 file.take_u64("rrdp-fallback-time")?
1042                 .map(Duration::from_secs)
1043                 .unwrap_or(DEFAULT_RRDP_FALLBACK_TIME)
1044             },
1045             rrdp_max_delta_count: {
1046                 file.take_usize("rrdp-max-delta-count")?
1047                 .unwrap_or(DEFAULT_RRDP_MAX_DELTA_COUNT)
1048             },
1049             rrdp_timeout: {
1050                 match file.take_u64("rrdp-timeout")? {
1051                     Some(0) => None,
1052                     Some(value) => Some(Duration::from_secs(value)),
1053                     None => Some(DEFAULT_RRDP_TIMEOUT)
1054                 }
1055             },
1056             rrdp_connect_timeout: {
1057                 file.take_u64("rrdp-connect-timeout")?.map(Duration::from_secs)
1058             },
1059             rrdp_local_addr: file.take_from_str("rrdp-local-addr")?,
1060             rrdp_root_certs: {
1061                 file.take_from_str_array("rrdp-root-certs")?
1062                     .unwrap_or_else(Vec::new)
1063             },
1064             rrdp_proxies: {
1065                 file.take_string_array("rrdp-proxies")?.unwrap_or_else(
1066                     Vec::new
1067                 )
1068             },
1069             rrdp_user_agent: DEFAULT_RRDP_USER_AGENT.to_string(),
1070             rrdp_keep_responses: file.take_path("rrdp-keep-responses")?,
1071             max_object_size: {
1072                 match file.take_u64("max-object-size")? {
1073                     Some(0) => None,
1074                     Some(value) => Some(value),
1075                     None => Some(DEFAULT_MAX_OBJECT_SIZE),
1076                 }
1077             },
1078             max_ca_depth: {
1079                 file.take_usize("max-ca-depth")?
1080                     .unwrap_or(DEFAULT_MAX_CA_DEPTH)
1081             },
1082             dirty_repository: file.take_bool("dirty")?.unwrap_or(false),
1083             validation_threads: {
1084                 file.take_small_usize("validation-threads")?
1085                     .unwrap_or_else(::num_cpus::get)
1086             },
1087             refresh: {
1088                 Duration::from_secs(
1089                     file.take_u64("refresh")?.unwrap_or(DEFAULT_REFRESH)
1090                 )
1091             },
1092             retry: {
1093                 Duration::from_secs(
1094                     file.take_u64("retry")?.unwrap_or(DEFAULT_RETRY)
1095                 )
1096             },
1097             expire: {
1098                 Duration::from_secs(
1099                     file.take_u64("expire")?.unwrap_or(DEFAULT_EXPIRE)
1100                 )
1101             },
1102             history_size: {
1103                 file.take_small_usize("history-size")?
1104                     .unwrap_or(DEFAULT_HISTORY_SIZE)
1105             },
1106             rtr_listen: {
1107                 file.take_from_str_array("rtr-listen")?
1108                     .unwrap_or_else(Vec::new)
1109             },
1110             http_listen: {
1111                 file.take_from_str_array("http-listen")?
1112                     .unwrap_or_else(Vec::new)
1113             },
1114             systemd_listen: file.take_bool("systemd-listen")?.unwrap_or(false),
1115             rtr_tcp_keepalive: {
1116                 match file.take_u64("rtr-tcp-keepalive")? {
1117                     Some(0) => None,
1118                     Some(keep) => Some(Duration::from_secs(keep)),
1119                     None => DEFAULT_RTR_TCP_KEEPALIVE,
1120                 }
1121             },
1122             rtr_client_metrics: {
1123                 file.take_bool("rtr-client-metrics")?.unwrap_or(false)
1124             },
1125             log_level: {
1126                 file.take_from_str("log-level")?.unwrap_or(LevelFilter::Warn)
1127             },
1128             log_target,
1129             pid_file: file.take_path("pid-file")?,
1130             working_dir: file.take_path("working-dir")?,
1131             chroot: file.take_path("chroot")?,
1132             user: file.take_string("user")?,
1133             group: file.take_string("group")?,
1134             tal_labels: file.take_string_map("tal-labels")?.unwrap_or_default(),
1135         };
1136 
1137         // XXX Remove in next breaking release.
1138         let _ = file.take_bool("rrdp-disable-gzip")?;
1139 
1140         file.check_exhausted()?;
1141         Ok(res)
1142     }
1143 
1144     /// Determines the logging target from the config file.
1145     ///
1146     /// This is the Unix version that also deals with syslog.
1147     #[cfg(unix)]
log_target_from_config_file( file: &mut ConfigFile ) -> Result<LogTarget, Failed>1148     fn log_target_from_config_file(
1149         file: &mut ConfigFile
1150     ) -> Result<LogTarget, Failed> {
1151         let facility = file.take_string("syslog-facility")?;
1152         let facility = facility.as_ref().map(AsRef::as_ref)
1153                                .unwrap_or("daemon");
1154         let facility = match Facility::from_str(facility) {
1155             Ok(value) => value,
1156             Err(_) => {
1157                 error!(
1158                     "Failed in config file {}: invalid syslog-facility.",
1159                     file.path.display()
1160                 );
1161                 return Err(Failed);
1162             }
1163         };
1164         let log_target = file.take_string("log")?;
1165         let log_file = file.take_path("log-file")?;
1166         match log_target.as_ref().map(AsRef::as_ref) {
1167             Some("default") | None => Ok(LogTarget::Default(facility)),
1168             Some("syslog") => Ok(LogTarget::Syslog(facility)),
1169             Some("stderr") =>  Ok(LogTarget::Stderr),
1170             Some("file") => {
1171                 match log_file {
1172                     Some(file) => Ok(LogTarget::File(file)),
1173                     None => {
1174                         error!(
1175                             "Failed in config file {}: \
1176                              log target \"file\" requires 'log-file' value.",
1177                             file.path.display()
1178                         );
1179                         Err(Failed)
1180                     }
1181                 }
1182             }
1183             Some(value) => {
1184                 error!(
1185                     "Failed in config file {}: \
1186                      invalid log target '{}'",
1187                      file.path.display(),
1188                      value
1189                 );
1190                 Err(Failed)
1191             }
1192         }
1193     }
1194 
1195     /// Determines the logging target from the config file.
1196     ///
1197     /// This is the non-Unix version that only logs to stderr or a file.
1198     #[cfg(not(unix))]
log_target_from_config_file( file: &mut ConfigFile ) -> Result<LogTarget, Failed>1199     fn log_target_from_config_file(
1200         file: &mut ConfigFile
1201     ) -> Result<LogTarget, Failed> {
1202         let log_target = file.take_string("log")?;
1203         let log_file = file.take_path("log-file")?;
1204         match log_target.as_ref().map(AsRef::as_ref) {
1205             Some("default") | Some("stderr") | None => Ok(LogTarget::Stderr),
1206             Some("file") => {
1207                 match log_file {
1208                     Some(file) => Ok(LogTarget::File(file)),
1209                     None => {
1210                         error!(
1211                             "Failed in config file {}: \
1212                              log target \"file\" requires 'log-file' value.",
1213                             file.path.display()
1214                         );
1215                         Err(Failed)
1216                     }
1217                 }
1218             }
1219             Some(value) => {
1220                 error!(
1221                     "Failed in config file {}: \
1222                      invalid log target '{}'",
1223                     file.path.display(), value
1224                 );
1225                 Err(Failed)
1226             }
1227         }
1228     }
1229 
1230     /// Creates a default config with the given paths.
1231     ///
1232     /// Uses default values for everything except for the cache and TAL
1233     /// directories which are provided.
default_with_paths(cache_dir: PathBuf, tal_dir: PathBuf) -> Self1234     fn default_with_paths(cache_dir: PathBuf, tal_dir: PathBuf) -> Self {
1235         Config {
1236             cache_dir,
1237             tal_dir,
1238             exceptions: Vec::new(),
1239             strict: DEFAULT_STRICT,
1240             stale: DEFAULT_STALE_POLICY,
1241             unsafe_vrps: DEFAULT_UNSAFE_VRPS_POLICY,
1242             unknown_objects: DEFAULT_UNKNOWN_OBJECTS_POLICY,
1243             allow_dubious_hosts: false,
1244             fresh: false,
1245             disable_rsync: false,
1246             rsync_command: "rsync".into(),
1247             rsync_args: None,
1248             rsync_timeout: Duration::from_secs(DEFAULT_RSYNC_TIMEOUT),
1249             disable_rrdp: false,
1250             rrdp_fallback_time: DEFAULT_RRDP_FALLBACK_TIME,
1251             rrdp_max_delta_count: DEFAULT_RRDP_MAX_DELTA_COUNT,
1252             rrdp_timeout: Some(DEFAULT_RRDP_TIMEOUT),
1253             rrdp_connect_timeout: None,
1254             rrdp_local_addr: None,
1255             rrdp_root_certs: Vec::new(),
1256             rrdp_proxies: Vec::new(),
1257             rrdp_user_agent: DEFAULT_RRDP_USER_AGENT.to_string(),
1258             rrdp_keep_responses: None,
1259             max_object_size: Some(DEFAULT_MAX_OBJECT_SIZE),
1260             max_ca_depth: DEFAULT_MAX_CA_DEPTH,
1261             dirty_repository: DEFAULT_DIRTY_REPOSITORY,
1262             validation_threads: ::num_cpus::get(),
1263             refresh: Duration::from_secs(DEFAULT_REFRESH),
1264             retry: Duration::from_secs(DEFAULT_RETRY),
1265             expire: Duration::from_secs(DEFAULT_EXPIRE),
1266             history_size: DEFAULT_HISTORY_SIZE,
1267             rtr_listen: Vec::new(),
1268             http_listen: Vec::new(),
1269             systemd_listen: false,
1270             rtr_tcp_keepalive: DEFAULT_RTR_TCP_KEEPALIVE,
1271             rtr_client_metrics: false,
1272             log_level: LevelFilter::Warn,
1273             log_target: LogTarget::default(),
1274             pid_file: None,
1275             working_dir: None,
1276             chroot: None,
1277             user: None,
1278             group: None,
1279             tal_labels: HashMap::new(),
1280         }
1281     }
1282 
1283     /// Alters paths so that they are relative to a possible chroot.
adjust_chroot_paths(&mut self) -> Result<(), Failed>1284     pub fn adjust_chroot_paths(&mut self) -> Result<(), Failed> {
1285         if let Some(ref chroot) = self.chroot {
1286             self.cache_dir = match self.cache_dir.strip_prefix(chroot) {
1287                 Ok(dir) => dir.into(),
1288                 Err(_) => {
1289                     error!(
1290                         "Fatal: Repository directory {} \
1291                          not under chroot {}.",
1292                          self.cache_dir.display(), chroot.display()
1293                     );
1294                     return Err(Failed)
1295                 }
1296             };
1297             self.tal_dir = match self.tal_dir.strip_prefix(chroot) {
1298                 Ok(dir) => dir.into(),
1299                 Err(_) => {
1300                     error!(
1301                         "Fatal: TAL directory {} not under chroot {}.",
1302                          self.tal_dir.display(), chroot.display()
1303                     );
1304                     return Err(Failed)
1305                 }
1306             };
1307             for item in &mut self.exceptions {
1308                 *item = match item.strip_prefix(chroot) {
1309                     Ok(path) => path.into(),
1310                     Err(_) => {
1311                         error!(
1312                             "Fatal: Exception file {} not under chroot {}.",
1313                              item.display(), chroot.display()
1314                         );
1315                         return Err(Failed)
1316                     }
1317                 }
1318             }
1319             if let LogTarget::File(ref mut file) = self.log_target {
1320                 *file = match file.strip_prefix(chroot) {
1321                     Ok(path) => path.into(),
1322                     Err(_) => {
1323                         error!(
1324                             "Fatal: Log file {} not under chroot {}.",
1325                              file.display(), chroot.display()
1326                         );
1327                         return Err(Failed)
1328                     }
1329                 };
1330             }
1331             if let Some(ref mut dir) = self.working_dir {
1332                 *dir = match dir.strip_prefix(chroot) {
1333                     Ok(path) => path.into(),
1334                     Err(_) => {
1335                         error!(
1336                             "Fatal: working directory {} not under chroot {}.",
1337                              dir.display(), chroot.display()
1338                         );
1339                         return Err(Failed)
1340                     }
1341                 }
1342             }
1343         }
1344         Ok(())
1345     }
1346 
1347     /// Returns a TOML representation of the config.
to_toml(&self) -> toml::Value1348     pub fn to_toml(&self) -> toml::Value {
1349         let mut res = toml::value::Table::new();
1350         res.insert(
1351             "repository-dir".into(),
1352             self.cache_dir.display().to_string().into()
1353         );
1354         res.insert(
1355             "tal-dir".into(),
1356             self.tal_dir.display().to_string().into()
1357         );
1358         res.insert(
1359             "exceptions".into(),
1360             toml::Value::Array(
1361                 self.exceptions.iter()
1362                     .map(|p| p.display().to_string().into())
1363                     .collect()
1364             )
1365         );
1366         res.insert("strict".into(), self.strict.into());
1367         res.insert("stale".into(), format!("{}", self.stale).into());
1368         res.insert(
1369             "unsafe-vrps".into(), format!("{}", self.unsafe_vrps).into()
1370         );
1371         res.insert(
1372             "unknown-objects".into(), format!("{}", self.unknown_objects).into()
1373         );
1374         res.insert(
1375             "allow-dubious-hosts".into(), self.allow_dubious_hosts.into()
1376         );
1377         res.insert("disable-rsync".into(), self.disable_rsync.into());
1378         res.insert("rsync-command".into(), self.rsync_command.clone().into());
1379         if let Some(ref args) = self.rsync_args {
1380             res.insert(
1381                 "rsync-args".into(),
1382                 toml::Value::Array(
1383                     args.iter().map(|a| a.clone().into()).collect()
1384                 )
1385             );
1386         }
1387         res.insert(
1388             "rsync-timeout".into(),
1389             (self.rsync_timeout.as_secs() as i64).into()
1390         );
1391         res.insert("disable-rrdp".into(), self.disable_rrdp.into());
1392         res.insert(
1393             "rrdp-fallback-time".into(),
1394             (self.rrdp_fallback_time.as_secs() as i64).into()
1395         );
1396         res.insert(
1397             "rrdp-max-delta-count".into(),
1398             i64::try_from(self.rrdp_max_delta_count).unwrap_or(i64::MAX).into()
1399         );
1400         res.insert(
1401             "rrdp-timeout".into(),
1402             match self.rrdp_timeout {
1403                 None => 0.into(),
1404                 Some(value) => {
1405                     value.as_secs().try_into().unwrap_or(i64::MAX).into()
1406                 }
1407             }
1408         );
1409         if let Some(timeout) = self.rrdp_connect_timeout {
1410             res.insert(
1411                 "rrdp-connect-timeout".into(),
1412                 (timeout.as_secs() as i64).into()
1413             );
1414         }
1415         if let Some(addr) = self.rrdp_local_addr {
1416             res.insert("rrdp-local-addr".into(), addr.to_string().into());
1417         }
1418         res.insert(
1419             "rrdp-root-certs".into(),
1420             toml::Value::Array(
1421                 self.rrdp_root_certs.iter()
1422                     .map(|p| p.display().to_string().into())
1423                     .collect()
1424             )
1425         );
1426         res.insert(
1427             "rrdp-proxies".into(),
1428             toml::Value::Array(
1429                 self.rrdp_proxies.iter().map(|s| s.clone().into()).collect()
1430             )
1431         );
1432         if let Some(path) = self.rrdp_keep_responses.as_ref() {
1433             res.insert(
1434                 "rrdp-keep-responses".into(),
1435                 format!("{}", path.display()).into()
1436             );
1437         }
1438         res.insert("max-object-size".into(),
1439             match self.max_object_size {
1440                 Some(value) => value as i64,
1441                 None => 0,
1442             }.into()
1443         );
1444         res.insert("max-ca-depth".into(),
1445             (self.max_ca_depth as i64).into()
1446         );
1447         res.insert("dirty".into(), self.dirty_repository.into());
1448         res.insert(
1449             "validation-threads".into(),
1450             (self.validation_threads as i64).into()
1451         );
1452         res.insert("refresh".into(), (self.refresh.as_secs() as i64).into());
1453         res.insert("retry".into(), (self.retry.as_secs() as i64).into());
1454         res.insert("expire".into(), (self.expire.as_secs() as i64).into());
1455         res.insert("history-size".into(), (self.history_size as i64).into());
1456         res.insert(
1457             "rtr-listen".into(),
1458             toml::Value::Array(
1459                 self.rtr_listen.iter().map(|a| a.to_string().into()).collect()
1460             )
1461         );
1462         res.insert(
1463             "http-listen".into(),
1464             toml::Value::Array(
1465                 self.http_listen.iter().map(|a| a.to_string().into()).collect()
1466             )
1467         );
1468         res.insert("systemd-listen".into(), self.systemd_listen.into());
1469         res.insert("rtr-tcp-keepalive".into(),
1470             match self.rtr_tcp_keepalive {
1471                 Some(keep) => (keep.as_secs() as i64).into(),
1472                 None => 0.into(),
1473             }
1474         );
1475         res.insert(
1476             "rtr-client-metrics".into(),
1477             self.rtr_client_metrics.into()
1478         );
1479         res.insert("log-level".into(), self.log_level.to_string().into());
1480         match self.log_target {
1481             #[cfg(unix)]
1482             LogTarget::Default(facility) => {
1483                 res.insert("log".into(), "default".into());
1484                 res.insert(
1485                     "syslog-facility".into(),
1486                     facility_to_string(facility).into()
1487                 );
1488             }
1489             #[cfg(unix)]
1490             LogTarget::Syslog(facility) => {
1491                 res.insert("log".into(), "syslog".into());
1492                 res.insert(
1493                     "syslog-facility".into(),
1494                     facility_to_string(facility).into()
1495                 );
1496             }
1497             LogTarget::Stderr => {
1498                 res.insert("log".into(), "stderr".into());
1499             }
1500             LogTarget::File(ref file) => {
1501                 res.insert("log".into(), "file".into());
1502                 res.insert(
1503                     "log-file".into(),
1504                     file.display().to_string().into()
1505                 );
1506             }
1507         }
1508         if let Some(ref file) = self.pid_file {
1509             res.insert("pid-file".into(), file.display().to_string().into());
1510         }
1511         if let Some(ref dir) = self.working_dir {
1512             res.insert("working-dir".into(), dir.display().to_string().into());
1513         }
1514         if let Some(ref dir) = self.chroot {
1515             res.insert("chroot".into(), dir.display().to_string().into());
1516         }
1517         if let Some(ref user) = self.user {
1518             res.insert("user".into(), user.clone().into());
1519         }
1520         if let Some(ref group) = self.group {
1521             res.insert("group".into(), group.clone().into());
1522         }
1523         if !self.tal_labels.is_empty() {
1524             res.insert(
1525                 "tal-labels".into(),
1526                 toml::Value::Array(
1527                     self.tal_labels.iter().map(|(left, right)| {
1528                         toml::Value::Array(vec![
1529                             left.clone().into(), right.clone().into()
1530                         ])
1531                     }).collect()
1532                 )
1533             );
1534         }
1535         res.into()
1536     }
1537 }
1538 
1539 
1540 //--- Default
1541 
1542 impl Default for Config {
default() -> Self1543     fn default() -> Self {
1544         match home_dir() {
1545             Some(dir) => {
1546                 let base = dir.join(".rpki-cache");
1547                 Config::default_with_paths(
1548                     base.join("repository"),
1549                     base.join("tals")
1550                 )
1551             }
1552             None => {
1553                 Config::default_with_paths(
1554                     PathBuf::from(""), PathBuf::from("")
1555                 )
1556             }
1557         }
1558     }
1559 }
1560 
1561 
1562 //--- Display
1563 
1564 impl fmt::Display for Config {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result1565     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1566         write!(f, "{}", self.to_toml())
1567     }
1568 }
1569 
1570 
1571 //------------ LogTarget -----------------------------------------------------
1572 
1573 /// The target to log to.
1574 #[derive(Clone, Debug)]
1575 pub enum LogTarget {
1576     /// Default.
1577     ///
1578     /// Logs to `Syslog(facility)` in daemon mode and `Stderr` otherwise.
1579     #[cfg(unix)]
1580     Default(Facility),
1581 
1582     /// Syslog.
1583     ///
1584     /// The argument is the syslog facility to use.
1585     #[cfg(unix)]
1586     Syslog(Facility),
1587 
1588     /// Stderr.
1589     Stderr,
1590 
1591     /// A file.
1592     ///
1593     /// The argument is the file name.
1594     File(PathBuf)
1595 }
1596 
1597 
1598 //--- Default
1599 
1600 #[cfg(unix)]
1601 impl Default for LogTarget {
default() -> Self1602     fn default() -> Self {
1603         LogTarget::Default(Facility::LOG_DAEMON)
1604     }
1605 }
1606 
1607 #[cfg(not(unix))]
1608 impl Default for LogTarget {
default() -> Self1609     fn default() -> Self {
1610         LogTarget::Stderr
1611     }
1612 }
1613 
1614 
1615 //--- PartialEq and Eq
1616 
1617 impl PartialEq for LogTarget {
eq(&self, other: &Self) -> bool1618     fn eq(&self, other: &Self) -> bool {
1619         match (self, other) {
1620             #[cfg(unix)]
1621             (&LogTarget::Default(s), &LogTarget::Default(o)) => {
1622                 (s as usize) == (o as usize)
1623             }
1624             #[cfg(unix)]
1625             (&LogTarget::Syslog(s), &LogTarget::Syslog(o)) => {
1626                 (s as usize) == (o as usize)
1627             }
1628             (&LogTarget::Stderr, &LogTarget::Stderr) => true,
1629             (&LogTarget::File(ref s), &LogTarget::File(ref o)) => {
1630                 s == o
1631             }
1632             _ => false
1633         }
1634     }
1635 }
1636 
1637 impl Eq for LogTarget { }
1638 
1639 
1640 //------------ FilterPolicy ---------------------------------------------------
1641 
1642 /// The policy for filtering.
1643 ///
1644 /// Various filters can be configured via this policy.
1645 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1646 pub enum FilterPolicy {
1647     /// Reject objects matched by the filter.
1648     Reject,
1649 
1650     /// Accept objects matched by the filter but log a warning.
1651     Warn,
1652 
1653     /// Quietly accept objects matched by the filter.
1654     Accept
1655 }
1656 
1657 impl FromStr for FilterPolicy {
1658     type Err = String;
1659 
from_str(s: &str) -> Result<Self, Self::Err>1660     fn from_str(s: &str) -> Result<Self, Self::Err> {
1661         match s {
1662             "reject" => Ok(FilterPolicy::Reject),
1663             "warn" => Ok(FilterPolicy::Warn),
1664             "accept" => Ok(FilterPolicy::Accept),
1665             _ => Err(format!("invalid policy '{}'", s))
1666         }
1667     }
1668 }
1669 
1670 impl fmt::Display for FilterPolicy {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result1671     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1672         f.write_str(match *self {
1673             FilterPolicy::Reject => "reject",
1674             FilterPolicy::Warn => "warn",
1675             FilterPolicy::Accept => "accept",
1676         })
1677     }
1678 }
1679 
1680 
1681 //------------ ConfigFile ----------------------------------------------------
1682 
1683 /// The content of a config file.
1684 ///
1685 /// This is a thin wrapper around `toml::Table` to make dealing with it more
1686 /// convenient.
1687 #[derive(Clone, Debug)]
1688 struct ConfigFile {
1689     /// The content of the file.
1690     content: toml::value::Table,
1691 
1692     /// The path to the config file.
1693     path: PathBuf,
1694 
1695     /// The directory we found the file in.
1696     ///
1697     /// This is used in relative paths.
1698     dir: PathBuf,
1699 }
1700 
1701 impl ConfigFile {
1702     /// Reads the config file at the given path.
1703     ///
1704     /// If there is no such file, returns `None`. If there is a file but it
1705     /// is broken, aborts.
1706     #[allow(clippy::verbose_file_reads)]
read(path: &Path) -> Result<Option<Self>, Failed>1707     fn read(path: &Path) -> Result<Option<Self>, Failed> {
1708         let mut file = match fs::File::open(path) {
1709             Ok(file) => file,
1710             Err(_) => return Ok(None)
1711         };
1712         let mut config = String::new();
1713         if let Err(err) = file.read_to_string(&mut config) {
1714             error!(
1715                 "Failed to read config file {}: {}",
1716                 path.display(), err
1717             );
1718             return Err(Failed);
1719         }
1720         Self::parse(&config, path).map(Some)
1721     }
1722 
1723     /// Parses the content of the file from a string.
parse(content: &str, path: &Path) -> Result<Self, Failed>1724     fn parse(content: &str, path: &Path) -> Result<Self, Failed> {
1725         let content = match toml::from_str(content) {
1726             Ok(toml::Value::Table(content)) => content,
1727             Ok(_) => {
1728                 error!(
1729                     "Failed to parse config file {}: Not a mapping.",
1730                     path.display()
1731                 );
1732                 return Err(Failed);
1733             }
1734             Err(err) => {
1735                 error!(
1736                     "Failed to parse config file {}: {}",
1737                     path.display(), err
1738                 );
1739                 return Err(Failed);
1740             }
1741         };
1742         let dir = if path.is_relative() {
1743             path.join(match env::current_dir() {
1744                 Ok(dir) => dir,
1745                 Err(err) => {
1746                     error!(
1747                         "Fatal: Can't determine current directory: {}.",
1748                         err
1749                     );
1750                     return Err(Failed);
1751                 }
1752             }).parent().unwrap().into() // a file always has a parent
1753         }
1754         else {
1755             path.parent().unwrap().into()
1756         };
1757         Ok(ConfigFile {
1758             content,
1759             path: path.into(),
1760             dir
1761         })
1762     }
1763 
1764     /// Takes a boolean value from the config file.
1765     ///
1766     /// The value is taken from the given `key`. Returns `Ok(None)` if there
1767     /// is no such key. Returns an error if the key exists but the value
1768     /// isn’t a booelan.
take_bool(&mut self, key: &str) -> Result<Option<bool>, Failed>1769     fn take_bool(&mut self, key: &str) -> Result<Option<bool>, Failed> {
1770         match self.content.remove(key) {
1771             Some(value) => {
1772                 if let toml::Value::Boolean(res) = value {
1773                     Ok(Some(res))
1774                 }
1775                 else {
1776                     error!(
1777                         "Failed in config file {}: \
1778                          '{}' expected to be a boolean.",
1779                         self.path.display(), key
1780                     );
1781                     Err(Failed)
1782                 }
1783             }
1784             None => Ok(None)
1785         }
1786     }
1787 
1788     /// Takes an unsigned integer value from the config file.
1789     ///
1790     /// The value is taken from the given `key`. Returns `Ok(None)` if there
1791     /// is no such key. Returns an error if the key exists but the value
1792     /// isn’t an integer or if it is negative.
take_u64(&mut self, key: &str) -> Result<Option<u64>, Failed>1793     fn take_u64(&mut self, key: &str) -> Result<Option<u64>, Failed> {
1794         match self.content.remove(key) {
1795             Some(value) => {
1796                 if let toml::Value::Integer(res) = value {
1797                     if res < 0 {
1798                         error!(
1799                             "Failed in config file {}: \
1800                             '{}' expected to be a positive integer.",
1801                             self.path.display(), key
1802                         );
1803                         Err(Failed)
1804                     }
1805                     else {
1806                         Ok(Some(res as u64))
1807                     }
1808                 }
1809                 else {
1810                     error!(
1811                         "Failed in config file {}: \
1812                          '{}' expected to be an integer.",
1813                         self.path.display(), key
1814                     );
1815                     Err(Failed)
1816                 }
1817             }
1818             None => Ok(None)
1819         }
1820     }
1821 
1822     /// Takes an unsigned integer value from the config file.
1823     ///
1824     /// The value is taken from the given `key`. Returns `Ok(None)` if there
1825     /// is no such key. Returns an error if the key exists but the value
1826     /// isn’t an integer or if it is negative.
take_usize(&mut self, key: &str) -> Result<Option<usize>, Failed>1827     fn take_usize(&mut self, key: &str) -> Result<Option<usize>, Failed> {
1828         match self.content.remove(key) {
1829             Some(value) => {
1830                 if let toml::Value::Integer(res) = value {
1831                     usize::try_from(res).map(Some).map_err(|_| {
1832                         error!(
1833                             "Failed in config file {}: \
1834                             '{}' expected to be a positive integer.",
1835                             self.path.display(), key
1836                         );
1837                         Failed
1838                     })
1839                 }
1840                 else {
1841                     error!(
1842                         "Failed in config file {}: \
1843                          '{}' expected to be an integer.",
1844                         self.path.display(), key
1845                     );
1846                     Err(Failed)
1847                 }
1848             }
1849             None => Ok(None)
1850         }
1851     }
1852 
1853     /// Takes a small unsigned integer value from the config file.
1854     ///
1855     /// While the result is returned as an `usize`, it must be in the
1856     /// range of a `u16`.
1857     ///
1858     /// The value is taken from the given `key`. Returns `Ok(None)` if there
1859     /// is no such key. Returns an error if the key exists but the value
1860     /// isn’t an integer or if it is out of bounds.
take_small_usize(&mut self, key: &str) -> Result<Option<usize>, Failed>1861     fn take_small_usize(&mut self, key: &str) -> Result<Option<usize>, Failed> {
1862         match self.content.remove(key) {
1863             Some(value) => {
1864                 if let toml::Value::Integer(res) = value {
1865                     if res < 0 {
1866                         error!(
1867                             "Failed in config file {}: \
1868                             '{}' expected to be a positive integer.",
1869                             self.path.display(), key
1870                         );
1871                         Err(Failed)
1872                     }
1873                     else if res > ::std::u16::MAX.into() {
1874                         error!(
1875                             "Failed in config file {}: \
1876                             value for '{}' is too large.",
1877                             self.path.display(), key
1878                         );
1879                         Err(Failed)
1880                     }
1881                     else {
1882                         Ok(Some(res as usize))
1883                     }
1884                 }
1885                 else {
1886                     error!(
1887                         "Failed in config file {}: \
1888                          '{}' expected to be a integer.",
1889                         self.path.display(), key
1890                     );
1891                     Err(Failed)
1892                 }
1893             }
1894             None => Ok(None)
1895         }
1896     }
1897 
1898     /// Takes a string value from the config file.
1899     ///
1900     /// The value is taken from the given `key`. Returns `Ok(None)` if there
1901     /// is no such key. Returns an error if the key exists but the value
1902     /// isn’t a string.
take_string(&mut self, key: &str) -> Result<Option<String>, Failed>1903     fn take_string(&mut self, key: &str) -> Result<Option<String>, Failed> {
1904         match self.content.remove(key) {
1905             Some(value) => {
1906                 if let toml::Value::String(res) = value {
1907                     Ok(Some(res))
1908                 }
1909                 else {
1910                     error!(
1911                         "Failed in config file {}: \
1912                          '{}' expected to be a string.",
1913                         self.path.display(), key
1914                     );
1915                     Err(Failed)
1916                 }
1917             }
1918             None => Ok(None)
1919         }
1920     }
1921 
1922     /// Takes a string encoded value from the config file.
1923     ///
1924     /// The value is taken from the given `key`. It is expected to be a
1925     /// string and will be converted to the final type via `FromStr::from_str`.
1926     ///
1927     /// Returns `Ok(None)` if the key doesn’t exist. Returns an error if the
1928     /// key exists but the value isn’t a string or conversion fails.
take_from_str<T>(&mut self, key: &str) -> Result<Option<T>, Failed> where T: FromStr, T::Err: fmt::Display1929     fn take_from_str<T>(&mut self, key: &str) -> Result<Option<T>, Failed>
1930     where T: FromStr, T::Err: fmt::Display {
1931         match self.take_string(key)? {
1932             Some(value) => {
1933                 match T::from_str(&value) {
1934                     Ok(some) => Ok(Some(some)),
1935                     Err(err) => {
1936                         error!(
1937                             "Failed in config file {}: \
1938                              illegal value in '{}': {}.",
1939                             self.path.display(), key, err
1940                         );
1941                         Err(Failed)
1942                     }
1943                 }
1944             }
1945             None => Ok(None)
1946         }
1947     }
1948 
1949     /// Takes a path value from the config file.
1950     ///
1951     /// The path is taken from the given `key`. It must be a string value.
1952     /// It is treated as relative to the directory of the config file. If it
1953     /// is indeed a relative path, it is expanded accordingly and an absolute
1954     /// path is returned.
1955     ///
1956     /// Returns `Ok(None)` if the key does not exist. Returns an error if the
1957     /// key exists but the value isn’t a string.
take_path(&mut self, key: &str) -> Result<Option<PathBuf>, Failed>1958     fn take_path(&mut self, key: &str) -> Result<Option<PathBuf>, Failed> {
1959         self.take_string(key).map(|opt| opt.map(|path| self.dir.join(path)))
1960     }
1961 
1962     /// Takes a mandatory path value from the config file.
1963     ///
1964     /// This is the pretty much the same as [`take_path`] but also returns
1965     /// an error if the key does not exist.
1966     ///
1967     /// [`take_path`]: #method.take_path
take_mandatory_path(&mut self, key: &str) -> Result<PathBuf, Failed>1968     fn take_mandatory_path(&mut self, key: &str) -> Result<PathBuf, Failed> {
1969         match self.take_path(key)? {
1970             Some(res) => Ok(res),
1971             None => {
1972                 error!(
1973                     "Failed in config file {}: missing required '{}'.",
1974                     self.path.display(), key
1975                 );
1976                 Err(Failed)
1977             }
1978         }
1979     }
1980 
1981     /// Takes an array of strings from the config file.
1982     ///
1983     /// The value is taken from the entry with the given `key` and, if
1984     /// present, the entry is removed. The value must be an array of strings.
1985     /// If the key is not present, returns `Ok(None)`. If the entry is present
1986     /// but not an array of strings, returns an error.
take_string_array( &mut self, key: &str ) -> Result<Option<Vec<String>>, Failed>1987     fn take_string_array(
1988         &mut self,
1989         key: &str
1990     ) -> Result<Option<Vec<String>>, Failed> {
1991         match self.content.remove(key) {
1992             Some(toml::Value::Array(vec)) => {
1993                 let mut res = Vec::new();
1994                 for value in vec.into_iter() {
1995                     if let toml::Value::String(value) = value {
1996                         res.push(value)
1997                     }
1998                     else {
1999                         error!(
2000                             "Failed in config file {}: \
2001                             '{}' expected to be a array of strings.",
2002                             self.path.display(),
2003                             key
2004                         );
2005                         return Err(Failed);
2006                     }
2007                 }
2008                 Ok(Some(res))
2009             }
2010             Some(_) => {
2011                 error!(
2012                     "Failed in config file {}: \
2013                      '{}' expected to be a array of strings.",
2014                     self.path.display(), key
2015                 );
2016                 Err(Failed)
2017             }
2018             None => Ok(None)
2019         }
2020     }
2021 
2022     /// Takes an array of string encoded values from the config file.
2023     ///
2024     /// The value is taken from the entry with the given `key` and, if
2025     /// present, the entry is removed. The value must be an array of strings.
2026     /// Each string is converted to the output type via `FromStr::from_str`.
2027     ///
2028     /// If the key is not present, returns `Ok(None)`. If the entry is present
2029     /// but not an array of strings or if converting any of the strings fails,
2030     /// returns an error.
take_from_str_array<T>( &mut self, key: &str ) -> Result<Option<Vec<T>>, Failed> where T: FromStr, T::Err: fmt::Display2031     fn take_from_str_array<T>(
2032         &mut self,
2033         key: &str
2034     ) -> Result<Option<Vec<T>>, Failed>
2035     where T: FromStr, T::Err: fmt::Display {
2036         match self.content.remove(key) {
2037             Some(toml::Value::Array(vec)) => {
2038                 let mut res = Vec::new();
2039                 for value in vec.into_iter() {
2040                     if let toml::Value::String(value) = value {
2041                         match T::from_str(&value) {
2042                             Ok(value) => res.push(value),
2043                             Err(err) => {
2044                                 error!(
2045                                     "Failed in config file {}: \
2046                                      Invalid value in '{}': {}",
2047                                     self.path.display(), key, err
2048                                 );
2049                                 return Err(Failed)
2050                             }
2051                         }
2052                     }
2053                     else {
2054                         error!(
2055                             "Failed in config file {}: \
2056                             '{}' expected to be a array of strings.",
2057                             self.path.display(),
2058                             key
2059                         );
2060                         return Err(Failed)
2061                     }
2062                 }
2063                 Ok(Some(res))
2064             }
2065             Some(_) => {
2066                 error!(
2067                     "Failed in config file {}: \
2068                      '{}' expected to be a array of strings.",
2069                     self.path.display(), key
2070                 );
2071                 Err(Failed)
2072             }
2073             None => Ok(None)
2074         }
2075     }
2076 
2077     /// Takes an array of paths from the config file.
2078     ///
2079     /// The values are taken from the given `key` which must be an array of
2080     /// strings. Each path is treated as relative to the directory of the
2081     /// config file. All paths are expanded if necessary and are returned as
2082     /// absolute paths.
2083     ///
2084     /// Returns `Ok(None)` if the key does not exist. Returns an error if the
2085     /// key exists but the value isn’t an array of string.
take_path_array( &mut self, key: &str ) -> Result<Option<Vec<PathBuf>>, Failed>2086     fn take_path_array(
2087         &mut self,
2088         key: &str
2089     ) -> Result<Option<Vec<PathBuf>>, Failed> {
2090         match self.content.remove(key) {
2091             Some(toml::Value::String(value)) => {
2092                 Ok(Some(vec![self.dir.join(value)]))
2093             }
2094             Some(toml::Value::Array(vec)) => {
2095                 let mut res = Vec::new();
2096                 for value in vec.into_iter() {
2097                     if let toml::Value::String(value) = value {
2098                         res.push(self.dir.join(value))
2099                     }
2100                     else {
2101                         error!(
2102                             "Failed in config file {}: \
2103                             '{}' expected to be a array of paths.",
2104                             self.path.display(),
2105                             key
2106                         );
2107                         return Err(Failed);
2108                     }
2109                 }
2110                 Ok(Some(res))
2111             }
2112             Some(_) => {
2113                 error!(
2114                     "Failed in config file {}: \
2115                      '{}' expected to be a array of paths.",
2116                     self.path.display(), key
2117                 );
2118                 Err(Failed)
2119             }
2120             None => Ok(None)
2121         }
2122     }
2123 
2124     /// Takes a string-to-string hashmap from the config file.
take_string_map( &mut self, key: &str ) -> Result<Option<HashMap<String, String>>, Failed>2125     fn take_string_map(
2126         &mut self,
2127         key: &str
2128     ) -> Result<Option<HashMap<String, String>>, Failed> {
2129         match self.content.remove(key) {
2130             Some(toml::Value::Array(vec)) => {
2131                 let mut res = HashMap::new();
2132                 for value in vec.into_iter() {
2133                     let mut pair = match value {
2134                         toml::Value::Array(pair) => pair.into_iter(),
2135                         _ => {
2136                             error!(
2137                                 "Failed in config file {}: \
2138                                 '{}' expected to be a array of string pairs.",
2139                                 self.path.display(),
2140                                 key
2141                             );
2142                             return Err(Failed);
2143                         }
2144                     };
2145                     let left = match pair.next() {
2146                         Some(toml::Value::String(value)) => value,
2147                         _ => {
2148                             error!(
2149                                 "Failed in config file {}: \
2150                                 '{}' expected to be a array of string pairs.",
2151                                 self.path.display(),
2152                                 key
2153                             );
2154                             return Err(Failed);
2155                         }
2156                     };
2157                     let right = match pair.next() {
2158                         Some(toml::Value::String(value)) => value,
2159                         _ => {
2160                             error!(
2161                                 "Failed in config file {}: \
2162                                 '{}' expected to be a array of string pairs.",
2163                                 self.path.display(),
2164                                 key
2165                             );
2166                             return Err(Failed);
2167                         }
2168                     };
2169                     if pair.next().is_some() {
2170                         error!(
2171                             "Failed in config file {}: \
2172                             '{}' expected to be a array of string pairs.",
2173                             self.path.display(),
2174                             key
2175                         );
2176                         return Err(Failed);
2177                     }
2178                     if res.insert(left, right).is_some() {
2179                         error!(
2180                             "Failed in config file {}: \
2181                             'duplicate item in '{}'.",
2182                             self.path.display(),
2183                             key
2184                         );
2185                         return Err(Failed);
2186                     }
2187                 }
2188                 Ok(Some(res))
2189             }
2190             Some(_) => {
2191                 error!(
2192                     "Failed in config file {}: \
2193                      '{}' expected to be a array of string pairs.",
2194                     self.path.display(), key
2195                 );
2196                 Err(Failed)
2197             }
2198             None => Ok(None)
2199         }
2200     }
2201 
2202     /// Checks whether the config file is now empty.
2203     ///
2204     /// If it isn’t, logs a complaint and returns an error.
check_exhausted(&self) -> Result<(), Failed>2205     fn check_exhausted(&self) -> Result<(), Failed> {
2206         if !self.content.is_empty() {
2207             print!(
2208                 "Failed in config file {}: Unknown settings ",
2209                 self.path.display()
2210             );
2211             let mut first = true;
2212             for key in self.content.keys() {
2213                 if !first {
2214                     print!(",");
2215                 }
2216                 else {
2217                     first = false
2218                 }
2219                 print!("{}", key);
2220             }
2221             error!(".");
2222             Err(Failed)
2223         }
2224         else {
2225             Ok(())
2226         }
2227     }
2228 }
2229 
2230 
2231 //------------ Helpers -------------------------------------------------------
2232 
2233 /// Try to convert a string encoded value.
2234 ///
2235 /// This helper function just changes error handling. Instead of returning
2236 /// the actual conversion error, it logs it as an invalid value for entry
2237 /// `key` and returns the standard error.
from_str_value_of<T>( matches: &ArgMatches, key: &str ) -> Result<Option<T>, Failed> where T: FromStr, T::Err: fmt::Display2238 fn from_str_value_of<T>(
2239     matches: &ArgMatches,
2240     key: &str
2241 ) -> Result<Option<T>, Failed>
2242 where T: FromStr, T::Err: fmt::Display {
2243     match matches.value_of(key) {
2244         Some(value) => {
2245             match T::from_str(value) {
2246                 Ok(value) => Ok(Some(value)),
2247                 Err(err) => {
2248                     error!(
2249                         "Invalid value for {}: {}.",
2250                         key, err
2251                     );
2252                     Err(Failed)
2253                 }
2254             }
2255         }
2256         None => Ok(None)
2257     }
2258 }
2259 
2260 /// Converts the syslog facility name to the facility type.
2261 #[cfg(unix)]
facility_to_string(facility: Facility) -> String2262 fn facility_to_string(facility: Facility) -> String {
2263     use syslog::Facility::*;
2264 
2265     match facility {
2266         LOG_KERN => "kern",
2267         LOG_USER => "user",
2268         LOG_MAIL => "mail",
2269         LOG_DAEMON => "daemon",
2270         LOG_AUTH => "auth",
2271         LOG_SYSLOG => "syslog",
2272         LOG_LPR => "lpr",
2273         LOG_NEWS => "news",
2274         LOG_UUCP => "uucp",
2275         LOG_CRON => "cron",
2276         LOG_AUTHPRIV => "authpriv",
2277         LOG_FTP => "ftp",
2278         LOG_LOCAL0 => "local0",
2279         LOG_LOCAL1 => "local1",
2280         LOG_LOCAL2 => "local2",
2281         LOG_LOCAL3 => "local3",
2282         LOG_LOCAL4 => "local4",
2283         LOG_LOCAL5 => "local5",
2284         LOG_LOCAL6 => "local6",
2285         LOG_LOCAL7 => "local7",
2286     }.into()
2287 }
2288 
2289 
2290 //============ Tests =========================================================
2291 
2292 #[cfg(test)]
2293 mod test {
2294     use super::*;
2295 
get_default_config() -> Config2296     fn get_default_config() -> Config {
2297         // Set $HOME so that home_dir always succeeds.
2298         ::std::env::set_var("HOME", "/home/test");
2299         Config::default()
2300     }
2301 
process_basic_args(args: &[&str]) -> Config2302     fn process_basic_args(args: &[&str]) -> Config {
2303         let mut config = get_default_config();
2304         config.apply_arg_matches(
2305             &Config::config_args(App::new("routinator"))
2306                 .get_matches_from_safe(args).unwrap(),
2307             Path::new("/test")
2308         ).unwrap();
2309         config
2310     }
2311 
process_server_args(args: &[&str]) -> Config2312     fn process_server_args(args: &[&str]) -> Config {
2313         let mut config = get_default_config();
2314         let matches = Config::server_args(Config::config_args(
2315                 App::new("routinator"))
2316         ).get_matches_from_safe(args).unwrap();
2317         config.apply_arg_matches(&matches, Path::new("/test")).unwrap();
2318         config.apply_server_arg_matches(&matches, Path::new("/test")).unwrap();
2319         config
2320     }
2321 
2322     #[test]
2323     #[cfg(unix)]
default_config()2324     fn default_config() {
2325         let config = get_default_config();
2326         assert_eq!(
2327             config.cache_dir,
2328             home_dir().unwrap().join(".rpki-cache").join("repository")
2329         );
2330         assert_eq!(
2331             config.tal_dir,
2332             home_dir().unwrap().join(".rpki-cache").join("tals")
2333         );
2334         assert!(config.exceptions.is_empty());
2335         assert_eq!(config.strict, DEFAULT_STRICT);
2336         assert_eq!(config.validation_threads, ::num_cpus::get());
2337         assert_eq!(config.refresh, Duration::from_secs(DEFAULT_REFRESH));
2338         assert_eq!(config.retry, Duration::from_secs(DEFAULT_RETRY));
2339         assert_eq!(config.expire, Duration::from_secs(DEFAULT_EXPIRE));
2340         assert_eq!(config.history_size, DEFAULT_HISTORY_SIZE);
2341         assert!(config.rtr_listen.is_empty());
2342         assert!(config.http_listen.is_empty());
2343         assert!(!config.systemd_listen);
2344         assert_eq!(config.log_level, LevelFilter::Warn);
2345         assert_eq!(config.log_target, LogTarget::Default(Facility::LOG_DAEMON));
2346     }
2347 
2348     #[test]
2349     #[cfg(unix)] // ... because of drive letters in absolute paths on Windows.
good_config_file()2350     fn good_config_file() {
2351         let config = ConfigFile::parse(
2352             "repository-dir = \"/repodir\"\n\
2353              tal-dir = \"taldir\"\n\
2354              exceptions = [\"ex1\", \"/ex2\"]\n\
2355              strict = true\n\
2356              validation-threads = 1000\n\
2357              refresh = 6\n\
2358              retry = 7\n\
2359              expire = 8\n\
2360              history-size = 5000\n\
2361              rtr-listen = [\"[2001:db8::4]:323\", \"192.0.2.4:323\"]\n\
2362              http-listen = [\"192.0.2.4:8080\"]\n\
2363              systemd-listen = true\n\
2364              log-level = \"info\"\n\
2365              log = \"file\"\n\
2366              log-file = \"foo.log\"",
2367             Path::new("/test/routinator.conf")
2368         ).unwrap();
2369         let config = Config::from_config_file(config).unwrap();
2370         assert_eq!(config.cache_dir.to_str().unwrap(), "/repodir");
2371         assert_eq!(config.tal_dir.to_str().unwrap(), "/test/taldir");
2372         assert_eq!(
2373             config.exceptions,
2374             vec![PathBuf::from("/test/ex1"), PathBuf::from("/ex2")]
2375         );
2376         assert!(config.strict);
2377         assert_eq!(config.validation_threads, 1000);
2378         assert_eq!(config.refresh, Duration::from_secs(6));
2379         assert_eq!(config.retry, Duration::from_secs(7));
2380         assert_eq!(config.expire, Duration::from_secs(8));
2381         assert_eq!(config.history_size, 5000);
2382         assert_eq!(
2383             config.rtr_listen,
2384             vec![
2385                 SocketAddr::from_str("[2001:db8::4]:323").unwrap(),
2386                 SocketAddr::from_str("192.0.2.4:323").unwrap(),
2387             ]
2388         );
2389         assert_eq!(
2390             config.http_listen,
2391             vec![SocketAddr::from_str("192.0.2.4:8080").unwrap()]
2392         );
2393         assert!(config.systemd_listen);
2394         assert_eq!(config.log_level, LevelFilter::Info);
2395         assert_eq!(
2396             config.log_target,
2397             LogTarget::File(PathBuf::from("/test/foo.log"))
2398         );
2399     }
2400 
2401     #[test]
2402     #[cfg(unix)] // ... because of drive letters in absolute paths on Windows.
minimal_config_file()2403     fn minimal_config_file() {
2404         let config = ConfigFile::parse(
2405             "repository-dir = \"/repodir\"\n\
2406              tal-dir = \"taldir\"",
2407             Path::new("/test/routinator.conf")
2408         ).unwrap();
2409         let config = Config::from_config_file(config).unwrap();
2410         assert_eq!(config.cache_dir.to_str().unwrap(), "/repodir");
2411         assert_eq!(config.tal_dir.to_str().unwrap(), "/test/taldir");
2412         assert!(config.exceptions.is_empty());
2413         assert!(!config.strict);
2414         assert_eq!(config.validation_threads, ::num_cpus::get());
2415         assert_eq!(config.refresh, Duration::from_secs(DEFAULT_REFRESH));
2416         assert_eq!(config.retry, Duration::from_secs(DEFAULT_RETRY));
2417         assert_eq!(config.expire, Duration::from_secs(DEFAULT_EXPIRE));
2418         assert_eq!(config.history_size, DEFAULT_HISTORY_SIZE);
2419         assert!(config.rtr_listen.is_empty());
2420         assert!(config.http_listen.is_empty());
2421         assert!(!config.systemd_listen);
2422         assert!(config.http_listen.is_empty());
2423         assert_eq!(config.log_level, LevelFilter::Warn);
2424         assert_eq!(
2425             config.log_target,
2426             LogTarget::default()
2427         );
2428     }
2429 
2430     #[test]
bad_config_file()2431     fn bad_config_file() {
2432         let config = ConfigFile::parse(
2433             "", Path::new("/test/routinator.conf")
2434         ).unwrap();
2435         assert!(Config::from_config_file(config).is_err());
2436         let config = ConfigFile::parse(
2437             "repository-dir=\"bla\"",
2438             Path::new("/test/routinator.conf")
2439         ).unwrap();
2440         assert!(Config::from_config_file(config).is_err());
2441         let config = ConfigFile::parse(
2442             "tal-dir=\"bla\"",
2443             Path::new("/test/routinator.conf")
2444         ).unwrap();
2445         assert!(Config::from_config_file(config).is_err());
2446     }
2447 
2448     #[test]
read_your_own_config()2449     fn read_your_own_config() {
2450         let out_config = get_default_config();
2451         let out_file = format!("{}", out_config.to_toml());
2452         let in_file = ConfigFile::parse(
2453             &out_file, Path::new("/test/routinator.conf")
2454         ).unwrap();
2455         let in_config = Config::from_config_file(in_file).unwrap();
2456         assert_eq!(out_config, in_config);
2457     }
2458 
2459     #[test]
2460     #[cfg(unix)]
basic_args()2461     fn basic_args() {
2462         let config = process_basic_args(&[
2463             "routinator", "-r", "/repository", "-t", "tals",
2464             "-x", "/x1", "--exceptions", "x2", "--strict",
2465             "--validation-threads", "2000",
2466             "--syslog", "--syslog-facility", "auth"
2467         ]);
2468         assert_eq!(config.cache_dir, Path::new("/repository"));
2469         assert_eq!(config.tal_dir, Path::new("/test/tals"));
2470         assert_eq!(
2471             config.exceptions, [Path::new("/x1"), Path::new("/test/x2")]
2472         );
2473         assert!(config.strict);
2474         assert_eq!(config.validation_threads, 2000);
2475         assert_eq!(config.log_target, LogTarget::Syslog(Facility::LOG_AUTH));
2476     }
2477 
2478     #[test]
verbosity()2479     fn verbosity() {
2480         let config = process_basic_args(&["routinator"]);
2481         assert_eq!(config.log_level, LevelFilter::Warn);
2482         let config = process_basic_args(&["routinator", "-v"]);
2483         assert_eq!(config.log_level, LevelFilter::Info);
2484         let config = process_basic_args(&["routinator", "-vv"]);
2485         assert_eq!(config.log_level, LevelFilter::Debug);
2486         let config = process_basic_args(&["routinator", "-q"]);
2487         assert_eq!(config.log_level, LevelFilter::Error);
2488         let config = process_basic_args(&["routinator", "-qq"]);
2489         assert_eq!(config.log_level, LevelFilter::Off);
2490     }
2491 
2492     #[test]
server_args()2493     fn server_args() {
2494         let config = process_server_args(&[
2495             "routinator", "--refresh", "7", "--retry", "8", "--expire", "9",
2496             "--history", "1000",
2497             "--rtr", "[2001:db8::4]:323",
2498             "--rtr", "192.0.2.4:323",
2499             "--http", "192.0.2.4:8080",
2500             "--systemd-listen",
2501         ]);
2502         assert_eq!(config.refresh, Duration::from_secs(7));
2503         assert_eq!(config.retry, Duration::from_secs(8));
2504         assert_eq!(config.expire, Duration::from_secs(9));
2505         assert_eq!(config.history_size, 1000);
2506         assert_eq!(
2507             config.rtr_listen,
2508             vec![
2509                 SocketAddr::from_str("[2001:db8::4]:323").unwrap(),
2510                 SocketAddr::from_str("192.0.2.4:323").unwrap(),
2511             ]
2512         );
2513         assert_eq!(
2514             config.http_listen,
2515             vec![SocketAddr::from_str("192.0.2.4:8080").unwrap()]
2516         );
2517         assert!(config.systemd_listen);
2518     }
2519 }
2520 
2521