1
//! Types and traits for converting objects to addresses which
2
//! Tor can connect to.
3

            
4
use crate::StreamPrefs;
5
use crate::err::ErrorDetail;
6
use std::fmt::Display;
7
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
8
use std::str::FromStr;
9
use thiserror::Error;
10
use tor_basic_utils::StrExt;
11
use tor_error::{ErrorKind, HasKind};
12

            
13
#[cfg(feature = "onion-service-client")]
14
use tor_hscrypto::pk::{HSID_ONION_SUFFIX, HsId};
15

            
16
/// Fake plastic imitation of some of the `tor-hs*` functionality
17
#[cfg(not(feature = "onion-service-client"))]
18
pub(crate) mod hs_dummy {
19
    use super::*;
20
    use tor_error::internal;
21
    use void::Void;
22

            
23
    /// Parsed hidden service identity - uninhabited, since not supported
24
    #[derive(Debug, Clone)]
25
    pub(crate) struct HsId(pub(crate) Void);
26

            
27
    impl PartialEq for HsId {
28
        fn eq(&self, _other: &Self) -> bool {
29
            void::unreachable(self.0)
30
        }
31
    }
32
    impl Eq for HsId {}
33

            
34
    /// Duplicates `tor-hscrypto::pk::HSID_ONION_SUFFIX`, ah well
35
    pub(crate) const HSID_ONION_SUFFIX: &str = ".onion";
36

            
37
    /// Must not be used other than for actual `.onion` addresses
38
    impl FromStr for HsId {
39
        type Err = ErrorDetail;
40

            
41
        fn from_str(s: &str) -> Result<Self, Self::Err> {
42
            if !s.ends_with(HSID_ONION_SUFFIX) {
43
                return Err(internal!("non-.onion passed to dummy HsId::from_str").into());
44
            }
45

            
46
            Err(ErrorDetail::OnionAddressNotSupported)
47
        }
48
    }
49
}
50
#[cfg(not(feature = "onion-service-client"))]
51
use hs_dummy::*;
52

            
53
// ----------------------------------------------------------------------
54

            
55
/// An object that can be converted to a [`TorAddr`] with a minimum of risk.
56
///
57
/// Typically, this trait will be implemented for a hostname or service name.
58
///
59
/// Don't implement this trait for IP addresses and similar types; instead,
60
/// implement [`DangerouslyIntoTorAddr`] for those.  (The trouble with accepting
61
/// IP addresses is that, in order to get an IP address, most programs will do a
62
/// local hostname lookup, which will leak the target address to the DNS
63
/// resolver. The `DangerouslyIntoTorAddr` trait provides a contract for careful
64
/// programs to say, "I have gotten this IP address from somewhere safe."  This
65
/// trait is for name-based addressing and similar, which _usually_ gets its
66
/// addresses from a safer source.)
67
///
68
/// [*See also: the `TorAddr` documentation.*](TorAddr)
69
///
70
/// # Design note
71
///
72
/// We use a separate trait here, instead of using `Into<TorAddr>` or
73
/// `TryInto<TorAddr>`, because `IntoTorAddr` implies additional guarantees
74
/// relating to privacy risk.  The separate trait alerts users that something
75
/// tricky is going on here, and encourages them to think twice before
76
/// implementing `IntoTorAddr` for their own types.
77
pub trait IntoTorAddr {
78
    /// Try to make a [`TorAddr`] to represent connecting to this
79
    /// address.
80
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError>;
81
}
82

            
83
/// An object that can be converted to a [`TorAddr`], but which it
84
/// might be risky to get in the first place if you're hoping for
85
/// anonymity.
86
///
87
/// For example, you can use this trait to convert a [`SocketAddr`]
88
/// into a [`TorAddr`], and it's safe to do that conversion.  But
89
/// where did you get the [`SocketAddr`] in the first place?  If it
90
/// comes from a local DNS lookup, then you have leaked the address
91
/// you were resolving to your DNS resolver, and probably your ISP.
92
///
93
/// [*See also: the `TorAddr` documentation.*](TorAddr)
94
pub trait DangerouslyIntoTorAddr {
95
    /// Try to make a [`TorAddr`] to represent connecting to `self`.
96
    ///
97
    /// By calling this function, the caller asserts that `self` was
98
    /// obtained from some secure, private mechanism, and **not** from a local
99
    /// DNS lookup or something similar.
100
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError>;
101
}
102

            
103
/// An address object that you can connect to over the Tor network.
104
///
105
/// When you're making a connection with Tor, you shouldn't do your DNS
106
/// lookups locally: that would leak your target address to your DNS server.
107
/// Instead, it's better to use a combination of a hostname and a port
108
/// directly.
109
///
110
/// The preferred way to create a `TorAddr` is via the [`IntoTorAddr`] trait,
111
/// using a hostname and a port (or a string containing a hostname and a
112
/// port).  It's also okay to use an IP and Port there, but only if they come
113
/// from some source _other than_ a local DNS lookup.
114
///
115
/// In order to discourage local hostname lookups, the functions that
116
/// construct a [`TorAddr`] from [`IpAddr`], [`SocketAddr`], and so
117
/// forth are labeled as "dangerous".
118
///
119
/// # Examples
120
///
121
/// Making a `TorAddr` from various "safe" sources:
122
///
123
/// ```rust
124
/// # use anyhow::Result;
125
/// # fn main() -> Result<()> {
126
/// use arti_client::IntoTorAddr;
127
///
128
/// let example_from_tuple = ("example.com", 80).into_tor_addr()?;
129
/// let example_from_string = "example.com:80".into_tor_addr()?;
130
///
131
/// assert_eq!(example_from_tuple, example_from_string);
132
/// # Ok(())
133
/// # }
134
/// ```
135
///
136
/// Making a `TorAddr` from an IP address and port:
137
///
138
/// > **Warning:** This example is only safe because we're not doing a DNS lookup; rather, the
139
/// > intent is to connect to a hardcoded IP address.
140
/// > If you're using [`DangerouslyIntoTorAddr`], pay careful attention to where your IP addresses
141
/// > are coming from, and whether there's a risk of information leakage.
142
///
143
/// ```rust
144
/// # use anyhow::Result;
145
/// # fn main() -> Result<()> {
146
/// use arti_client::DangerouslyIntoTorAddr;
147
/// use std::net::{IpAddr, SocketAddr};
148
///
149
/// let quad_one_dns: SocketAddr = "1.1.1.1:53".parse()?;
150
/// let addr_from_socketaddr = quad_one_dns.into_tor_addr_dangerously()?;
151
///
152
/// let quad_one_ip: IpAddr = "1.1.1.1".parse()?;
153
/// let addr_from_tuple = (quad_one_ip, 53).into_tor_addr_dangerously()?;
154
///
155
/// assert_eq!(addr_from_socketaddr, addr_from_tuple);
156
/// # Ok(())
157
/// # }
158
/// ```
159
#[derive(Debug, Clone, Eq, PartialEq)]
160
pub struct TorAddr {
161
    /// The target host.
162
    host: Host,
163
    /// The target port number.
164
    port: u16,
165
}
166

            
167
/// How to make a stream to this `TorAddr`?
168
///
169
/// This is a separate type, returned from `address.rs` to `client.rs`,
170
/// so that we can test our "how to make a connection" logic and policy,
171
/// in isolation, without a whole Tor client.
172
#[derive(Debug, PartialEq, Eq)]
173
pub(crate) enum StreamInstructions {
174
    /// Create an exit circuit suitable for port, and then make a stream to `hostname`
175
    Exit {
176
        /// Hostname
177
        hostname: String,
178
        /// Port
179
        port: u16,
180
    },
181
    /// Create a hidden service connection to hsid, and then make a stream to `hostname`
182
    ///
183
    /// `HsId`, and therefore this variant, is uninhabited, unless the feature is enabled
184
    Hs {
185
        /// The target hidden service
186
        hsid: HsId,
187
        /// The hostname (used for subdomains, sent to the peer)
188
        hostname: String,
189
        /// Port
190
        port: u16,
191
    },
192
}
193

            
194
/// How to resolve this Tor host address into IP address(es)
195
#[derive(PartialEq, Eq, Debug)]
196
pub(crate) enum ResolveInstructions {
197
    /// Create an exit circuit without port restrictions, and ask the exit
198
    Exit(String),
199
    /// Simply return this
200
    Return(Vec<IpAddr>),
201
}
202

            
203
impl TorAddr {
204
    /// Construct a TorAddr from its constituent parts, rejecting it if the
205
    /// port is zero.
206
59
    fn new(host: Host, port: u16) -> Result<Self, TorAddrError> {
207
59
        if port == 0 {
208
1
            Err(TorAddrError::BadPort)
209
        } else {
210
58
            Ok(TorAddr { host, port })
211
        }
212
59
    }
213

            
214
    /// Construct a `TorAddr` from any object that implements
215
    /// [`IntoTorAddr`].
216
33
    pub fn from<A: IntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
217
33
        addr.into_tor_addr()
218
33
    }
