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        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
488impl Connect<Unresolved> {
489    /// Expand all variables within this `Connect` to their concrete forms.
490    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Connect<Resolved>, ResolveError> {
491        let socket = self.socket.resolve(resolver)?;
492        let socket_canonical = self
493            .socket_canonical
494            .as_ref()
495            .map(|sc| sc.resolve(resolver))
496            .transpose()?;
497        let auth = self.auth.resolve(resolver)?;
498        let socket_address_file = self
499            .socket_address_file
500            .as_ref()
501            .map(|p| p.path(resolver))
502            .transpose()?;
503        Connect {
504            socket,
505            socket_canonical,
506            auth,
507            socket_address_file,
508            superuser: self.superuser,
509        }
510        .validate()
511    }
512}
513
514impl Connect<Resolved> {
515    /// Return this `Connect` only if its parts are valid and compatible.
516    fn validate(self) -> Result<Self, ResolveError> {
517        use general::SocketAddr::{Inet, Unix};
518        for bind_addr in self.socket.bind_to_addresses() {
519            match (bind_addr, &self.auth) {
520                (Inet(addr), _) if !addr.ip().is_loopback() => {
521                    return Err(ResolveError::AddressNotLoopback);
522                }
523                (Inet(_), Auth::None) => return Err(ResolveError::AuthNotCompatible),
524                (_, 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    }
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")]
561pub(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
577impl Auth<Unresolved> {
578    /// Expand all variables within this `Auth` to their concrete forms.
579    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Auth<Resolved>, ResolveError> {
580        match self {
581            Auth::None => Ok(Auth::None),
582            Auth::Cookie { path } => Ok(Auth::Cookie {
583                path: path.path(resolver)?,
584            }),
585            Auth::Unrecognized(x) => Ok(Auth::Unrecognized(x.clone())),
586        }
587    }
588}
589
590impl 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)]
611struct Unresolved;
612impl 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)]
621pub(crate) struct Resolved;
622impl 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)]
634pub(crate) struct AddrWithStr<A>
635where
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}
648impl<A> AddrWithStr<A>
649where
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}
663impl AddrWithStr<String> {
664    /// Convert an `AddrWithStr<String>` into its substituted form.
665    pub(crate) fn resolve(
666        &self,
667        resolver: &CfgPathResolver,
668    ) -> Result<AddrWithStr<general::SocketAddr>, ResolveError> {
669        let AddrWithStr { string, addr } = self;
670        let addr: CfgAddr = addr.parse()?;
671        let substituted = addr.substitutions_will_apply();
672        let addr = addr.address(resolver)?;
673        let string = if substituted {
674            addr.try_to_string().ok_or(ResolveError::PathNotString)?
675        } else {
676            string.clone()
677        };
678        Ok(AddrWithStr { string, addr })
679    }
680}
681impl<A> FromStr for AddrWithStr<A>
682where
683    A: Clone + Debug + FromStr,
684{
685    type Err = <A as FromStr>::Err;
686
687    fn from_str(s: &str) -> Result<Self, Self::Err> {
688        let addr = s.parse()?;
689        let string = s.to_owned();
690        Ok(Self { string, addr })
691    }
692}
693
694impl<A> std::fmt::Display for AddrWithStr<A>
695where
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.
706fn 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)]
718mod 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]
745builtin = "abort"
746"#,
747        );
748
749        let _e2 = parse(
750            r#"
751[connect]
752socket = "unix:/var/run/arti/rpc_socket"
753auth = "none"
754"#,
755        );
756
757        let _e3 = parse(
758            r#"
759[connect]
760socket = "inet:[::1]:9191"
761socket_canonical = "inet:[::1]:2020"
762
763auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
764"#,
765        );
766
767        let _e4 = parse(
768            r#"
769[connect]
770socket = "inet:[::1]:9191"
771socket_canonical = "inet:[::1]:2020"
772
773[connect.auth.cookie]
774path = "/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]
789builtin = "abort"
790
791[connect]
792socket = "inet:[::1]:9191"
793socket_canonical = "inet:[::1]:2020"
794
795auth = { 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]
807socket = "inet:[::1]:9191"
808socket_canonical = "inet:[::1]:2020"
809
810[connect.auth.esp]
811telekinetic_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]
820socket = "inet:[::1]:9191"
821socket_canonical = "inet:[::1]:2020"
822
823auth = "foo"
824"#
825        .parse()
826        .unwrap();
827        let err = r.resolve(&resolver).err();
828        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
829    }
830}