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