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
    #[allow(clippy::string_slice)] // TODO
244
18
    pub(crate) fn into_stream_instructions(
245
18
        self,
246
18
        cfg: &crate::config::ClientAddrConfig,
247
18
        prefs: &StreamPrefs,
248
18
    ) -> Result<StreamInstructions, ErrorDetail> {
249
18
        self.enforce_config(cfg, prefs)?;
250

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

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

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

            
298
4
        let () = enforce_config_result?;
299

            
300
4
        Ok(instructions)
301
14
    }
302

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
493
impl IntoTorAddr for String {
494
    #[allow(clippy::string_slice)] // TODO
495
4
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
496
4
        self[..].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
    #[allow(clippy::string_slice)] // TODO
517
2
    fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
518
2
        let (host, port) = self;
519
2
        (&host[..], port).into_tor_addr()
520
2
    }
521
}
522

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

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

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

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

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

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

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

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

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

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

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

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

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

            
619
        prefs
620
    }
621

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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