1
//! Handling for arti's configuration formats.
2

            
3
use arti_client::config::{
4
    SystemConfig, SystemConfigBuilder, TorClientConfig, TorClientConfigBuilder,
5
};
6
use derive_builder::Builder;
7
use serde::Deserialize;
8
use std::convert::TryFrom;
9
use tor_config::{CfgPath, ConfigBuildError};
10

            
11
/// Default options to use for our configuration.
12
pub(crate) const ARTI_DEFAULTS: &str = concat!(include_str!("./arti_defaults.toml"),);
13

            
14
/// Structure to hold our application configuration options
15
2
#[derive(Deserialize, Debug, Default, Clone, Builder, Eq, PartialEq)]
16
#[serde(deny_unknown_fields)]
17
#[builder(build_fn(error = "ConfigBuildError"))]
18
3
#[builder(derive(Deserialize))]
19
pub struct ApplicationConfig {
20
    /// If true, we should watch our configuration files for changes, and reload
21
    /// our configuration when they change.
22
    ///
23
    /// Note that this feature may behave in unexpected ways if the path to the
24
    /// directory holding our configuration files changes its identity (because
25
    /// an intermediate symlink is changed, because the directory is removed and
26
    /// recreated, or for some other reason).
27
    #[serde(default)]
28
    #[builder(default)]
29
    watch_configuration: bool,
30
}
31

            
32
impl ApplicationConfig {
33
    /// Return true if we're configured to watch for configuration changes.
34
    pub fn watch_configuration(&self) -> bool {
35
        self.watch_configuration
36
    }
37
}
38

            
39
/// Structure to hold our logging configuration options
40
4
#[derive(Deserialize, Debug, Clone, Builder, Eq, PartialEq)]
41
#[serde(deny_unknown_fields)]
42
#[non_exhaustive] // TODO(nickm) remove public elements when I revise this.
43
#[builder(build_fn(error = "ConfigBuildError"))]
44
3
#[builder(derive(Deserialize))]
45
pub struct LoggingConfig {
46
    /// Filtering directives that determine tracing levels as described at
47
    /// <https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/targets/struct.Targets.html#impl-FromStr>
48
    ///
49
    /// You can override this setting with the -l, --log-level command line parameter.
50
    ///
51
    /// Example: "info,tor_proto::channel=trace"
52
    #[serde(default = "default_console_filter")]
53
    #[builder(default = "default_console_filter()", setter(into, strip_option))]
54
    console: Option<String>,
55

            
56
    /// Filtering directives for the journald logger.
57
    ///
58
    /// Only takes effect if Arti is built with the `journald` filter.
59
    #[serde(default)]
60
    #[builder(default, setter(into, strip_option))]
61
    journald: Option<String>,
62

            
63
    /// Configuration for one or more logfiles.
64
    #[serde(default)]
65
    #[builder(default)]
66
    file: Vec<LogfileConfig>,
67
}
68

            
69
/// Return a default tracing filter value for `logging.console`.
70
#[allow(clippy::unnecessary_wraps)]
71
2
fn default_console_filter() -> Option<String> {
72
2
    Some("debug".to_owned())
73
2
}
74

            
75
impl Default for LoggingConfig {
76
2
    fn default() -> Self {
77
2
        Self::builder().build().expect("Default builder failed")
78
2
    }
79
}
80

            
81
impl LoggingConfig {
82
    /// Return a new LoggingConfigBuilder
83
2
    pub fn builder() -> LoggingConfigBuilder {
84
2
        LoggingConfigBuilder::default()
85
2
    }
86

            
87
    /// Return the configured journald filter, if one is present
88
    pub fn journald_filter(&self) -> Option<&str> {
89
        match self.journald {
90
            Some(ref s) if !s.is_empty() => Some(s.as_str()),
91
            _ => None,
92
        }
93
    }
94

            
95
    /// Return the configured stdout filter, if one is present
96
    pub fn console_filter(&self) -> Option<&str> {
97
        match self.console {
98
            Some(ref s) if !s.is_empty() => Some(s.as_str()),
99
            _ => None,
100
        }
101
    }
102

            
103
    /// Return a list of the configured log files
104
    pub fn logfiles(&self) -> &[LogfileConfig] {
105
        &self.file
106
    }
107
}
108

            
109
/// Configuration information for an (optionally rotating) logfile.
110
#[derive(Deserialize, Debug, Builder, Clone, Eq, PartialEq)]
111
pub struct LogfileConfig {
112
    /// How often to rotate the file?
113
    #[serde(default)]
114
    #[builder(default)]
115
    rotate: LogRotation,
116
    /// Where to write the files?
117
    path: CfgPath,
118
    /// Filter to apply before writing
119
    filter: String,
120
}
121

            
122
/// How often to rotate a log file
123
#[derive(Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
124
#[non_exhaustive]
125
#[serde(rename_all = "lowercase")]
126
pub enum LogRotation {
127
    /// Rotate logs daily
128
    Daily,
129
    /// Rotate logs hourly
130
    Hourly,
131
    /// Never rotate the log
132
    Never,
133
}
134

            
135
impl Default for LogRotation {
136
    fn default() -> Self {
137
        Self::Never
138
    }
139
}
140

            
141
impl LogfileConfig {
142
    /// Return a new [`LogfileConfigBuilder`]
143
    pub fn builder() -> LogfileConfigBuilder {
144
        LogfileConfigBuilder::default()
145
    }
146

            
147
    /// Return the configured rotation interval.
148
    pub fn rotate(&self) -> LogRotation {
149
        self.rotate
150
    }
151

            
152
    /// Return the configured path to the log file.
153
    pub fn path(&self) -> &CfgPath {
154
        &self.path
155
    }
156

            
157
    /// Return the configured filter.
158
    pub fn filter(&self) -> &str {
159
        &self.filter
160
    }
161
}
162

            
163
/// Configuration for one or more proxy listeners.
164
4
#[derive(Deserialize, Debug, Clone, Builder, Eq, PartialEq)]
165
#[serde(deny_unknown_fields)]
166
#[builder(build_fn(error = "ConfigBuildError"))]
167
3
#[builder(derive(Deserialize))]
168
pub struct ProxyConfig {
169
    /// Port to listen on (at localhost) for incoming SOCKS
170
    /// connections.
171
    #[serde(default = "default_socks_port")]
172
    #[builder(default = "default_socks_port()")]
173
    socks_port: Option<u16>,
174
}
175

            
176
/// Return the default value for `socks_port`
177
#[allow(clippy::unnecessary_wraps)]
178
2
fn default_socks_port() -> Option<u16> {
179
2
    Some(9150)
180
2
}
181

            
182
impl Default for ProxyConfig {
183
2
    fn default() -> Self {
184
2
        Self::builder().build().expect("Default builder failed")
185
2
    }
186
}
187

            
188
impl ProxyConfig {
189
    /// Return a new [`ProxyConfigBuilder`].
190
2
    pub fn builder() -> ProxyConfigBuilder {
191
2
        ProxyConfigBuilder::default()
192
2
    }
193

            
194
    /// Return the configured SOCKS port for this proxy configuration,
195
    /// if one is enabled.
196
    pub fn socks_port(&self) -> Option<u16> {
197
        self.socks_port
198
    }
199
}
200

            
201
/// Structure to hold Arti's configuration options, whether from a
202
/// configuration file or the command line.
203
//
204
/// These options are declared in a public crate outside of `arti` so that other
205
/// applications can parse and use them, if desired.  If you're only embedding
206
/// arti via `arti-client`, and you don't want to use Arti's configuration
207
/// format, use [`arti_client::TorClientConfig`] instead.
208
///
209
/// By default, Arti will run using the default Tor network, store state and
210
/// cache information to a per-user set of directories shared by all
211
/// that user's applications, and run a SOCKS client on a local port.
212
///
213
/// NOTE: These are NOT the final options or their final layout. Expect NO
214
/// stability here.
215
2
#[derive(Debug, Clone, Eq, PartialEq, Default)]
216
pub struct ArtiConfig {
217
    /// Configuration for application behavior.
218
    application: ApplicationConfig,
219

            
220
    /// Configuration for proxy listeners
221
    proxy: ProxyConfig,
222

            
223
    /// Logging configuration
224
    logging: LoggingConfig,
225

            
226
    /// Information on system resources used by Arti.
227
    system: SystemConfig,
228

            
229
    /// Configuration of the actual Tor client
230
    tor: TorClientConfig,
231
}
232

            
233
impl TryFrom<config::Config> for ArtiConfig {
234
    type Error = config::ConfigError;
235
1
    fn try_from(cfg: config::Config) -> Result<ArtiConfig, Self::Error> {
236
1
        let builder: ArtiConfigBuilder = cfg.try_deserialize()?;
237
1
        builder
238
1
            .build()
239
1
            .map_err(|e| config::ConfigError::Foreign(Box::new(e)))
240
1
    }
241
}
242

            
243
// This handwritten impl ought not to exist, but it is needed until #374 is done.
244
impl From<ArtiConfigBuilder> for TorClientConfigBuilder {
245
2
    fn from(cfg: ArtiConfigBuilder) -> TorClientConfigBuilder {
246
2
        cfg.tor
247
2
    }
248
}
249

            
250
impl ArtiConfig {
251
    /// Construct a [`TorClientConfig`] based on this configuration.
252
1
    pub fn tor_client_config(&self) -> Result<TorClientConfig, ConfigBuildError> {
253
1
        Ok(self.tor.clone())
254
1
    }
255

            
256
    /// Return a new ArtiConfigBuilder.
257
1
    pub fn builder() -> ArtiConfigBuilder {
258
1
        ArtiConfigBuilder::default()
259
1
    }
260

            
261
    /// Return the [`ApplicationConfig`] for this configuration.
262
    pub fn application(&self) -> &ApplicationConfig {
263
        &self.application
264
    }
265

            
266
    /// Return the [`LoggingConfig`] for this configuration.
267
    pub fn logging(&self) -> &LoggingConfig {
268
        &self.logging
269
    }
270

            
271
    /// Return the [`ProxyConfig`] for this configuration.
272
    pub fn proxy(&self) -> &ProxyConfig {
273
        &self.proxy
274
    }
275
}
276

            
277
/// Builder object used to construct an ArtiConfig.
278
///
279
/// Most code won't need this, and should use [`TorClientConfigBuilder`] instead.
280
///
281
/// Unlike other builder types in Arti, this builder works by exposing an
282
/// inner builder for each section in the [`TorClientConfig`].
283
16
#[derive(Default, Clone, Deserialize)]
284
// This ought to be replaced by a derive-builder generated struct (probably as part of #374),
285
// but currently derive-builder can't do this.
286
pub struct ArtiConfigBuilder {
287
    /// Builder for the actual Tor client.
288
    #[serde(flatten)]
289
    tor: TorClientConfigBuilder,
290

            
291
    /// Builder for the application section
292
    #[serde(default)]
293
    application: ApplicationConfigBuilder,
294
    /// Builder for the proxy section.
295
    #[serde(default)]
296
    proxy: ProxyConfigBuilder,
297
    /// Builder for the logging section.
298
    #[serde(default)]
299
    logging: LoggingConfigBuilder,
300
    /// Builder for system resource configuration.
301
    #[serde(default)]
302
    system: SystemConfigBuilder,
303
}
304

            
305
impl ArtiConfigBuilder {
306
    /// Try to construct a new [`ArtiConfig`] from this builder.
307
2
    pub fn build(&self) -> Result<ArtiConfig, ConfigBuildError> {
308
2
        let application = self
309
2
            .application
310
2
            .build()
311
2
            .map_err(|e| e.within("application"))?;
312
2
        let proxy = self.proxy.build().map_err(|e| e.within("proxy"))?;
313
2
        let logging = self.logging.build().map_err(|e| e.within("logging"))?;
314
2
        let system = self.system.build().map_err(|e| e.within("system"))?;
315
2
        let tor = TorClientConfigBuilder::from(self.clone());
316
2
        let tor = tor.build()?;
317
2
        Ok(ArtiConfig {
318
2
            application,
319
2
            proxy,
320
2
            logging,
321
2
            system,
322
2
            tor,
323
2
        })
324
2
    }
325

            
326
    /// Return a mutable reference to an [`ApplicationConfigBuilder`] to use in
327
    /// configuring the Arti process.
328
    pub fn application(&mut self) -> &mut ApplicationConfigBuilder {
329
        &mut self.application
330
    }
331

            
332
    /// Return a mutable reference to a [`ProxyConfig`] to use in
333
    /// configuring the Arti process.
334
1
    pub fn proxy(&mut self) -> &mut ProxyConfigBuilder {
335
1
        &mut self.proxy
336
1
    }
337

            
338
    /// Return a mutable reference to a
339
    /// [`LoggingConfigBuilder`]
340
    /// to use in configuring the Arti process.
341
1
    pub fn logging(&mut self) -> &mut LoggingConfigBuilder {
342
1
        &mut self.logging
343
1
    }
344

            
345
    /// Return a mutable reference to a `TorClientConfigBuilder`.
346
    /// to use in configuring the underlying Tor network.
347
    ///
348
    /// Most programs shouldn't need to alter this configuration: it's only for
349
    /// cases when you need to use a nonstandard set of Tor directory authorities
350
    /// and fallback caches.
351
8
    pub fn tor(&mut self) -> &mut TorClientConfigBuilder {
352
8
        &mut self.tor
353
8
    }
354

            
355
    /// Return a mutable reference to a [`SystemConfigBuilder`].
356
    ///
357
    /// This section controls the system parameters used by Arti.
358
    pub fn system(&mut self) -> &mut SystemConfigBuilder {
359
        &mut self.system
360
    }
361
}
362

            
363
#[cfg(test)]
364
mod test {
365
    #![allow(clippy::unwrap_used)]
366

            
367
    use arti_client::config::dir;
368
    use std::convert::TryInto;
369
    use std::time::Duration;
370

            
371
    use super::*;
372

            
373
    #[test]
374
    fn default_config() {
375
        // TODO: this is duplicate code.
376
        let cfg = config::Config::builder()
377
            .add_source(config::File::from_str(
378
                ARTI_DEFAULTS,
379
                config::FileFormat::Toml,
380
            ))
381
            .build()
382
            .unwrap();
383

            
384
        let parsed: ArtiConfig = cfg.try_into().unwrap();
385
        let default = ArtiConfig::default();
386
        assert_eq!(&parsed, &default);
387

            
388
        // Make sure that the client configuration this gives us is the default one.
389
        let client_config = parsed.tor_client_config().unwrap();
390
        let dflt_client_config = TorClientConfig::default();
391
        assert_eq!(&client_config, &dflt_client_config);
392
    }
393

            
394
    #[test]
395
    fn builder() {
396
        use arti_client::config::dir::DownloadSchedule;
397
        use tor_config::CfgPath;
398
        let sec = std::time::Duration::from_secs(1);
399

            
400
        let auth = dir::Authority::builder()
401
            .name("Fred")
402
            .v3ident([22; 20].into())
403
            .build()
404
            .unwrap();
405
        let fallback = dir::FallbackDir::builder()
406
            .rsa_identity([23; 20].into())
407
            .ed_identity([99; 32].into())
408
            .orports(vec!["127.0.0.7:7".parse().unwrap()])
409
            .build()
410
            .unwrap();
411

            
412
        let mut bld = ArtiConfig::builder();
413
        bld.proxy().socks_port(Some(9999));
414
        bld.logging().console("warn");
415
        bld.tor()
416
            .tor_network()
417
            .authorities(vec![auth])
418
            .fallback_caches(vec![fallback]);
419
        bld.tor()
420
            .storage()
421
            .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
422
            .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
423
        bld.tor()
424
            .download_schedule()
425
            .retry_certs(DownloadSchedule::new(10, sec, 3))
426
            .retry_microdescs(DownloadSchedule::new(30, 10 * sec, 9));
427
        bld.tor()
428
            .override_net_params()
429
            .insert("wombats-per-quokka".to_owned(), 7);
430
        bld.tor()
431
            .path_rules()
432
            .ipv4_subnet_family_prefix(20)
433
            .ipv6_subnet_family_prefix(48);
434
        bld.tor()
435
            .preemptive_circuits()
436
            .disable_at_threshold(12)
437
            .initial_predicted_ports(vec![80, 443])
438
            .prediction_lifetime(Duration::from_secs(3600))
439
            .min_exit_circs_for_port(2);
440
        bld.tor()
441
            .circuit_timing()
442
            .max_dirtiness(90 * sec)
443
            .request_timeout(10 * sec)
444
            .request_max_retries(22)
445
            .request_loyalty(3600 * sec);
446
        bld.tor().address_filter().allow_local_addrs(true);
447

            
448
        let val = bld.build().unwrap();
449

            
450
        assert_ne!(val, ArtiConfig::default());
451
    }
452
}