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
    /// Check whether authenticating this connect point grants superuser permission.
58
    pub fn superuser_permission(&self) -> crate::SuperuserPermission {
59
        self.0.superuser_permission()
60
    }
61

            
62
    /// Return true if this connect point is an explicit abort.
63
    pub fn is_explicit_abort(&self) -> bool {
64
        self.0.is_explicit_abort()
65
    }
66
}
67

            
68
impl ResolvedConnectPoint {
69
    /// Check whether authenticating this connect point grants superuser permission.
70
    pub fn superuser_permission(&self) -> crate::SuperuserPermission {
71
        self.0.superuser_permission()
72
    }
73

            
74
    /// Return true if this connect point is an explicit abort.
75
    pub fn is_explicit_abort(&self) -> bool {
76
        self.0.is_explicit_abort()
77
    }
78
}
79

            
80
impl FromStr for ParsedConnectPoint {
81
    type Err = ParseError;
82

            
83
58
    fn from_str(s: &str) -> Result<Self, Self::Err> {
84
58
        let de: ConnectPointDe = toml::from_str(s).map_err(ParseError::InvalidConnectPoint)?;
85
52
        Ok(ParsedConnectPoint(de.try_into()?))
86
58
    }
87
}
88

            
89
/// A failure from [`ParsedConnectPoint::from_str()`].
90
#[derive(Clone, Debug, thiserror::Error)]
91
#[non_exhaustive]
92
pub enum ParseError {
93
    /// The input was not valid toml, or was an invalid connect point.
94
    #[error("Invalid connect point")]
95
    InvalidConnectPoint(#[source] toml::de::Error),
96
    /// The input had sections or members
97
    /// that are not allowed to appear in the same connect point.
98
    #[error("Conflicting members in connect point")]
99
    ConflictingMembers,
100
    /// The input was valid toml, but did not have any recognized
101
    /// connect point section.
102
    #[error("Unrecognized format on connect point")]
103
    UnrecognizedFormat,
104
    /// An inet-auto address was provided in a connect point
105
    /// that was not a loopback address.
106
    ///
107
    /// (Note that this error is only generated for inet-auto addresses.
108
    /// Other non-loopback addresses cause a [`ResolveError::AddressNotLoopback`].)
109
    #[error("inet-auto address was not a loopback address")]
110
    AutoAddressNotLoopback,
111
}
112
impl HasClientErrorAction for ParseError {
113
    fn client_action(&self) -> crate::ClientErrorAction {
114
        use crate::ClientErrorAction as A;
115
        match self {
116
            ParseError::InvalidConnectPoint(_) => A::Abort,
117
            ParseError::ConflictingMembers => A::Abort,
118
            ParseError::AutoAddressNotLoopback => A::Decline,
119
            ParseError::UnrecognizedFormat => A::Decline,
120
        }
121
    }
122
}
123

            
124
/// A failure from [`ParsedConnectPoint::resolve()`].
125
#[derive(Clone, Debug, thiserror::Error)]
126
#[non_exhaustive]
127
pub enum ResolveError {
128
    /// There was a path in the connect point that we couldn't resolve.
129
    #[error("Unable to resolve variables in path")]
130
    InvalidPath(#[from] CfgPathError),
131
    ///  There was an address in the connect point that we couldn't parse.
132
    #[error("Unable to parse address")]
133
    UnparseableAddr(#[from] AddrParseError),
134
    /// There was an address in the connect point that we couldn't resolve.
135
    #[error("Unable to resolve variables in address")]
136
    InvalidAddr(#[from] CfgAddrError),
137
    /// After substitution, we couldn't expand the path to a string.
138
    #[error("Cannot represent expanded path as string")]
139
    PathNotString,
140
    /// Address is not a loopback address.
141
    #[error("Tried to bind or connect to a non-loopback TCP address")]
142
    AddressNotLoopback,
143
    /// Authorization mechanism not compatible with address family
144
    #[error("Authorization type not compatible with address family")]
145
    AuthNotCompatible,
146
    /// Authorization mechanism not recognized
147
    #[error("Authorization type not recognized as a supported type")]
148
    AuthNotRecognized,
149
    /// Address type not supported by the RPC connect point subsystem.
150
    ///
151
    /// (This can only happen if somebody adds new variants to `general::SocketAddr`.)
152
    #[error("Address type not recognized")]
153
    AddressTypeNotRecognized,
154
    /// The address was incompatible with the presence or absence of socket_address_file.
155
    #[error("inet-auto without socket_address_file, or vice versa")]
156
    AutoIncompatibleWithSocketFile,
157
    /// The name of a file or AF_UNIX socket address was a relative path.
158
    #[error("Path was not absolute")]
159
    PathNotAbsolute,
160
}
161
impl HasClientErrorAction for ResolveError {
162
    fn client_action(&self) -> crate::ClientErrorAction {
163
        use crate::ClientErrorAction as A;
164
        match self {
165
            ResolveError::InvalidPath(e) => e.client_action(),
166
            ResolveError::UnparseableAddr(e) => e.client_action(),
167
            ResolveError::InvalidAddr(e) => e.client_action(),
168
            ResolveError::PathNotString => A::Decline,
169
            ResolveError::AddressNotLoopback => A::Decline,
170
            ResolveError::AuthNotCompatible => A::Abort,
171
            ResolveError::AuthNotRecognized => A::Decline,
172
            ResolveError::AddressTypeNotRecognized => A::Decline,
173
            ResolveError::PathNotAbsolute => A::Abort,
174
            ResolveError::AutoIncompatibleWithSocketFile => A::Abort,
175
        }
176
    }
177
}
178

            
179
/// Implementation type for a connect point.
180
///
181
/// This type is hidden so that the enum fields remain private.
182
/// It is parameterized on a [`Addresses`] trait,
183
/// to indicate whether it is in resolved or unresolved form.
184
#[derive(Clone, Debug)]
185
pub(crate) enum ConnectPointEnum<R: Addresses> {
186
    /// Connect by opening a socket to a [`general::SocketAddr`]
187
    Connect(Connect<R>),
188
    /// Connect by some built-in mechanism.
189
    ///
190
    /// (Or, in the case of Abort, do not connect at all.)
191
    Builtin(Builtin),
192
}
193

            
194
/// Trait to hold types that vary depending on whether a connect point is resolved or not.
195
//
196
// Note: We could use instead separate `PATH` and `ADDR` parameters,
197
// but this approach makes specifying bounds significantly easier.
198
pub(crate) trait Addresses {
199
    /// Type to represent addresses that we can open a socket to.
200
    type SocketAddr: Clone + std::fmt::Debug;
201
    /// Type to represent paths on the filesystem.
202
    type Path: Clone + std::fmt::Debug;
203
}
204

            
205
/// Representation of a connect point as deserialized.
206
///
207
/// We could instead deserialize [`ConnectPointEnum`] directly,
208
/// but that would restrict our error-handling:
209
/// the `toml` crate doesn't make it easy to distinguish
210
/// one kind of parse error from another.
211
///
212
/// TODO We should revisit this choice when we add more variants
213
/// or more auxiliary tables.
214
#[derive(Deserialize, Clone, Debug)]
215
struct ConnectPointDe {
216
    /// A "connect" table.
217
    connect: Option<Connect<Unresolved>>,
218
    /// A "builtin" table.
219
    builtin: Option<Builtin>,
220
}
221
impl TryFrom<ConnectPointDe> for ConnectPointEnum<Unresolved> {
222
    type Error = ParseError;
223

            
224
52
    fn try_from(value: ConnectPointDe) -> Result<Self, Self::Error> {
225
4
        match value {
226
            ConnectPointDe {
227
46
                connect: Some(c),
228
                builtin: None,
229
46
            } => Ok(ConnectPointEnum::Connect(c)),
230
            ConnectPointDe {
231
                connect: None,
232
2
                builtin: Some(b),
233
2
            } => Ok(ConnectPointEnum::Builtin(b)),
234
            ConnectPointDe {
235
                connect: Some(_),
236
                builtin: Some(_),
237
2
            } => Err(ParseError::ConflictingMembers),
238
            // This didn't have either recognized section,
239
            // so it is likely itn an unrecognized format.
240
2
            _ => Err(ParseError::UnrecognizedFormat),
241
        }
242
52
    }
243
}
244

            
245
impl<R: Addresses> ConnectPointEnum<R> {
246
    /// Check whether authenticating this connect point grants superuser permission.
247
    fn superuser_permission(&self) -> crate::SuperuserPermission {
248
        use crate::SuperuserPermission::*;
249
        match self {
250
            ConnectPointEnum::Connect(connect) => {
251
                if connect.superuser {
252
                    Allowed
253
                } else {
254
                    NotAllowed
255
                }
256
            }
257
            ConnectPointEnum::Builtin(_) => NotAllowed,
258
        }
259
    }
260

            
261
    /// Return true if this connect point is an explicit abort.
262
    fn is_explicit_abort(&self) -> bool {
263
        matches!(
264
            self,
265
            ConnectPointEnum::Builtin(Builtin {
266
                builtin: BuiltinVariant::Abort
267
            })
268
        )
269
    }
270
}
271

            
272
/// A "builtin" connect point.
273
///
274
/// This represents an approach to connecting that is handled purely
275
/// within arti.  In the future, this might include "embedded" or "owned";
276
/// but for now, it only includes "abort".
277
#[derive(Deserialize, Clone, Debug)]
278
pub(crate) struct Builtin {
279
    /// Actual strategy of built-in behavior to implement.
280
    pub(crate) builtin: BuiltinVariant,
281
}
282

            
283
/// A particular built-in strategy.
284
#[derive(Deserialize, Clone, Debug)]
285
#[serde(rename_all = "lowercase")]
286
pub(crate) enum BuiltinVariant {
287
    /// This connect point must fail,
288
    /// and no subsequent connect points may be tried.
289
    Abort,
290
}
291

            
292
/// Information for a connect point that is implemented by making a socket connection to an address.
293
#[derive(Deserialize, Clone, Debug)]
294
#[serde(bound = "R::Path : Deserialize<'de>, AddrWithStr<R::SocketAddr> : Deserialize<'de>")]
295
pub(crate) struct Connect<R: Addresses> {
296
    /// The address of the socket at which the client should try to reach the RPC server,
297
    /// and which the RPC server should bind.
298
    pub(crate) socket: ConnectAddress<R>,
299
    /// The address of the socket which the RPC server believes it is actually listening at.
300
    ///
301
    /// If absent, defaults to `socket`.
302
    ///
303
    /// This value is only needs to be different from `socket`
304
    /// in cases where cookie authentication is in use,
305
    /// and the client is sandboxed somehow (such as behind a NAT, or inside a container).
306
    pub(crate) socket_canonical: Option<AddrWithStr<R::SocketAddr>>,
307
    /// The authentication that the client should try to use,
308
    /// and which the server should require.
309
    pub(crate) auth: Auth<R>,
310
    /// A file in which the actual value of an `inet-auto` address should be stored.
311
    pub(crate) socket_address_file: Option<R::Path>,
312
    /// If true, we should allow authenticated connections to this connect point to acquire
313
    /// superuser permissions.
314
    #[serde(default)]
315
    pub(crate) superuser: bool,
316
}
317

            
318
/// A target of a [`Connect`] connpt.
319
///
320
/// Can be either a socket address, or an inet-auto address.
321
#[derive(Deserialize, Clone, Debug)]
322
#[serde(bound = "R::Path : Deserialize<'de>, AddrWithStr<R::SocketAddr> : Deserialize<'de>")]
323
#[serde(untagged, expecting = "a network schema and address")]
324
pub(crate) enum ConnectAddress<R: Addresses> {
325
    /// A socket address with an unspecified port.
326
    InetAuto(InetAutoAddress),
327
    /// A specified socket address.
328
    Socket(AddrWithStr<R::SocketAddr>),
329
}
330

            
331
/// Instructions to bind to an address chosen by the OS.
332
#[derive(Clone, Debug, DeserializeFromStr)]
333
pub(crate) struct InetAutoAddress {
334
    /// The address that the relay should bind to, or None if any loopback address is okay.
335
    ///
336
    /// Must be a loopback address.
337
    bind: Option<IpAddr>,
338
}
339
impl std::fmt::Display for InetAutoAddress {
340
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341
        match self.bind {
342
            Some(a) => write!(f, "inet-auto:{a}"),
343
            None => write!(f, "inet-auto:auto"),
344
        }
345
    }
346
}
347
impl FromStr for InetAutoAddress {
348
    type Err = ParseError;
349

            
350
48
    fn from_str(s: &str) -> Result<Self, Self::Err> {
351
48
        let Some(addr_part) = s.strip_prefix("inet-auto:") else {
352
48
            return Err(ParseError::UnrecognizedFormat);
353
        };
354
        if addr_part == "auto" {
355
            return Ok(InetAutoAddress { bind: None });
356
        }
357
        let Ok(addr) = IpAddr::from_str(addr_part) else {
358
            return Err(ParseError::UnrecognizedFormat);
359
        };
360
        if addr.is_loopback() {
361
            Ok(InetAutoAddress { bind: Some(addr) })
362
        } else {
363
            Err(ParseError::AutoAddressNotLoopback)
364
        }
365
48
    }
366
}
367

            
368
impl InetAutoAddress {
369
    /// Return a list of addresses to bind to.
370
    fn bind_to_addresses(&self) -> Vec<general::SocketAddr> {
371
        match self {
372
            InetAutoAddress { bind: None } => vec![
373
                net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 0).into(),
374
                net::SocketAddr::new(net::Ipv6Addr::LOCALHOST.into(), 0).into(),
375
            ],
376
            InetAutoAddress { bind: Some(ip) } => {
377
                vec![net::SocketAddr::new(*ip, 0).into()]
378
            }
379
        }
380
    }
381

            
382
    /// Having parsed `addr`, make sure it is a possible instantiation of this address.
383
    ///
384
    /// Return an error if it is not.
385
    #[cfg(feature = "rpc-client")]
386
    pub(crate) fn validate_parsed_address(
387
        &self,
388
        addr: &general::SocketAddr,
389
    ) -> Result<(), crate::ConnectError> {
390
        use general::SocketAddr::Inet;
391
        for sa in self.bind_to_addresses() {
392
            if let (Inet(specified), Inet(got)) = (sa, addr) {
393
                if specified.port() == 0 && specified.ip() == got.ip() {
394
                    return Ok(());
395
                }
396
            }
397
        }
398

            
399
        Err(crate::ConnectError::SocketAddressFileMismatch)
400
    }
401
}
402

            
403
/// The representation of an address as written into a socket file.
404
#[derive(Clone, Debug)]
405
#[cfg_attr(feature = "rpc-client", derive(Deserialize))]
406
#[cfg_attr(feature = "rpc-server", derive(serde::Serialize))]
407
pub(crate) struct AddressFile {
408
    /// The address to which the server is bound.
409
    pub(crate) address: String,
410
}
411

            
412
impl<R: Addresses> ConnectAddress<R> {
413
    /// Return true if this is an inet-auto address.
414
    fn is_auto(&self) -> bool {
415
        matches!(self, ConnectAddress::InetAuto { .. })
416
    }
417
}
418
impl ConnectAddress<Unresolved> {
419
    /// Expand all variables within this ConnectAddress to their concrete forms.
420
4
    fn resolve(
421
4
        &self,
422
4
        resolver: &CfgPathResolver,
423
4
    ) -> Result<ConnectAddress<Resolved>, ResolveError> {
424
        use ConnectAddress::*;
425
4
        match self {
426
            InetAuto(a) => Ok(InetAuto(a.clone())),
427
4
            Socket(s) => Ok(Socket(s.resolve(resolver)?)),
428
        }
429
4
    }
430
}
431
impl ConnectAddress<Resolved> {
432
    /// Return a list of addresses to bind to.
433
4
    fn bind_to_addresses(&self) -> Vec<general::SocketAddr> {
434
        use ConnectAddress::*;
435
4
        match self {
436
            InetAuto(a) => a.bind_to_addresses(),
437
4
            Socket(a) => vec![a.as_ref().clone()],
438
        }
439
4
    }
440

            
441
    /// Bind a single address from this `ConnectAddress`,
442
    /// or return an error if none can be bound.
443
    #[cfg(feature = "rpc-server")]
444
    pub(crate) async fn bind<R>(
445
        &self,
446
        runtime: &R,
447
    ) -> Result<(R::Listener, String), crate::ConnectError>
448
    where
449
        R: NetStreamProvider<general::SocketAddr>,
450
    {
451
        use crate::ConnectError;
452
        match self {
453
            ConnectAddress::InetAuto(auto) => {
454
                let bind_one =
455
                     async |addr: &general::SocketAddr| -> Result<(R::Listener, String), crate::ConnectError>  {
456
                        let listener = runtime.listen(addr).await?;
457
                        let local_addr = listener.local_addr()?.try_to_string().ok_or_else(|| ConnectError::Internal("Can't represent auto socket as string!".into()))?;
458
                        Ok((listener,local_addr))
459
                    };
460

            
461
                let mut first_error = None;
462

            
463
                for addr in auto.bind_to_addresses() {
464
                    match bind_one(&addr).await {
465
                        Ok(result) => {
466
                            return Ok(result);
467
                        }
468
                        Err(e) => {
469
                            if first_error.is_none() {
470
                                first_error = Some(e);
471
                            }
472
                        }
473
                    }
474
                }
475
                // if we reach here, we only got errors.
476
                Err(first_error.unwrap_or_else(|| {
477
                    ConnectError::Internal("No auto addresses to bind!?".into())
478
                }))
479
            }
480
            ConnectAddress::Socket(addr) => {
481
                let listener = runtime.listen(addr.as_ref()).await?;
482
                Ok((listener, addr.as_str().to_owned()))
483
            }
484
        }
485
    }
486
}
487

            
488
impl Connect<Unresolved> {
489
    /// Expand all variables within this `Connect` to their concrete forms.
490
4
    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Connect<Resolved>, ResolveError> {
491
4
        let socket = self.socket.resolve(resolver)?;
492
4
        let socket_canonical = self
493
4
            .socket_canonical
494
4
            .as_ref()
495
6
            .map(|sc| sc.resolve(resolver))
496
4
            .transpose()?;
497
4
        let auth = self.auth.resolve(resolver)?;
498
4
        let socket_address_file = self
499
4
            .socket_address_file
500
4
            .as_ref()
501
4
            .map(|p| p.path(resolver))
502
4
            .transpose()?;
503
4
        Connect {
504
4
            socket,
505
4
            socket_canonical,
506
4
            auth,
507
4
            socket_address_file,
508
4
            superuser: self.superuser,
509
4
        }
510
4
        .validate()
511
4
    }
512
}
513

            
514
impl Connect<Resolved> {
515
    /// Return this `Connect` only if its parts are valid and compatible.
516
4
    fn validate(self) -> Result<Self, ResolveError> {
517
        use general::SocketAddr::{Inet, Unix};
518
4
        for bind_addr in self.socket.bind_to_addresses() {
519
4
            match (bind_addr, &self.auth) {
520
4
                (Inet(addr), _) if !addr.ip().is_loopback() => {
521
                    return Err(ResolveError::AddressNotLoopback);
522
                }
523
                (Inet(_), Auth::None) => return Err(ResolveError::AuthNotCompatible),
524
4
                (_, Auth::Unrecognized(_)) => return Err(ResolveError::AuthNotRecognized),
525
                (Inet(_), Auth::Cookie { .. }) => {}
526
                (Unix(_), _) => {}
527
                (_, _) => return Err(ResolveError::AddressTypeNotRecognized),
528
            };
529
        }
530
        if self.socket.is_auto() != self.socket_address_file.is_some() {
531
            return Err(ResolveError::AutoIncompatibleWithSocketFile);
532
        }
533
        self.check_absolute_paths()?;
534
        Ok(self)
535
4
    }
536

            
537
    /// Return an error if some path in this `Connect` is not absolute.
538
    fn check_absolute_paths(&self) -> Result<(), ResolveError> {
539
        for bind_addr in self.socket.bind_to_addresses() {
540
            sockaddr_check_absolute(&bind_addr)?;
541
        }
542
        if let Some(sa) = &self.socket_canonical {
543
            sockaddr_check_absolute(sa.as_ref())?;
544
        }
545
        self.auth.check_absolute_paths()?;
546
        if self
547
            .socket_address_file
548
            .as_ref()
549
            .is_some_and(|p| !p.is_absolute())
550
        {
551
            return Err(ResolveError::PathNotAbsolute);
552
        }
553
        Ok(())
554
    }
555
}
556

            
557
/// An authentication method for RPC implementations to use,
558
/// along with its related parameters.
559
#[derive(Deserialize, Clone, Debug)]
560
#[serde(rename_all = "lowercase")]
561
pub(crate) enum Auth<R: Addresses> {
562
    /// No authentication is needed or should be expected.
563
    None,
564
    /// Cookie-based authentication should be used.
565
    Cookie {
566
        /// Path to the cookie file.
567
        path: R::Path,
568
    },
569
    /// Unrecognized authentication method.
570
    ///
571
    /// (Serde will deserialize into this whenever the auth field
572
    /// is something unrecognized.)
573
    #[serde(untagged)]
574
    Unrecognized(toml::Value),
575
}
576

            
577
impl Auth<Unresolved> {
578
    /// Expand all variables within this `Auth` to their concrete forms.
579
4
    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Auth<Resolved>, ResolveError> {
580
4
        match self {
581
            Auth::None => Ok(Auth::None),
582
            Auth::Cookie { path } => Ok(Auth::Cookie {
583
                path: path.path(resolver)?,
584
            }),
585
4
            Auth::Unrecognized(x) => Ok(Auth::Unrecognized(x.clone())),
586
        }
587
4
    }
588
}
589

            
590
impl Auth<Resolved> {
591
    /// Return an error if any path in `self` is not absolute..
592
    fn check_absolute_paths(&self) -> Result<(), ResolveError> {
593
        match self {
594
            Auth::None => Ok(()),
595
            Auth::Cookie { path } => {
596
                if path.is_absolute() {
597
                    Ok(())
598
                } else {
599
                    Err(ResolveError::PathNotAbsolute)
600
                }
601
            }
602
            Auth::Unrecognized(_) => Ok(()),
603
        }
604
    }
605
}
606

            
607
/// Type parameters for unresolved connect points
608
//
609
// This derive should be needless, but it permits derive(Clone,Debug) elsewhere.
610
#[derive(Clone, Debug)]
611
struct Unresolved;
612
impl Addresses for Unresolved {
613
    type SocketAddr = String;
614
    type Path = CfgPath;
615
}
616

            
617
/// Type parameters for resolved connect points
618
//
619
// This derive should be needless, but it permits derive(Clone,Debug) elsewhere.
620
#[derive(Clone, Debug)]
621
pub(crate) struct Resolved;
622
impl Addresses for Resolved {
623
    type SocketAddr = general::SocketAddr;
624
    type Path = PathBuf;
625
}
626

            
627
/// Represent an address type along with the string it was decoded from.
628
///
629
/// We use this type in connect points because, for some kinds of authentication,
630
/// we need the literal input string that created the address.
631
#[derive(
632
    Clone, Debug, derive_more::AsRef, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
633
)]
634
pub(crate) struct AddrWithStr<A>
635
where
636
    A: Clone + Debug,
