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::{chain, Itertools};
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::{impl_standard_builder, ConfigBuildError};
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
1804
    fn addrs(&self) -> &[SocketAddr] {
116
1804
        self.0.addrs.addrs()
117
1804
    }
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
54
#[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
795
    fn try_from(input: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> {
168
        use BridgeConfigBuilderSerde::*;
169
795
        match input {
170
779
            BridgeLine(s) => s.parse(),
171
16
            Dict(d) => Ok(d),
172
        }
173
795
    }
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
574
    pub fn get_transport(&self) -> Option<&str> {
217
574
        self.transport.as_deref()
218
574
    }
219
}
220

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

            
228
1024
        // Error construction helpers
229
1032
        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
1024
        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
1024
        let invalid = |field: String, problem: &dyn Display| ConfigBuildError::Invalid {
240
            field,
241
            problem: problem.to_string(),
242
        };
243

            
244
1024
        let transp: TransportId = transport
245
1024
            .parse()
246
1024
            .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
1024
        let addrs = match () {
251
1024
            () if transp.is_builtin() => {
252
561
                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
557
                }
258
                #[allow(clippy::unnecessary_filter_map)] // for consistency
259
580
                let addrs = addrs.iter().filter_map(|ba| {
260
                    #[allow(clippy::redundant_pattern_matching)] // for consistency
261
553
                    if let Some(sa) = ba.as_socketaddr() {
262
549
                        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
580
                }).collect::<Result<Vec<SocketAddr>,&str>>().map_err(|problem| inconsist_transp(
271
4
                    "addrs",
272
4
                    problem,
273
559
                ))?;
274
553
                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
549
                }
280
549
                ChannelMethod::Direct(addrs)
281
            }
282

            
283
            #[cfg(feature = "pt-client")]
284
463
            () if transp.as_pluggable().is_some() => {
285
463
                let transport = transp.into_pluggable().expect("became not pluggable!");
286
459
                let addr =
287
463
                    match addrs {
288
463
                        [] => PtTargetAddr::None,
289
459
                        [addr] => Some(addr.clone()).into(),
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
459
                let mut target = PtTarget::new(transport, addr);
296
459
                for (i, (k, v)) in settings.iter().enumerate() {
297
                    // Using PtTargetSettings TryFrom would prevent us reporting the index i
298
459
                    target
299
459
                        .push_setting(k, v)
300
459
                        .map_err(|e| invalid(format!("settings.{}", i), &e))?;
301
                }
302
459
                ChannelMethod::Pluggable(target)
303
            }
304

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

            
317
1008
        let mut rsa_id = None;
318
1008
        let mut ed_id = None;
319

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

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

            
350
1006
        let rsa_id = rsa_id.ok_or_else(|| ConfigBuildError::Invalid {
351
4
            field: "ids".into(),
352
4
            problem: "need an RSA identity".into(),
353
1006
        })?;
354

            
355
1000
        Ok(BridgeConfig(
356
1000
            Inner {
357
1000
                addrs,
358
1000
                rsa_id,
359
1000
                ed_id,
360
1000
            }
361
1000
            .into(),
362
1000
        ))
363
1024
    }
364
}
365

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

            
376
1029
    fn from_str(s: &str) -> Result<Self, Self::Err> {
377
1029
        let bridge: Inner = s.parse()?;
378

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

            
405
1029
        let ids = chain!(
406
1029
            iter::once(bridge.rsa_id.into()),
407
1029
            bridge.ed_id.into_iter().map(Into::into),
408
1029
        )
409
1029
        .collect_vec();
410
1029

            
411
1029
        Ok(BridgeConfigBuilder {
412
1029
            transport: Some(transport),
413
1029
            addrs: Some(addrs),
414
1029
            settings: Some(settings),
415
1029
            ids: Some(ids),
416
1029
        })
417
1029
    }
