1use derive_deftly::Deftly;
3use serde::{Deserialize, Serialize};
4use std::{net::SocketAddr, ops::RangeInclusive, str::FromStr, sync::Arc};
5use tor_config::ConfigBuildError;
6use tor_config::derive::prelude::*;
7use tracing::warn;
8
9#[cfg(unix)]
10use std::os::unix::net::SocketAddr as UnixSocketAddr;
11#[derive(Clone, Debug, Deftly, Eq, PartialEq)]
13#[derive_deftly(TorConfig)]
14#[deftly(tor_config(no_default_trait, pre_build = "Self::validate"))]
15pub struct ProxyConfig {
16 #[deftly(tor_config(list(element(clone), listtype = "ProxyRuleList"), default = "vec![]"))]
19 pub(crate) proxy_ports: Vec<ProxyRule>,
20 }
24
25impl ProxyConfigBuilder {
26 fn validate(&self) -> Result<(), ConfigBuildError> {
28 let mut covered = rangemap::RangeInclusiveSet::<u16>::new();
30 for rule in self.proxy_ports.access_opt().iter().flatten() {
31 let range = &rule.source.0;
32 if covered.gaps(range).next().is_none() {
33 return Err(ConfigBuildError::Invalid {
34 field: "proxy_ports".into(),
35 problem: format!("Port pattern {} is not reachable", rule.source),
36 });
37 }
38 covered.insert(range.clone());
39 }
40
41 let mut any_forward = false;
43 for rule in self.proxy_ports.access_opt().iter().flatten() {
44 if let ProxyAction::Forward(_, target) = &rule.target {
45 any_forward = true;
46 if !target.is_sufficiently_private() {
47 warn!(
53 "Onion service target {} does not look like a private address. \
54 Do you really mean to send connections onto the public internet?",
55 target
56 );
57 }
58 }
59 }
60
61 if !any_forward {
62 warn!("Onion service is not configured to accept any connections.");
63 }
64
65 Ok(())
66 }
67}
68
69impl ProxyConfig {
70 pub(crate) fn resolve_port_for_begin(&self, port: u16) -> Option<&ProxyAction> {
73 self.proxy_ports
74 .iter()
75 .find(|rule| rule.source.matches_port(port))
76 .map(|rule| &rule.target)
77 }
78}
79
80#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
84#[serde(from = "ProxyRuleAsTuple", into = "ProxyRuleAsTuple")]
88pub struct ProxyRule {
89 source: ProxyPattern,
91 target: ProxyAction,
93}
94
95type ProxyRuleAsTuple = (ProxyPattern, ProxyAction);
97impl From<ProxyRuleAsTuple> for ProxyRule {
98 fn from(value: ProxyRuleAsTuple) -> Self {
99 Self {
100 source: value.0,
101 target: value.1,
102 }
103 }
104}
105impl From<ProxyRule> for ProxyRuleAsTuple {
106 fn from(value: ProxyRule) -> Self {
107 (value.source, value.target)
108 }
109}
110impl ProxyRule {
111 pub fn new(source: ProxyPattern, target: ProxyAction) -> Self {
113 Self { source, target }
114 }
115}
116
117#[derive(Clone, Debug, serde::Deserialize, serde_with::SerializeDisplay, Eq, PartialEq)]
119#[serde(try_from = "ProxyPatternAsEnum")]
120pub struct ProxyPattern(RangeInclusive<u16>);
121
122#[derive(serde::Deserialize)]
124#[serde(untagged)]
125enum ProxyPatternAsEnum {
126 Number(u16),
128 String(String),
130}
131
132impl TryFrom<ProxyPatternAsEnum> for ProxyPattern {
133 type Error = ProxyConfigError;
134
135 fn try_from(value: ProxyPatternAsEnum) -> Result<Self, Self::Error> {
136 match value {
137 ProxyPatternAsEnum::Number(port) => Self::one_port(port),
138 ProxyPatternAsEnum::String(s) => Self::from_str(&s),
139 }
140 }
141}
142
143impl FromStr for ProxyPattern {
144 type Err = ProxyConfigError;
145
146 fn from_str(s: &str) -> Result<Self, Self::Err> {
147 use ProxyConfigError as PCE;
148 if s == "*" {
149 Ok(Self::all_ports())
150 } else if let Some((left, right)) = s.split_once('-') {
151 let left: u16 = left
152 .parse()
153 .map_err(|e| PCE::InvalidPort(left.to_string(), e))?;
154 let right: u16 = right
155 .parse()
156 .map_err(|e| PCE::InvalidPort(right.to_string(), e))?;
157 Self::port_range(left, right)
158 } else {
159 let port = s.parse().map_err(|e| PCE::InvalidPort(s.to_string(), e))?;
160 Self::one_port(port)
161 }
162 }
163}
164impl std::fmt::Display for ProxyPattern {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 match self.0.clone().into_inner() {
167 (start, end) if start == end => write!(f, "{}", start),
168 (1, 65535) => write!(f, "*"),
169 (start, end) => write!(f, "{}-{}", start, end),
170 }
171 }
172}
173
174impl ProxyPattern {
175 pub fn all_ports() -> Self {
177 Self::check(1, 65535).expect("Somehow, 1-65535 was not a valid pattern")
178 }
179 pub fn one_port(port: u16) -> Result<Self, ProxyConfigError> {
183 Self::check(port, port)
184 }
185 pub fn port_range(low: u16, high: u16) -> Result<Self, ProxyConfigError> {
189 Self::check(low, high)
190 }
191
192 pub(crate) fn matches_port(&self, port: u16) -> bool {
194 self.0.contains(&port)
195 }
196
197 fn check(start: u16, end: u16) -> Result<ProxyPattern, ProxyConfigError> {
200 use ProxyConfigError as PCE;
201 match (start, end) {
202 (_, 0) => Err(PCE::ZeroPort),
203 (0, n) => Ok(Self(1..=n)),
204 (low, high) if low > high => Err(PCE::EmptyPortRange),
205 (low, high) => Ok(Self(low..=high)),
206 }
207 }
208}
209
210#[derive(
215 Clone,
216 Debug,
217 Default,
218 serde_with::DeserializeFromStr,
219 serde_with::SerializeDisplay,
220 Eq,
221 PartialEq,
222 strum::EnumDiscriminants,
223)]
224#[strum_discriminants(derive(Hash, strum::EnumIter))] #[strum_discriminants(derive(strum::IntoStaticStr), strum(serialize_all = "snake_case"))]
226#[strum_discriminants(vis(pub(crate)))]
227#[non_exhaustive]
228pub enum ProxyAction {
229 #[default]
231 DestroyCircuit,
232 Forward(Encapsulation, TargetAddr),
235 RejectStream,
237 IgnoreStream,
239}
240
241#[derive(Clone, Debug)]
243#[non_exhaustive]
244pub enum TargetAddr {
245 Inet(SocketAddr),
247 #[cfg(unix)]
255 Unix(UnixSocketAddr),
256}
257
258impl PartialEq for TargetAddr {
259 fn eq(&self, other: &Self) -> bool {
262 match (self, other) {
263 (TargetAddr::Inet(a), TargetAddr::Inet(b)) => a == b,
264 #[cfg(unix)]
265 (TargetAddr::Unix(a), TargetAddr::Unix(b)) => a.as_pathname() == b.as_pathname(),
266 _ => false,
267 }
268 }
269}
270
271impl Eq for TargetAddr {}
272
273impl TargetAddr {
274 fn is_sufficiently_private(&self) -> bool {
278 use std::net::IpAddr;
279 match self {
280 #[cfg(unix)]
282 TargetAddr::Unix(_) => true,
283
284 TargetAddr::Inet(sa) => match sa.ip() {
285 IpAddr::V4(ip) => ip.is_loopback() || ip.is_unspecified() || ip.is_private(),
286 IpAddr::V6(ip) => ip.is_loopback() || ip.is_unspecified(),
287 },
288 }
289 }
290}
291
292impl FromStr for TargetAddr {
293 type Err = ProxyConfigError;
294
295 fn from_str(s: &str) -> Result<Self, Self::Err> {
296 use ProxyConfigError as PCE;
297
298 fn looks_like_attempted_addr(s: &str) -> bool {
300 s.starts_with(|c: char| c.is_ascii_digit())
301 || s.strip_prefix('[')
302 .map(|rhs| rhs.starts_with(|c: char| c.is_ascii_hexdigit() || c == ':'))
303 .unwrap_or(false)
304 }
305
306 #[cfg(unix)]
307 if let Some(path) = s.strip_prefix("unix:") {
308 return Ok(Self::Unix(UnixSocketAddr::from_pathname(path).map_err(
309 |e| ProxyConfigError::InvalidUnixAddr {
310 path: s.to_string(),
311 source_error: Arc::new(e),
312 },
313 )?));
314 }
315 if let Some(addr) = s.strip_prefix("inet:") {
316 Ok(Self::Inet(addr.parse().map_err(|e| {
317 PCE::InvalidTargetAddr(addr.to_string(), e)
318 })?))
319 } else if looks_like_attempted_addr(s) {
320 Ok(Self::Inet(
322 s.parse()
323 .map_err(|e| PCE::InvalidTargetAddr(s.to_string(), e))?,
324 ))
325 } else {
326 Err(PCE::UnrecognizedTargetType(s.to_string()))
327 }
328 }
329}
330
331impl std::fmt::Display for TargetAddr {
332 #![allow(clippy::disallowed_methods)]
333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 match self {
335 TargetAddr::Inet(a) => write!(f, "inet:{}", a),
336 #[cfg(unix)]
337 TargetAddr::Unix(p) => match p.as_pathname() {
338 Some(path) => write!(f, "unix:{}", path.display()),
339 None => write!(f, "unix:<unnamed>"),
340 },
341 }
342 }
343}
344
345#[derive(Clone, Debug, Default, Eq, PartialEq)]
350#[non_exhaustive]
351pub enum Encapsulation {
352 #[default]
358 Simple,
359}
360
361impl FromStr for ProxyAction {
362 type Err = ProxyConfigError;
363
364 fn from_str(s: &str) -> Result<Self, Self::Err> {
365 if s == "destroy" {
366 Ok(Self::DestroyCircuit)
367 } else if s == "reject" {
368 Ok(Self::RejectStream)
369 } else if s == "ignore" {
370 Ok(Self::IgnoreStream)
371 } else if let Some(addr) = s.strip_prefix("simple:") {
372 Ok(Self::Forward(Encapsulation::Simple, addr.parse()?))
373 } else {
374 Ok(Self::Forward(Encapsulation::Simple, s.parse()?))
375 }
376 }
377}
378
379impl std::fmt::Display for ProxyAction {
380 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381 match self {
382 ProxyAction::DestroyCircuit => write!(f, "destroy"),
383 ProxyAction::Forward(Encapsulation::Simple, addr) => write!(f, "simple:{}", addr),
384 ProxyAction::RejectStream => write!(f, "reject"),
385 ProxyAction::IgnoreStream => write!(f, "ignore"),
386 }
387 }
388}
389
390#[derive(Debug, Clone, thiserror::Error)]
392#[non_exhaustive]
393pub enum ProxyConfigError {
394 #[error("Could not parse onion service target type {0:?}")]
396 UnrecognizedTargetType(String),
397
398 #[error("Could not parse onion service target address {0:?}")]
400 InvalidTargetAddr(String, #[source] std::net::AddrParseError),
401
402 #[error("Invalid unix socket address:'{path}': '{source_error}'")]
405 InvalidUnixAddr {
406 path: String,
408 #[source]
409 source_error: Arc<std::io::Error>,
411 },
412
413 #[error("Could not parse onion service source port {0:?}")]
415 InvalidPort(String, #[source] std::num::ParseIntError),
416
417 #[error("Zero is not a valid port.")]
419 ZeroPort,
420
421 #[error("Port range is empty.")]
423 EmptyPortRange,
424}
425
426#[cfg(test)]
427mod test {
428 #![allow(clippy::bool_assert_comparison)]
430 #![allow(clippy::clone_on_copy)]
431 #![allow(clippy::dbg_macro)]
432 #![allow(clippy::mixed_attributes_style)]
433 #![allow(clippy::print_stderr)]
434 #![allow(clippy::print_stdout)]
435 #![allow(clippy::single_char_pattern)]
436 #![allow(clippy::unwrap_used)]
437 #![allow(clippy::unchecked_time_subtraction)]
438 #![allow(clippy::useless_vec)]
439 #![allow(clippy::needless_pass_by_value)]
440 #![allow(clippy::string_slice)] use super::*;
443
444 #[test]
445 fn pattern_ok() {
446 use ProxyPattern as P;
447 assert_eq!(P::from_str("*").unwrap(), P(1..=65535));
448 assert_eq!(P::from_str("100").unwrap(), P(100..=100));
449 assert_eq!(P::from_str("100-200").unwrap(), P(100..=200));
450 assert_eq!(P::from_str("0-200").unwrap(), P(1..=200));
451 }
452
453 #[test]
454 fn pattern_display() {
455 use ProxyPattern as P;
456 assert_eq!(P::all_ports().to_string(), "*");
457 assert_eq!(P::one_port(100).unwrap().to_string(), "100");
458 assert_eq!(P::port_range(100, 200).unwrap().to_string(), "100-200");
459 }
460
461 #[test]
462 fn pattern_err() {
463 use ProxyConfigError as PCE;
464 use ProxyPattern as P;
465 assert!(matches!(P::from_str("fred"), Err(PCE::InvalidPort(_, _))));
466 assert!(matches!(
467 P::from_str("100-fred"),
468 Err(PCE::InvalidPort(_, _))
469 ));
470 assert!(matches!(P::from_str("100-42"), Err(PCE::EmptyPortRange)));
471 }
472
473 #[test]
474 fn target_ok() {
475 use Encapsulation::Simple;
476 use ProxyAction as T;
477 use TargetAddr as A;
478 assert!(matches!(T::from_str("reject"), Ok(T::RejectStream)));
479 assert!(matches!(T::from_str("ignore"), Ok(T::IgnoreStream)));
480 assert!(matches!(T::from_str("destroy"), Ok(T::DestroyCircuit)));
481 let sa: SocketAddr = "192.168.1.1:50".parse().unwrap();
482 assert!(
483 matches!(T::from_str("192.168.1.1:50"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
484 );
485 assert!(
486 matches!(T::from_str("inet:192.168.1.1:50"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
487 );
488 let sa: SocketAddr = "[::1]:999".parse().unwrap();
489 assert!(matches!(T::from_str("[::1]:999"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa));
490 assert!(
491 matches!(T::from_str("inet:[::1]:999"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
492 );
493 }
500
501 #[test]
502 fn target_display() {
503 use Encapsulation::Simple;
504 use ProxyAction as T;
505 use TargetAddr as A;
506
507 assert_eq!(T::RejectStream.to_string(), "reject");
508 assert_eq!(T::IgnoreStream.to_string(), "ignore");
509 assert_eq!(T::DestroyCircuit.to_string(), "destroy");
510 assert_eq!(
511 T::Forward(Simple, A::Inet("192.168.1.1:50".parse().unwrap())).to_string(),
512 "simple:inet:192.168.1.1:50"
513 );
514 assert_eq!(
515 T::Forward(Simple, A::Inet("[::1]:999".parse().unwrap())).to_string(),
516 "simple:inet:[::1]:999"
517 );
518 }
525
526 #[test]
527 fn target_err() {
528 use ProxyAction as T;
529 use ProxyConfigError as PCE;
530
531 assert!(matches!(
532 T::from_str("sdakljf"),
533 Err(PCE::UnrecognizedTargetType(_))
534 ));
535
536 assert!(matches!(
537 T::from_str("inet:hello"),
538 Err(PCE::InvalidTargetAddr(_, _))
539 ));
540 assert!(matches!(
541 T::from_str("inet:wwww.example.com:80"),
542 Err(PCE::InvalidTargetAddr(_, _))
543 ));
544
545 assert!(matches!(
546 T::from_str("127.1:80"),
547 Err(PCE::InvalidTargetAddr(_, _))
548 ));
549 assert!(matches!(
550 T::from_str("inet:127.1:80"),
551 Err(PCE::InvalidTargetAddr(_, _))
552 ));
553 assert!(matches!(
554 T::from_str("127.1:80"),
555 Err(PCE::InvalidTargetAddr(_, _))
556 ));
557 assert!(matches!(
558 T::from_str("inet:2130706433:80"),
559 Err(PCE::InvalidTargetAddr(_, _))
560 ));
561
562 assert!(matches!(
563 T::from_str("128.256.cats.and.dogs"),
564 Err(PCE::InvalidTargetAddr(_, _))
565 ));
566 }
567
568 #[test]
569 fn deserialize() {
570 use Encapsulation::Simple;
571 use TargetAddr as A;
572 let ex = r#"{
573 "proxy_ports": [
574 [ "443", "127.0.0.1:11443" ],
575 [ "80", "ignore" ],
576 [ "*", "destroy" ]
577 ]
578 }"#;
579 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
580 let cfg = bld.build().unwrap();
581 assert_eq!(cfg.proxy_ports.len(), 3);
582 assert_eq!(cfg.proxy_ports[0].source.0, 443..=443);
583 assert_eq!(cfg.proxy_ports[1].source.0, 80..=80);
584 assert_eq!(cfg.proxy_ports[2].source.0, 1..=65535);
585
586 assert_eq!(
587 cfg.proxy_ports[0].target,
588 ProxyAction::Forward(Simple, A::Inet("127.0.0.1:11443".parse().unwrap()))
589 );
590 assert_eq!(cfg.proxy_ports[1].target, ProxyAction::IgnoreStream);
591 assert_eq!(cfg.proxy_ports[2].target, ProxyAction::DestroyCircuit);
592 }
593
594 #[test]
595 fn validation_fail() {
596 let ex = r#"{
598 "proxy_ports": [
599 [ "2-300", "127.0.0.1:11443" ],
600 [ "301-999", "ignore" ],
601 [ "30-310", "destroy" ]
602 ]
603 }"#;
604 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
605 match bld.build() {
606 Err(ConfigBuildError::Invalid { field, problem }) => {
607 assert_eq!(field, "proxy_ports");
608 assert_eq!(problem, "Port pattern 30-310 is not reachable");
609 }
610 other => panic!("Expected an Invalid error; got {other:?}"),
611 }
612
613 let ex = r#"{
615 "proxy_ports": [
616 [ "2-300", "127.0.0.1:11443" ],
617 [ "302-999", "ignore" ],
618 [ "30-310", "destroy" ]
619 ]
620 }"#;
621 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
622 assert!(bld.build().is_ok());
623 }
624
625 #[test]
626 fn demo() {
627 let b: ProxyConfigBuilder = toml::de::from_str(
628 r#"
629proxy_ports = [
630 [ 80, "127.0.0.1:10080"],
631 ["22", "destroy"],
632 ["265", "ignore"],
633 # ["1-1024", "unix:/var/run/allium-cepa/socket"], # TODO (#1246))
634]
635"#,
636 )
637 .unwrap();
638 let c = b.build().unwrap();
639 assert_eq!(c.proxy_ports.len(), 3);
640 assert_eq!(
641 c.proxy_ports[0],
642 ProxyRule::new(
643 ProxyPattern::one_port(80).unwrap(),
644 ProxyAction::Forward(
645 Encapsulation::Simple,
646 TargetAddr::Inet("127.0.0.1:10080".parse().unwrap())
647 )
648 )
649 );
650 assert_eq!(
651 c.proxy_ports[1],
652 ProxyRule::new(
653 ProxyPattern::one_port(22).unwrap(),
654 ProxyAction::DestroyCircuit
655 )
656 );
657 assert_eq!(
658 c.proxy_ports[2],
659 ProxyRule::new(
660 ProxyPattern::one_port(265).unwrap(),
661 ProxyAction::IgnoreStream
662 )
663 );
664 }
677}