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
110
    fn new(host: Host, port: u16) -> Result<Self, TorAddrError> {
207
110
        if port == 0 {
208
2
            Err(TorAddrError::BadPort)
209
        } else {
210
108
            Ok(TorAddr { host, port })
211
        }
212
110
    }
213

            
214
    /// Construct a `TorAddr` from any object that implements
215
    /// [`IntoTorAddr`].
216
58
    pub fn from<A: IntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
217
58
        addr.into_tor_addr()
218
58
    }
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
14
    pub fn dangerously_from<A: DangerouslyIntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
226
14
        addr.into_tor_addr_dangerously()
227
14
    }
228

            
229
    /// Return true if this is an IP address (rather than a hostname).
230
10
    pub fn is_ip_address(&self) -> bool {
231
10
        matches!(&self.host, Host::Ip(_))
232
10
    }
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
18
    pub(crate) fn into_stream_instructions(
244
18
        self,
245
18
        cfg: &crate::config::ClientAddrConfig,
246
18
        prefs: &StreamPrefs,
247
18
    ) -> Result<StreamInstructions, ErrorDetail> {
248
18
        self.enforce_config(cfg, prefs)?;
249

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

            
277
    /// Get instructions for how to make a stream to this address
278
14
    pub(crate) fn into_resolve_instructions(
279
14
        self,
280
14
        cfg: &crate::config::ClientAddrConfig,
281
14
        prefs: &StreamPrefs,
282
14
    ) -> 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
14
        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
16
        let instructions = (move || {
292
14
            Ok(match self.host {
293
2
                Host::Hostname(hostname) => ResolveInstructions::Exit(hostname),
294
2
                Host::Ip(ip) => ResolveInstructions::Return(vec![ip]),
295
10
                Host::Onion(_) => return Err(ErrorDetail::OnionAddressResolveRequest),
296
            })
297
10
        })()?;
298

            
299
4
        let () = enforce_config_result?;
300

            
301
4
        Ok(instructions)
302
14
    }
303

            
304
    /// Return true if the `host` in this address is local.
305
56
    fn is_local(&self) -> bool {
306
56
        self.host.is_local()
307
56
    }
308

            
309
    /// Give an error if this address doesn't conform to the rules set in
310
    /// `cfg`.
311
56
    fn enforce_config(
312
56
        &self,
313
56
        cfg: &crate::config::ClientAddrConfig,
314
56
        #[allow(unused_variables)] // will only be used in certain configurations
315
56
        prefs: &StreamPrefs,
316
56
    ) -> Result<(), ErrorDetail> {
317
56
        if !cfg.allow_local_addrs && self.is_local() {
318
2
            return Err(ErrorDetail::LocalAddress);
319
54
        }
320

            
321
54
        if let Host::Hostname(addr) = &self.host {
322
16
            if !is_valid_hostname(addr) {
323
                // This ought not to occur, because it violates Host's invariant
324
2
                return Err(ErrorDetail::InvalidHostname);
325
14
            }
326
14
            if addr.ends_with_ignore_ascii_case(HSID_ONION_SUFFIX) {
327
                // This ought not to occur, because it violates Host's invariant
328
2
                return Err(ErrorDetail::OnionAddressNotSupported);
329
12
            }
330
38
        }
331

            
332
50
        if let Host::Onion(_name) = &self.host {
333
            cfg_if::cfg_if! {
334
                if #[cfg(feature = "onion-service-client")] {
335
26
                    if !prefs.connect_to_onion_services.as_bool().unwrap_or(cfg.allow_onion_addrs) {
336
4
                        return Err(ErrorDetail::OnionAddressDisabled);
337
22
                    }
338
                } else {
339
                    return Err(ErrorDetail::OnionAddressNotSupported);
340
                }
341
            }
342
24
        }
343

            
344
46
        Ok(())
345
56
    }
346
}
347

            
348
impl std::fmt::Display for TorAddr {
349
28
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350
20
        match self.host {
351
10
            Host::Ip(IpAddr::V6(addr)) => write!(f, "[{}]:{}", addr, self.port),
352
18
            _ => write!(f, "{}:{}", self.host, self.port),
353
        }
354
28
    }