418
}
419

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

            
428
impl FromStr for BridgeConfig {
429
    type Err = BridgeParseError;
430

            
431
874
    fn from_str(s: &str) -> Result<Self, Self::Err> {
432
874
        let inner = s.parse()?;
433
846
        Ok(BridgeConfig(Arc::new(inner)))
434
874
    }
435
}
436

            
437
impl FromStr for Inner {
438
    type Err = BridgeParseError;
439

            
440
1903
    fn from_str(s: &str) -> Result<Self, Self::Err> {
441
        use BridgeParseError as BPE;
442

            
443
1903
        let mut s = s.trim().split_ascii_whitespace().peekable();
444

            
445
        // This implements the parsing of bridge lines.
446
        // Refer to the specification in the rustdoc comment for `Bridge`.
447

            
448
        //  * Optionally, the word `Bridge` ...
449

            
450
1903
        let bridge_word = s.peek().ok_or(BPE::Empty)?;
451
1901
        if bridge_word.eq_ignore_ascii_case("bridge") {
452
12
            s.next();
453
1889
        }
454

            
455
        //  * Optionally, the name of the pluggable transport to use.
456
        //  * The `Host:ORPort` to connect to.
457

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

            
474
                #[cfg(feature = "pt-client")]
475
                {
476
518
                    let pt_name = word.parse().map_err(|pt_error| BPE::InvalidPtOrAddr {
477
4
                        word: word.to_string(),
478
4
                        pt_error,
479
518
                    })?;
480
512
                    let addr = s
481
512
                        .next()
482
534
                        .map(|s| s.parse())
483
512
                        .transpose()
484
513
                        .map_err(|source| BPE::InvalidIPtHostAddr {
485
2
                            word: word.to_string(),
486
2
                            source,
487
513
                        })?
488
510
                        .unwrap_or(PtTargetAddr::None);
489
510
                    ChannelMethod::Pluggable(PtTarget::new(pt_name, addr))
490
                }
491
            }
492
        };
493

            
494
        //  * One or more identity key fingerprints,
495

            
496
1891
        let mut rsa_id = None;
497
1891
        let mut ed_id = None;
498

            
499
4286
        while let Some(word) = s.peek() {
500
            // Helper to generate the errors if the same key type is specified more than once
501
3001
            let check_several = |was_some| {
502
2399
                if was_some {
503
4
                    Err(BPE::MultipleIdentitiesOfSameType {
504
4
                        word: word.to_string(),
505
4
                    })
506
                } else {
507
2395
                    Ok(())
508
                }
509
2399
            };
510

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

            
533
        //  * When a pluggable transport is in use,
534
        //    zero or more `key=value` parameters to pass to the transport
535

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

            
541
        #[cfg(feature = "pt-client")]
542
2399
        for word in s {
543
521
            let (k, v) = word.split_once('=').ok_or_else(|| BPE::InvalidPtKeyValue {
544
2
                word: word.to_string(),
545
521
            })?;
546

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

            
559
1879
        let rsa_id = rsa_id.ok_or(BPE::NoRsaIdentity)?;
560
1875
        Ok(Inner {
561
1875
            addrs: method,
562
1875
            rsa_id,
563
1875
            ed_id,
564
1875
        })
565
1903
    }
566
}
567

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

            
576
        //  * Optionally, the name of the pluggable transport to use.
577
        //  * The `Host:ORPort` to connect to.
578

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

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

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

            
602
        //  * One or more identity key fingerprints,
603

            
604
7488
        write!(f, " {}", rsa_id)?;
605
7488
        if let Some(ed_id) = ed_id {
606
10
            write!(f, " ed25519:{}", ed_id)?;
607
7478
        }
608

            
609
        //  * When a pluggable transport is in use,
610
        //    zero or more `key=value` parameters to pass to the transport
611

            
612
        #[cfg(not(feature = "pt-client"))]
613
        let _: Option<()> = settings;
614

            
615
        #[cfg(feature = "pt-client")]
616
7488
        for (k, v) in settings.into_iter().flatten() {
617
18
            write!(f, " {}={}", k, v)?;
618
        }
619

            
620
7488
        Ok(())
621
7488
    }
622
}
623

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

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

            
650
    fn mk_direct(s: &str) -> ChannelMethod {
651
        ChannelMethod::Direct(vec![s.parse().unwrap()])
652
    }
653

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

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

            
674
                let display = got.to_string();
675
                assert_eq!(display, sl[0]);
676
            }
677
        };
678

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

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

            
714
        #[cfg(feature = "pt-client")]
715
        chk(&[
716
            "obfs4 some-host:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE iat-mode=1",
717
            "obfs4 some-host:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE 0BAC39417268B96B9F514E7F63FA6FBA1A788955 iat-mode=1",
718
        ], Inner {
719
            addrs: mk_pt_target(
720
                "obfs4",
721
                PtTargetAddr::HostPort("some-host".into(), 80),
722
                &[
723
                    ("iat-mode", "1"),
724
                ],
725
            ),
726
            rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
727
            ed_id: Some(mk_ed("dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")),
728
        });
729

            
730
        chk(
731
            &[
732
                "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955",
733
                "Bridge 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
734
            ],
735
            Inner {
736
                addrs: mk_direct("38.229.33.83:80"),
737
                rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
738
                ed_id: None,
739
            },
740
        );