219
    /// Construct a `TorAddr` from any object that implements
220
    /// [`DangerouslyIntoTorAddr`].
221
    ///
222
    /// See [`DangerouslyIntoTorAddr`] for an explanation of why the
223
    /// style of programming supported by this function is dangerous
224
    /// to use.
225
7
    pub fn dangerously_from<A: DangerouslyIntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
226
7
        addr.into_tor_addr_dangerously()
227
7
    }
228

            
229
    /// Return true if this is an IP address (rather than a hostname).
230
5
    pub fn is_ip_address(&self) -> bool {
231
5
        matches!(&self.host, Host::Ip(_))
232
5
    }
233

            
234
    /// If this TorAddr is an explicit IP address, return a reference to that [`IpAddr`].
235
    pub fn as_ip_address(&self) -> Option<&IpAddr> {
236
        match &self.host {
237
            Host::Ip(a) => Some(a),
238
            _ => None,
239
        }
240
    }
241

            
242
    /// Get instructions for how to make a stream to this address
243
9
    pub(crate) fn into_stream_instructions(
244
9
        self,
245
9
        cfg: &crate::config::ClientAddrConfig,
246
9
        prefs: &StreamPrefs,
247
9
    ) -> Result<StreamInstructions, ErrorDetail> {
248
9
        self.enforce_config(cfg, prefs)?;
249

            
250
8
        let port = self.port;
251
8
        Ok(match self.host {
252
3
            Host::Hostname(hostname) => StreamInstructions::Exit { hostname, port },
253
1
            Host::Ip(ip) => StreamInstructions::Exit {
254
1
                hostname: ip.to_string(),
255
1
                port,
256
1
            },
257
4
            Host::Onion(onion) => {
258
                // The HS is identified by the last two domain name components
259
4
                let rhs = onion
260
4
                    .rmatch_indices('.')
261
4
                    .nth(1)
262
4
                    .map(|(i, _)| i + 1)
263
4
                    .unwrap_or(0);
264
4
                let rhs = onion
265
4
                    .get(rhs..)
266
4
                    .expect("character index was not a valid index!?");
267
4
                let hsid = rhs.parse()?;
268
4
                StreamInstructions::Hs {
269
4
                    hsid,
270
4
                    port,
271
4
                    hostname: onion,
272
4
                }
273
            }
274
        })
275
9
    }