637
{
638
    /// The string representation of the address.
639
    ///
640
    /// For inet addresses, this is the value that appeared in the configuration.
641
    /// For unix domain sockets, this is the value that appeared in the configuration,
642
    /// after shell expansion.
643
    string: String,
644
    /// The address itself.
645
    #[as_ref]
646
    addr: A,
647
}
648
impl<A> AddrWithStr<A>
649
where
650
    A: Clone + Debug,
651
{
652
    /// Return the string representation of this address,
653
    /// for use in the authentication handshake.
654
    pub(crate) fn as_str(&self) -> &str {
655
        self.string.as_str()
656
    }
657

            
658
    /// Replace the string representation of this address with the one in `other`.
659
    pub(crate) fn set_string_from<B: Clone + Debug>(&mut self, other: &AddrWithStr<B>) {
660
        self.string = other.string.clone();
661
    }
662
}
663
impl AddrWithStr<String> {
664
    /// Convert an `AddrWithStr<String>` into its substituted form.
665
8
    pub(crate) fn resolve(
666
8
        &self,
667
8
        resolver: &CfgPathResolver,
668
8
    ) -> Result<AddrWithStr<general::SocketAddr>, ResolveError> {
669
8
        let AddrWithStr { string, addr } = self;
670
8
        let addr: CfgAddr = addr.parse()?;
671
8
        let substituted = addr.substitutions_will_apply();
672
8
        let addr = addr.address(resolver)?;
673
8
        let string = if substituted {
674
            addr.try_to_string().ok_or(ResolveError::PathNotString)?
675
        } else {
676
8
            string.clone()
677
        };
678
8
        Ok(AddrWithStr { string, addr })
679
8
    }
680
}
681
impl<A> FromStr for AddrWithStr<A>
682
where
683
    A: Clone + Debug + FromStr,
