1
//! Connect point types, and the code to parse them and resolve them.
2

            
3
use serde::Deserialize;
4
use serde_with::DeserializeFromStr;
5
use std::{
6
    fmt::Debug,
7
    net::{self, IpAddr},
8
    path::PathBuf,
9
    str::FromStr,
10
};
11
use tor_config_path::{
12
    CfgPath, CfgPathError, CfgPathResolver,
13
    addr::{CfgAddr, CfgAddrError},
14
};
15
use tor_general_addr::general::{self, AddrParseError};
16
#[cfg(feature = "rpc-server")]
17
use tor_rtcompat::{NetStreamListener, NetStreamProvider};
18

            
19
use crate::HasClientErrorAction;
20

            
21
/// A connect point, as deserialized from TOML.
22
///
23
/// Connect points tell an RPC client how to reach an RPC server,
24
/// and tell an RPC server where and how to listen for connections for RPC clients.
25
///
26
/// This type may have members containing symbolic paths, such as
27
/// `${USER_HOME}` or `${ARTI_LOCAL_STATE}`.
28
/// To convert these paths to a usable format,
29
/// invoke [`ParsedConnectPoint::resolve()`] on this object.
30
#[derive(Clone, Debug)]
31
pub struct ParsedConnectPoint(ConnectPointEnum<Unresolved>);
32

            
33
/// A connect point, with all paths resolved.
34
///
35
/// Connect points tell an RPC client how to reach an RPC server,
36
/// and tell an RPC server where and how to listen for connections for RPC clients.
37
///
38
/// This type is returned by [`ParsedConnectPoint::resolve()`],
39
/// and can be used to connect or bind.
40
#[derive(Clone, Debug)]
41
pub struct ResolvedConnectPoint(pub(crate) ConnectPointEnum<Resolved>);
42

            
43
impl ParsedConnectPoint {
44
    /// Try to resolve all symbolic paths in this connect point,
45
    /// using the rules of [`CfgPath`] and [`CfgAddr`].
46
4
    pub fn resolve(
47
4
        &self,
48
4
        resolver: &CfgPathResolver,
49
4
    ) -> Result<ResolvedConnectPoint, ResolveError> {
50
        use ConnectPointEnum as CPE;
51
4
        Ok(ResolvedConnectPoint(match &self.0 {
52
4
            CPE::Connect(connect) => CPE::Connect(connect.resolve(resolver)?),
53
            CPE::Builtin(builtin) => CPE::Builtin(builtin.clone()),
54
        }))
55
4
    }
56
}
57

            
58
impl FromStr for ParsedConnectPoint {
59
    type Err = ParseError;
60

            
61
58
    fn from_str(s: &str) -> Result<Self, Self::Err> {
62
58
        let de: ConnectPointDe = toml::from_str(s).map_err(ParseError::InvalidConnectPoint)?;
63
52
        Ok(ParsedConnectPoint(de.try_into()?))
64
58
    }
65
}
66

            
67
/// A failure from [`ParsedConnectPoint::from_str()`].
68
#[derive(Clone, Debug, thiserror::Error)]
69
#[non_exhaustive]
70
pub enum ParseError {
71
    /// The input was not valid toml, or was an invalid connect point.
72
    #[error("Invalid connect point")]
73
    InvalidConnectPoint(#[source] toml::de::Error),
74
    /// The input had sections or members
75
    /// that are not allowed to appear in the same connect point.
76
    #[error("Conflicting members in connect point")]
77
    ConflictingMembers,
78
    /// The input was valid toml, but did not have any recognized
79
    /// connect point section.
80
    #[error("Unrecognized format on connect point")]
81
    UnrecognizedFormat,
82
    /// An inet-auto address was provided in a connect point
83
    /// that was not a loopback address.
84
    ///
85
    /// (Note that this error is only generated for inet-auto addresses.
86
    /// Other non-loopback addresses cause a [`ResolveError::AddressNotLoopback`].)
87
    #[error("inet-auto address was not a loopback address")]
88
    AutoAddressNotLoopback,
89
}
90
impl HasClientErrorAction for ParseError {
91
    fn client_action(&self) -> crate::ClientErrorAction {
92
        use crate::ClientErrorAction as A;
93
        match self {
94
            ParseError::InvalidConnectPoint(_) => A::Abort,
95
            ParseError::ConflictingMembers => A::Abort,
96
            ParseError::AutoAddressNotLoopback => A::Decline,
97
            ParseError::UnrecognizedFormat => A::Decline,
98
        }
99
    }
100
}
101

            
102
/// A failure from [`ParsedConnectPoint::resolve()`].
103
#[derive(Clone, Debug, thiserror::Error)]
104
#[non_exhaustive]
105
pub enum ResolveError {
106
    /// There was a path in the connect point that we couldn't resolve.
107
    #[error("Unable to resolve variables in path")]
108
    InvalidPath(#[from] CfgPathError),
109
    ///  There was an address in the connect point that we couldn't parse.
110
    #[error("Unable to parse address")]
111
    UnparseableAddr(#[from] AddrParseError),
112
    /// There was an address in the connect point that we couldn't resolve.
113
    #[error("Unable to resolve variables in address")]
114
    InvalidAddr(#[from] CfgAddrError),
115
    /// After substitution, we couldn't expand the path to a string.
116
    #[error("Cannot represent expanded path as string")]
117
    PathNotString,
118
    /// Address is not a loopback address.
119
    #[error("Tried to bind or connect to a non-loopback TCP address")]
120
    AddressNotLoopback,
121
    /// Authorization mechanism not compatible with address family
122
    #[error("Authorization type not compatible with address family")]
123
    AuthNotCompatible,
124
    /// Authorization mechanism not recognized
125
    #[error("Authorization type not recognized as a supported type")]
126
    AuthNotRecognized,
127
    /// Address type not supported by the RPC connect point subsystem.
128
    ///
129
    /// (This can only happen if somebody adds new variants to `general::SocketAddr`.)
130
    #[error("Address type not recognized")]
131
    AddressTypeNotRecognized,
132
    /// The address was incompatible with the presence or absence of socket_address_file.
133
    #[error("inet-auto without socket_address_file, or vice versa")]
134
    AutoIncompatibleWithSocketFile,
135
    /// The name of a file or AF_UNIX socket address was a relative path.
136
    #[error("Path was not absolute")]
137
    PathNotAbsolute,
138
}
139
impl HasClientErrorAction for ResolveError {
140
    fn client_action(&self) -> crate::ClientErrorAction {
141
        use crate::ClientErrorAction as A;
142
        match self {
143
            ResolveError::InvalidPath(e) => e.client_action(),
144
            ResolveError::UnparseableAddr(e) => e.client_action(),
145
            ResolveError::InvalidAddr(e) => e.client_action(),
146
            ResolveError::PathNotString => A::Decline,
147
            ResolveError::AddressNotLoopback => A::Decline,
148
            ResolveError::AuthNotCompatible => A::Abort,
149
            ResolveError::AuthNotRecognized => A::Decline,
150
            ResolveError::AddressTypeNotRecognized => A::Decline,
151
            ResolveError::PathNotAbsolute => A::Abort,
152
            ResolveError::AutoIncompatibleWithSocketFile => A::Abort,
153
        }
154
    }
155
}
156

            
157
/// Implementation type for a connect point.
158
///
159
/// This type is hidden so that the enum fields remain private.
160
/// It is parameterized on a [`Addresses`] trait,
161
/// to indicate whether it is in resolved or unresolved form.
162
#[derive(Clone, Debug)]
163
pub(crate) enum ConnectPointEnum<R: Addresses> {
164
    /// Connect by opening a socket to a [`general::SocketAddr`]
165
    Connect(Connect<R>),
166
    /// Connect by some built-in mechanism.
167
    ///
168
    /// (Or, in the case of Abort, do not connect at all.)
169
    Builtin(Builtin),
170
}
171

            
172
/// Trait to hold types that vary depending on whether a connect point is resolved or not.
173
//
174
// Note: We could use instead separate `PATH` and `ADDR` parameters,
175
// but this approach makes specifying bounds significantly easier.
176
pub(crate) trait Addresses {
177
    /// Type to represent addresses that we can open a socket to.
178
    type SocketAddr: Clone + std::fmt::Debug;
179
    /// Type to represent paths on the filesystem.
180
    type Path: Clone + std::fmt::Debug;
181
}
182

            
183
/// Representation of a connect point as deserialized.
184
///
185
/// We could instead deserialize [`ConnectPointEnum`] directly,
186
/// but that would restrict our error-handling:
187
/// the `toml` crate doesn't make it easy to distinguish
188
/// one kind of parse error from another.
189
///
190
/// TODO We should revisit this choice when we add more variants
191
/// or more auxiliary tables.
192
#[derive(Deserialize, Clone, Debug)]
193
struct ConnectPointDe {
194
    /// A "connect" table.
195
    connect: Option<Connect<Unresolved>>,
196
    /// A "builtin" table.
197
    builtin: Option<Builtin>,
198
}
199
impl TryFrom<ConnectPointDe> for ConnectPointEnum<Unresolved> {
200
    type Error = ParseError;
201

            
202
52
    fn try_from(value: ConnectPointDe) -> Result<Self, Self::Error> {
203
4
        match value {
204
            ConnectPointDe {
205
46
                connect: Some(c),
206
                builtin: None,
207
46
            } => Ok(ConnectPointEnum::Connect(c)),
208
            ConnectPointDe {
209
                connect: None,
210
2
                builtin: Some(b),
211
2
            } => Ok(ConnectPointEnum::Builtin(b)),
212
            ConnectPointDe {
213
                connect: Some(_),
214
                builtin: Some(_),
215
2
            } => Err(ParseError::ConflictingMembers),
216
            // This didn't have either recognized section,
217
            // so it is likely itn an unrecognized format.
218
2
            _ => Err(ParseError::UnrecognizedFormat),
219
        }
220
52
    }
221
}
222

            
223
/// A "builtin" connect point.
224
///
225
/// This represents an approach to connecting that is handled purely
226
/// within arti.  In the future, this might include "embedded" or "owned";
227
/// but for now, it only includes "abort".
228
#[derive(Deserialize, Clone, Debug)]
229
pub(crate) struct Builtin {
230
    /// Actual strategy of built-in behavior to implement.
231
    pub(crate) builtin: BuiltinVariant,
232
}
233

            
234
/// A particular built-in strategy.
235
#[derive(Deserialize, Clone, Debug)]
236
#[serde(rename_all = "lowercase")]
237
pub(crate) enum BuiltinVariant {
238
    /// This connect point must fail,
239
    /// and no subsequent connect points may be tried.
240
    Abort,
241
}
242

            
243
/// Information for a connect point that is implemented by making a socket connection to an address.
244
#[derive(Deserialize, Clone, Debug)]
245
#[serde(bound = "R::Path : Deserialize<'de>, AddrWithStr<R::SocketAddr> : Deserialize<'de>")]
246
pub(crate) struct Connect<R: Addresses> {
247
    /// The address of the socket at which the client should try to reach the RPC server,
248
    /// and which the RPC server should bind.
249
    pub(crate) socket: ConnectAddress<R>,
250
    /// The address of the socket which the RPC server believes it is actually listening at.
251
    ///
252
    /// If absent, defaults to `socket`.
253
    ///
254
    /// This value is only needs to be different from `socket`
255
    /// in cases where cookie authentication is in use,
256
    /// and the client is sandboxed somehow (such as behind a NAT, or inside a container).
257
    pub(crate) socket_canonical: Option<AddrWithStr<R::SocketAddr>>,
258
    /// The authentication that the client should try to use,
259
    /// and which the server should require.
260
    pub(crate) auth: Auth<R>,
261
    /// A file in which the actual value of an `inet-auto` address should be stored.
262
    pub(crate) socket_address_file: Option<R::Path>,
263
}
264

            
265
/// A target of a [`Connect`] connpt.
266
///
267
/// Can be either a socket address, or an inet-auto address.
268
#[derive(Deserialize, Clone, Debug)]
269
#[serde(bound = "R::Path : Deserialize<'de>, AddrWithStr<R::SocketAddr> : Deserialize<'de>")]
270
#[serde(untagged, expecting = "a network schema and address")]
271
pub(crate) enum ConnectAddress<R: Addresses> {
272
    /// A socket address with an unspecified port.
273
    InetAuto(InetAutoAddress),
274
    /// A specified socket address.
275
    Socket(AddrWithStr<R::SocketAddr>),
276
}
277

            
278
/// Instructions to bind to an address chosen by the OS.
279
#[derive(Clone, Debug, DeserializeFromStr)]
280
pub(crate) struct InetAutoAddress {
281
    /// The address that the relay should bind to, or None if any loopback address is okay.
282
    ///
283
    /// Must be a loopback address.
284
    bind: Option<IpAddr>,
285
}
286
impl std::fmt::Display for InetAutoAddress {
287
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288
        match self.bind {
289
            Some(a) => write!(f, "inet-auto:{a}"),
290
            None => write!(f, "inet-auto:auto"),
291
        }
292
    }
293
}
294
impl FromStr for InetAutoAddress {
295
    type Err = ParseError;
296

            
297
48
    fn from_str(s: &str) -> Result<Self, Self::Err> {
298
48
        let Some(addr_part) = s.strip_prefix("inet-auto:") else {
299
48
            return Err(ParseError::UnrecognizedFormat);
300
        };
301
        if addr_part == "auto" {
302
            return Ok(InetAutoAddress { bind: None });
303
        }
304
        let Ok(addr) = IpAddr::from_str(addr_part) else {
305
            return Err(ParseError::UnrecognizedFormat);
306
        };
307
        if addr.is_loopback() {
308
            Ok(InetAutoAddress { bind: Some(addr) })
309
        } else {
310
            Err(ParseError::AutoAddressNotLoopback)
311
        }
312
48
    }
313
}
314

            
315
impl InetAutoAddress {
316
    /// Return a list of addresses to bind to.
317
    fn bind_to_addresses(&self) -> Vec<general::SocketAddr> {
318
        match self {
319
            InetAutoAddress { bind: None } => vec![
320
                net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 0).into(),
321
                net::SocketAddr::new(net::Ipv6Addr::LOCALHOST.into(), 0).into(),
322
            ],
323
            InetAutoAddress { bind: Some(ip) } => {
324
                vec![net::SocketAddr::new(*ip, 0).into()]
325
            }
326
        }
327
    }
328

            
329
    /// Having parsed `addr`, make sure it is a possible instantiation of this address.
330
    ///
331
    /// Return an error if it is not.
332
    #[cfg(feature = "rpc-client")]
333
    pub(crate) fn validate_parsed_address(
334
        &self,
335
        addr: &general::SocketAddr,
336
    ) -> Result<(), crate::ConnectError> {
337
        use general::SocketAddr::Inet;
338
        for sa in self.bind_to_addresses() {
339
            if let (Inet(specified), Inet(got)) = (sa, addr) {
340
                if specified.port() == 0 && specified.ip() == got.ip() {
341
                    return Ok(());
342
                }
343
            }
344
        }
345

            
346
        Err(crate::ConnectError::SocketAddressFileMismatch)
347
    }
348
}
349

            
350
/// The representation of an address as written into a socket file.
351
#[derive(Clone, Debug)]
352
#[cfg_attr(feature = "rpc-client", derive(Deserialize))]
353
#[cfg_attr(feature = "rpc-server", derive(serde::Serialize))]
354
pub(crate) struct AddressFile {
355
    /// The address to which the server is bound.
356
    pub(crate) address: String,
357
}
358

            
359
impl<R: Addresses> ConnectAddress<R> {
360
    /// Return true if this is an inet-auto address.
361
    fn is_auto(&self) -> bool {
362
        matches!(self, ConnectAddress::InetAuto { .. })
363
    }
364
}
365
impl ConnectAddress<Unresolved> {
366
    /// Expand all variables within this ConnectAddress to their concrete forms.
367
4
    fn resolve(
368
4
        &self,
369
4
        resolver: &CfgPathResolver,
370
4
    ) -> Result<ConnectAddress<Resolved>, ResolveError> {
371
        use ConnectAddress::*;
372
4
        match self {
373
            InetAuto(a) => Ok(InetAuto(a.clone())),
374
4
            Socket(s) => Ok(Socket(s.resolve(resolver)?)),
375
        }
376
4
    }
377
}
378
impl ConnectAddress<Resolved> {
379
    /// Return a list of addresses to bind to.
380
4
    fn bind_to_addresses(&self) -> Vec<general::SocketAddr> {
381
        use ConnectAddress::*;
382
4
        match self {
383
            InetAuto(a) => a.bind_to_addresses(),
384
4
            Socket(a) => vec![a.as_ref().clone()],
385
        }
386
4
    }
387

            
388
    /// Bind a single address from this `ConnectAddress`,
389
    /// or return an error if none can be bound.
390
    #[cfg(feature = "rpc-server")]
391
    pub(crate) async fn bind<R>(
392
        &self,
393
        runtime: &R,
394
    ) -> Result<(R::Listener, String), crate::ConnectError>
395
    where
396
        R: NetStreamProvider<general::SocketAddr>,
397
    {
398
        use crate::ConnectError;
399
        match self {
400
            ConnectAddress::InetAuto(auto) => {
401
                let bind_one =
402
                     async |addr: &general::SocketAddr| -> Result<(R::Listener, String), crate::ConnectError>  {
403
                        let listener = runtime.listen(addr).await?;
404
                        let local_addr = listener.local_addr()?.try_to_string().ok_or_else(|| ConnectError::Internal("Can't represent auto socket as string!".into()))?;
405
                        Ok((listener,local_addr))
406
                    };
407

            
408
                let mut first_error = None;
409

            
410
                for addr in auto.bind_to_addresses() {
411
                    match bind_one(&addr).await {
412
                        Ok(result) => {
413
                            return Ok(result);
414
                        }
415
                        Err(e) => {
416
                            if first_error.is_none() {
417
                                first_error = Some(e);
418
                            }
419
                        }
420
                    }
421
                }
422
                // if we reach here, we only got errors.
423
                Err(first_error.unwrap_or_else(|| {
424
                    ConnectError::Internal("No auto addresses to bind!?".into())
425
                }))
426
            }
427
            ConnectAddress::Socket(addr) => {
428
                let listener = runtime.listen(addr.as_ref()).await?;
429
                Ok((listener, addr.as_str().to_owned()))
430
            }
431
        }
432
    }
433
}
434

            
435
impl Connect<Unresolved> {
436
    /// Expand all variables within this `Connect` to their concrete forms.
437
4
    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Connect<Resolved>, ResolveError> {
438
4
        let socket = self.socket.resolve(resolver)?;
439
4
        let socket_canonical = self
440
4
            .socket_canonical
441
4
            .as_ref()
442
6
            .map(|sc| sc.resolve(resolver))
443
4
            .transpose()?;
444
4
        let auth = self.auth.resolve(resolver)?;
445
4
        let socket_address_file = self
446
4
            .socket_address_file
447
4
            .as_ref()
448
4
            .map(|p| p.path(resolver))
449
4
            .transpose()?;
450
4
        Connect {
451
4
            socket,
452
4
            socket_canonical,
453
4
            auth,
454
4
            socket_address_file,
455
4
        }
456
4
        .validate()
457
4
    }
458
}
459

            
460
impl Connect<Resolved> {
461
    /// Return this `Connect` only if its parts are valid and compatible.
462
4
    fn validate(self) -> Result<Self, ResolveError> {
463
        use general::SocketAddr::{Inet, Unix};
464
4
        for bind_addr in self.socket.bind_to_addresses() {
465
4
            match (bind_addr, &self.auth) {
466
4
                (Inet(addr), _) if !addr.ip().is_loopback() => {
467
                    return Err(ResolveError::AddressNotLoopback);
468
                }
469
                (Inet(_), Auth::None) => return Err(ResolveError::AuthNotCompatible),
470
4
                (_, Auth::Unrecognized(_)) => return Err(ResolveError::AuthNotRecognized),
471
                (Inet(_), Auth::Cookie { .. }) => {}
472
                (Unix(_), _) => {}
473
                (_, _) => return Err(ResolveError::AddressTypeNotRecognized),
474
            };
475
        }
476
        if self.socket.is_auto() != self.socket_address_file.is_some() {
477
            return Err(ResolveError::AutoIncompatibleWithSocketFile);
478
        }
479
        self.check_absolute_paths()?;
480
        Ok(self)
481
4
    }
482

            
483
    /// Return an error if some path in this `Connect` is not absolute.
484
    fn check_absolute_paths(&self) -> Result<(), ResolveError> {
485
        for bind_addr in self.socket.bind_to_addresses() {
486
            sockaddr_check_absolute(&bind_addr)?;
487
        }
488
        if let Some(sa) = &self.socket_canonical {
489
            sockaddr_check_absolute(sa.as_ref())?;
490
        }
491
        self.auth.check_absolute_paths()?;
492
        if self
493
            .socket_address_file
494
            .as_ref()
495
            .is_some_and(|p| !p.is_absolute())
496
        {
497
            return Err(ResolveError::PathNotAbsolute);
498
        }
499
        Ok(())
500
    }
501
}
502

            
503
/// An authentication method for RPC implementations to use,
504
/// along with its related parameters.
505
#[derive(Deserialize, Clone, Debug)]
506
#[serde(rename_all = "lowercase")]
507
pub(crate) enum Auth<R: Addresses> {
508
    /// No authentication is needed or should be expected.
509
    None,
510
    /// Cookie-based authentication should be used.
511
    Cookie {
512
        /// Path to the cookie file.
513
        path: R::Path,
514
    },
515
    /// Unrecognized authentication method.
516
    ///
517
    /// (Serde will deserialize into this whenever the auth field
518
    /// is something unrecognized.)
519
    #[serde(untagged)]
520
    Unrecognized(toml::Value),
521
}
522

            
523
impl Auth<Unresolved> {
524
    /// Expand all variables within this `Auth` to their concrete forms.
525
4
    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Auth<Resolved>, ResolveError> {
526
4
        match self {
527
            Auth::None => Ok(Auth::None),
528
            Auth::Cookie { path } => Ok(Auth::Cookie {
529
                path: path.path(resolver)?,
530
            }),
531
4
            Auth::Unrecognized(x) => Ok(Auth::Unrecognized(x.clone())),
532
        }
533
4
    }
534
}
535

            
536
impl Auth<Resolved> {
537
    /// Return an error if any path in `self` is not absolute..
538
    fn check_absolute_paths(&self) -> Result<(), ResolveError> {
539
        match self {
540
            Auth::None => Ok(()),
541
            Auth::Cookie { path } => {
542
                if path.is_absolute() {
543
                    Ok(())
544
                } else {
545
                    Err(ResolveError::PathNotAbsolute)
546
                }
547
            }
548
            Auth::Unrecognized(_) => Ok(()),
549
        }
550
    }
551
}
552

            
553
/// Type parameters for unresolved connect points
554
//
555
// This derive should be needless, but it permits derive(Clone,Debug) elsewhere.
556
#[derive(Clone, Debug)]
557
struct Unresolved;
558
impl Addresses for Unresolved {
559
    type SocketAddr = String;
560
    type Path = CfgPath;
561
}
562

            
563
/// Type parameters for resolved connect points
564
//
565
// This derive should be needless, but it permits derive(Clone,Debug) elsewhere.
566
#[derive(Clone, Debug)]
567
pub(crate) struct Resolved;
568
impl Addresses for Resolved {
569
    type SocketAddr = general::SocketAddr;
570
    type Path = PathBuf;
571
}
572

            
573
/// Represent an address type along with the string it was decoded from.
574
///
575
/// We use this type in connect points because, for some kinds of authentication,
576
/// we need the literal input string that created the address.
577
#[derive(
578
    Clone, Debug, derive_more::AsRef, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
579
)]
580
pub(crate) struct AddrWithStr<A>
581
where
582
    A: Clone + Debug,