276

            
277
    /// Get instructions for how to make a stream to this address
278
11
    pub(crate) fn into_resolve_instructions(
279
11
        self,
280
11
        cfg: &crate::config::ClientAddrConfig,
281
11
        prefs: &StreamPrefs,
282
11
    ) -> Result<ResolveInstructions, ErrorDetail> {
283
        // We defer enforcing the config until we see if this is a .onion,
284
        // in which case it's always doomed and we want to return *our* error,
285
        // not any problem with the configuration or preferences.
286
        // But we must *calculate* the error now because instructions consumes self.
287
11
        let enforce_config_result = self.enforce_config(cfg, prefs);
288

            
289
        // This IEFE is so that any use of `return` doesn't bypass
290
        // checking the enforce_config result
291
11
        let instructions = (move || {
292
11
            Ok(match self.host {
293
3
                Host::Hostname(hostname) => ResolveInstructions::Exit(hostname),
294
3
                Host::Ip(ip) => ResolveInstructions::Return(vec![ip]),
295
5
                Host::Onion(_) => return Err(ErrorDetail::OnionAddressResolveRequest),
296
            })
297
5
        })()?;
298

            
299
6
        let () = enforce_config_result?;
300

            
301
4
        Ok(instructions)
302
11
    }
303

            
304
    /// Return true if `addr` is a globally reachable address.
305
    ///
306
    /// Used for deciding if this [`TorAddr`] is an acceptable BEGIN target
307
    /// (typically, the target of a BEGIN must be globally reachable).
308
    ///
309
    /// See [`is_globally_reachable_unicast`].
310
30
    fn is_globally_reachable_unicast(&self) -> bool {
311
30
        self.host.is_globally_reachable_unicast()
312
30
    }
313

            
314
    /// Give an error if this address doesn't conform to the rules set in
315
    /// `cfg`.
316
32
    fn enforce_config(
317
32
        &self,
318
32
        cfg: &crate::config::ClientAddrConfig,
319
32
        #[allow(unused_variables)] // will only be used in certain configurations
320
32
        prefs: &StreamPrefs,
321
32
    ) -> Result<(), ErrorDetail> {
322
32
        let allow_local_addrs = prefs
323
32
            .connect_to_local_addrs
324
32
            .as_bool()
325
32
            .unwrap_or(cfg.allow_local_addrs);
326

            
327
32
        if !allow_local_addrs && !self.is_globally_reachable_unicast() {
328
3
            return Err(ErrorDetail::LocalAddress);
329
29
        }
330

            
331
29
        if let Host::Hostname(addr) = &self.host {
332
9
            if !is_valid_hostname(addr) {
333
                // This ought not to occur, because it violates Host's invariant
334
1
                return Err(ErrorDetail::InvalidHostname);
335
8
            }
336
8
            if addr.ends_with_ignore_ascii_case(HSID_ONION_SUFFIX) {
337
                // This ought not to occur, because it violates Host's invariant
338
1
                return Err(ErrorDetail::OnionAddressNotSupported);
339
7
            }
340
20
        }
341

            
342
27
        if let Host::Onion(_name) = &self.host {
343
            cfg_if::cfg_if! {
344
                if #[cfg(feature = "onion-service-client")] {
345
13
                    if !prefs.connect_to_onion_services.as_bool().unwrap_or(cfg.allow_onion_addrs) {
346
2
                        return Err(ErrorDetail::OnionAddressDisabled);
347
11
                    }
348
                } else {
349
                    return Err(ErrorDetail::OnionAddressNotSupported);
350
                }
351
            }
352
14
        }
353

            
354
25
        Ok(())
355
32
    }
356
}
357

            
358
impl std::fmt::Display for TorAddr {
359
14
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360
10
        match self.host {
361
5
            Host::Ip(IpAddr::V6(addr)) => write!(f, "[{}]:{}", addr, self.port),
362
9
            _ => write!(f, "{}:{}", self.host, self.port),
363
        }
364
14
    }