741

            
742
        chk(
743
            &[
744
                "[2001:db8::42]:123 $0bac39417268b96b9f514e7f63fa6fba1a788955",
745
                "[2001:0db8::42]:123 $0bac39417268b96b9f514e7f63fa6fba1a788955",
746
            ],
747
            Inner {
748
                addrs: mk_direct("[2001:0db8::42]:123"),
749
                rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
750
                ed_id: None,
751
            },
752
        );
753

            
754
        chk(&[
755
            "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
756
            "38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
757
        ], Inner {
758
            addrs: mk_direct("38.229.33.83:80"),
759
            rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
760
            ed_id: Some(mk_ed("dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")),
761
        });
762

            
763
        chk_e(
764
            &[
765
                "38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
766
                "Bridge 38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
767
            ],
768
            "lacks specification of RSA identity key",
769
        );
770

            
771
        chk_e(&["", "bridge"], "Bridge line was empty");
772

            
773
        chk_e(
774
            &["999.329.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955"],
775
            // Some Rust versions say "invalid socket address syntax",
776
            // some "invalid IP address syntax"
777
            r#"Cannot parse "999.329.33.83:80" as direct bridge IpAddress:ORPort"#,
778
        );
779

            
780
        chk_e(
781
            &[
782
                "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value",
783
                "Bridge 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value",
784
            ],
785
            "Parameters supplied but not valid without a pluggable transport",
786
        );
787

            
788
        chk_e(
789
            &[
790
                "bridge bridge some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
791
                "yikes! some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
792
            ],
793
            #[cfg(feature = "pt-client")]
794
            r" is not a valid pluggable transport ID), nor as direct bridge IpAddress:ORPort",
795
            #[cfg(not(feature = "pt-client"))]
796
            "is not an IpAddress:ORPort), but support disabled in cargo features",
797
        );
798

            
799
        #[cfg(feature = "pt-client")]
800
        chk_e(
801
            &["obfs4 garbage 0BAC39417268B96B9F514E7F63FA6FBA1A788955"],
802
            "as pluggable transport Host:ORPort",
803
        );
804

            
805
        #[cfg(feature = "pt-client")]
806
        chk_e(
807
            &["obfs4 some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value garbage"],
808
            r#"Expected PT key=value parameter, found "garbage" (which lacks an equals sign"#,
809
        );
810

            
811
        #[cfg(feature = "pt-client")]
812
        chk_e(
813
            &["obfs4 some-host:80 garbage"],
814
            r#"Cannot parse "garbage" as identity key (Invalid base64 data), or PT key=value"#,
815
        );
816

            
817
        chk_e(
818
            &[
819
                "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 23AC39417268B96B9F514E7F63FA6FBA1A788955",
820
                "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE xGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
821
            ],
822
            "More than one identity of the same type specified",
823
        );
824
    }
825

            
826
    #[test]
827
    fn config_api() {
828
        let chk_bridgeline = |line: &str, jsons: &[&str], f: &dyn Fn(&mut BridgeConfigBuilder)| {
829
            eprintln!(" ---- chk_bridgeline ----\n{}", line);
830

            
831
            let mut bcb = BridgeConfigBuilder::default();
832
            f(&mut bcb);
833
            let built = bcb.build().unwrap();
834
            assert_eq!(&built, &line.parse::<BridgeConfig>().unwrap());
835

            
836
            let parsed_b: BridgeConfigBuilder = line.parse().unwrap();
837
            assert_eq!(&built, &parsed_b.build().unwrap());
838

            
839
            let re_serialized = serde_json::to_value(&bcb).unwrap();
840
            assert_eq!(re_serialized, serde_json::Value::String(line.to_string()));
841

            
842
            for json in jsons {
843
                let from_dict: BridgeConfigBuilder = serde_json::from_str(json).unwrap();
844
                assert_eq!(&from_dict, &bcb);
845
                assert_eq!(&built, &from_dict.build().unwrap());
846
            }
847
        };
848

            
849
        chk_bridgeline(
850
            "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
851
            &[r#"{
852
                "addrs": ["38.229.33.83:80"],
853
                "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
854
                      "$0bac39417268b96b9f514e7f63fa6fba1a788955"]
855
            }"#],
856
            &|bcb| {
857
                bcb.addrs().push("38.229.33.83:80".parse().unwrap());
858
                bcb.ids().push("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE".parse().unwrap());
859
                bcb.ids().push("$0bac39417268b96b9f514e7f63fa6fba1a788955".parse().unwrap());
860
            }
861
        );
862

            
863
        #[cfg(feature = "pt-client")]