583
{
584
    /// The string representation of the address.
585
    ///
586
    /// For inet addresses, this is the value that appeared in the configuration.
587
    /// For unix domain sockets, this is the value that appeared in the configuration,
588
    /// after shell expansion.
589
    string: String,
590
    /// The address itself.
591
    #[as_ref]
592
    addr: A,
593
}
594
impl<A> AddrWithStr<A>
595
where
596
    A: Clone + Debug,
597
{
598
    /// Return the string representation of this address,
599
    /// for use in the authentication handshake.
600
    pub(crate) fn as_str(&self) -> &str {
601
        self.string.as_str()
602
    }
603

            
604
    /// Replace the string representation of this address with the one in `other`.
605
    pub(crate) fn set_string_from<B: Clone + Debug>(&mut self, other: &AddrWithStr<B>) {
606
        self.string = other.string.clone();
607
    }
608
}
609
impl AddrWithStr<String> {
610
    /// Convert an `AddrWithStr<String>` into its substituted form.
611
8
    pub(crate) fn resolve(
612
8
        &self,
613
8
        resolver: &CfgPathResolver,
614
8
    ) -> Result<AddrWithStr<general::SocketAddr>, ResolveError> {
615
8
        let AddrWithStr { string, addr } = self;
616
8
        let addr: CfgAddr = addr.parse()?;
617
8
        let substituted = addr.substitutions_will_apply();
618
8
        let addr = addr.address(resolver)?;
619
8
        let string = if substituted {
620
            addr.try_to_string().ok_or(ResolveError::PathNotString)?
621
        } else {
622
8
            string.clone()
623
        };
624
8
        Ok(AddrWithStr { string, addr })
625
8
    }
626
}
627
impl<A> FromStr for AddrWithStr<A>
628
where
629
    A: Clone + Debug + FromStr,