684
{
685
    type Err = <A as FromStr>::Err;
686

            
687
80
    fn from_str(s: &str) -> Result<Self, Self::Err> {
688
80
        let addr = s.parse()?;
689
80
        let string = s.to_owned();
690
80
        Ok(Self { string, addr })
691
80
    }
692
}
693

            
694
impl<A> std::fmt::Display for AddrWithStr<A>
695
where
696
    A: Clone + Debug + std::fmt::Display,
697
{
698
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
699
        write!(f, "{}", self.string)
700
    }
701
}
702

            
703
/// Return true if `s` is an absolute address.
704
///
705
/// All IP addresses are considered absolute.
706
fn sockaddr_check_absolute(s: &general::SocketAddr) -> Result<(), ResolveError> {
707
    match s {
708
        general::SocketAddr::Inet(_) => Ok(()),
709
        general::SocketAddr::Unix(sa) => match sa.as_pathname() {
710
            Some(p) if !p.is_absolute() => Err(ResolveError::PathNotAbsolute),
711
            _ => Ok(()),
712
        },
713
        _ => Err(ResolveError::AddressTypeNotRecognized),
714
    }
715
}
716

            
717
#[cfg(test)]
718
mod test {
719
    // @@ begin test lint list maintained by maint/add_warning @@
720
    #![allow(clippy::bool_assert_comparison)]
721
    #![allow(clippy::clone_on_copy)]
722
    #![allow(clippy::dbg_macro)]
723
    #![allow(clippy::mixed_attributes_style)]
724
    #![allow(clippy::print_stderr)]
725
    #![allow(clippy::print_stdout)]
726
    #![allow(clippy::single_char_pattern)]
727
    #![allow(clippy::unwrap_used)]
728
    #![allow(clippy::unchecked_time_subtraction)]
729
    #![allow(clippy::useless_vec)]
730
    #![allow(clippy::needless_pass_by_value)]
731
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
732

            
733
    use super::*;
734
    use assert_matches::assert_matches;
735

            
736
    fn parse(s: &str) -> ParsedConnectPoint {
737
        s.parse().unwrap()
738
    }
739

            
740
    #[test]
741
    fn examples() {
742
        let _e1 = parse(
743
            r#"
744
[builtin]
745
builtin = "abort"
746
"#,
747
        );
748

            
749
        let _e2 = parse(
750
            r#"
751
[connect]
752
socket = "unix:/var/run/arti/rpc_socket"
753
auth = "none"
754
"#,
755
        );
756

            
757
        let _e3 = parse(
758
            r#"
759
[connect]
760
socket = "inet:[::1]:9191"
761
socket_canonical = "inet:[::1]:2020"
762

            
763
auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
764
"#,
765
        );
766

            
767
        let _e4 = parse(
768
            r#"
769
[connect]
770
socket = "inet:[::1]:9191"
771
socket_canonical = "inet:[::1]:2020"
772

            
773
[connect.auth.cookie]
774
path = "/home/user/.arti_rpc/cookie"
775
"#,
776
        );
777
    }
778

            
779
    #[test]
780
    fn parse_errors() {
781
        let r: Result<ParsedConnectPoint, _> = "not a toml string".parse();
782
        assert_matches!(r, Err(ParseError::InvalidConnectPoint(_)));
783

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

            
787
        let r: Result<ParsedConnectPoint, _> = r#"
788
[builtin]
789
builtin = "abort"
790

            
791
[connect]
792
socket = "inet:[::1]:9191"
793
socket_canonical = "inet:[::1]:2020"
794

            
795
auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
796
"#
797
        .parse();
798
        assert_matches!(r, Err(ParseError::ConflictingMembers));
799
    }
800

            
801
    #[test]
802
    fn resolve_errors() {
803
        let resolver = CfgPathResolver::default();
804

            
805
        let r: ParsedConnectPoint = r#"
806
[connect]
807
socket = "inet:[::1]:9191"
808
socket_canonical = "inet:[::1]:2020"
809

            
810
[connect.auth.esp]
811
telekinetic_handshake = 3
812
"#
813
        .parse()
814
        .unwrap();
815
        let err = r.resolve(&resolver).err();
816
        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
817

            
818
        let r: ParsedConnectPoint = r#"
819
[connect]
820
socket = "inet:[::1]:9191"
821
socket_canonical = "inet:[::1]:2020"
822

            
823
auth = "foo"
824
"#
825
        .parse()
826
        .unwrap();
827
        let err = r.resolve(&resolver).err();
828
        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
829
    }
830
}