365
}
366

            
367
/// An error created while making or using a [`TorAddr`].
368
//
369
// NOTE: Unlike ErrorDetail, this is a `pub` enum: Do not make breaking changes
370
// to it, or expose lower-level errors in it, without careful consideration!
371
#[derive(Debug, Error, Clone, Eq, PartialEq)]
372
#[non_exhaustive]
373
pub enum TorAddrError {
374
    /// Tried to parse a string that can never be interpreted as a valid host.
375
    #[error("String can never be a valid hostname")]
376
    InvalidHostname,
377
    /// Tried to parse a string as an `address:port`, but it had no port.
378
    #[error("No port found in string")]
379
    NoPort,
380
    /// Tried to parse a port that wasn't a valid nonzero `u16`.
381
    #[error("Could not parse port")]
382
    BadPort,
383
}
384

            
385
impl HasKind for TorAddrError {
386
3
    fn kind(&self) -> ErrorKind {
387
        use ErrorKind as EK;
388
        use TorAddrError as TAE;
389

            
390
3
        match self {
391
1
            TAE::InvalidHostname => EK::InvalidStreamTarget,
392
1
            TAE::NoPort => EK::InvalidStreamTarget,
393
1
            TAE::BadPort => EK::InvalidStreamTarget,
394
        }
395
3
    }
396
}
397

            
398
/// A host that Tor can connect to: either a hostname or an IP address.
399
//
400
// We use `String` in here, and pass that directly to (for example)
401
// `HsId::from_str`, or `begin_stream`.
402
// In theory we could use a couple of newtypes or something, but
403
//  * The stringly-typed `HsId::from_str` call (on a string known to end `.onion`)
404
//    appears precisely in `into_stream_instructions` which knows what it's doing;
405
//  * The stringly-typed .onion domain name must be passed in the
406
//    StreamInstructions so that we can send it to the HS for its vhosting.
407
#[derive(Clone, Debug, Eq, PartialEq)]
408
enum Host {
409
    /// A hostname.
410
    ///
411
    /// This variant should never be used if the `Ip`
412
    /// variant could be used instead.
413
    /// Ie, it must not be a stringified IP address.
414
    ///
415
    /// Likewise, this variant must *not* be used for a `.onion` address.
416
    /// Even if we have `.onion` support compiled out, we use the `Onion` variant for that.
417
    ///
418
    /// But, this variant might *not* be on the public internet.
419
    /// For example, it might be `localhost`.
420
    Hostname(String),
421
    /// An IP address.
422
    Ip(IpAddr),
423
    /// The address of a hidden service (`.onion` service).
424
    ///
425
    /// We haven't validated that the base32 makes any kind of sense, yet.
426
    /// We do that when we try to connect.
427
    Onion(String),
428
}
429

            
430
impl FromStr for Host {
431
    type Err = TorAddrError;
432
45
    fn from_str(s: &str) -> Result<Host, TorAddrError> {
433
45
        if s.ends_with_ignore_ascii_case(".onion") && is_valid_hostname(s) {
434
9
            Ok(Host::Onion(s.to_owned()))
435
36
        } else if let Ok(ip_addr) = s.parse() {
436
15
            Ok(Host::Ip(ip_addr))
437
21
        } else if is_valid_hostname(s) {
438
            // TODO(nickm): we might someday want to reject some kinds of bad
439
            // hostnames here, rather than when we're about to connect to them.
440
            // But that would be an API break, and maybe not what people want.
441
            // Maybe instead we should have a method to check whether a hostname
442
            // is "bad"? Not sure; we'll need to decide the right behavior here.
443
20
            Ok(Host::Hostname(s.to_owned()))
444
        } else {
445
1
            Err(TorAddrError::InvalidHostname)
446
        }
447
45
    }
448
}
449

            
450
impl Host {
451
    /// Return true if this address is one that is not "internal": that is,
452
    /// if `addr` is a globally reachable unicast address.
453
45
    fn is_globally_reachable_unicast(&self) -> bool {
454
45
        match self {
455
12
            Host::Hostname(name) => !name.eq_ignore_ascii_case("localhost"),
456
20
            Host::Ip(ip) => is_globally_reachable_unicast(*ip),
457
13
            Host::Onion(_) => true,
458
        }
459
45
    }
460
}
461

            
462
/// Return true if `addr` is a globally reachable unicast address.
463
///
464
/// Returns false if `addr` is any of the following:
465
///
466
///   * the loopback address (127.0.0.1/8, ::1). See RFC1122, RFC4291
467
///   * a private address, as defined in RFC1918
468
///   * unspecified (0.0.0.0, ::)
469
///   * part of the Shared Address Space defined in RFC6598 (100.64.0.0/10)
470
///   * a unique local address (fc00::/7). See RFC4193
471
///   * a unicast address with link-local scope, as defined in RFC4291
472
///
473
/// Used for deciding whether an address is a valid BEGIN target.
474
/// Additionally, we use this to determine whether to allow a reverse DNS lookup
475
/// (ordinarily, reverse lookups for non-globally reachable addresses are rejected),
476
/// and to filter out any non-globally reachable addresses from the exit-provided
477
/// RESOLVED responses.
478
///
479
/// False negatives are allowed, but false positives are not
480
/// (to avoid unintentionally opening connections to local services,
481
/// either on the exit, or on the client itself).
482
///
483
/// Semantically, this function is roughly equivalent to checking
484
/// `(IpAddr::is_global() && !IpAddr::is_broadcast() && !IpAddr::is_multicast())`,
485
/// with the only difference being that our implementation of the `is_global()` check
486
/// does not cover
487
///
488
///   * addresses reserved for benchmarking (RFC2544, RFC5180)
489
///   * addresses reserved for documentation (RFC5737, RFC3849, RFC9637)
490
///   * v4 addresses reserved by IANA for future use (RFC1112)
491
///   * v6 discard-only address block (`100::/64`)
492
///   * v6 IETF special assignments of `2001::/23`
493
///   * 6to4 (`2002::/16`) – it's not explicitly documented as globally reachable,
494
///     IANA says N/A.
495
///   * segment routing (SRv6) SIDs (`5f00::/16`)
496
///
497
// Note(gabi): for the sake of completeness, we may want to extend this function to
498
// cover the ranges mentioned above too, because in theory, these addresses
499
// shouldn't be reachable anyway.
500
//
501
// TODO: we may want to rewrite this function using IpAddr::is_global() once
502
// that becomes stable.
503
20
pub(crate) fn is_globally_reachable_unicast(addr: IpAddr) -> bool {
504
    // This ensures we handle IPv4-mapped addresses correctly
505
20
    let addr = addr.to_canonical();
506
    // TODO: use is_global once it's stable, perhaps.
507
    // NOTE: Contrast this with is_sufficiently_private in tor-hsproxy,
508
    // which has a different purpose. Also see #1159.
509
    // The purpose of _this_ test is to find addresses that cannot
510
    // meaningfully be connected to over Tor, and that the exit
511
    // will not accept.
512
    //
513
    // TODO: we may want to extend this to cover more non-routable ranges
514
    // (for example, the reserved documentation prefixes)
515
20
    !(match addr {
516
11
        IpAddr::V4(v4) => {
517
11
            v4.is_loopback() // RFC1122 (127.0.0.0/8)
518
8
                || v4.is_private() // RFC1918
519
6
                || v4.is_unspecified() // 0.0.0.0
520
5
                || v4.is_link_local() // RFC3927 (169.254.0.0/16)
521
5
                || is_shared(v4) // RFC6598
522
4
                || v4.is_broadcast() // RFC919
523
3
                || v4.is_multicast() // RFC6771
524
        }
525
9
        IpAddr::V6(v6) => {
526
9
            v6.is_loopback() // RFC4291 (::1)
527
8
                || v6.is_unspecified() // RFC4291 (::)
528
7
                || v6.is_unique_local() // RFC4193 (fc00::/7)
529
6
                || v6.is_unicast_link_local() // RFC4291 (fe80::/10)
530
5
                || v6.is_multicast() // RFC4291
531
        }
532
    })
533
20
}
534
/// Returns [`true`] if this address is part of the Shared Address Space defined in
535
/// [IETF RFC 6598] (`100.64.0.0/10`).
536
///
537
/// [IETF RFC 6598]: https://tools.ietf.org/html/rfc6598
538
///
539
// TODO: use IPv4::is_shared() when it becomes stable.
540
// See <https://github.com/rust-lang/rust/issues/137259>
541
5
fn is_shared(addr: Ipv4Addr) -> bool {
542
5
    addr.octets()[0] == 100 && (addr.octets()[1] & 0b1100_0000 == 0b0100_0000)
543
5
}
544

            
545
impl std::fmt::Display for Host {
546
9
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547
9
        match self {
548
3
            Host::Hostname(s) => Display::fmt(s, f),
549
5
            Host::Ip(ip) => Display::fmt(ip, f),
550
1
            Host::Onion(onion) => Display::fmt(onion, f),
551
        }
552
9
    }
