1use crate::err::ErrorDetail;
5use crate::StreamPrefs;
6use std::fmt::Display;
7use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
8use std::str::FromStr;
9use thiserror::Error;
10use tor_basic_utils::StrExt;
11
12#[cfg(feature = "onion-service-client")]
13use tor_hscrypto::pk::{HsId, HSID_ONION_SUFFIX};
14
15#[cfg(not(feature = "onion-service-client"))]
17pub(crate) mod hs_dummy {
18 use super::*;
19 use tor_error::internal;
20 use void::Void;
21
22 #[derive(Debug, Clone)]
24 pub(crate) struct HsId(pub(crate) Void);
25
26 impl PartialEq for HsId {
27 fn eq(&self, _other: &Self) -> bool {
28 void::unreachable(self.0)
29 }
30 }
31 impl Eq for HsId {}
32
33 pub(crate) const HSID_ONION_SUFFIX: &str = ".onion";
35
36 impl FromStr for HsId {
38 type Err = ErrorDetail;
39
40 fn from_str(s: &str) -> Result<Self, Self::Err> {
41 if !s.ends_with(HSID_ONION_SUFFIX) {
42 return Err(internal!("non-.onion passed to dummy HsId::from_str").into());
43 }
44
45 Err(ErrorDetail::OnionAddressNotSupported)
46 }
47 }
48}
49#[cfg(not(feature = "onion-service-client"))]
50use hs_dummy::*;
51
52pub trait IntoTorAddr {
77 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError>;
80}
81
82pub trait DangerouslyIntoTorAddr {
94 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError>;
100}
101
102#[derive(Debug, Clone, Eq, PartialEq)]
159pub struct TorAddr {
160 host: Host,
162 port: u16,
164}
165
166#[derive(Debug, PartialEq, Eq)]
172pub(crate) enum StreamInstructions {
173 Exit {
175 hostname: String,
177 port: u16,
179 },
180 Hs {
184 hsid: HsId,
186 hostname: String,
188 port: u16,
190 },
191}
192
193#[derive(PartialEq, Eq, Debug)]
195pub(crate) enum ResolveInstructions {
196 Exit(String),
198 Return(Vec<IpAddr>),
200}
201
202impl TorAddr {
203 fn new(host: Host, port: u16) -> Result<Self, TorAddrError> {
206 if port == 0 {
207 Err(TorAddrError::BadPort)
208 } else {
209 Ok(TorAddr { host, port })
210 }
211 }
212
213 pub fn from<A: IntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
216 addr.into_tor_addr()
217 }
218 pub fn dangerously_from<A: DangerouslyIntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
225 addr.into_tor_addr_dangerously()
226 }
227
228 pub fn is_ip_address(&self) -> bool {
230 matches!(&self.host, Host::Ip(_))
231 }
232
233 pub(crate) fn into_stream_instructions(
235 self,
236 cfg: &crate::config::ClientAddrConfig,
237 prefs: &StreamPrefs,
238 ) -> Result<StreamInstructions, ErrorDetail> {
239 self.enforce_config(cfg, prefs)?;
240
241 let port = self.port;
242 Ok(match self.host {
243 Host::Hostname(hostname) => StreamInstructions::Exit { hostname, port },
244 Host::Ip(ip) => StreamInstructions::Exit {
245 hostname: ip.to_string(),
246 port,
247 },
248 Host::Onion(onion) => {
249 let rhs = onion
251 .rmatch_indices('.')
252 .nth(1)
253 .map(|(i, _)| i + 1)
254 .unwrap_or(0);
255 let rhs = &onion[rhs..];
256 let hsid = rhs.parse()?;
257 StreamInstructions::Hs {
258 hsid,
259 port,
260 hostname: onion,
261 }
262 }
263 })
264 }
265
266 pub(crate) fn into_resolve_instructions(
268 self,
269 cfg: &crate::config::ClientAddrConfig,
270 prefs: &StreamPrefs,
271 ) -> Result<ResolveInstructions, ErrorDetail> {
272 let enforce_config_result = self.enforce_config(cfg, prefs);
277
278 let instructions = (move || {
281 Ok(match self.host {
282 Host::Hostname(hostname) => ResolveInstructions::Exit(hostname),
283 Host::Ip(ip) => ResolveInstructions::Return(vec![ip]),
284 Host::Onion(_) => return Err(ErrorDetail::OnionAddressResolveRequest),
285 })
286 })()?;
287
288 let () = enforce_config_result?;
289
290 Ok(instructions)
291 }
292
293 fn is_local(&self) -> bool {
295 self.host.is_local()
296 }
297
298 fn enforce_config(
301 &self,
302 cfg: &crate::config::ClientAddrConfig,
303 #[allow(unused_variables)] prefs: &StreamPrefs,
305 ) -> Result<(), ErrorDetail> {
306 if !cfg.allow_local_addrs && self.is_local() {
307 return Err(ErrorDetail::LocalAddress);
308 }
309
310 if let Host::Hostname(addr) = &self.host {
311 if !is_valid_hostname(addr) {
312 return Err(ErrorDetail::InvalidHostname);
314 }
315 if addr.ends_with_ignore_ascii_case(HSID_ONION_SUFFIX) {
316 return Err(ErrorDetail::OnionAddressNotSupported);
318 }
319 }
320
321 if let Host::Onion(_name) = &self.host {
322 cfg_if::cfg_if! {
323 if #[cfg(feature = "onion-service-client")] {
324 if !prefs.connect_to_onion_services.as_bool().unwrap_or(cfg.allow_onion_addrs) {
325 return Err(ErrorDetail::OnionAddressDisabled);
326 }
327 } else {
328 return Err(ErrorDetail::OnionAddressNotSupported);
329 }
330 }
331 }
332
333 Ok(())
334 }
335}
336
337impl std::fmt::Display for TorAddr {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 match self.host {
340 Host::Ip(IpAddr::V6(addr)) => write!(f, "[{}]:{}", addr, self.port),
341 _ => write!(f, "{}:{}", self.host, self.port),
342 }
343 }
344}
345
346#[derive(Debug, Error, Clone, Eq, PartialEq)]
351#[non_exhaustive]
352pub enum TorAddrError {
354 #[error("String can never be a valid hostname")]
356 InvalidHostname,
357 #[error("No port found in string")]
359 NoPort,
360 #[error("Could not parse port")]
362 BadPort,
363}
364
365#[derive(Clone, Debug, Eq, PartialEq)]
375enum Host {
376 Hostname(String),
388 Ip(IpAddr),
390 Onion(String),
395}
396
397impl FromStr for Host {
398 type Err = TorAddrError;
399 fn from_str(s: &str) -> Result<Host, TorAddrError> {
400 if s.ends_with_ignore_ascii_case(".onion") && is_valid_hostname(s) {
401 Ok(Host::Onion(s.to_owned()))
402 } else if let Ok(ip_addr) = s.parse() {
403 Ok(Host::Ip(ip_addr))
404 } else if is_valid_hostname(s) {
405 Ok(Host::Hostname(s.to_owned()))
411 } else {
412 Err(TorAddrError::InvalidHostname)
413 }
414 }
415}
416
417impl Host {
418 fn is_local(&self) -> bool {
421 match self {
422 Host::Hostname(name) => name.eq_ignore_ascii_case("localhost"),
423 Host::Ip(IpAddr::V4(ip)) => ip.is_loopback() || ip.is_private(),
430 Host::Ip(IpAddr::V6(ip)) => ip.is_loopback(),
431 Host::Onion(_) => false,
432 }
433 }
434}
435
436impl std::fmt::Display for Host {
437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438 match self {
439 Host::Hostname(s) => Display::fmt(s, f),
440 Host::Ip(ip) => Display::fmt(ip, f),
441 Host::Onion(onion) => Display::fmt(onion, f),
442 }
443 }
444}
445
446impl IntoTorAddr for TorAddr {
447 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
448 Ok(self)
449 }
450}
451
452impl<A: IntoTorAddr + Clone> IntoTorAddr for &A {
453 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
454 self.clone().into_tor_addr()
455 }
456}
457
458impl IntoTorAddr for &str {
459 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
460 if let Ok(sa) = SocketAddr::from_str(self) {
461 TorAddr::new(Host::Ip(sa.ip()), sa.port())
462 } else {
463 let (host, port) = self.rsplit_once(':').ok_or(TorAddrError::NoPort)?;
464 let host = host.parse()?;
465 let port = port.parse().map_err(|_| TorAddrError::BadPort)?;
466 TorAddr::new(host, port)
467 }
468 }
469}
470
471impl IntoTorAddr for String {
472 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
473 self[..].into_tor_addr()
474 }
475}
476
477impl FromStr for TorAddr {
478 type Err = TorAddrError;
479 fn from_str(s: &str) -> Result<Self, TorAddrError> {
480 s.into_tor_addr()
481 }
482}
483
484impl IntoTorAddr for (&str, u16) {
485 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
486 let (host, port) = self;
487 let host = host.parse()?;
488 TorAddr::new(host, port)
489 }
490}
491
492impl IntoTorAddr for (String, u16) {
493 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
494 let (host, port) = self;
495 (&host[..], port).into_tor_addr()
496 }
497}
498
499impl<T: DangerouslyIntoTorAddr + Clone> DangerouslyIntoTorAddr for &T {
500 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
501 self.clone().into_tor_addr_dangerously()
502 }
503}
504
505impl DangerouslyIntoTorAddr for (IpAddr, u16) {
506 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
507 let (addr, port) = self;
508 TorAddr::new(Host::Ip(addr), port)
509 }
510}
511
512impl DangerouslyIntoTorAddr for (Ipv4Addr, u16) {
513 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
514 let (addr, port) = self;
515 TorAddr::new(Host::Ip(addr.into()), port)
516 }
517}
518
519impl DangerouslyIntoTorAddr for (Ipv6Addr, u16) {
520 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
521 let (addr, port) = self;
522 TorAddr::new(Host::Ip(addr.into()), port)
523 }
524}
525
526impl DangerouslyIntoTorAddr for SocketAddr {
527 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
528 let (addr, port) = (self.ip(), self.port());
529 (addr, port).into_tor_addr_dangerously()
530 }
531}
532
533impl DangerouslyIntoTorAddr for SocketAddrV4 {
534 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
535 let (addr, port) = (self.ip(), self.port());
536 (*addr, port).into_tor_addr_dangerously()
537 }
538}
539
540impl DangerouslyIntoTorAddr for SocketAddrV6 {
541 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
542 let (addr, port) = (self.ip(), self.port());
543 (*addr, port).into_tor_addr_dangerously()
544 }
545}
546
547fn is_valid_hostname(hostname: &str) -> bool {
551 hostname_validator::is_valid(hostname)
552}
553
554#[cfg(test)]
555mod test {
556 #![allow(clippy::bool_assert_comparison)]
558 #![allow(clippy::clone_on_copy)]
559 #![allow(clippy::dbg_macro)]
560 #![allow(clippy::mixed_attributes_style)]
561 #![allow(clippy::print_stderr)]
562 #![allow(clippy::print_stdout)]
563 #![allow(clippy::single_char_pattern)]
564 #![allow(clippy::unwrap_used)]
565 #![allow(clippy::unchecked_duration_subtraction)]
566 #![allow(clippy::useless_vec)]
567 #![allow(clippy::needless_pass_by_value)]
568 use super::*;
570
571 fn mk_stream_prefs() -> StreamPrefs {
573 let prefs = crate::StreamPrefs::default();
574
575 #[cfg(feature = "onion-service-client")]
576 let prefs = {
577 let mut prefs = prefs;
578 prefs.connect_to_onion_services(tor_config::BoolOrAuto::Explicit(true));
579 prefs
580 };
581
582 prefs
583 }
584
585 #[test]
586 fn validate_hostname() {
587 assert!(is_valid_hostname("torproject.org"));
589 assert!(is_valid_hostname("Tor-Project.org"));
590 assert!(is_valid_hostname("example.onion"));
591 assert!(is_valid_hostname("some.example.onion"));
592
593 assert!(!is_valid_hostname("-torproject.org"));
595 assert!(!is_valid_hostname("_torproject.org"));
596 assert!(!is_valid_hostname("tor_project1.org"));
597 assert!(!is_valid_hostname("iwanna$money.org"));
598 }
599
600 #[test]
601 fn validate_addr() {
602 use crate::err::ErrorDetail;
603 fn val<A: IntoTorAddr>(addr: A) -> Result<TorAddr, ErrorDetail> {
604 let toraddr = addr.into_tor_addr()?;
605 toraddr.enforce_config(&Default::default(), &mk_stream_prefs())?;
606 Ok(toraddr)
607 }
608
609 assert!(val("[2001:db8::42]:20").is_ok());
610 assert!(val(("2001:db8::42", 20)).is_ok());
611 assert!(val(("198.151.100.42", 443)).is_ok());
612 assert!(val("198.151.100.42:443").is_ok());
613 assert!(val("www.torproject.org:443").is_ok());
614 assert!(val(("www.torproject.org", 443)).is_ok());
615
616 #[cfg(feature = "onion-service-client")]
618 {
619 assert!(val("example.onion:80").is_ok());
620 assert!(val(("example.onion", 80)).is_ok());
621
622 match val("eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443") {
623 Ok(TorAddr {
624 host: Host::Onion(_),
625 ..
626 }) => {}
627 x => panic!("{x:?}"),
628 }
629 }
630
631 assert!(matches!(
632 val("-foobar.net:443"),
633 Err(ErrorDetail::InvalidHostname)
634 ));
635 assert!(matches!(
636 val("www.torproject.org"),
637 Err(ErrorDetail::Address(TorAddrError::NoPort))
638 ));
639
640 assert!(matches!(
641 val("192.168.0.1:80"),
642 Err(ErrorDetail::LocalAddress)
643 ));
644 assert!(matches!(
645 val(TorAddr::new(Host::Hostname("foo@bar".to_owned()), 553).unwrap()),
646 Err(ErrorDetail::InvalidHostname)
647 ));
648 assert!(matches!(
649 val(TorAddr::new(Host::Hostname("foo.onion".to_owned()), 80).unwrap()),
650 Err(ErrorDetail::OnionAddressNotSupported)
651 ));
652 }
653
654 #[test]
655 fn local_addrs() {
656 fn is_local_hostname(s: &str) -> bool {
657 let h: Host = s.parse().unwrap();
658 h.is_local()
659 }
660
661 assert!(is_local_hostname("localhost"));
662 assert!(is_local_hostname("loCALHOST"));
663 assert!(is_local_hostname("127.0.0.1"));
664 assert!(is_local_hostname("::1"));
665 assert!(is_local_hostname("192.168.0.1"));
666
667 assert!(!is_local_hostname("www.example.com"));
668 }
669
670 #[test]
671 fn is_ip_address() {
672 fn ip(s: &str) -> bool {
673 TorAddr::from(s).unwrap().is_ip_address()
674 }
675
676 assert!(ip("192.168.0.1:80"));
677 assert!(ip("[::1]:80"));
678 assert!(ip("[2001:db8::42]:65535"));
679 assert!(!ip("example.com:80"));
680 assert!(!ip("example.onion:80"));
681 }
682
683 #[test]
684 fn stream_instructions() {
685 use StreamInstructions as SI;
686
687 fn sap(s: &str) -> Result<StreamInstructions, ErrorDetail> {
688 TorAddr::from(s)
689 .unwrap()
690 .into_stream_instructions(&Default::default(), &mk_stream_prefs())
691 }
692
693 assert_eq!(
694 sap("[2001:db8::42]:9001").unwrap(),
695 SI::Exit {
696 hostname: "2001:db8::42".to_owned(),
697 port: 9001
698 },
699 );
700 assert_eq!(
701 sap("example.com:80").unwrap(),
702 SI::Exit {
703 hostname: "example.com".to_owned(),
704 port: 80
705 },
706 );
707
708 {
709 let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
710 let onion = format!("sss1234.www.{}.onion", b32);
711 let got = sap(&format!("{}:443", onion));
712
713 #[cfg(feature = "onion-service-client")]
714 assert_eq!(
715 got.unwrap(),
716 SI::Hs {
717 hsid: format!("{}.onion", b32).parse().unwrap(),
718 hostname: onion,
719 port: 443,
720 }
721 );
722
723 #[cfg(not(feature = "onion-service-client"))]
724 assert!(matches!(got, Err(ErrorDetail::OnionAddressNotSupported)));
725 }
726 }
727
728 #[test]
729 fn resolve_instructions() {
730 use ResolveInstructions as RI;
731
732 fn sap(s: &str) -> Result<ResolveInstructions, ErrorDetail> {
733 TorAddr::from(s)
734 .unwrap()
735 .into_resolve_instructions(&Default::default(), &Default::default())
736 }
737
738 assert_eq!(
739 sap("[2001:db8::42]:9001").unwrap(),
740 RI::Return(vec!["2001:db8::42".parse().unwrap()]),
741 );
742 assert_eq!(
743 sap("example.com:80").unwrap(),
744 RI::Exit("example.com".to_owned()),
745 );
746 assert!(matches!(
747 sap("example.onion:80"),
748 Err(ErrorDetail::OnionAddressResolveRequest),
749 ));
750 }
751
752 #[test]
753 fn bad_ports() {
754 assert_eq!(
755 TorAddr::from("www.example.com:squirrel"),
756 Err(TorAddrError::BadPort)
757 );
758 assert_eq!(
759 TorAddr::from("www.example.com:0"),
760 Err(TorAddrError::BadPort)
761 );
762 }
763
764 #[test]
765 fn prefs_onion_services() {
766 use crate::err::ErrorDetailDiscriminants;
767 use tor_error::{ErrorKind, HasKind as _};
768 use ErrorDetailDiscriminants as EDD;
769 use ErrorKind as EK;
770
771 #[allow(clippy::redundant_closure)] let prefs_def = || StreamPrefs::default();
773
774 let addr: TorAddr = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443"
775 .parse()
776 .unwrap();
777
778 fn map(
779 got: Result<impl Sized, ErrorDetail>,
780 ) -> Result<(), (ErrorDetailDiscriminants, ErrorKind)> {
781 got.map(|_| ())
782 .map_err(|e| (ErrorDetailDiscriminants::from(&e), e.kind()))
783 }
784
785 let check_stream = |prefs, expected| {
786 let got = addr
787 .clone()
788 .into_stream_instructions(&Default::default(), &prefs);
789 assert_eq!(map(got), expected, "{prefs:?}");
790 };
791 let check_resolve = |prefs| {
792 let got = addr
793 .clone()
794 .into_resolve_instructions(&Default::default(), &prefs);
795 let expected = Err((EDD::OnionAddressResolveRequest, EK::NotImplemented));
798 assert_eq!(map(got), expected, "{prefs:?}");
799 };
800
801 cfg_if::cfg_if! {
802 if #[cfg(feature = "onion-service-client")] {
803 use tor_config::BoolOrAuto as B;
804 let prefs_of = |yn| {
805 let mut prefs = StreamPrefs::default();
806 prefs.connect_to_onion_services(yn);
807 prefs
808 };
809 check_stream(prefs_def(), Ok(()));
810 check_stream(prefs_of(B::Auto), Ok(()));
811 check_stream(prefs_of(B::Explicit(true)), Ok(()));
812 check_stream(prefs_of(B::Explicit(false)), Err((EDD::OnionAddressDisabled, EK::ForbiddenStreamTarget)));
813
814 check_resolve(prefs_def());
815 check_resolve(prefs_of(B::Auto));
816 check_resolve(prefs_of(B::Explicit(true)));
817 check_resolve(prefs_of(B::Explicit(false)));
818 } else {
819 check_stream(prefs_def(), Err((EDD::OnionAddressNotSupported, EK::FeatureDisabled)));
820
821 check_resolve(prefs_def());
822 }
823 }
824 }
825
826 #[test]
827 fn convert_safe() {
828 fn check<A: IntoTorAddr>(a: A, s: &str) {
829 let a1 = TorAddr::from(a).unwrap();
830 let a2 = s.parse().unwrap();
831 assert_eq!(a1, a2);
832 assert_eq!(&a1.to_string(), s);
833 }
834
835 check(("www.example.com", 8000), "www.example.com:8000");
836 check(
837 TorAddr::from(("www.example.com", 8000)).unwrap(),
838 "www.example.com:8000",
839 );
840 check(
841 TorAddr::from(("www.example.com", 8000)).unwrap(),
842 "www.example.com:8000",
843 );
844 let addr = "[2001:db8::0042]:9001".to_owned();
845 check(&addr, "[2001:db8::42]:9001");
846 check(addr, "[2001:db8::42]:9001");
847 check(("2001:db8::0042".to_owned(), 9001), "[2001:db8::42]:9001");
848 check(("example.onion", 80), "example.onion:80");
849 }
850
851 #[test]
852 fn convert_dangerous() {
853 fn check<A: DangerouslyIntoTorAddr>(a: A, s: &str) {
854 let a1 = TorAddr::dangerously_from(a).unwrap();
855 let a2 = TorAddr::from(s).unwrap();
856 assert_eq!(a1, a2);
857 assert_eq!(&a1.to_string(), s);
858 }
859
860 let ip: IpAddr = "203.0.133.6".parse().unwrap();
861 let ip4: Ipv4Addr = "203.0.133.7".parse().unwrap();
862 let ip6: Ipv6Addr = "2001:db8::42".parse().unwrap();
863 let sa: SocketAddr = "203.0.133.8:80".parse().unwrap();
864 let sa4: SocketAddrV4 = "203.0.133.8:81".parse().unwrap();
865 let sa6: SocketAddrV6 = "[2001:db8::43]:82".parse().unwrap();
866
867 #[allow(clippy::needless_borrow)]
869 #[allow(clippy::needless_borrows_for_generic_args)]
870 check(&(ip, 443), "203.0.133.6:443");
871 check((ip, 443), "203.0.133.6:443");
872 check((ip4, 444), "203.0.133.7:444");
873 check((ip6, 445), "[2001:db8::42]:445");
874 check(sa, "203.0.133.8:80");
875 check(sa4, "203.0.133.8:81");
876 check(sa6, "[2001:db8::43]:82");
877 }
878}