Skip to main content

tor_rpc_connect/
connpt.rs

1//! Connect point types, and the code to parse them and resolve them.
2
3use serde::Deserialize;
4use serde_with::DeserializeFromStr;
5use std::{
6    fmt::Debug,
7    net::{self, IpAddr},
8    path::PathBuf,
9    str::FromStr,
10};
11use tor_config_path::{
12    CfgPath, CfgPathError, CfgPathResolver,
13    addr::{CfgAddr, CfgAddrError},
14};
15use tor_general_addr::general::{self, AddrParseError};
16#[cfg(feature = "rpc-server")]
17use tor_rtcompat::{NetStreamListener, NetStreamProvider};
18
19use 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)]
31pub 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)]
41pub struct ResolvedConnectPoint(pub(crate) ConnectPointEnum<Resolved>);
42
43impl ParsedConnectPoint {
44    /// Try to resolve all symbolic paths in this connect point,
45    /// using the rules of [`CfgPath`] and [`CfgAddr`].
46    pub fn resolve(
47        &self,
48        resolver: &CfgPathResolver,
49    ) -> Result<ResolvedConnectPoint, ResolveError> {
50        use ConnectPointEnum as CPE;
51        Ok(ResolvedConnectPoint(match &self.0 {
52            CPE::Connect(connect) => CPE::Connect(connect.resolve(resolver)?),
53            CPE::Builtin(builtin) => CPE::Builtin(builtin.clone()),
54        }))
55    }
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
68impl 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
80impl FromStr for ParsedConnectPoint {
81    type Err = ParseError;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        let de: ConnectPointDe = toml::from_str(s).map_err(ParseError::InvalidConnectPoint)?;
85        Ok(ParsedConnectPoint(de.try_into()?))
86    }
87}
88
89/// A failure from [`ParsedConnectPoint::from_str()`].
90#[derive(Clone, Debug, thiserror::Error)]
91#[non_exhaustive]
92pub 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}
112impl 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]
127pub 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}
161impl 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)]
185pub(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.
198pub(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)]
215struct ConnectPointDe {
216    /// A "connect" table.
217    connect: Option<Connect<Unresolved>>,
218    /// A "builtin" table.
219    builtin: Option<Builtin>,
220}
221impl TryFrom<ConnectPointDe> for ConnectPointEnum<Unresolved> {
222    type Error = ParseError;
223
224    fn try_from(value: ConnectPointDe) -> Result<Self, Self::Error> {
225        match value {
226            ConnectPointDe {
227                connect: Some(c),
228                builtin: None,
229            } => Ok(ConnectPointEnum::Connect(c)),
230            ConnectPointDe {
231                connect: None,
232                builtin: Some(b),
233            } => Ok(ConnectPointEnum::Builtin(b)),
234            ConnectPointDe {
235                connect: Some(_),
236                builtin: Some(_),
237            } => Err(ParseError::ConflictingMembers),
238            // This didn't have either recognized section,
239            // so it is likely itn an unrecognized format.
240            _ => Err(ParseError::UnrecognizedFormat),
241        }
242    }
243}
244
245impl<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)]
278pub(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")]
286pub(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>")]
295pub(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")]
324pub(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)]
333pub(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}
339impl 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}
347impl FromStr for InetAutoAddress {
348    type Err = ParseError;
349
350    fn from_str(s: &str) -> Result<Self, Self::Err> {
351        let Some(addr_part) = s.strip_prefix("inet-auto:") else {
352            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    }
366}
367
368impl 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))]
407pub(crate) struct AddressFile {
408    /// The address to which the server is bound.
409    pub(crate) address: String,
410}
411
412impl<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}
418impl ConnectAddress<Unresolved> {
419    /// Expand all variables within this ConnectAddress to their concrete forms.
420    fn resolve(
421        &self,
422        resolver: &CfgPathResolver,
423    ) -> Result<ConnectAddress<Resolved>, ResolveError> {
424        use ConnectAddress::*;
425        match self {
426            InetAuto(a) => Ok(InetAuto(a.clone())),
427            Socket(s) => Ok(Socket(s.resolve(resolver)?)),
428        }
429    }
430}
431impl ConnectAddress<Resolved> {
432    /// Return a list of addresses to bind to.
433    fn bind_to_addresses(&self) -> Vec<general::SocketAddr> {
434        use ConnectAddress::*;
435        match self {
436            InetAuto(a) => a.bind_to_addresses(),
437            Socket(a) => vec![a.as_ref().clone()],
438        }
439    }
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
453        // We don't use any custom options on the listening socket.
454        let listen_options = Default::default();
455
456        match self {
457            ConnectAddress::InetAuto(auto) => {
458                let bind_one =
459                     async |addr: &general::SocketAddr| -> Result<(R::Listener, String), crate::ConnectError>  {
460                        let listener = runtime.listen(addr, &listen_options).await?;
461                        let local_addr = listener.local_addr()?.try_to_string().ok_or_else(|| ConnectError::Internal("Can't represent auto socket as string!".into()))?;
462                        Ok((listener,local_addr))
463                    };
464
465                let mut first_error = None;
466
467                for addr in auto.bind_to_addresses() {
468                    match bind_one(&addr).await {
469                        Ok(result) => {
470                            return Ok(result);
471                        }
472                        Err(e) => {
473                            if first_error.is_none() {
474                                first_error = Some(e);
475                            }
476                        }
477                    }
478                }
479                // if we reach here, we only got errors.
480                Err(first_error.unwrap_or_else(|| {
481                    ConnectError::Internal("No auto addresses to bind!?".into())
482                }))
483            }
484            ConnectAddress::Socket(addr) => {
485                let listener = runtime.listen(addr.as_ref(), &listen_options).await?;
486                Ok((listener, addr.as_str().to_owned()))
487            }
488        }
489    }
490}
491
492impl Connect<Unresolved> {
493    /// Expand all variables within this `Connect` to their concrete forms.
494    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Connect<Resolved>, ResolveError> {
495        let socket = self.socket.resolve(resolver)?;
496        let socket_canonical = self
497            .socket_canonical
498            .as_ref()
499            .map(|sc| sc.resolve(resolver))
500            .transpose()?;
501        let auth = self.auth.resolve(resolver)?;
502        let socket_address_file = self
503            .socket_address_file
504            .as_ref()
505            .map(|p| p.path(resolver))
506            .transpose()?;
507        Connect {
508            socket,
509            socket_canonical,
510            auth,
511            socket_address_file,
512            superuser: self.superuser,
513        }
514        .validate()
515    }
516}
517
518impl Connect<Resolved> {
519    /// Return this `Connect` only if its parts are valid and compatible.
520    fn validate(self) -> Result<Self, ResolveError> {
521        use general::SocketAddr::{Inet, Unix};
522        for bind_addr in self.socket.bind_to_addresses() {
523            match (bind_addr, &self.auth) {
524                (Inet(addr), _) if !addr.ip().is_loopback() => {
525                    return Err(ResolveError::AddressNotLoopback);
526                }
527                (Inet(_), Auth::None) => return Err(ResolveError::AuthNotCompatible),
528                (_, Auth::Unrecognized(_)) => return Err(ResolveError::AuthNotRecognized),
529                (Inet(_), Auth::Cookie { .. }) => {}
530                (Unix(_), _) => {}
531                (_, _) => return Err(ResolveError::AddressTypeNotRecognized),
532            };
533        }
534        if self.socket.is_auto() != self.socket_address_file.is_some() {
535            return Err(ResolveError::AutoIncompatibleWithSocketFile);
536        }
537        self.check_absolute_paths()?;
538        Ok(self)
539    }
540
541    /// Return an error if some path in this `Connect` is not absolute.
542    fn check_absolute_paths(&self) -> Result<(), ResolveError> {
543        for bind_addr in self.socket.bind_to_addresses() {
544            sockaddr_check_absolute(&bind_addr)?;
545        }
546        if let Some(sa) = &self.socket_canonical {
547            sockaddr_check_absolute(sa.as_ref())?;
548        }
549        self.auth.check_absolute_paths()?;
550        if self
551            .socket_address_file
552            .as_ref()
553            .is_some_and(|p| !p.is_absolute())
554        {
555            return Err(ResolveError::PathNotAbsolute);
556        }
557        Ok(())
558    }
559}
560
561/// An authentication method for RPC implementations to use,
562/// along with its related parameters.
563#[derive(Deserialize, Clone, Debug)]
564#[serde(rename_all = "lowercase")]
565pub(crate) enum Auth<R: Addresses> {
566    /// No authentication is needed or should be expected.
567    None,
568    /// Cookie-based authentication should be used.
569    Cookie {
570        /// Path to the cookie file.
571        path: R::Path,
572    },
573    /// Unrecognized authentication method.
574    ///
575    /// (Serde will deserialize into this whenever the auth field
576    /// is something unrecognized.)
577    #[serde(untagged)]
578    Unrecognized(toml::Value),
579}
580
581impl Auth<Unresolved> {
582    /// Expand all variables within this `Auth` to their concrete forms.
583    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Auth<Resolved>, ResolveError> {
584        match self {
585            Auth::None => Ok(Auth::None),
586            Auth::Cookie { path } => Ok(Auth::Cookie {
587                path: path.path(resolver)?,
588            }),
589            Auth::Unrecognized(x) => Ok(Auth::Unrecognized(x.clone())),
590        }
591    }
592}
593
594impl Auth<Resolved> {
595    /// Return an error if any path in `self` is not absolute..
596    fn check_absolute_paths(&self) -> Result<(), ResolveError> {
597        match self {
598            Auth::None => Ok(()),
599            Auth::Cookie { path } => {
600                if path.is_absolute() {
601                    Ok(())
602                } else {
603                    Err(ResolveError::PathNotAbsolute)
604                }
605            }
606            Auth::Unrecognized(_) => Ok(()),
607        }
608    }
609}
610
611/// Type parameters for unresolved connect points
612//
613// This derive should be needless, but it permits derive(Clone,Debug) elsewhere.
614#[derive(Clone, Debug)]
615struct Unresolved;
616impl Addresses for Unresolved {
617    type SocketAddr = String;
618    type Path = CfgPath;
619}
620
621/// Type parameters for resolved connect points
622//
623// This derive should be needless, but it permits derive(Clone,Debug) elsewhere.
624#[derive(Clone, Debug)]
625pub(crate) struct Resolved;
626impl Addresses for Resolved {
627    type SocketAddr = general::SocketAddr;
628    type Path = PathBuf;
629}
630
631/// Represent an address type along with the string it was decoded from.
632///
633/// We use this type in connect points because, for some kinds of authentication,
634/// we need the literal input string that created the address.
635#[derive(
636    Clone, Debug, derive_more::AsRef, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
637)]
638pub(crate) struct AddrWithStr<A>
639where
640    A: Clone + Debug,
641{
642    /// The string representation of the address.
643    ///
644    /// For inet addresses, this is the value that appeared in the configuration.
645    /// For unix domain sockets, this is the value that appeared in the configuration,
646    /// after shell expansion.
647    string: String,
648    /// The address itself.
649    #[as_ref]
650    addr: A,
651}
652impl<A> AddrWithStr<A>
653where
654    A: Clone + Debug,
655{
656    /// Return the string representation of this address,
657    /// for use in the authentication handshake.
658    pub(crate) fn as_str(&self) -> &str {
659        self.string.as_str()
660    }
661
662    /// Replace the string representation of this address with the one in `other`.
663    pub(crate) fn set_string_from<B: Clone + Debug>(&mut self, other: &AddrWithStr<B>) {
664        self.string = other.string.clone();
665    }
666}
667impl AddrWithStr<String> {
668    /// Convert an `AddrWithStr<String>` into its substituted form.
669    pub(crate) fn resolve(
670        &self,
671        resolver: &CfgPathResolver,
672    ) -> Result<AddrWithStr<general::SocketAddr>, ResolveError> {
673        let AddrWithStr { string, addr } = self;
674        let addr: CfgAddr = addr.parse()?;
675        let substituted = addr.substitutions_will_apply();
676        let addr = addr.address(resolver)?;
677        let string = if substituted {
678            addr.try_to_string().ok_or(ResolveError::PathNotString)?
679        } else {
680            string.clone()
681        };
682        Ok(AddrWithStr { string, addr })
683    }
684}
685impl<A> FromStr for AddrWithStr<A>
686where
687    A: Clone + Debug + FromStr,
688{
689    type Err = <A as FromStr>::Err;
690
691    fn from_str(s: &str) -> Result<Self, Self::Err> {
692        let addr = s.parse()?;
693        let string = s.to_owned();
694        Ok(Self { string, addr })
695    }
696}
697
698impl<A> std::fmt::Display for AddrWithStr<A>
699where
700    A: Clone + Debug + std::fmt::Display,
701{
702    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
703        write!(f, "{}", self.string)
704    }
705}
706
707/// Return true if `s` is an absolute address.
708///
709/// All IP addresses are considered absolute.
710fn sockaddr_check_absolute(s: &general::SocketAddr) -> Result<(), ResolveError> {
711    match s {
712        general::SocketAddr::Inet(_) => Ok(()),
713        general::SocketAddr::Unix(sa) => match sa.as_pathname() {
714            Some(p) if !p.is_absolute() => Err(ResolveError::PathNotAbsolute),
715            _ => Ok(()),
716        },
717        _ => Err(ResolveError::AddressTypeNotRecognized),
718    }
719}
720
721#[cfg(test)]
722mod test {
723    // @@ begin test lint list maintained by maint/add_warning @@
724    #![allow(clippy::bool_assert_comparison)]
725    #![allow(clippy::clone_on_copy)]
726    #![allow(clippy::dbg_macro)]
727    #![allow(clippy::mixed_attributes_style)]
728    #![allow(clippy::print_stderr)]
729    #![allow(clippy::print_stdout)]
730    #![allow(clippy::single_char_pattern)]
731    #![allow(clippy::unwrap_used)]
732    #![allow(clippy::unchecked_time_subtraction)]
733    #![allow(clippy::useless_vec)]
734    #![allow(clippy::needless_pass_by_value)]
735    #![allow(clippy::string_slice)] // See arti#2571
736    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
737
738    use super::*;
739    use assert_matches::assert_matches;
740
741    fn parse(s: &str) -> ParsedConnectPoint {
742        s.parse().unwrap()
743    }
744
745    #[test]
746    fn examples() {
747        let _e1 = parse(
748            r#"
749[builtin]
750builtin = "abort"
751"#,
752        );
753
754        let _e2 = parse(
755            r#"
756[connect]
757socket = "unix:/var/run/arti/rpc_socket"
758auth = "none"
759"#,
760        );
761
762        let _e3 = parse(
763            r#"
764[connect]
765socket = "inet:[::1]:9191"
766socket_canonical = "inet:[::1]:2020"
767
768auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
769"#,
770        );
771
772        let _e4 = parse(
773            r#"
774[connect]
775socket = "inet:[::1]:9191"
776socket_canonical = "inet:[::1]:2020"
777
778[connect.auth.cookie]
779path = "/home/user/.arti_rpc/cookie"
780"#,
781        );
782    }
783
784    #[test]
785    fn parse_errors() {
786        let r: Result<ParsedConnectPoint, _> = "not a toml string".parse();
787        assert_matches!(r, Err(ParseError::InvalidConnectPoint(_)));
788
789        let r: Result<ParsedConnectPoint, _> = "[squidcakes]".parse();
790        assert_matches!(r, Err(ParseError::UnrecognizedFormat));
791
792        let r: Result<ParsedConnectPoint, _> = r#"
793[builtin]
794builtin = "abort"
795
796[connect]
797socket = "inet:[::1]:9191"
798socket_canonical = "inet:[::1]:2020"
799
800auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
801"#
802        .parse();
803        assert_matches!(r, Err(ParseError::ConflictingMembers));
804    }
805
806    #[test]
807    fn resolve_errors() {
808        let resolver = CfgPathResolver::default();
809
810        let r: ParsedConnectPoint = r#"
811[connect]
812socket = "inet:[::1]:9191"
813socket_canonical = "inet:[::1]:2020"
814
815[connect.auth.esp]
816telekinetic_handshake = 3
817"#
818        .parse()
819        .unwrap();
820        let err = r.resolve(&resolver).err();
821        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
822
823        let r: ParsedConnectPoint = r#"
824[connect]
825socket = "inet:[::1]:9191"
826socket_canonical = "inet:[::1]:2020"
827
828auth = "foo"
829"#
830        .parse()
831        .unwrap();
832        let err = r.resolve(&resolver).err();
833        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
834    }
835}