553
}
554

            
555
impl IntoTorAddr for TorAddr {
556
4
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
557
4
        Ok(self)
558
4
    }
559
}
560

            
561
impl<A: IntoTorAddr + Clone> IntoTorAddr for &A {
562
1
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
563
1
        self.clone().into_tor_addr()
564
1
    }
565
}
566

            
567
impl IntoTorAddr for &str {
568
44
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
569
44
        if let Ok(sa) = SocketAddr::from_str(self) {
570
22
            TorAddr::new(Host::Ip(sa.ip()), sa.port())
571
        } else {
572
22
            let (host, port) = self.rsplit_once(':').ok_or(TorAddrError::NoPort)?;
573
21
            let host = host.parse()?;
574
20
            let port = port.parse().map_err(|_| TorAddrError::BadPort)?;
575
19
            TorAddr::new(host, port)
576
        }
577
44
    }
578
}
579

            
580
impl IntoTorAddr for String {
581
2
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
582
2
        self.as_str().into_tor_addr()
583
2
    }
584
}
585

            
586
impl FromStr for TorAddr {
587
    type Err = TorAddrError;
588
8
    fn from_str(s: &str) -> Result<Self, TorAddrError> {
589
8
        s.into_tor_addr()
590
8
    }
591
}
592

            
593
impl IntoTorAddr for (&str, u16) {
594
9
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
595
9
        let (host, port) = self;
596
9
        let host = host.parse()?;
597
9
        TorAddr::new(host, port)
598
9
    }
599
}
600

            
601
impl IntoTorAddr for (String, u16) {
602
1
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
603
1
        let (host, port) = self;
604
1
        (host.as_str(), port).into_tor_addr()
605
1
    }
606
}
607

            
608
impl<T: DangerouslyIntoTorAddr + Clone> DangerouslyIntoTorAddr for &T {
609
1
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
610
1
        self.clone().into_tor_addr_dangerously()
611
1
    }
612
}
613

            
614
impl DangerouslyIntoTorAddr for (IpAddr, u16) {
615
3
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
616
3
        let (addr, port) = self;
617
3
        TorAddr::new(Host::Ip(addr), port)
618
3
    }
619
}
620

            
621
impl DangerouslyIntoTorAddr for (Ipv4Addr, u16) {
622
2
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
623
2
        let (addr, port) = self;
624
2
        TorAddr::new(Host::Ip(addr.into()), port)
625
2
    }
626
}
627

            
628
impl DangerouslyIntoTorAddr for (Ipv6Addr, u16) {
629
2
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
630
2
        let (addr, port) = self;
631
2
        TorAddr::new(Host::Ip(addr.into()), port)
632
2
    }
