1use 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#[derive(Clone, Debug)]
31pub struct ParsedConnectPoint(ConnectPointEnum<Unresolved>);
32
33#[derive(Clone, Debug)]
41pub struct ResolvedConnectPoint(pub(crate) ConnectPointEnum<Resolved>);
42
43impl ParsedConnectPoint {
44 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 pub fn superuser_permission(&self) -> crate::SuperuserPermission {
59 self.0.superuser_permission()
60 }
61
62 pub fn is_explicit_abort(&self) -> bool {
64 self.0.is_explicit_abort()
65 }
66}
67
68impl ResolvedConnectPoint {
69 pub fn superuser_permission(&self) -> crate::SuperuserPermission {
71 self.0.superuser_permission()
72 }
73
74 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#[derive(Clone, Debug, thiserror::Error)]
91#[non_exhaustive]
92pub enum ParseError {
93 #[error("Invalid connect point")]
95 InvalidConnectPoint(#[source] toml::de::Error),
96 #[error("Conflicting members in connect point")]
99 ConflictingMembers,
100 #[error("Unrecognized format on connect point")]
103 UnrecognizedFormat,
104 #[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#[derive(Clone, Debug, thiserror::Error)]
126#[non_exhaustive]
127pub enum ResolveError {
128 #[error("Unable to resolve variables in path")]
130 InvalidPath(#[from] CfgPathError),
131 #[error("Unable to parse address")]
133 UnparseableAddr(#[from] AddrParseError),
134 #[error("Unable to resolve variables in address")]
136 InvalidAddr(#[from] CfgAddrError),
137 #[error("Cannot represent expanded path as string")]
139 PathNotString,
140 #[error("Tried to bind or connect to a non-loopback TCP address")]
142 AddressNotLoopback,
143 #[error("Authorization type not compatible with address family")]
145 AuthNotCompatible,
146 #[error("Authorization type not recognized as a supported type")]
148 AuthNotRecognized,
149 #[error("Address type not recognized")]
153 AddressTypeNotRecognized,
154 #[error("inet-auto without socket_address_file, or vice versa")]
156 AutoIncompatibleWithSocketFile,
157 #[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#[derive(Clone, Debug)]
185pub(crate) enum ConnectPointEnum<R: Addresses> {
186 Connect(Connect<R>),
188 Builtin(Builtin),
192}
193
194pub(crate) trait Addresses {
199 type SocketAddr: Clone + std::fmt::Debug;
201 type Path: Clone + std::fmt::Debug;
203}
204
205#[derive(Deserialize, Clone, Debug)]
215struct ConnectPointDe {
216 connect: Option<Connect<Unresolved>>,
218 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 _ => Err(ParseError::UnrecognizedFormat),
241 }
242 }
243}
244
245impl<R: Addresses> ConnectPointEnum<R> {
246 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 fn is_explicit_abort(&self) -> bool {
263 matches!(
264 self,
265 ConnectPointEnum::Builtin(Builtin {
266 builtin: BuiltinVariant::Abort
267 })
268 )
269 }
270}
271
272#[derive(Deserialize, Clone, Debug)]
278pub(crate) struct Builtin {
279 pub(crate) builtin: BuiltinVariant,
281}
282
283#[derive(Deserialize, Clone, Debug)]
285#[serde(rename_all = "lowercase")]
286pub(crate) enum BuiltinVariant {
287 Abort,
290}
291
292#[derive(Deserialize, Clone, Debug)]
294#[serde(bound = "R::Path : Deserialize<'de>, AddrWithStr<R::SocketAddr> : Deserialize<'de>")]
295pub(crate) struct Connect<R: Addresses> {
296 pub(crate) socket: ConnectAddress<R>,
299 pub(crate) socket_canonical: Option<AddrWithStr<R::SocketAddr>>,
307 pub(crate) auth: Auth<R>,
310 pub(crate) socket_address_file: Option<R::Path>,
312 #[serde(default)]
315 pub(crate) superuser: bool,
316}
317
318#[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 InetAuto(InetAutoAddress),
327 Socket(AddrWithStr<R::SocketAddr>),
329}
330
331#[derive(Clone, Debug, DeserializeFromStr)]
333pub(crate) struct InetAutoAddress {
334 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 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 #[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#[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 pub(crate) address: String,
410}
411
412impl<R: Addresses> ConnectAddress<R> {
413 fn is_auto(&self) -> bool {
415 matches!(self, ConnectAddress::InetAuto { .. })
416 }
417}
418impl ConnectAddress<Unresolved> {
419 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 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 #[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 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 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 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 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 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#[derive(Deserialize, Clone, Debug)]
564#[serde(rename_all = "lowercase")]
565pub(crate) enum Auth<R: Addresses> {
566 None,
568 Cookie {
570 path: R::Path,
572 },
573 #[serde(untagged)]
578 Unrecognized(toml::Value),
579}
580
581impl Auth<Unresolved> {
582 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 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#[derive(Clone, Debug)]
615struct Unresolved;
616impl Addresses for Unresolved {
617 type SocketAddr = String;
618 type Path = CfgPath;
619}
620
621#[derive(Clone, Debug)]
625pub(crate) struct Resolved;
626impl Addresses for Resolved {
627 type SocketAddr = general::SocketAddr;
628 type Path = PathBuf;
629}
630
631#[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 string: String,
648 #[as_ref]
650 addr: A,
651}
652impl<A> AddrWithStr<A>
653where
654 A: Clone + Debug,
655{
656 pub(crate) fn as_str(&self) -> &str {
659 self.string.as_str()
660 }
661
662 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 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
707fn 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 #![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)] 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}