1
//! Configuration logic and types for bridges.
2

            
3
use std::fmt::{self, Display};
4
use std::iter;
5
use std::net::SocketAddr;
6
use std::str::FromStr;
7
use std::sync::Arc;
8

            
9
use itertools::{Itertools, chain};
10
use serde::{Deserialize, Serialize};
11

            
12
use tor_basic_utils::derive_serde_raw;
13
use tor_config::define_list_builder_accessors;
14
use tor_config::{ConfigBuildError, impl_standard_builder};
15
use tor_linkspec::RelayId;
16
use tor_linkspec::TransportId;
17
use tor_linkspec::{ChanTarget, ChannelMethod, HasChanMethod};
18
use tor_linkspec::{HasAddrs, HasRelayIds, RelayIdRef, RelayIdType};
19
use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
20

            
21
use tor_linkspec::BridgeAddr;
22

            
23
#[cfg(feature = "pt-client")]
24
use tor_linkspec::{PtTarget, PtTargetAddr};
25

            
26
mod err;
27
pub use err::BridgeParseError;
28

            
29
/// A relay not listed on the main tor network, used for anticensorship.
30
///
31
/// This object represents a bridge as configured by the user or by software
32
/// running on the user's behalf.
33
///
34
/// # Pieces of a bridge configuration.
35
///
36
/// A bridge configuration contains:
37
///   * Optionally, the name of a pluggable transport (q.v.) to use.
38
///   * Zero or more addresses at which to contact the bridge.
39
///     These can either be regular IP addresses, hostnames, or arbitrary strings
40
///     to be interpreted by the pluggable transport.
41
///   * One or more cryptographic [identities](tor_linkspec::RelayId) for the bridge.
42
///   * Zero or more optional "key=value" string parameters to pass to the pluggable
43
///     transport when contacting to this bridge.
44
///
45
/// # String representation
46
///
47
/// Can be parsed from, and represented as, a "bridge line" string,
48
/// using the [`FromStr`] and [`Display`] implementations.
49
///
50
/// The syntax supported is a sequence of words,
51
/// separated by ASCII whitespace,
52
/// in the following order:
53
///
54
///  * Optionally, the word `Bridge` (or a case variant thereof).
55
///    (`Bridge` is not part of a bridge line, but is ignored here
56
///    for convenience when copying a line out of a C Tor `torrc`.)
57
///
58
///  * Optionally, the name of the pluggable transport to use.
59
///    If not supplied, Arti will make the connection directly, itself.
60
///
61
///  * The `Host:ORPort` to connect to.
62
///    `Host` can be an IPv4 address, or an IPv6 address in brackets `[ ]`.
63
///    When a pluggable transport is in use, `Host` can also be a hostname;
64
///    or
65
///    if the transport supports operating without a specified address.
66
///    `Host:ORPort` can be omitted and replaced with `-`.
67
///
68
///  * One or more identity key fingerprints,
69
///    each in one of the supported (RSA or ed25519) fingerprint formats.
70
///    Currently, supplying an RSA key is required; an ed25519 key is optional.
71
///
72
///  * When a pluggable transport is in use,
73
///    zero or more `key=value` parameters to pass to the transport
74
///    (smuggled in the SOCKS handshake, as described in the Tor PT specification).
75
///
76
/// This type is cheap to clone: it is a newtype around an `Arc`.
77
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
78
pub struct BridgeConfig(Arc<Inner>);
79

            
80
/// Configuration for a bridge - actual data
81
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
82
struct Inner {
83
    /// Address and transport via which the bridge can be reached, and
84
    /// the parameters for those transports.
85
    ///
86
    /// Restriction: This `addrs` may NOT contain more than one address,
87
    /// and it must be a variant supported by the code in this crate:
88
    /// ie, currently, `Direct` or `Pluggable`.
89
    addrs: ChannelMethod,
90

            
91
    /// The RSA identity of the bridge.
92
    rsa_id: RsaIdentity,
93

            
94
    /// The Ed25519 identity of the bridge.
95
    ed_id: Option<Ed25519Identity>,
96
}
97

            
98
impl HasRelayIds for BridgeConfig {
99
    fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
100
        match key_type {
101
            RelayIdType::Ed25519 => self.0.ed_id.as_ref().map(RelayIdRef::Ed25519),
102
            RelayIdType::Rsa => Some(RelayIdRef::Rsa(&self.0.rsa_id)),
103
            _ => None,
104
        }
105
    }
106
}
107

            
108
impl HasChanMethod for BridgeConfig {
109
    fn chan_method(&self) -> ChannelMethod {
110
        self.0.addrs.clone()
111
    }
112
}
113

            
114
impl HasAddrs for BridgeConfig {
115
1936
    fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
116
1936
        self.0.addrs.addrs()
117
1936
    }