355
}
356

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

            
375
impl HasKind for TorAddrError {
376
6
    fn kind(&self) -> ErrorKind {
377
        use ErrorKind as EK;
378
        use TorAddrError as TAE;
379

            
380
6
        match self {
381
2
            TAE::InvalidHostname => EK::InvalidStreamTarget,
382
2
            TAE::NoPort => EK::InvalidStreamTarget,
383
2
            TAE::BadPort => EK::InvalidStreamTarget,
384
        }
385
6
    }
386
}
387

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

            
420
impl FromStr for Host {
421
    type Err = TorAddrError;
422
68
    fn from_str(s: &str) -> Result<Host, TorAddrError> {
423
68
        if s.ends_with_ignore_ascii_case(".onion") && is_valid_hostname(s) {
424
18
            Ok(Host::Onion(s.to_owned()))
425
50
        } else if let Ok(ip_addr) = s.parse() {
426
12
            Ok(Host::Ip(ip_addr))
427
38
        } else if is_valid_hostname(s) {
428
            // TODO(nickm): we might someday want to reject some kinds of bad
429
            // hostnames here, rather than when we're about to connect to them.
430
            // But that would be an API break, and maybe not what people want.
431
            // Maybe instead we should have a method to check whether a hostname
432
            // is "bad"? Not sure; we'll need to decide the right behavior here.
433
36
            Ok(Host::Hostname(s.to_owned()))
434
        } else {
435
2
            Err(TorAddrError::InvalidHostname)
436
        }
437
68
    }
438
}
439

            
440
impl Host {
441
    /// Return true if this address is one that is "internal": that is,
442
    /// relative to the particular host that is resolving it.
443
68
    fn is_local(&self) -> bool {
444
20
        match self {
445
22
            Host::Hostname(name) => name.eq_ignore_ascii_case("localhost"),
446
            // TODO: use is_global once it's stable, perhaps.
447
            // NOTE: Contrast this with is_sufficiently_private in tor-hsproxy,
448
            // which has a different purpose. Also see #1159.
449
            // The purpose of _this_ test is to find addresses that cannot
450
            // meaningfully be connected to over Tor, and that the exit
451
            // will not accept.
452
10
            Host::Ip(IpAddr::V4(ip)) => ip.is_loopback() || ip.is_private(),
453
10
            Host::Ip(IpAddr::V6(ip)) => ip.is_loopback(),
454
26
            Host::Onion(_) => false,
455
        }
456
68
    }
457
}
458

            
459
impl std::fmt::Display for Host {
460
18
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461
18
        match self {
462
6
            Host::Hostname(s) => Display::fmt(s, f),
463
10
            Host::Ip(ip) => Display::fmt(ip, f),
464
2
            Host::Onion(onion) => Display::fmt(onion, f),
465
        }
466
18
    }
467
}
468

            
469
impl IntoTorAddr for TorAddr {
470
8
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
471
8
        Ok(self)
472
8
    }
473
}
474

            
475
impl<A: IntoTorAddr + Clone> IntoTorAddr for &A {
476
2
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
477
2
        self.clone().into_tor_addr()
478
2
    }
479
}
480

            
481
impl IntoTorAddr for &str {
482
80
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
483
80
        if let Ok(sa) = SocketAddr::from_str(self) {
484
40
            TorAddr::new(Host::Ip(sa.ip()), sa.port())
485
        } else {
486
40
            let (host, port) = self.rsplit_once(':').ok_or(TorAddrError::NoPort)?;
487
38
            let host = host.parse()?;
488
36
            let port = port.parse().map_err(|_| TorAddrError::BadPort)?;
489
34
            TorAddr::new(host, port)
490
        }
491
80
    }
492
}
493

            
494
impl IntoTorAddr for String {
495
4
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
496
4
        self.as_str().into_tor_addr()
497
4
    }
