1use serde::Deserialize;
4use std::{fmt::Debug, path::PathBuf, str::FromStr};
5use tor_config_path::{
6 addr::{CfgAddr, CfgAddrError},
7 CfgPath, CfgPathError, CfgPathResolver,
8};
9use tor_general_addr::general;
10
11use crate::HasClientErrorAction;
12
13#[derive(Clone, Debug)]
23pub struct ParsedConnectPoint(ConnectPointEnum<Unresolved>);
24
25#[derive(Clone, Debug)]
33pub struct ResolvedConnectPoint(pub(crate) ConnectPointEnum<Resolved>);
34
35impl ParsedConnectPoint {
36 pub fn resolve(
39 &self,
40 resolver: &CfgPathResolver,
41 ) -> Result<ResolvedConnectPoint, ResolveError> {
42 use ConnectPointEnum as CPE;
43 Ok(ResolvedConnectPoint(match &self.0 {
44 CPE::Connect(connect) => CPE::Connect(connect.resolve(resolver)?),
45 CPE::Builtin(builtin) => CPE::Builtin(builtin.clone()),
46 }))
47 }
48}
49
50impl FromStr for ParsedConnectPoint {
51 type Err = ParseError;
52
53 fn from_str(s: &str) -> Result<Self, Self::Err> {
54 let de: ConnectPointDe = toml::from_str(s).map_err(ParseError::InvalidConnectPoint)?;
55 Ok(ParsedConnectPoint(de.try_into()?))
56 }
57}
58
59#[derive(Clone, Debug, thiserror::Error)]
61#[non_exhaustive]
62pub enum ParseError {
63 #[error("Invalid connect point")]
65 InvalidConnectPoint(#[source] toml::de::Error),
66 #[error("Conflicting members in connect point")]
69 ConflictingMembers,
70 #[error("Unrecognized format on connect point")]
73 UnrecognizedFormat,
74}
75impl HasClientErrorAction for ParseError {
76 fn client_action(&self) -> crate::ClientErrorAction {
77 use crate::ClientErrorAction as A;
78 match self {
79 ParseError::InvalidConnectPoint(_) => A::Abort,
80 ParseError::ConflictingMembers => A::Abort,
81 ParseError::UnrecognizedFormat => A::Decline,
82 }
83 }
84}
85
86#[derive(Clone, Debug, thiserror::Error)]
88#[non_exhaustive]
89pub enum ResolveError {
90 #[error("Unable to resolve variables in path")]
92 InvalidPath(#[from] CfgPathError),
93 #[error("Unable to resolve variables in address")]
95 InvalidAddr(#[from] CfgAddrError),
96 #[error("Cannot represent expanded path as string")]
98 PathNotString,
99 #[error("Tried to bind or connect to a non-loopback TCP address")]
101 AddressNotLoopback,
102 #[error("Authorization type not compatible with address family")]
104 AuthNotCompatible,
105 #[error("Authorization type not recognized as a supported type")]
107 AuthNotRecognized,
108 #[error("Address type not recognized")]
112 AddressTypeNotRecognized,
113 #[error("Path was not absolute")]
115 PathNotAbsolute,
116}
117impl HasClientErrorAction for ResolveError {
118 fn client_action(&self) -> crate::ClientErrorAction {
119 use crate::ClientErrorAction as A;
120 match self {
121 ResolveError::InvalidPath(e) => e.client_action(),
122 ResolveError::InvalidAddr(e) => e.client_action(),
123 ResolveError::PathNotString => A::Decline,
124 ResolveError::AddressNotLoopback => A::Decline,
125 ResolveError::AuthNotCompatible => A::Abort,
126 ResolveError::AuthNotRecognized => A::Decline,
127 ResolveError::AddressTypeNotRecognized => A::Decline,
128 ResolveError::PathNotAbsolute => A::Abort,
129 }
130 }
131}
132
133#[derive(Clone, Debug)]
139pub(crate) enum ConnectPointEnum<R: Addresses> {
140 Connect(Connect<R>),
142 Builtin(Builtin),
146}
147
148pub(crate) trait Addresses {
153 type SocketAddr: Clone + std::fmt::Debug;
155 type Path: Clone + std::fmt::Debug;
157}
158
159#[derive(Deserialize, Clone, Debug)]
169struct ConnectPointDe {
170 connect: Option<Connect<Unresolved>>,
172 builtin: Option<Builtin>,
174}
175impl TryFrom<ConnectPointDe> for ConnectPointEnum<Unresolved> {
176 type Error = ParseError;
177
178 fn try_from(value: ConnectPointDe) -> Result<Self, Self::Error> {
179 match value {
180 ConnectPointDe {
181 connect: Some(c),
182 builtin: None,
183 } => Ok(ConnectPointEnum::Connect(c)),
184 ConnectPointDe {
185 connect: None,
186 builtin: Some(b),
187 } => Ok(ConnectPointEnum::Builtin(b)),
188 ConnectPointDe {
189 connect: Some(_),
190 builtin: Some(_),
191 } => Err(ParseError::ConflictingMembers),
192 _ => Err(ParseError::UnrecognizedFormat),
195 }
196 }
197}
198
199#[derive(Deserialize, Clone, Debug)]
205pub(crate) struct Builtin {
206 pub(crate) builtin: BuiltinVariant,
208}
209
210#[derive(Deserialize, Clone, Debug)]
212#[serde(rename_all = "lowercase")]
213pub(crate) enum BuiltinVariant {
214 Abort,
217}
218
219#[derive(Deserialize, Clone, Debug)]
220#[serde(bound = "R::Path : Deserialize<'de>, AddrWithStr<R::SocketAddr> : Deserialize<'de>")]
221pub(crate) struct Connect<R: Addresses> {
222 pub(crate) socket: AddrWithStr<R::SocketAddr>,
225 pub(crate) socket_canonical: Option<AddrWithStr<R::SocketAddr>>,
233 pub(crate) auth: Auth<R>,
236}
237
238impl Connect<Unresolved> {
239 fn resolve(&self, resolver: &CfgPathResolver) -> Result<Connect<Resolved>, ResolveError> {
241 let socket = self.socket.resolve(resolver)?;
242 let socket_canonical = self
243 .socket_canonical
244 .as_ref()
245 .map(|sc| sc.resolve(resolver))
246 .transpose()?;
247 let auth = self.auth.resolve(resolver)?;
248 Connect {
249 socket,
250 socket_canonical,
251 auth,
252 }
253 .validate()
254 }
255}
256
257impl Connect<Resolved> {
258 fn validate(self) -> Result<Self, ResolveError> {
260 use general::SocketAddr::{Inet, Unix};
261 match (self.socket.as_ref(), &self.auth) {
262 (Inet(addr), _) if !addr.ip().is_loopback() => {
263 return Err(ResolveError::AddressNotLoopback)
264 }
265 (Inet(_), Auth::None) => return Err(ResolveError::AuthNotCompatible),
266 (_, Auth::Unrecognized(_)) => return Err(ResolveError::AuthNotRecognized),
267 (Inet(_), Auth::Cookie { .. }) => {}
268 (Unix(_), _) => {}
269 (_, _) => return Err(ResolveError::AddressTypeNotRecognized),
270 };
271 self.check_absolute_paths()?;
272 Ok(self)
273 }
274
275 fn check_absolute_paths(&self) -> Result<(), ResolveError> {
277 sockaddr_check_absolute(self.socket.as_ref())?;
278 if let Some(sa) = &self.socket_canonical {
279 sockaddr_check_absolute(sa.as_ref())?;
280 }
281 self.auth.check_absolute_paths()?;
282 Ok(())
283 }
284}
285
286#[derive(Deserialize, Clone, Debug)]
289#[serde(rename_all = "lowercase")]
290pub(crate) enum Auth<R: Addresses> {
291 None,
293 Cookie {
295 path: R::Path,
297 },
298 #[serde(untagged)]
303 Unrecognized(toml::Value),
304}
305
306impl Auth<Unresolved> {
307 fn resolve(&self, resolver: &CfgPathResolver) -> Result<Auth<Resolved>, ResolveError> {
309 match self {
310 Auth::None => Ok(Auth::None),
311 Auth::Cookie { path } => Ok(Auth::Cookie {
312 path: path.path(resolver)?,
313 }),
314 Auth::Unrecognized(x) => Ok(Auth::Unrecognized(x.clone())),
315 }
316 }
317}
318
319impl Auth<Resolved> {
320 fn check_absolute_paths(&self) -> Result<(), ResolveError> {
322 match self {
323 Auth::None => Ok(()),
324 Auth::Cookie { path } => {
325 if path.is_absolute() {
326 Ok(())
327 } else {
328 Err(ResolveError::PathNotAbsolute)
329 }
330 }
331 Auth::Unrecognized(_) => Ok(()),
332 }
333 }
334}
335
336#[derive(Clone, Debug)]
340struct Unresolved;
341impl Addresses for Unresolved {
342 type SocketAddr = CfgAddr;
343 type Path = CfgPath;
344}
345
346#[derive(Clone, Debug)]
350pub(crate) struct Resolved;
351impl Addresses for Resolved {
352 type SocketAddr = general::SocketAddr;
353 type Path = PathBuf;
354}
355
356#[derive(Clone, Debug, derive_more::AsRef, serde_with::DeserializeFromStr)]
361pub(crate) struct AddrWithStr<A>
362where
363 A: Clone + Debug,
364{
365 string: String,
371 #[as_ref]
373 addr: A,
374}
375impl<A> AddrWithStr<A>
376where
377 A: Clone + Debug,
378{
379 pub(crate) fn as_str(&self) -> &str {
382 self.string.as_str()
383 }
384}
385impl AddrWithStr<CfgAddr> {
386 pub(crate) fn resolve(
388 &self,
389 resolver: &CfgPathResolver,
390 ) -> Result<AddrWithStr<general::SocketAddr>, ResolveError> {
391 let AddrWithStr { string, addr } = self;
392 let substituted = addr.substitutions_will_apply();
393 let addr = addr.address(resolver)?;
394 let string = if substituted {
395 addr.try_to_string().ok_or(ResolveError::PathNotString)?
396 } else {
397 string.clone()
398 };
399 Ok(AddrWithStr { string, addr })
400 }
401}
402impl<A> FromStr for AddrWithStr<A>
403where
404 A: Clone + Debug + FromStr,
405{
406 type Err = <A as FromStr>::Err;
407
408 fn from_str(s: &str) -> Result<Self, Self::Err> {
409 let addr = s.parse()?;
410 let string = s.to_owned();
411 Ok(Self { string, addr })
412 }
413}
414
415fn sockaddr_check_absolute(s: &general::SocketAddr) -> Result<(), ResolveError> {
419 match s {
420 general::SocketAddr::Inet(_) => Ok(()),
421 general::SocketAddr::Unix(sa) => match sa.as_pathname() {
422 Some(p) if !p.is_absolute() => Err(ResolveError::PathNotAbsolute),
423 _ => Ok(()),
424 },
425 _ => Err(ResolveError::AddressTypeNotRecognized),
426 }
427}
428
429#[cfg(test)]
430mod test {
431 #![allow(clippy::bool_assert_comparison)]
433 #![allow(clippy::clone_on_copy)]
434 #![allow(clippy::dbg_macro)]
435 #![allow(clippy::mixed_attributes_style)]
436 #![allow(clippy::print_stderr)]
437 #![allow(clippy::print_stdout)]
438 #![allow(clippy::single_char_pattern)]
439 #![allow(clippy::unwrap_used)]
440 #![allow(clippy::unchecked_duration_subtraction)]
441 #![allow(clippy::useless_vec)]
442 #![allow(clippy::needless_pass_by_value)]
443 use super::*;
446 use assert_matches::assert_matches;
447
448 fn parse(s: &str) -> ParsedConnectPoint {
449 s.parse().unwrap()
450 }
451
452 #[test]
453 fn examples() {
454 let _e1 = parse(
455 r#"
456[builtin]
457builtin = "abort"
458"#,
459 );
460
461 let _e2 = parse(
462 r#"
463[connect]
464socket = "unix:/var/run/arti/rpc_socket"
465auth = "none"
466"#,
467 );
468
469 let _e3 = parse(
470 r#"
471[connect]
472socket = "inet:[::1]:9191"
473socket_canonical = "inet:[::1]:2020"
474
475auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
476"#,
477 );
478
479 let _e4 = parse(
480 r#"
481[connect]
482socket = "inet:[::1]:9191"
483socket_canonical = "inet:[::1]:2020"
484
485[connect.auth.cookie]
486path = "/home/user/.arti_rpc/cookie"
487"#,
488 );
489 }
490
491 #[test]
492 fn parse_errors() {
493 let r: Result<ParsedConnectPoint, _> = "not a toml string".parse();
494 assert_matches!(r, Err(ParseError::InvalidConnectPoint(_)));
495
496 let r: Result<ParsedConnectPoint, _> = "[squidcakes]".parse();
497 assert_matches!(r, Err(ParseError::UnrecognizedFormat));
498
499 let r: Result<ParsedConnectPoint, _> = r#"
500[builtin]
501builtin = "abort"
502
503[connect]
504socket = "inet:[::1]:9191"
505socket_canonical = "inet:[::1]:2020"
506
507auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
508"#
509 .parse();
510 assert_matches!(r, Err(ParseError::ConflictingMembers));
511 }
512
513 #[test]
514 fn resolve_errors() {
515 let resolver = CfgPathResolver::default();
516
517 let r: ParsedConnectPoint = r#"
518[connect]
519socket = "inet:[::1]:9191"
520socket_canonical = "inet:[::1]:2020"
521
522[connect.auth.esp]
523telekinetic_handshake = 3
524"#
525 .parse()
526 .unwrap();
527 let err = r.resolve(&resolver).err();
528 assert_matches!(err, Some(ResolveError::AuthNotRecognized));
529
530 let r: ParsedConnectPoint = r#"
531[connect]
532socket = "inet:[::1]:9191"
533socket_canonical = "inet:[::1]:2020"
534
535auth = "foo"
536"#
537 .parse()
538 .unwrap();
539 let err = r.resolve(&resolver).err();
540 assert_matches!(err, Some(ResolveError::AuthNotRecognized));
541 }
542}