118
}
119

            
120
impl ChanTarget for BridgeConfig {}
121

            
122
derive_serde_raw! {
123
/// Builder for a `BridgeConfig`.
124
///
125
/// Construct this with [`BridgeConfigBuilder::default()`] or [`BridgeConfig::builder()`],
126
/// call setter methods, and then call `build().`
127
//
128
// `BridgeConfig` contains a `ChannelMethod`.  This is convenient for its users,
129
// but means we can't use `#[derive(Builder)]` to autogenerate this.
130
#[derive(Deserialize, Serialize, Default, Clone, Debug)]
131
#[serde(try_from="BridgeConfigBuilderSerde", into="BridgeConfigBuilderSerde")]
132
#[cfg_attr(test, derive(Eq, PartialEq))]
133
pub struct BridgeConfigBuilder = "BridgeConfigBuilder" {
134
    /// The `PtTransportName`, but not yet parsed or checked.
135
    ///
136
    /// `""` and `"-"` and `"bridge"` all mean "do not use a pluggable transport".
137
    transport: Option<String>,
138

            
139
    /// Host:ORPort
140
    ///
141
    /// When using a pluggable transport, only one address is allowed.
142
    addrs: Option<Vec<BridgeAddr>>,
143

            
144
    /// IDs
145
    ///
146
    /// No more than one ID of each type is permitted.
147
    ids: Option<Vec<RelayId>>,
148

            
149
    /// Settings (for the transport)
150
    settings: Option<Vec<(String, String)>>,
151
}
152
}
153
impl_standard_builder! { BridgeConfig: !Default }
154

            
155
/// serde representation of a `BridgeConfigBuilder`
156
#[derive(Serialize, Deserialize)]
157
#[serde(untagged)]
158
enum BridgeConfigBuilderSerde {
159
    /// We understand a bridge line
160
    BridgeLine(String),
161
    /// We understand a dictionary matching BridgeConfigBuilder
162
    Dict(#[serde(with = "BridgeConfigBuilder_Raw")] BridgeConfigBuilder),
163
}
164

            
165
impl TryFrom<BridgeConfigBuilderSerde> for BridgeConfigBuilder {
166
    type Error = BridgeParseError;
167
852
    fn try_from(input: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> {
168
        use BridgeConfigBuilderSerde::*;
169
852
        match input {
170
836
            BridgeLine(s) => s.parse(),
171
16
            Dict(d) => Ok(d),
172
        }
173
852
    }
174
}
175

            
176
impl From<BridgeConfigBuilder> for BridgeConfigBuilderSerde {
177
16
    fn from(input: BridgeConfigBuilder) -> BridgeConfigBuilderSerde {
178
        use BridgeConfigBuilderSerde::*;
179
        // Try to serialize as a bridge line if we can
180
16
        match input.build() {
181
4
            Ok(bridge) => BridgeLine(bridge.to_string()),
182
12
            Err(_) => Dict(input),
183
        }
184
16
    }
185
}
186

            
187
impl BridgeConfigBuilder {
188
    /// Set the transport protocol name (eg, a pluggable transport) to use.
189
    ///
190
    /// The empty string `""`, a single hyphen `"-"`, and the word `"bridge"`,
191
    /// all mean to connect directly;
192
    /// i.e., passing one of this is equivalent to
193
    /// calling [`direct()`](BridgeConfigBuilder::direct).
194
    ///
195
    /// The value is not checked at this point.
196
8
    pub fn transport(&mut self, transport: impl Into<String>) -> &mut Self {
197
8
        self.transport = Some(transport.into());
198
8
        self
199
8
    }
200

            
201
    /// Specify to use a direct connection.
202
    pub fn direct(&mut self) -> &mut Self {
203
        self.transport("")
204
    }
205

            
206
    /// Add a pluggable transport setting
207
2
    pub fn push_setting(&mut self, k: impl Into<String>, v: impl Into<String>) -> &mut Self {
208
2
        self.settings().push((k.into(), v.into()));
209
2
        self
210
2
    }
211

            
212
    /// Inspect the transport name (ie, the protocol)
213
    ///
214
    /// Has not necessarily been validated, so not a `PtTransportName`.
215
    /// If none has yet been specified, returns `None`.
216
616
    pub fn get_transport(&self) -> Option<&str> {
217
616
        self.transport.as_deref()
218
616
    }
219
}
220

            
221
impl BridgeConfigBuilder {
222
    /// Build a `BridgeConfig`
223
1096
    pub fn build(&self) -> Result<BridgeConfig, ConfigBuildError> {
224
1096
        let transport = self.transport.as_deref().unwrap_or_default();
225
1096
        let addrs = self.addrs.as_deref().unwrap_or_default();
226
1096
        let settings = self.settings.as_deref().unwrap_or_default();
227

            
228
        // Error construction helpers
229
1096
        let inconsist_transp = |field: &str, problem: &str| ConfigBuildError::Inconsistent {
230
16
            fields: vec![field.into(), "transport".into()],
231
16
            problem: problem.into(),
232
16
        };
233
1096
        let unsupported =
234
            |field: String, problem: &dyn Display| ConfigBuildError::NoCompileTimeSupport {
235
                field,
236
                problem: problem.to_string(),
237
            };
238
        #[cfg_attr(not(feature = "pt-client"), allow(unused_variables))]
239
1096
        let invalid = |field: String, problem: &dyn Display| ConfigBuildError::Invalid {
240
            field,
241
            problem: problem.to_string(),
242
        };
243

            
244
1096
        let transp: TransportId = transport
245
1096
            .parse()
246
1096
            .map_err(|e| invalid("transport".into(), &e))?;
247

            
248
        // This match seems redundant, but it allows us to apply #[cfg] to the branches,
249
        // which isn't possible with `if ... else ...`.
250
1096
        let addrs = match () {
251
1096
            () if transp.is_builtin() => {
252
600
                if !settings.is_empty() {
253
4
                    return Err(inconsist_transp(
254
4
                        "settings",
255
4
                        "Specified `settings` for a direct bridge connection",
256
4
                    ));
257
596
                }
258
                #[allow(clippy::unnecessary_filter_map)] // for consistency
259
619
                let addrs = addrs.iter().filter_map(|ba| {
260
                    #[allow(clippy::redundant_pattern_matching)] // for consistency
261
592
                    if let Some(sa) = ba.as_socketaddr() {
262
588
                        Some(Ok(*sa))
263
4
                    } else if let Some(_) = ba.as_host_port() {
264
4
                        Some(Err(
265
4
                            "`addrs` contains hostname and port, but only numeric addresses are supported for a direct bridge connection",
266
4
                        ))
267
                    } else {
268
                        unreachable!("BridgeAddr is neither addr nor named")
269
                    }
270
619
                }).collect::<Result<Vec<SocketAddr>,&str>>().map_err(|problem| inconsist_transp(
271
4
                    "addrs",
272
4
                    problem,
273
4
                ))?;
274
592
                if addrs.is_empty() {
275
4
                    return Err(inconsist_transp(
276
4
                        "addrs",
277
4
                        "Missing `addrs` for a direct bridge connection",
278
4
                    ));
279
588
                }
280
588
                ChannelMethod::Direct(addrs)
281
            }
282

            
283
            #[cfg(feature = "pt-client")]
284
496
            () if transp.as_pluggable().is_some() => {
285
496
                let transport = transp.into_pluggable().expect("became not pluggable!");
286
496
                let addr = match addrs {
287
496
                    [] => PtTargetAddr::None,
288
492
                    [addr] => Some(addr.clone()).into(),
289
4
                    [_, _, ..] => {
290
4
                        return Err(inconsist_transp(
291
4
                            "addrs",
292
4
                            "Transport (non-direct bridge) only supports a single nominal address",
293
4
                        ));
294
                    }
295
                };
296
492
                let mut target = PtTarget::new(transport, addr);
297
492
                for (i, (k, v)) in settings.iter().enumerate() {
298
                    // Using PtTargetSettings TryFrom would prevent us reporting the index i
299
492
                    target
300
492
                        .push_setting(k, v)
301
492
                        .map_err(|e| invalid(format!("settings.{}", i), &e))?;
302
                }
303
492
                ChannelMethod::Pluggable(target)
304
            }
305

            
306
            () => {
307
                // With current code, this can only happen if tor-linkspec has pluggable
308
                // transports enabled, but we don't.  But if `TransportId` gains other
309
                // inner variants, it would trigger.
310
                return Err(unsupported(
311
                    "transport".into(),
312
                    &format_args!(
313
                        "support for selected transport '{}' disabled in tor-guardmgr cargo features",
314
                        transp
315
                    ),
316
                ));
317
            }
318
        };
319

            
320
1080
        let mut rsa_id = None;
321
1080
        let mut ed_id = None;
322

            
323
        /// Helper to store an id in `rsa_id` or `ed_id`
324
1576
        fn store_id<T: Clone>(
325
1576
            u: &mut Option<T>,
326
1576
            desc: &str,
327
1576
            v: &T,
328
1576
        ) -> Result<(), ConfigBuildError> {
329
1576
            if u.is_some() {
330
4
                Err(ConfigBuildError::Invalid {
331
4
                    field: "ids".into(),
332
4
                    problem: format!("multiple different ids of the same type ({})", desc),
333
4
                })
334
            } else {
335
1572
                *u = Some(v.clone());
336
1572
                Ok(())
337
            }
338
1576
        }
339

            
340
1576
        for (i, id) in self.ids.as_deref().unwrap_or_default().iter().enumerate() {
341
1576
            match id {
342
1072
                RelayId::Rsa(rsa) => store_id(&mut rsa_id, "RSA", rsa)?,
343
504
                RelayId::Ed25519(ed) => store_id(&mut ed_id, "ed25519", ed)?,
344
                other => {
345
                    return Err(unsupported(
346
                        format!("ids.{}", i),
347
                        &format_args!("unsupported bridge id type {}", other.id_type()),
348
                    ));
349
                }
350
            }
351
        }
352

            
353
1076
        let rsa_id = rsa_id.ok_or_else(|| ConfigBuildError::Invalid {
354
4
            field: "ids".into(),
355
4
            problem: "need an RSA identity".into(),
356
6
        })?;
357

            
358
1072
        Ok(BridgeConfig(
359
1072
            Inner {
360
1072
                addrs,
361
1072
                rsa_id,
362
1072
                ed_id,
363
1072
            }
364
1072
            .into(),
365
1072
        ))
366
1096
    }
367
}
368

            
369
/// `BridgeConfigBuilder` parses the same way as `BridgeConfig`
370
//
371
// We implement it this way round (rather than having the `impl FromStr for BridgeConfig`
372
// call this and then `build`, because the `BridgeConfig` parser
373
// does a lot of bespoke checking of the syntax and semantics.
374
// Doing it the other way, we'd have to unwrap a supposedly-never-existing `ConfigBuildError`,
375
// in `BridgeConfig`'s `FromStr` impl.
376
impl FromStr for BridgeConfigBuilder {
377
    type Err = BridgeParseError;
378

            
379
1104
    fn from_str(s: &str) -> Result<Self, Self::Err> {
380
1104
        let bridge: Inner = s.parse()?;
381

            
382
1104
        let (transport, addrs, settings) = match bridge.addrs {
383
574
            ChannelMethod::Direct(addrs) => (
384
574
                "".into(),
385
574
                addrs
386
574
                    .into_iter()
387
574
                    .map(BridgeAddr::new_addr_from_sockaddr)
388
574
                    .collect(),
389
574
                vec![],
390
574
            ),
391
            #[cfg(feature = "pt-client")]
392
530
            ChannelMethod::Pluggable(target) => {
393
530
                let (transport, addr, settings) = target.into_parts();
394
530
                let addr: Option<BridgeAddr> = addr.into();
395
530
                let addrs = addr.into_iter().collect_vec();
396
                // TODO transport.to_string() clones transport and then drops it
397
                // PtTransportName::into_inner ought to exist but was deleted
398
                // in 119e5f6f754251e0d2db7731f9a7044764f4653e
399
530
                (transport.to_string(), addrs, settings.into_inner())
400
            }
401
            other => {
402
                return Err(BridgeParseError::UnsupportedChannelMethod {
403
                    method: Box::new(other),
404
                });
405
            }
406
        };
407

            
408
1104
        let ids = chain!(
409
1104
            iter::once(bridge.rsa_id.into()),
410
1104
            bridge.ed_id.into_iter().map(Into::into),
411
        )
412
1104
        .collect_vec();
413

            
414
1104
        Ok(BridgeConfigBuilder {
415
1104
            transport: Some(transport),
416
1104
            addrs: Some(addrs),
417
1104
            settings: Some(settings),
418
1104
            ids: Some(ids),
419
1104
        })
420
1104
    }
421
}
422

            
423
define_list_builder_accessors! {
424
    struct BridgeConfigBuilder {
425
        pub addrs: [BridgeAddr],
426
        pub ids: [RelayId],
427
        pub settings: [(String,String)],
428
    }
429
}
430

            
431
impl FromStr for BridgeConfig {
432
    type Err = BridgeParseError;
433

            
434
934
    fn from_str(s: &str) -> Result<Self, Self::Err> {
435
934
        let inner = s.parse()?;
436
906
        Ok(BridgeConfig(Arc::new(inner)))
437
934
    }
438
}
439

            
440
impl FromStr for Inner {
441
    type Err = BridgeParseError;
442

            
443
2038
    fn from_str(s: &str) -> Result<Self, Self::Err> {
444
        use BridgeParseError as BPE;
445

            
446
2038
        let mut s = s.trim().split_ascii_whitespace().peekable();
447

            
448
        // This implements the parsing of bridge lines.
449
        // Refer to the specification in the rustdoc comment for `Bridge`.
450

            
451
        //  * Optionally, the word `Bridge` ...
452

            
453
2038
        let bridge_word = s.peek().ok_or(BPE::Empty)?;
454
2036
        if bridge_word.eq_ignore_ascii_case("bridge") {
455
12
            s.next();
456
2024
        }
457

            
458
        //  * Optionally, the name of the pluggable transport to use.
459
        //  * The `Host:ORPort` to connect to.
460

            
461
        #[cfg_attr(not(feature = "pt-client"), allow(unused_mut))]
462
2026
        let mut method = {
463
2036
            let word = s.next().ok_or(BPE::Empty)?;
464
2034
            if word.contains(':') {
465
                // Not a PT name.  Hope it's an address:port.
466
1482
                let addr = word.parse().map_err(|addr_error| BPE::InvalidIpAddrOrPt {
467
2
                    word: word.to_string(),
468
2
                    addr_error,
469
3
                })?;
470
1480
                ChannelMethod::Direct(vec![addr])
471
            } else {
472
                #[cfg(not(feature = "pt-client"))]
473
                return Err(BPE::PluggableTransportsNotSupported {
474
                    word: word.to_string(),
475
                });
476

            
477
                #[cfg(feature = "pt-client")]
478
                {
479
552
                    let pt_name = word.parse().map_err(|pt_error| BPE::InvalidPtOrAddr {
480
4
                        word: word.to_string(),
481
4
                        pt_error,
482
6
                    })?;
483
548
                    let addr = s
484
548
                        .next()
485
570
                        .map(|s| s.parse())
486
548
                        .transpose()
487
548
                        .map_err(|source| BPE::InvalidIPtHostAddr {
488
2
                            word: word.to_string(),
489
2
                            source,
490
3
                        })?
491
546
                        .unwrap_or(PtTargetAddr::None);
492
546
                    ChannelMethod::Pluggable(PtTarget::new(pt_name, addr))
493
                }
494
            }
495
        };
496

            
497
        //  * One or more identity key fingerprints,
498

            
499
2026
        let mut rsa_id = None;
500
2026
        let mut ed_id = None;
501

            
502
4592
        while let Some(word) = s.peek() {
503
            // Helper to generate the errors if the same key type is specified more than once
504
3208
            let check_several = |was_some| {
505
2570
                if was_some {
506
4
                    Err(BPE::MultipleIdentitiesOfSameType {
507
4
                        word: word.to_string(),
508
4
                    })
509
                } else {
510
2566
                    Ok(())
511
                }
512
2570
            };
513

            
514
3120
            match word.parse() {
515
550
                Err(id_error) => {
516
550
                    if word.contains('=') {
517
                        // Not a fingerprint, then, but a key=value.
518
548
                        break;
519
2
                    }
520
2
                    return Err(BPE::InvalidIdentityOrParameter {
521
2
                        word: word.to_string(),
522
2
                        id_error,
523
2
                    });
524
                }
525
548
                Ok(RelayId::Ed25519(id)) => check_several(ed_id.replace(id).is_some())?,
526
2022
                Ok(RelayId::Rsa(id)) => check_several(rsa_id.replace(id).is_some())?,
527
                Ok(_) => {
528
                    return Err(BPE::UnsupportedIdentityType {
529
                        word: word.to_string(),
530
                    })?;
531
                }
532
            }
533
2566
            s.next();
534
        }
535

            
536
        //  * When a pluggable transport is in use,
537
        //    zero or more `key=value` parameters to pass to the transport
538

            
539
        #[cfg(not(feature = "pt-client"))]
540
        if s.next().is_some() {
541
            return Err(BPE::DirectParametersNotAllowed);
542
        }
543

            
544
        #[cfg(feature = "pt-client")]
545
2570
        for word in s {
546
556
            let (k, v) = word.split_once('=').ok_or_else(|| BPE::InvalidPtKeyValue {
547
2
                word: word.to_string(),
548
3
            })?;
549

            
550
554
            match &mut method {
551
4
                ChannelMethod::Direct(_) => return Err(BPE::DirectParametersNotAllowed),
552
550
                ChannelMethod::Pluggable(t) => t.push_setting(k, v).map_err(|source| {
553
                    BPE::InvalidPluggableTransportSetting {
554
                        word: word.to_string(),
555
                        source,
556
                    }
557
                })?,
558
                other => panic!("made ourselves an unsupported ChannelMethod {:?}", other),
559
            }
560
        }
561

            
562
2014
        let rsa_id = rsa_id.ok_or(BPE::NoRsaIdentity)?;
563
2010
        Ok(Inner {
564
2010
            addrs: method,
565
2010
            rsa_id,
566
2010
            ed_id,
567
2010
        })
568
2038
    }
569
}
570

            
571
impl Display for BridgeConfig {
572
8034
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
573
        let Inner {
574
8034
            addrs,
575
8034
            rsa_id,
576
8034
            ed_id,
577
8034
        } = &*self.0;
578

            
579
        //  * Optionally, the name of the pluggable transport to use.
580
        //  * The `Host:ORPort` to connect to.
581

            
582
8034
        let settings = match addrs {
583
8022
            ChannelMethod::Direct(a) => {
584
8022
                if a.len() == 1 {
585
8022
                    write!(f, "{}", a[0])?;
586
                } else {
587
                    panic!("Somehow created a Bridge config with multiple addrs.");
588
                }
589
8022
                None
590
            }
591

            
592
            #[cfg(feature = "pt-client")]
593
12
            ChannelMethod::Pluggable(target) => {
594
12
                write!(f, "{} {}", target.transport(), target.addr())?;
595
12
                Some(target.settings())
596
            }
597

            
598
            _ => {
599
                // This shouldn't happen, but panicking seems worse than outputting this
600
                write!(f, "[unsupported channel method, cannot display properly]")?;
601
                return Ok(());
602
            }
603
        };
604

            
605
        //  * One or more identity key fingerprints,
606

            
607
8034
        write!(f, " {}", rsa_id)?;
608
8034
        if let Some(ed_id) = ed_id {
609
10
            write!(f, " ed25519:{}", ed_id)?;
610
8024
        }
611

            
612
        //  * When a pluggable transport is in use,
613
        //    zero or more `key=value` parameters to pass to the transport
614

            
615
        #[cfg(not(feature = "pt-client"))]
616
        let _: Option<()> = settings;
617

            
618
        #[cfg(feature = "pt-client")]
619
8034
        for (k, v) in settings.into_iter().flatten() {
620
18
            write!(f, " {}={}", k, v)?;
621
        }
622

            
623
8034
        Ok(())
624
8034
    }
625
}
626

            
627
#[cfg(test)]
628
mod test {
629
    // @@ begin test lint list maintained by maint/add_warning @@
630
    #![allow(clippy::bool_assert_comparison)]
631
    #![allow(clippy::clone_on_copy)]
632
    #![allow(clippy::dbg_macro)]
633
    #![allow(clippy::mixed_attributes_style)]
634
    #![allow(clippy::print_stderr)]
635
    #![allow(clippy::print_stdout)]
636
    #![allow(clippy::single_char_pattern)]
637
    #![allow(clippy::unwrap_used)]
638
    #![allow(clippy::unchecked_time_subtraction)]
639
    #![allow(clippy::useless_vec)]
640
    #![allow(clippy::needless_pass_by_value)]
641
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
642
    use super::*;
643

            
644
    #[cfg(feature = "pt-client")]
645
    fn mk_pt_target(name: &str, addr: PtTargetAddr, params: &[(&str, &str)]) -> ChannelMethod {
646
        let mut target = PtTarget::new(name.parse().unwrap(), addr);
647
        for &(k, v) in params {
648
            target.push_setting(k, v).unwrap();
649
        }
650
        ChannelMethod::Pluggable(target)
651
    }
652

            
653
    fn mk_direct(s: &str) -> ChannelMethod {
654
        ChannelMethod::Direct(vec![s.parse().unwrap()])
655
    }
656

            
657
    fn mk_rsa(s: &str) -> RsaIdentity {
658
        match s.parse().unwrap() {
659
            RelayId::Rsa(y) => y,
660
            _ => panic!("not rsa {:?}", s),
661
        }
662
    }
663
    fn mk_ed(s: &str) -> Ed25519Identity {
664
        match s.parse().unwrap() {
665
            RelayId::Ed25519(y) => y,
666
            _ => panic!("not ed {:?}", s),
667
        }
668
    }
669

            
670
    #[test]
671
    fn bridge_lines() {
672
        let chk = |sl: &[&str], exp: Inner| {
673
            for s in sl {
674
                let got: BridgeConfig = s.parse().expect(s);
675
                assert_eq!(*got.0, exp, "{:?}", s);
676

            
677
                let display = got.to_string();
678
                assert_eq!(display, sl[0]);
679
            }
680
        };
681

            
682
        let chk_e = |sl: &[&str], exp: &str| {
683
            for s in sl {
684
                let got: Result<BridgeConfig, _> = s.parse();
685
                let got = got.expect_err(s);
686
                let got_s = got.to_string();
687
                assert!(
688
                    got_s.contains(exp),
689
                    "{:?} => {:?} ({}) not {}",
690
                    s,
691
                    &got,
692
                    &got_s,
693
                    exp
694
                );
695
            }
696
        };
697

            
698
        // example from https://tb-manual.torproject.org/bridges/, with cert= truncated
699
        #[cfg(feature = "pt-client")]
700
        chk(
701
            &[
702
                "obfs4 38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op iat-mode=1",
703
                "obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op iat-mode=1",
704
                "Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op iat-mode=1",
705
            ],
706
            Inner {
707
                addrs: mk_pt_target(
708
                    "obfs4",
709
                    PtTargetAddr::IpPort("38.229.33.83:80".parse().unwrap()),
710
                    &[
711
                        (
712
                            "cert",
713
                            "VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op",
714
                        ),
715
                        ("iat-mode", "1"),
716
                    ],
717
                ),
718
                rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
719
                ed_id: None,
720
            },
721
        );
722

            
723
        #[cfg(feature = "pt-client")]
724
        chk(
725
            &[
726
                "obfs4 some-host:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE iat-mode=1",
727
                "obfs4 some-host:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE 0BAC39417268B96B9F514E7F63FA6FBA1A788955 iat-mode=1",
728
            ],
729
            Inner {
730
                addrs: mk_pt_target(
731
                    "obfs4",
732
                    PtTargetAddr::HostPort("some-host".into(), 80),
733
                    &[("iat-mode", "1")],
734
                ),
735
                rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
736
                ed_id: Some(mk_ed("dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")),
737
            },
738
        );
739

            
740
        chk(
741
            &[
742
                "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955",
743
                "Bridge 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
744
            ],
745
            Inner {
746
                addrs: mk_direct("38.229.33.83:80"),
747
                rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
748
                ed_id: None,
749
            },
750
        );
751

            
752
        chk(
753
            &[
754
                "[2001:db8::42]:123 $0bac39417268b96b9f514e7f63fa6fba1a788955",
755
                "[2001:0db8::42]:123 $0bac39417268b96b9f514e7f63fa6fba1a788955",
756
            ],
757
            Inner {
758
                addrs: mk_direct("[2001:0db8::42]:123"),
759
                rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
760
                ed_id: None,
761
            },
762
        );
763

            
764
        chk(
765
            &[
766
                "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
767
                "38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
768
            ],
769
            Inner {
770
                addrs: mk_direct("38.229.33.83:80"),
771
                rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
772
                ed_id: Some(mk_ed("dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")),
773
            },
774
        );
775

            
776
        chk_e(
777
            &[
778
                "38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
779
                "Bridge 38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
780
            ],
781
            "lacks specification of RSA identity key",
782
        );
783

            
784
        chk_e(&["", "bridge"], "Bridge line was empty");
785

            
786
        chk_e(
787
            &["999.329.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955"],
788
            // Some Rust versions say "invalid socket address syntax",
789
            // some "invalid IP address syntax"
790
            r#"Cannot parse "999.329.33.83:80" as direct bridge IpAddress:ORPort"#,
791
        );
792

            
793
        chk_e(
794
            &[
795
                "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value",
796
                "Bridge 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value",
797
            ],
798
            "Parameters supplied but not valid without a pluggable transport",
799
        );
800

            
801
        chk_e(
802
            &[
803
                "bridge bridge some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
804
                "yikes! some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
805
            ],
806
            #[cfg(feature = "pt-client")]
807
            r" is not a valid pluggable transport ID), nor as direct bridge IpAddress:ORPort",
808
            #[cfg(not(feature = "pt-client"))]
809
            "is not an IpAddress:ORPort), but support disabled in cargo features",
810
        );
811

            
812
        #[cfg(feature = "pt-client")]
813
        chk_e(
814
            &["obfs4 garbage 0BAC39417268B96B9F514E7F63FA6FBA1A788955"],
815
            "as pluggable transport Host:ORPort",
816
        );
817

            
818
        #[cfg(feature = "pt-client")]
819
        chk_e(
820
            &["obfs4 some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value garbage"],
821
            r#"Expected PT key=value parameter, found "garbage" (which lacks an equals sign"#,
822
        );
823

            
824
        #[cfg(feature = "pt-client")]
825
        chk_e(
826
            &["obfs4 some-host:80 garbage"],
827
            r#"Cannot parse "garbage" as identity key (Invalid base64 data), or PT key=value"#,
828
        );
829

            
830
        chk_e(
831
            &[
832
                "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 23AC39417268B96B9F514E7F63FA6FBA1A788955",
833
                "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE xGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
834
            ],
835
            "More than one identity of the same type specified",
836
        );
837
    }
838

            
839
    #[test]
840
    fn config_api() {
841
        let chk_bridgeline = |line: &str, jsons: &[&str], f: &dyn Fn(&mut BridgeConfigBuilder)| {
842
            eprintln!(" ---- chk_bridgeline ----\n{}", line);
843

            
844
            let mut bcb = BridgeConfigBuilder::default();
845
            f(&mut bcb);
846
            let built = bcb.build().unwrap();
847
            assert_eq!(&built, &line.parse::<BridgeConfig>().unwrap());
848

            
849
            let parsed_b: BridgeConfigBuilder = line.parse().unwrap();
850
            assert_eq!(&built, &parsed_b.build().unwrap());
851

            
852
            let re_serialized = serde_json::to_value(&bcb).unwrap();
853
            assert_eq!(re_serialized, serde_json::Value::String(line.to_string()));
854

            
855
            for json in jsons {
856
                let from_dict: BridgeConfigBuilder = serde_json::from_str(json).unwrap();
857
                assert_eq!(&from_dict, &bcb);
858
                assert_eq!(&built, &from_dict.build().unwrap());
859
            }
860
        };
861

            
862
        chk_bridgeline(
863
            "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
864
            &[r#"{
865
                "addrs": ["38.229.33.83:80"],
866
                "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
867
                      "$0bac39417268b96b9f514e7f63fa6fba1a788955"]
868
            }"#],
869
            &|bcb| {
870
                bcb.addrs().push("38.229.33.83:80".parse().unwrap());
871
                bcb.ids().push(
872
                    "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
873
                        .parse()
874
                        .unwrap(),
875
                );
876
                bcb.ids()
877
                    .push("$0bac39417268b96b9f514e7f63fa6fba1a788955".parse().unwrap());
878
            },
879
        );
880

            
881
        #[cfg(feature = "pt-client")]
882
        chk_bridgeline(
883
            "obfs4 some-host:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 iat-mode=1",
884
            &[r#"{
885
                "transport": "obfs4",
886
                "addrs": ["some-host:80"],
887
                "ids": ["$0bac39417268b96b9f514e7f63fa6fba1a788955"],
888
                "settings": [["iat-mode", "1"]]
889
            }"#],
890
            &|bcb| {
891
                bcb.transport("obfs4");
892
                bcb.addrs().push("some-host:80".parse().unwrap());
893
                bcb.ids()
894
                    .push("$0bac39417268b96b9f514e7f63fa6fba1a788955".parse().unwrap());
895
                bcb.push_setting("iat-mode", "1");
896
            },
897
        );
898

            
899
        let chk_broken = |emsg: &str, jsons: &[&str], f: &dyn Fn(&mut BridgeConfigBuilder)| {
900
            eprintln!(" ---- chk_bridgeline ----\n{:?}", emsg);
901

            
902
            let mut bcb = BridgeConfigBuilder::default();
903
            f(&mut bcb);
904

            
905
            for json in jsons {
906
                let from_dict: BridgeConfigBuilder = serde_json::from_str(json).unwrap();
907
                assert_eq!(&from_dict, &bcb);
908
            }
909

            
910
            let err = bcb.build().expect_err("succeeded?!");
911
            let got_emsg = err.to_string();
912
            assert!(
913
                got_emsg.contains(emsg),
914
                "wrong error message: got_emsg={:?} err={:?} expected={:?}",
915
                &got_emsg,
916
                &err,
917
                emsg,
918
            );
919

            
920
            // This is a kludge.  When we serialize `Option<Vec<_>>` as JSON,
921
            // we get a `Null` entry.  These `Null`s aren't in our test cases and we don't
922
            // really want them, although it's OK that they're there in the JSON.
923
            // The TOML serialization omits them completely, though.
924
            // So, we serialize the builder as TOML, and then convert the TOML to JSON Value.
925
            // That launders out the `Null`s and gives us the same Value as our original JSON.
926
            let toml_got = toml::to_string(&bcb).unwrap();
927
            let json_got: serde_json::Value = toml::from_str(&toml_got).unwrap();
928
            let json_exp: serde_json::Value = serde_json::from_str(jsons[0]).unwrap();
929
            assert_eq!(&json_got, &json_exp);
930
        };
931

            
932
        chk_broken(
933
            "Specified `settings` for a direct bridge connection",
934
            &[r#"{
935
                "settings": [["hi","there"]]
936
            }"#],
937
            &|bcb| {
938
                bcb.settings().push(("hi".into(), "there".into()));
939
            },
940
        );
941

            
942
        #[cfg(not(feature = "pt-client"))]
943
        chk_broken(
944
            "Not compiled with pluggable transport support",
945
            &[r#"{
946
                "transport": "obfs4"
947
            }"#],
948
            &|bcb| {
949
                bcb.transport("obfs4");
950
            },
951
        );
952

            
953
        #[cfg(feature = "pt-client")]
954
        chk_broken(
955
            "only numeric addresses are supported for a direct bridge connection",
956
            &[r#"{
957
                "transport": "bridge",
958
                "addrs": ["some-host:80"]
959
            }"#],
960
            &|bcb| {
961
                bcb.transport("bridge");
962
                bcb.addrs().push("some-host:80".parse().unwrap());
963
            },
964
        );
965

            
966
        chk_broken(
967
            "Missing `addrs` for a direct bridge connection",
968
            &[r#"{
969
                "transport": "-"
970
            }"#],
971
            &|bcb| {
972
                bcb.transport("-");
973
            },
974
        );
975

            
976
        #[cfg(feature = "pt-client")]
977
        chk_broken(
978
            "only supports a single nominal address",
979
            &[r#"{
980
                "transport": "obfs4",
981
                "addrs": ["some-host:80", "38.229.33.83:80"]
982
            }"#],
983
            &|bcb| {
984
                bcb.transport("obfs4");
985
                bcb.addrs().push("some-host:80".parse().unwrap());
986
                bcb.addrs().push("38.229.33.83:80".parse().unwrap());
987
            },
988
        );
989

            
990
        chk_broken(
991
            "multiple different ids of the same type (ed25519)",
992
            &[r#"{
993
                "addrs": ["38.229.33.83:80"],
994
                "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
995
                        "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISA"]
996
            }"#],
997
            &|bcb| {
998
                bcb.addrs().push("38.229.33.83:80".parse().unwrap());
999
                bcb.ids().push(
                    "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
                        .parse()
                        .unwrap(),
                );
                bcb.ids().push(
                    "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISA"
                        .parse()
                        .unwrap(),
                );
            },
        );
        chk_broken(
            "need an RSA identity",
            &[r#"{
                "addrs": ["38.229.33.83:80"],
                "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"]
            }"#],
            &|bcb| {
                bcb.addrs().push("38.229.33.83:80".parse().unwrap());
                bcb.ids().push(
                    "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
                        .parse()
                        .unwrap(),
                );
            },
        );
    }
}