633
}
634

            
635
impl DangerouslyIntoTorAddr for SocketAddr {
636
1
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
637
1
        let (addr, port) = (self.ip(), self.port());
638
1
        (addr, port).into_tor_addr_dangerously()
639
1
    }
640
}
641

            
642
impl DangerouslyIntoTorAddr for SocketAddrV4 {
643
1
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
644
1
        let (addr, port) = (self.ip(), self.port());
645
1
        (*addr, port).into_tor_addr_dangerously()
646
1
    }
647
}
648

            
649
impl DangerouslyIntoTorAddr for SocketAddrV6 {
650
1
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
651
1
        let (addr, port) = (self.ip(), self.port());
652
1
        (*addr, port).into_tor_addr_dangerously()
653
1
    }
654
}
655

            
656
/// Check whether `hostname` is a valid hostname or not.
657
///
658
/// (Note that IPv6 addresses don't follow these rules.)
659
47
fn is_valid_hostname(hostname: &str) -> bool {
660
47
    hostname_validator::is_valid(hostname)
661
47
}
662

            
663
#[cfg(test)]
664
mod test {
665
    // @@ begin test lint list maintained by maint/add_warning @@
666
    #![allow(clippy::bool_assert_comparison)]
667
    #![allow(clippy::clone_on_copy)]
668
    #![allow(clippy::dbg_macro)]
669
    #![allow(clippy::mixed_attributes_style)]
670
    #![allow(clippy::print_stderr)]
671
    #![allow(clippy::print_stdout)]
672
    #![allow(clippy::single_char_pattern)]
673
    #![allow(clippy::unwrap_used)]
674
    #![allow(clippy::unchecked_time_subtraction)]
675
    #![allow(clippy::useless_vec)]
676
    #![allow(clippy::needless_pass_by_value)]
677
    #![allow(clippy::string_slice)] // See arti#2571
678
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
679
    use super::*;
680

            
681
    #[test]
682
    fn test_error_kind() {
683
        use tor_error::ErrorKind as EK;
684

            
685
        assert_eq!(
686
            TorAddrError::InvalidHostname.kind(),
687
            EK::InvalidStreamTarget
688
        );
689
        assert_eq!(TorAddrError::NoPort.kind(), EK::InvalidStreamTarget);
690
        assert_eq!(TorAddrError::BadPort.kind(), EK::InvalidStreamTarget);
691
    }
692

            
693
    /// Make a `StreamPrefs` with `.onion` enabled, if cfg-enabled
694
    fn mk_stream_prefs() -> StreamPrefs {
695
        let prefs = crate::StreamPrefs::default();
696

            
697
        #[cfg(feature = "onion-service-client")]
698
        let prefs = {
699
            let mut prefs = prefs;
700
            prefs.connect_to_onion_services(tor_config::BoolOrAuto::Explicit(true));
701
            prefs
702
        };
703

            
704
        prefs
705
    }
706

            
707
    #[test]
708
    fn validate_hostname() {
709
        // Valid hostname tests
710
        assert!(is_valid_hostname("torproject.org"));
711
        assert!(is_valid_hostname("Tor-Project.org"));
712
        assert!(is_valid_hostname("example.onion"));
713
        assert!(is_valid_hostname("some.example.onion"));
714

            
715
        // Invalid hostname tests
716
        assert!(!is_valid_hostname("-torproject.org"));
717
        assert!(!is_valid_hostname("_torproject.org"));
718
        assert!(!is_valid_hostname("tor_project1.org"));
719
        assert!(!is_valid_hostname("iwanna$money.org"));
720
    }
721

            
722
    #[test]
723
    fn validate_addr() {
724
        use crate::err::ErrorDetail;
725
        fn val<A: IntoTorAddr>(addr: A) -> Result<TorAddr, ErrorDetail> {
726
            let toraddr = addr.into_tor_addr()?;
727
            toraddr.enforce_config(&Default::default(), &mk_stream_prefs())?;
728
            Ok(toraddr)
729
        }
730

            
731
        assert!(val("[2001:db8::42]:20").is_ok());
732
        assert!(val(("2001:db8::42", 20)).is_ok());
733
        assert!(val(("198.151.100.42", 443)).is_ok());
734
        assert!(val("198.151.100.42:443").is_ok());
735
        assert!(val("www.torproject.org:443").is_ok());
736
        assert!(val(("www.torproject.org", 443)).is_ok());
737

            
738
        // When HS disabled, tested elsewhere, see: stream_instructions, prefs_onion_services
739
        #[cfg(feature = "onion-service-client")]
740
        {
741
            assert!(val("example.onion:80").is_ok());
742
            assert!(val(("example.onion", 80)).is_ok());
743

            
744
            match val("eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443") {
745
                Ok(TorAddr {
746
                    host: Host::Onion(_),
747
                    ..
748
                }) => {}
749
                x => panic!("{x:?}"),
750
            }
751
        }
752

            
753
        assert!(matches!(
754
            val("-foobar.net:443"),
755
            Err(ErrorDetail::InvalidHostname)
756
        ));
757
        assert!(matches!(
758
            val("www.torproject.org"),
759
            Err(ErrorDetail::Address(TorAddrError::NoPort))
760
        ));
761

            
762
        assert!(matches!(
763
            val("192.168.0.1:80"),
764
            Err(ErrorDetail::LocalAddress)
765
        ));
766
        assert!(matches!(
767
            val(TorAddr::new(Host::Hostname("foo@bar".to_owned()), 553).unwrap()),
768
            Err(ErrorDetail::InvalidHostname)
769
        ));
770
        assert!(matches!(
771
            val(TorAddr::new(Host::Hostname("foo.onion".to_owned()), 80).unwrap()),
772
            Err(ErrorDetail::OnionAddressNotSupported)
773
        ));
774
    }
775

            
776
    #[test]
777
    fn local_addrs() {
778
        fn is_local_hostname(s: &str) -> bool {
779
            let h: Host = s.parse().unwrap();
780
            !h.is_globally_reachable_unicast()
781
        }
782

            
783
        assert!(is_local_hostname("localhost"));
784
        assert!(is_local_hostname("loCALHOST"));
785
        assert!(is_local_hostname("127.0.0.1"));
786
        assert!(is_local_hostname("::1"));
787
        assert!(is_local_hostname("::ffff:127.0.0.1"));
788
        assert!(is_local_hostname("192.168.0.1"));
789
        assert!(is_local_hostname("0.0.0.0"));
790
        assert!(is_local_hostname("::"));
791
        assert!(is_local_hostname("100.64.0.1"));
792
        assert!(is_local_hostname("fc00::"));
793
        assert!(is_local_hostname("fe80::"));
794
        assert!(is_local_hostname("255.255.255.255"));
795
        assert!(is_local_hostname("224.1.1.1"));
796
        assert!(is_local_hostname("ff00::"));
797

            
798
        assert!(!is_local_hostname("www.example.com"));
799
    }
800

            
801
    #[test]
802
    fn is_ip_address() {
803
        fn ip(s: &str) -> bool {
804
            TorAddr::from(s).unwrap().is_ip_address()
805
        }
806

            
807
        assert!(ip("192.168.0.1:80"));
808
        assert!(ip("[::1]:80"));
809
        assert!(ip("[2001:db8::42]:65535"));
810
        assert!(!ip("example.com:80"));
811
        assert!(!ip("example.onion:80"));
812
    }
813

            
814
    #[test]
815
    fn stream_instructions() {
816
        use StreamInstructions as SI;
817

            
818
        fn sap(s: &str) -> Result<StreamInstructions, ErrorDetail> {
819
            TorAddr::from(s)
820
                .unwrap()
821
                .into_stream_instructions(&Default::default(), &mk_stream_prefs())
822
        }
823

            
824
        assert_eq!(
825
            sap("[2001:db8::42]:9001").unwrap(),
826
            SI::Exit {
827
                hostname: "2001:db8::42".to_owned(),
828
                port: 9001
829
            },
830
        );
831
        assert_eq!(
832
            sap("example.com:80").unwrap(),
833
            SI::Exit {
834
                hostname: "example.com".to_owned(),
835
                port: 80
836
            },
837
        );
838

            
839
        {
840
            let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
841
            let onion = format!("sss1234.www.{}.onion", b32);
842
            let got = sap(&format!("{}:443", onion));
843

            
844
            #[cfg(feature = "onion-service-client")]
845
            assert_eq!(
846
                got.unwrap(),
847
                SI::Hs {
848
                    hsid: format!("{}.onion", b32).parse().unwrap(),
849
                    hostname: onion,
850
                    port: 443,
851
                }
852
            );
853

            
854
            #[cfg(not(feature = "onion-service-client"))]
855
            assert!(matches!(got, Err(ErrorDetail::OnionAddressNotSupported)));
856
        }
857
    }
858

            
859
    #[test]
860
    fn resolve_instructions() {
861
        use ResolveInstructions as RI;
862

            
863
        fn sap(s: &str, prefs: &StreamPrefs) -> Result<ResolveInstructions, ErrorDetail> {
864
            TorAddr::from(s)
865
                .unwrap()
866
                .into_resolve_instructions(&Default::default(), prefs)
867
        }
868

            
869
        let default_prefs = Default::default();
870
        assert_eq!(
871
            sap("[2001:db8::42]:9001", &default_prefs).unwrap(),
872
            RI::Return(vec!["2001:db8::42".parse().unwrap()]),
873
        );
874
        assert_eq!(
875
            sap("example.com:80", &default_prefs).unwrap(),
876
            RI::Exit("example.com".to_owned()),
877
        );
878
        assert!(matches!(
879
            sap("example.onion:80", &default_prefs),
880
            Err(ErrorDetail::OnionAddressResolveRequest),
881
        ));
882
        assert!(matches!(
883
            sap("localhost:80", &default_prefs),
884
            Err(ErrorDetail::LocalAddress),
885
        ));
886
        assert!(matches!(
887
            sap("127.0.0.1:80", &default_prefs),
888
            Err(ErrorDetail::LocalAddress),
889
        ));
890

            
891
        let mut connect_to_local_addrs = StreamPrefs::new();
892
        connect_to_local_addrs.connect_to_local_addrs(tor_config::BoolOrAuto::Explicit(true));
893

            
894
        assert_eq!(
895
            sap("localhost:80", &connect_to_local_addrs).unwrap(),
896
            RI::Exit("localhost".to_owned()),
897
        );
898
        assert_eq!(
899
            sap("127.0.0.1:80", &connect_to_local_addrs).unwrap(),
900
            RI::Return(vec!["127.0.0.1".parse().unwrap()]),
901
        );
902
    }
903

            
904
    #[test]
905
    fn bad_ports() {
906
        assert_eq!(
907
            TorAddr::from("www.example.com:squirrel"),
908
            Err(TorAddrError::BadPort)
909
        );
910
        assert_eq!(
911
            TorAddr::from("www.example.com:0"),
912
            Err(TorAddrError::BadPort)
913
        );
914
    }
915

            
916
    #[test]
917
    fn prefs_onion_services() {
918
        use crate::err::ErrorDetailDiscriminants;
919
        use ErrorDetailDiscriminants as EDD;
920
        use ErrorKind as EK;
921
        use tor_error::{ErrorKind, HasKind as _};
922

            
923
        #[allow(clippy::redundant_closure)] // for symmetry with prefs_of, below, and clarity
924
        let prefs_def = || StreamPrefs::default();
925

            
926
        let addr: TorAddr = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443"
927
            .parse()
928
            .unwrap();
929

            
930
        fn map(
931
            got: Result<impl Sized, ErrorDetail>,
932
        ) -> Result<(), (ErrorDetailDiscriminants, ErrorKind)> {
933
            got.map(|_| ())
934
                .map_err(|e| (ErrorDetailDiscriminants::from(&e), e.kind()))
935
        }
936

            
937
        let check_stream = |prefs, expected| {
938
            let got = addr
939
                .clone()
940
                .into_stream_instructions(&Default::default(), &prefs);
941
            assert_eq!(map(got), expected, "{prefs:?}");
942
        };
943
        let check_resolve = |prefs| {
944
            let got = addr
945
                .clone()
946
                .into_resolve_instructions(&Default::default(), &prefs);
947
            // This should be OnionAddressResolveRequest no matter if .onion is compiled in or enabled.
948
            // Since compiling it in, or enabling it, won't help.
949
            let expected = Err((EDD::OnionAddressResolveRequest, EK::NotImplemented));
950
            assert_eq!(map(got), expected, "{prefs:?}");
951
        };
952

            
953
        cfg_if::cfg_if! {
954
            if #[cfg(feature = "onion-service-client")] {
955
                use tor_config::BoolOrAuto as B;
956
                let prefs_of = |yn| {
957
                    let mut prefs = StreamPrefs::default();
958
                    prefs.connect_to_onion_services(yn);
959
                    prefs
960
                };
961
                check_stream(prefs_def(), Ok(()));
962
                check_stream(prefs_of(B::Auto), Ok(()));
963
                check_stream(prefs_of(B::Explicit(true)), Ok(()));
964
                check_stream(prefs_of(B::Explicit(false)), Err((EDD::OnionAddressDisabled, EK::ForbiddenStreamTarget)));
965

            
966
                check_resolve(prefs_def());
967
                check_resolve(prefs_of(B::Auto));
968
                check_resolve(prefs_of(B::Explicit(true)));
969
                check_resolve(prefs_of(B::Explicit(false)));
970
            } else {
971
                check_stream(prefs_def(), Err((EDD::OnionAddressNotSupported, EK::FeatureDisabled)));
972

            
973
                check_resolve(prefs_def());
974
            }
975
        }
976
    }