630
{
631
    type Err = <A as FromStr>::Err;
632

            
633
80
    fn from_str(s: &str) -> Result<Self, Self::Err> {
634
80
        let addr = s.parse()?;
635
80
        let string = s.to_owned();
636
80
        Ok(Self { string, addr })
637
80
    }
638
}
639

            
640
impl<A> std::fmt::Display for AddrWithStr<A>
641
where
642
    A: Clone + Debug + std::fmt::Display,
643
{
644
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
645
        write!(f, "{}", self.string)
646
    }
647
}
648

            
649
/// Return true if `s` is an absolute address.
650
///
651
/// All IP addresses are considered absolute.
652
fn sockaddr_check_absolute(s: &general::SocketAddr) -> Result<(), ResolveError> {
653
    match s {
654
        general::SocketAddr::Inet(_) => Ok(()),
655
        general::SocketAddr::Unix(sa) => match sa.as_pathname() {
656
            Some(p) if !p.is_absolute() => Err(ResolveError::PathNotAbsolute),
657
            _ => Ok(()),
658
        },
659
        _ => Err(ResolveError::AddressTypeNotRecognized),
660
    }
661
}
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
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
678

            
679
    use super::*;
680
    use assert_matches::assert_matches;
681

            
682
    fn parse(s: &str) -> ParsedConnectPoint {
683
        s.parse().unwrap()
684
    }