864
        chk_bridgeline(
865
            "obfs4 some-host:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 iat-mode=1",
866
            &[r#"{
867
                "transport": "obfs4",
868
                "addrs": ["some-host:80"],
869
                "ids": ["$0bac39417268b96b9f514e7f63fa6fba1a788955"],
870
                "settings": [["iat-mode", "1"]]
871
            }"#],
872
            &|bcb| {
873
                bcb.transport("obfs4");
874
                bcb.addrs().push("some-host:80".parse().unwrap());
875
                bcb.ids()
876
                    .push("$0bac39417268b96b9f514e7f63fa6fba1a788955".parse().unwrap());
877
                bcb.push_setting("iat-mode", "1");
878
            },
879
        );
880

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

            
884
            let mut bcb = BridgeConfigBuilder::default();
885
            f(&mut bcb);
886

            
887
            for json in jsons {
888
                let from_dict: BridgeConfigBuilder = serde_json::from_str(json).unwrap();
889
                assert_eq!(&from_dict, &bcb);
890
            }
891

            
892
            let err = bcb.build().expect_err("succeeded?!");
893
            let got_emsg = err.to_string();
894
            assert!(
895
                got_emsg.contains(emsg),
896
                "wrong error message: got_emsg={:?} err={:?} expected={:?}",
897
                &got_emsg,
898
                &err,
899
                emsg,
900
            );
901

            
902
            // This is a kludge.  When we serialize `Option<Vec<_>>` as JSON,
903
            // we get a `Null` entry.  These `Null`s aren't in our test cases and we don't
904
            // really want them, although it's OK that they're there in the JSON.
905
            // The TOML serialization omits them completely, though.
906
            // So, we serialize the builder as TOML, and then convert the TOML to JSON Value.
907
            // That launders out the `Null`s and gives us the same Value as our original JSON.
908
            let toml_got = toml::to_string(&bcb).unwrap();
909
            let json_got: serde_json::Value = toml::from_str(&toml_got).unwrap();
910
            let json_exp: serde_json::Value = serde_json::from_str(jsons[0]).unwrap();
911
            assert_eq!(&json_got, &json_exp);
912
        };
913

            
914
        chk_broken(
915
            "Specified `settings` for a direct bridge connection",
916
            &[r#"{
917
                "settings": [["hi","there"]]
918
            }"#],
919
            &|bcb| {
920
                bcb.settings().push(("hi".into(), "there".into()));
921
            },
922
        );
923

            
924
        #[cfg(not(feature = "pt-client"))]
925
        chk_broken(
926
            "Not compiled with pluggable transport support",
927
            &[r#"{
928
                "transport": "obfs4"
929
            }"#],
930
            &|bcb| {
931
                bcb.transport("obfs4");
932
            },
933
        );
934

            
935
        #[cfg(feature = "pt-client")]
936
        chk_broken(
937
            "only numeric addresses are supported for a direct bridge connection",
938
            &[r#"{
939
                "transport": "bridge",
940
                "addrs": ["some-host:80"]
941
            }"#],
942
            &|bcb| {
943
                bcb.transport("bridge");
944
                bcb.addrs().push("some-host:80".parse().unwrap());
945
            },
946
        );
947

            
948
        chk_broken(
949
            "Missing `addrs` for a direct bridge connection",
950
            &[r#"{
951
                "transport": "-"
952
            }"#],
953
            &|bcb| {
954
                bcb.transport("-");
955
            },
956
        );
957

            
958
        #[cfg(feature = "pt-client")]
959
        chk_broken(
960
            "only supports a single nominal address",
961
            &[r#"{
962
                "transport": "obfs4",
963
                "addrs": ["some-host:80", "38.229.33.83:80"]
964
            }"#],
965
            &|bcb| {
966
                bcb.transport("obfs4");
967
                bcb.addrs().push("some-host:80".parse().unwrap());
968
                bcb.addrs().push("38.229.33.83:80".parse().unwrap());
969
            },
970
        );
971

            
972
        chk_broken(
973
            "multiple different ids of the same type (ed25519)",
974
            &[r#"{
975
                "addrs": ["38.229.33.83:80"],
976
                "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
977
                        "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISA"]
978
            }"#],
979
            &|bcb| {
980
                bcb.addrs().push("38.229.33.83:80".parse().unwrap());
981
                bcb.ids().push(
982
                    "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
983
                        .parse()
984
                        .unwrap(),
985
                );
986
                bcb.ids().push(
987
                    "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISA"
988
                        .parse()
989
                        .unwrap(),
990
                );
991
            },
992
        );
993

            
994
        chk_broken(
995
            "need an RSA identity",
996
            &[r#"{
997
                "addrs": ["38.229.33.83:80"],
998
                "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"]
999
            }"#],
            &|bcb| {
                bcb.addrs().push("38.229.33.83:80".parse().unwrap());
                bcb.ids().push(
                    "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
                        .parse()
                        .unwrap(),
                );
            },
        );
    }
}