498
}
499

            
500
impl FromStr for TorAddr {
501
    type Err = TorAddrError;
502
16
    fn from_str(s: &str) -> Result<Self, TorAddrError> {
503
16
        s.into_tor_addr()
504
16
    }
505
}
506

            
507
impl IntoTorAddr for (&str, u16) {
508
18
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
509
18
        let (host, port) = self;
510
18
        let host = host.parse()?;
511
18
        TorAddr::new(host, port)
512
18
    }
513
}
514

            
515
impl IntoTorAddr for (String, u16) {
516
2
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
517
2
        let (host, port) = self;
518
2
        (host.as_str(), port).into_tor_addr()
519
2
    }
520
}
521

            
522
impl<T: DangerouslyIntoTorAddr + Clone> DangerouslyIntoTorAddr for &T {
523
2
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
524
2
        self.clone().into_tor_addr_dangerously()
525
2
    }
526
}
527

            
528
impl DangerouslyIntoTorAddr for (IpAddr, u16) {
529
6
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
530
6
        let (addr, port) = self;
531
6
        TorAddr::new(Host::Ip(addr), port)
532
6
    }
533
}
534

            
535
impl DangerouslyIntoTorAddr for (Ipv4Addr, u16) {
536
4
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
537
4
        let (addr, port) = self;
538
4
        TorAddr::new(Host::Ip(addr.into()), port)
539
4
    }
540
}
541

            
542
impl DangerouslyIntoTorAddr for (Ipv6Addr, u16) {
543
4
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
544
4
        let (addr, port) = self;
545
4
        TorAddr::new(Host::Ip(addr.into()), port)
546
4
    }
547
}
548

            
549
impl DangerouslyIntoTorAddr for SocketAddr {
550
2
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
551
2
        let (addr, port) = (self.ip(), self.port());
552
2
        (addr, port).into_tor_addr_dangerously()
553
2
    }
554
}
555

            
556
impl DangerouslyIntoTorAddr for SocketAddrV4 {
557
2
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
558
2
        let (addr, port) = (self.ip(), self.port());
559
2
        (*addr, port).into_tor_addr_dangerously()
560
2
    }
561
}
562

            
563
impl DangerouslyIntoTorAddr for SocketAddrV6 {
564
2
    fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
565
2
        let (addr, port) = (self.ip(), self.port());
566
2
        (*addr, port).into_tor_addr_dangerously()
567
2
    }