977

            
978
    #[test]
979
    fn convert_safe() {
980
        fn check<A: IntoTorAddr>(a: A, s: &str) {
981
            let a1 = TorAddr::from(a).unwrap();
982
            let a2 = s.parse().unwrap();
983
            assert_eq!(a1, a2);
984
            assert_eq!(&a1.to_string(), s);
985
        }
986

            
987
        check(("www.example.com", 8000), "www.example.com:8000");
988
        check(
989
            TorAddr::from(("www.example.com", 8000)).unwrap(),
990
            "www.example.com:8000",
991
        );
992
        check(
993
            TorAddr::from(("www.example.com", 8000)).unwrap(),
994
            "www.example.com:8000",
995
        );
996
        let addr = "[2001:db8::0042]:9001".to_owned();
997
        check(&addr, "[2001:db8::42]:9001");
998
        check(addr, "[2001:db8::42]:9001");
999
        check(("2001:db8::0042".to_owned(), 9001), "[2001:db8::42]:9001");
        check(("example.onion", 80), "example.onion:80");
    }
    #[test]
    fn convert_dangerous() {
        fn check<A: DangerouslyIntoTorAddr>(a: A, s: &str) {
            let a1 = TorAddr::dangerously_from(a).unwrap();
            let a2 = TorAddr::from(s).unwrap();
            assert_eq!(a1, a2);
            assert_eq!(&a1.to_string(), s);
        }
        let ip: IpAddr = "203.0.133.6".parse().unwrap();
        let ip4: Ipv4Addr = "203.0.133.7".parse().unwrap();
        let ip6: Ipv6Addr = "2001:db8::42".parse().unwrap();
        let sa: SocketAddr = "203.0.133.8:80".parse().unwrap();
        let sa4: SocketAddrV4 = "203.0.133.8:81".parse().unwrap();
        let sa6: SocketAddrV6 = "[2001:db8::43]:82".parse().unwrap();
        // This tests impl DangerouslyIntoTorAddr for &T
        #[allow(clippy::needless_borrow)]
        #[allow(clippy::needless_borrows_for_generic_args)]
        check(&(ip, 443), "203.0.133.6:443");
        check((ip, 443), "203.0.133.6:443");
        check((ip4, 444), "203.0.133.7:444");
        check((ip6, 445), "[2001:db8::42]:445");
        check(sa, "203.0.133.8:80");
        check(sa4, "203.0.133.8:81");
        check(sa6, "[2001:db8::43]:82");
    }
}