685

            
686
    #[test]
687
    fn examples() {
688
        let _e1 = parse(
689
            r#"
690
[builtin]
691
builtin = "abort"
692
"#,
693
        );
694

            
695
        let _e2 = parse(
696
            r#"
697
[connect]
698
socket = "unix:/var/run/arti/rpc_socket"
699
auth = "none"
700
"#,
701
        );
702

            
703
        let _e3 = parse(
704
            r#"
705
[connect]
706
socket = "inet:[::1]:9191"
707
socket_canonical = "inet:[::1]:2020"
708

            
709
auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
710
"#,
711
        );
712

            
713
        let _e4 = parse(
714
            r#"
715
[connect]
716
socket = "inet:[::1]:9191"
717
socket_canonical = "inet:[::1]:2020"
718

            
719
[connect.auth.cookie]
720
path = "/home/user/.arti_rpc/cookie"
721
"#,
722
        );
723
    }
724

            
725
    #[test]
726
    fn parse_errors() {
727
        let r: Result<ParsedConnectPoint, _> = "not a toml string".parse();
728
        assert_matches!(r, Err(ParseError::InvalidConnectPoint(_)));
729

            
730
        let r: Result<ParsedConnectPoint, _> = "[squidcakes]".parse();
731
        assert_matches!(r, Err(ParseError::UnrecognizedFormat));
732

            
733
        let r: Result<ParsedConnectPoint, _> = r#"
734
[builtin]
735
builtin = "abort"
736

            
737
[connect]
738
socket = "inet:[::1]:9191"
739
socket_canonical = "inet:[::1]:2020"
740

            
741
auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
742
"#
743
        .parse();
744
        assert_matches!(r, Err(ParseError::ConflictingMembers));
745
    }
746

            
747
    #[test]
748
    fn resolve_errors() {
749
        let resolver = CfgPathResolver::default();
750

            
751
        let r: ParsedConnectPoint = r#"
752
[connect]
753
socket = "inet:[::1]:9191"
754
socket_canonical = "inet:[::1]:2020"
755

            
756
[connect.auth.esp]
757
telekinetic_handshake = 3
758
"#
759
        .parse()
760
        .unwrap();
761
        let err = r.resolve(&resolver).err();
762
        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
763

            
764
        let r: ParsedConnectPoint = r#"
765
[connect]
766
socket = "inet:[::1]:9191"
767
socket_canonical = "inet:[::1]:2020"
768

            
769
auth = "foo"
770
"#
771
        .parse()
772
        .unwrap();
773
        let err = r.resolve(&resolver).err();
774
        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
775
    }
776
}