568
}
569

            
570
/// Check whether `hostname` is a valid hostname or not.
571
///
572
/// (Note that IPv6 addresses don't follow these rules.)
573
88
fn is_valid_hostname(hostname: &str) -> bool {
574
88
    hostname_validator::is_valid(hostname)
575
88
}
576

            
577
#[cfg(test)]
578
mod test {
579
    // @@ begin test lint list maintained by maint/add_warning @@
580
    #![allow(clippy::bool_assert_comparison)]
581
    #![allow(clippy::clone_on_copy)]
582
    #![allow(clippy::dbg_macro)]
583
    #![allow(clippy::mixed_attributes_style)]
584
    #![allow(clippy::print_stderr)]
585
    #![allow(clippy::print_stdout)]
586
    #![allow(clippy::single_char_pattern)]
587
    #![allow(clippy::unwrap_used)]
588
    #![allow(clippy::unchecked_time_subtraction)]
589
    #![allow(clippy::useless_vec)]
590
    #![allow(clippy::needless_pass_by_value)]
591
    #![allow(clippy::string_slice)] // See arti#2571
592
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
593
    use super::*;
594

            
595
    #[test]
596
    fn test_error_kind() {
597
        use tor_error::ErrorKind as EK;
598

            
599
        assert_eq!(
600
            TorAddrError::InvalidHostname.kind(),
601
            EK::InvalidStreamTarget
602
        );
603
        assert_eq!(TorAddrError::NoPort.kind(), EK::InvalidStreamTarget);
604
        assert_eq!(TorAddrError::BadPort.kind(), EK::InvalidStreamTarget);
605
    }
606

            
607
    /// Make a `StreamPrefs` with `.onion` enabled, if cfg-enabled
608
    fn mk_stream_prefs() -> StreamPrefs {
609
        let prefs = crate::StreamPrefs::default();
610

            
611
        #[cfg(feature = "onion-service-client")]
612
        let prefs = {
613
            let mut prefs = prefs;
614
            prefs.connect_to_onion_services(tor_config::BoolOrAuto::Explicit(true));
615
            prefs
616
        };
617

            
618
        prefs
619
    }
620

            
621
    #[test]
622
    fn validate_hostname() {
623
        // Valid hostname tests
624
        assert!(is_valid_hostname("torproject.org"));
625
        assert!(is_valid_hostname("Tor-Project.org"));
626
        assert!(is_valid_hostname("example.onion"));
627
        assert!(is_valid_hostname("some.example.onion"));
628

            
629
        // Invalid hostname tests
630
        assert!(!is_valid_hostname("-torproject.org"));
631
        assert!(!is_valid_hostname("_torproject.org"));
632
        assert!(!is_valid_hostname("tor_project1.org"));
633
        assert!(!is_valid_hostname("iwanna$money.org"));
634
    }
635

            
636
    #[test]
637
    fn validate_addr() {
638
        use crate::err::ErrorDetail;
639
        fn val<A: IntoTorAddr>(addr: A) -> Result<TorAddr, ErrorDetail> {
640
            let toraddr = addr.into_tor_addr()?;
641
            toraddr.enforce_config(&Default::default(), &mk_stream_prefs())?;
642
            Ok(toraddr)
643
        }
644

            
645
        assert!(val("[2001:db8::42]:20").is_ok());
646
        assert!(val(("2001:db8::42", 20)).is_ok());
647
        assert!(val(("198.151.100.42", 443)).is_ok());
648
        assert!(val("198.151.100.42:443").is_ok());
649
        assert!(val("www.torproject.org:443").is_ok());
650
        assert!(val(("www.torproject.org", 443)).is_ok());
651

            
652
        // When HS disabled, tested elsewhere, see: stream_instructions, prefs_onion_services
653
        #[cfg(feature = "onion-service-client")]
654
        {
655
            assert!(val("example.onion:80").is_ok());
656
            assert!(val(("example.onion", 80)).is_ok());
657

            
658
            match val("eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443") {
659
                Ok(TorAddr {
660
                    host: Host::Onion(_),
661
                    ..
662
                }) => {}
663
                x => panic!("{x:?}"),
664
            }
665
        }
666

            
667
        assert!(matches!(
668
            val("-foobar.net:443"),
669
            Err(ErrorDetail::InvalidHostname)
670
        ));
671
        assert!(matches!(
672
            val("www.torproject.org"),
673
            Err(ErrorDetail::Address(TorAddrError::NoPort))
674
        ));
675

            
676
        assert!(matches!(
677
            val("192.168.0.1:80"),
678
            Err(ErrorDetail::LocalAddress)
679
        ));
680
        assert!(matches!(
681
            val(TorAddr::new(Host::Hostname("foo@bar".to_owned()), 553).unwrap()),
682
            Err(ErrorDetail::InvalidHostname)
683
        ));
684
        assert!(matches!(
685
            val(TorAddr::new(Host::Hostname("foo.onion".to_owned()), 80).unwrap()),
686
            Err(ErrorDetail::OnionAddressNotSupported)
687
        ));
688
    }
689

            
690
    #[test]
691
    fn local_addrs() {
692
        fn is_local_hostname(s: &str) -> bool {
693
            let h: Host = s.parse().unwrap();
694
            h.is_local()
695
        }
696

            
697
        assert!(is_local_hostname("localhost"));
698
        assert!(is_local_hostname("loCALHOST"));
699
        assert!(is_local_hostname("127.0.0.1"));
700
        assert!(is_local_hostname("::1"));
701
        assert!(is_local_hostname("192.168.0.1"));
702

            
703
        assert!(!is_local_hostname("www.example.com"));
704
    }
705

            
706
    #[test]
707
    fn is_ip_address() {
708
        fn ip(s: &str) -> bool {
709
            TorAddr::from(s).unwrap().is_ip_address()
710
        }
711

            
712
        assert!(ip("192.168.0.1:80"));
713
        assert!(ip("[::1]:80"));
714
        assert!(ip("[2001:db8::42]:65535"));
715
        assert!(!ip("example.com:80"));
716
        assert!(!ip("example.onion:80"));
717
    }
718

            
719
    #[test]
720
    fn stream_instructions() {
721
        use StreamInstructions as SI;
722

            
723
        fn sap(s: &str) -> Result<StreamInstructions, ErrorDetail> {
724
            TorAddr::from(s)
725
                .unwrap()
726
                .into_stream_instructions(&Default::default(), &mk_stream_prefs())
727
        }
728

            
729
        assert_eq!(
730
            sap("[2001:db8::42]:9001").unwrap(),
731
            SI::Exit {
732
                hostname: "2001:db8::42".to_owned(),
733
                port: 9001
734
            },
735
        );
736
        assert_eq!(
737
            sap("example.com:80").unwrap(),
738
            SI::Exit {
739
                hostname: "example.com".to_owned(),
740
                port: 80
741
            },
742
        );
743

            
744
        {
745
            let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
746
            let onion = format!("sss1234.www.{}.onion", b32);
747
            let got = sap(&format!("{}:443", onion));
748

            
749
            #[cfg(feature = "onion-service-client")]
750
            assert_eq!(
751
                got.unwrap(),
752
                SI::Hs {
753
                    hsid: format!("{}.onion", b32).parse().unwrap(),
754
                    hostname: onion,
755
                    port: 443,
756
                }
757
            );
758

            
759
            #[cfg(not(feature = "onion-service-client"))]
760
            assert!(matches!(got, Err(ErrorDetail::OnionAddressNotSupported)));
761
        }
762
    }
763

            
764
    #[test]
765
    fn resolve_instructions() {
766
        use ResolveInstructions as RI;
767

            
768
        fn sap(s: &str) -> Result<ResolveInstructions, ErrorDetail> {
769
            TorAddr::from(s)
770
                .unwrap()
771
                .into_resolve_instructions(&Default::default(), &Default::default())
772
        }
773

            
774
        assert_eq!(
775
            sap("[2001:db8::42]:9001").unwrap(),
776
            RI::Return(vec!["2001:db8::42".parse().unwrap()]),
777
        );
778
        assert_eq!(
779
            sap("example.com:80").unwrap(),
780
            RI::Exit("example.com".to_owned()),
781
        );
782
        assert!(matches!(
783
            sap("example.onion:80"),
784
            Err(ErrorDetail::OnionAddressResolveRequest),
785
        ));
786
    }
787

            
788
    #[test]
789
    fn bad_ports() {
790
        assert_eq!(
791
            TorAddr::from("www.example.com:squirrel"),
792
            Err(TorAddrError::BadPort)
793
        );
794
        assert_eq!(
795
            TorAddr::from("www.example.com:0"),
796
            Err(TorAddrError::BadPort)
797
        );
798
    }
799

            
800
    #[test]
801
    fn prefs_onion_services() {
802
        use crate::err::ErrorDetailDiscriminants;
803
        use ErrorDetailDiscriminants as EDD;
804
        use ErrorKind as EK;
805
        use tor_error::{ErrorKind, HasKind as _};
806

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

            
810
        let addr: TorAddr = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443"
811
            .parse()
812
            .unwrap();
813

            
814
        fn map(
815
            got: Result<impl Sized, ErrorDetail>,
816
        ) -> Result<(), (ErrorDetailDiscriminants, ErrorKind)> {
817
            got.map(|_| ())
818
                .map_err(|e| (ErrorDetailDiscriminants::from(&e), e.kind()))
819
        }
820

            
821
        let check_stream = |prefs, expected| {
822
            let got = addr
823
                .clone()
824
                .into_stream_instructions(&Default::default(), &prefs);
825
            assert_eq!(map(got), expected, "{prefs:?}");
826
        };
827
        let check_resolve = |prefs| {
828
            let got = addr
829
                .clone()
830
                .into_resolve_instructions(&Default::default(), &prefs);
831
            // This should be OnionAddressResolveRequest no matter if .onion is compiled in or enabled.
832
            // Since compiling it in, or enabling it, won't help.
833
            let expected = Err((EDD::OnionAddressResolveRequest, EK::NotImplemented));
834
            assert_eq!(map(got), expected, "{prefs:?}");
835
        };
836

            
837
        cfg_if::cfg_if! {
838
            if #[cfg(feature = "onion-service-client")] {
839
                use tor_config::BoolOrAuto as B;
840
                let prefs_of = |yn| {
841
                    let mut prefs = StreamPrefs::default();
842
                    prefs.connect_to_onion_services(yn);
843
                    prefs
844
                };
845
                check_stream(prefs_def(), Ok(()));
846
                check_stream(prefs_of(B::Auto), Ok(()));
847
                check_stream(prefs_of(B::Explicit(true)), Ok(()));
848
                check_stream(prefs_of(B::Explicit(false)), Err((EDD::OnionAddressDisabled, EK::ForbiddenStreamTarget)));
849

            
850
                check_resolve(prefs_def());
851
                check_resolve(prefs_of(B::Auto));
852
                check_resolve(prefs_of(B::Explicit(true)));
853
                check_resolve(prefs_of(B::Explicit(false)));
854
            } else {
855
                check_stream(prefs_def(), Err((EDD::OnionAddressNotSupported, EK::FeatureDisabled)));
856

            
857
                check_resolve(prefs_def());
858
            }
859
        }
860
    }
861

            
862
    #[test]
863
    fn convert_safe() {
864
        fn check<A: IntoTorAddr>(a: A, s: &str) {
865
            let a1 = TorAddr::from(a).unwrap();
866
            let a2 = s.parse().unwrap();
867
            assert_eq!(a1, a2);
868
            assert_eq!(&a1.to_string(), s);
869
        }
870

            
871
        check(("www.example.com", 8000), "www.example.com:8000");
872
        check(
873
            TorAddr::from(("www.example.com", 8000)).unwrap(),
874
            "www.example.com:8000",
875
        );
876
        check(
877
            TorAddr::from(("www.example.com", 8000)).unwrap(),
878
            "www.example.com:8000",
879
        );
880
        let addr = "[2001:db8::0042]:9001".to_owned();
881
        check(&addr, "[2001:db8::42]:9001");
882
        check(addr, "[2001:db8::42]:9001");
883
        check(("2001:db8::0042".to_owned(), 9001), "[2001:db8::42]:9001");
884
        check(("example.onion", 80), "example.onion:80");
885
    }
886

            
887
    #[test]
888
    fn convert_dangerous() {
889
        fn check<A: DangerouslyIntoTorAddr>(a: A, s: &str) {
890
            let a1 = TorAddr::dangerously_from(a).unwrap();
891
            let a2 = TorAddr::from(s).unwrap();
892
            assert_eq!(a1, a2);
893
            assert_eq!(&a1.to_string(), s);
894
        }
895

            
896
        let ip: IpAddr = "203.0.133.6".parse().unwrap();
897
        let ip4: Ipv4Addr = "203.0.133.7".parse().unwrap();
898
        let ip6: Ipv6Addr = "2001:db8::42".parse().unwrap();
899
        let sa: SocketAddr = "203.0.133.8:80".parse().unwrap();
900
        let sa4: SocketAddrV4 = "203.0.133.8:81".parse().unwrap();
901
        let sa6: SocketAddrV6 = "[2001:db8::43]:82".parse().unwrap();
902

            
903
        // This tests impl DangerouslyIntoTorAddr for &T
904
        #[allow(clippy::needless_borrow)]
905
        #[allow(clippy::needless_borrows_for_generic_args)]
906
        check(&(ip, 443), "203.0.133.6:443");
907
        check((ip, 443), "203.0.133.6:443");
908
        check((ip4, 444), "203.0.133.7:444");
909
        check((ip6, 445), "[2001:db8::42]:445");
910
        check(sa, "203.0.133.8:80");
911
        check(sa4, "203.0.133.8:81");
912
        check(sa6, "[2001:db8::43]:82");
913
    }
914
}