Skip to main content

tor_interface/
tor_provider.rs

1// standard
2use std::any::Any;
3use std::boxed::Box;
4use std::convert::TryFrom;
5use std::io::{Read, Write};
6use std::net::{SocketAddr, TcpListener, TcpStream};
7use std::ops::{Deref, DerefMut};
8use std::str::FromStr;
9use std::sync::OnceLock;
10
11// extern crates
12use domain::base::name::Name;
13use idna::uts46::{Hyphens, Uts46};
14use idna::{domain_to_ascii_cow, AsciiDenyList};
15use regex::Regex;
16
17// internal crates
18use crate::tor_crypto::*;
19
20/// Various `tor_provider` errors.
21#[derive(thiserror::Error, Debug)]
22pub enum Error {
23    #[error("Failed to parse '{0}' as {1}")]
24    /// Failure parsing some string into a type
25    ParseFailure(String, String),
26
27    #[error("{0}")]
28    /// Other miscellaneous error
29    Generic(String),
30
31    #[error("Function not implemented")]
32    /// Placeholder for unimplemented functions
33    NotImplemented,
34}
35
36//
37// OnionAddr
38//
39
40/// A version 3 onion service address.
41///
42/// Version 3 Onion Service addresses const of a [`crate::tor_crypto::V3OnionServiceId`] and a 16-bit port number.
43#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
44pub struct OnionAddrV3 {
45    pub(crate) service_id: V3OnionServiceId,
46    pub(crate) virt_port: u16,
47}
48
49impl OnionAddrV3 {
50    /// Create a new `OnionAddrV3` from a [`crate::tor_crypto::V3OnionServiceId`] and port number.
51    pub fn new(service_id: V3OnionServiceId, virt_port: u16) -> OnionAddrV3 {
52        OnionAddrV3 {
53            service_id,
54            virt_port,
55        }
56    }
57
58    /// Return the service id associated with this onion address.
59    pub fn service_id(&self) -> &V3OnionServiceId {
60        &self.service_id
61    }
62
63    /// Return the port numebr associated with this onion address.
64    pub fn virt_port(&self) -> u16 {
65        self.virt_port
66    }
67}
68
69impl std::fmt::Display for OnionAddrV3 {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        write!(f, "{}.onion:{}", self.service_id, self.virt_port)
72    }
73}
74
75/// An onion service address analog to [`std::net::SocketAddr`]
76#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub enum OnionAddr {
78    V3(OnionAddrV3),
79}
80
81impl FromStr for OnionAddr {
82    type Err = Error;
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        static ONION_SERVICE_PATTERN: OnceLock<Regex> = OnceLock::new();
85        let onion_service_pattern = ONION_SERVICE_PATTERN.get_or_init(|| {
86            Regex::new(r"(?m)^(?P<service_id>[a-z2-7]{56})\.onion:(?P<port>[1-9][0-9]{0,4})$")
87                .unwrap()
88        });
89
90        if let Some(caps) = onion_service_pattern.captures(s.to_lowercase().as_ref()) {
91            let service_id = caps
92                .name("service_id")
93                .expect("missing service_id group")
94                .as_str()
95                .to_lowercase();
96            let port = caps.name("port").expect("missing port group").as_str();
97            if let (Ok(service_id), Ok(port)) = (
98                V3OnionServiceId::from_string(service_id.as_ref()),
99                u16::from_str(port),
100            ) {
101                return Ok(OnionAddr::V3(OnionAddrV3::new(service_id, port)));
102            }
103        }
104        Err(Self::Err::ParseFailure(
105            s.to_string(),
106            "OnionAddr".to_string(),
107        ))
108    }
109}
110
111impl std::fmt::Display for OnionAddr {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        match self {
114            OnionAddr::V3(onion_addr) => onion_addr.fmt(f),
115        }
116    }
117}
118
119//
120// DomainAddr
121//
122
123/// A domain name analog to `std::net::SocketAddr`
124///
125/// A `DomainAddr` must not end in ".onion"
126#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
127pub struct DomainAddr {
128    domain: String,
129    port: u16,
130}
131
132/// A `DomainAddr` has a domain name (scuh as `www.example.com`) and a port
133impl DomainAddr {
134    /// Returns the domain name associated with this domain address.
135    pub fn domain(&self) -> &str {
136        self.domain.as_ref()
137    }
138
139    /// Returns the port number associated with this domain address.
140    pub fn port(&self) -> u16 {
141        self.port
142    }
143}
144
145impl std::fmt::Display for DomainAddr {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        let uts46: Uts46 = Default::default();
148        let (ui_str, _err) = uts46.to_user_interface(
149            self.domain.as_bytes(),
150            AsciiDenyList::URL,
151            Hyphens::Allow,
152            |_, _, _| -> bool { false },
153        );
154        write!(f, "{}:{}", ui_str, self.port)
155    }
156}
157
158impl TryFrom<(String, u16)> for DomainAddr {
159    type Error = Error;
160
161    fn try_from(value: (String, u16)) -> Result<Self, Self::Error> {
162        let (domain, port) = (&value.0, value.1);
163        if let Ok(domain) = domain_to_ascii_cow(domain.as_bytes(), AsciiDenyList::URL) {
164            let domain = domain.to_string();
165            if let Ok(domain) = Name::<Vec<u8>>::from_str(domain.as_ref()) {
166                let domain = domain.to_string();
167                if !domain.ends_with(".onion") {
168                    return Ok(Self { domain, port });
169                }
170            }
171        }
172        Err(Self::Error::ParseFailure(
173            format!("{}:{}", domain, port),
174            "DomainAddr".to_string(),
175        ))
176    }
177}
178
179impl FromStr for DomainAddr {
180    type Err = Error;
181    fn from_str(s: &str) -> Result<Self, Self::Err> {
182        static DOMAIN_PATTERN: OnceLock<Regex> = OnceLock::new();
183        let domain_pattern = DOMAIN_PATTERN
184            .get_or_init(|| Regex::new(r"(?m)^(?P<domain>.*):(?P<port>[1-9][0-9]{0,4})$").unwrap());
185        if let Some(caps) = domain_pattern.captures(s) {
186            let domain = caps
187                .name("domain")
188                .expect("missing domain group")
189                .as_str()
190                .to_string();
191            let port = caps.name("port").expect("missing port group").as_str();
192            if let Ok(port) = u16::from_str(port) {
193                return Self::try_from((domain, port));
194            }
195        }
196        Err(Self::Err::ParseFailure(
197            s.to_string(),
198            "DomainAddr".to_string(),
199        ))
200    }
201}
202
203//
204// TargetAddr
205//
206
207/// An enum representing the various types of addresses a [`TorProvider`] implementation may connect to.
208#[derive(Clone, Debug, PartialEq, Eq)]
209pub enum TargetAddr {
210    /// An ip address and port
211    Socket(std::net::SocketAddr),
212    /// An onion-service id and virtual port
213    OnionService(OnionAddr),
214    /// A domain name and port
215    Domain(DomainAddr),
216}
217
218impl From<(V3OnionServiceId, u16)> for TargetAddr {
219    fn from(target_tuple: (V3OnionServiceId, u16)) -> Self {
220        TargetAddr::OnionService(OnionAddr::V3(OnionAddrV3::new(
221            target_tuple.0,
222            target_tuple.1,
223        )))
224    }
225}
226
227impl FromStr for TargetAddr {
228    type Err = Error;
229    fn from_str(s: &str) -> Result<Self, Self::Err> {
230        if let Ok(socket_addr) = SocketAddr::from_str(s) {
231            return Ok(TargetAddr::Socket(socket_addr));
232        } else if let Ok(onion_addr) = OnionAddr::from_str(s) {
233            return Ok(TargetAddr::OnionService(onion_addr));
234        } else if let Ok(domain_addr) = DomainAddr::from_str(s) {
235            return Ok(TargetAddr::Domain(domain_addr));
236        }
237        Err(Self::Err::ParseFailure(
238            s.to_string(),
239            "TargetAddr".to_string(),
240        ))
241    }
242}
243
244impl TryFrom<(String, u16)> for TargetAddr {
245    type Error = Error;
246
247    fn try_from(value: (String, u16)) -> Result<Self, Self::Error> {
248        let host = value.0;
249        let port = value.1;
250        let address = format!("{host}:{port}");
251        TargetAddr::from_str(&address)
252    }
253}
254
255impl std::fmt::Display for TargetAddr {
256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257        match self {
258            TargetAddr::Socket(socket_addr) => socket_addr.fmt(f),
259            TargetAddr::OnionService(onion_addr) => onion_addr.fmt(f),
260            TargetAddr::Domain(domain_addr) => domain_addr.fmt(f),
261        }
262    }
263}
264
265/// Various events possibly returned by a [`TorProvider`] implementation's `update()` method.
266#[derive(Debug)]
267pub enum TorEvent {
268    /// A status update received connecting to the Tor Network.
269    BootstrapStatus {
270        /// A number from 0 to 100 for how through the bootstrap process the `TorProvider` is.
271        progress: u32,
272        /// A short string to identify the current phase of the bootstrap process.
273        tag: String,
274        /// A longer string with a summary of the current phase of the bootstrap process.
275        summary: String,
276    },
277    /// Indicates successful connection to the Tor Network. The [`TorProvider::connect()`] and [`TorProvider::listener()`] methods may now be used.
278    BootstrapComplete,
279    /// Messages which may be useful for troubleshooting.
280    LogReceived {
281        /// A message
282        line: String,
283    },
284    /// An onion-service has been published to the Tor Network and may now be reachable by clients.
285    OnionServicePublished {
286        /// The service-id of the onion-service which has been published.
287        service_id: V3OnionServiceId,
288    },
289    /// TorProvider::connect_async() call completed successfully
290    ConnectComplete {
291        handle: ConnectHandle,
292        stream: OnionStream,
293    },
294    /// TorProvider::connect_async() call failed
295    ConnectFailed { handle: ConnectHandle, error: Error },
296}
297
298/// A `CircuitToken` is used to specify circuits used to connect to clearnet services.
299pub type CircuitToken = usize;
300
301/// A `ConnectHandle` is used to associate a connect request with returned stream
302pub type ConnectHandle = usize;
303
304//
305// Onion Stream
306//
307
308/// A wrapper around a [`std::net::TcpStream`] with some Tor-specific customisations
309///
310/// An onion-listener can be constructed using the [`TorProvider::connect()`] method.
311#[derive(Debug)]
312pub struct OnionStream {
313    pub(crate) stream: TcpStream,
314    pub(crate) local_addr: Option<OnionAddr>,
315    pub(crate) peer_addr: Option<TargetAddr>,
316}
317
318impl Deref for OnionStream {
319    type Target = TcpStream;
320    fn deref(&self) -> &Self::Target {
321        &self.stream
322    }
323}
324
325impl DerefMut for OnionStream {
326    fn deref_mut(&mut self) -> &mut Self::Target {
327        &mut self.stream
328    }
329}
330
331impl From<OnionStream> for TcpStream {
332    fn from(onion_stream: OnionStream) -> Self {
333        onion_stream.stream
334    }
335}
336
337impl Read for OnionStream {
338    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
339        self.stream.read(buf)
340    }
341}
342
343impl Write for OnionStream {
344    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
345        self.stream.write(buf)
346    }
347
348    fn flush(&mut self) -> Result<(), std::io::Error> {
349        self.stream.flush()
350    }
351}
352
353impl OnionStream {
354    /// Returns the target address of the remote peer of this onion connection.
355    pub fn peer_addr(&self) -> Option<TargetAddr> {
356        self.peer_addr.clone()
357    }
358
359    /// Returns the onion address of the local connection for an incoming onion-service connection. Returns `None` for outgoing connections.
360    pub fn local_addr(&self) -> Option<OnionAddr> {
361        self.local_addr.clone()
362    }
363
364    /// Tries to clone the underlying connection and data. A simple pass-through to [`std::net::TcpStream::try_clone()`].
365    pub fn try_clone(&self) -> Result<Self, std::io::Error> {
366        Ok(Self {
367            stream: self.stream.try_clone()?,
368            local_addr: self.local_addr.clone(),
369            peer_addr: self.peer_addr.clone(),
370        })
371    }
372}
373
374//
375// Onion Listener
376//
377
378pub(crate) type OnionListenerDropFn = Box<dyn FnMut(Box<dyn Any>) + Send>;
379
380/// A wrapper around a [`std::net::TcpListener`] with some Tor-specific customisations.
381///
382/// An onion-listener can be constructed using the [`TorProvider::listener()`] method.
383pub struct OnionListener {
384    pub(crate) listener: TcpListener,
385    pub(crate) onion_addr: OnionAddr,
386    pub(crate) data: Option<Box<dyn Any + Send>>,
387    pub(crate) drop: Option<OnionListenerDropFn>,
388}
389
390impl OnionListener {
391    /// Construct an `OnionListener`. The `data` and `drop` parameters are to allow custom `TorProvider` implementations their own data and cleanup procedures.
392    pub(crate) fn new<T: 'static + Send>(
393        listener: TcpListener,
394        onion_addr: OnionAddr,
395        data: T,
396        mut drop: impl FnMut(T) + 'static + Send,
397    ) -> Self {
398        // marshall our data into an Any
399        let data: Option<Box<dyn Any + Send>> = Some(Box::new(data));
400        // marhsall our drop into a function which takes an Any
401        let drop: Option<OnionListenerDropFn> =
402            Some(Box::new(move |data: Box<dyn std::any::Any>| {
403                // encapsulate extracting our data from the Any
404                if let Ok(data) = data.downcast::<T>() {
405                    // and call our provided drop
406                    drop(*data);
407                }
408            }));
409
410        Self {
411            listener,
412            onion_addr,
413            data,
414            drop,
415        }
416    }
417
418    /// Moves the underlying `TcpListener` into or out of nonblocking mode.
419    pub fn set_nonblocking(&self, nonblocking: bool) -> Result<(), std::io::Error> {
420        self.listener.set_nonblocking(nonblocking)
421    }
422
423    /// Accept a new incoming connection from this listener.
424    pub fn accept(&self) -> Result<Option<OnionStream>, std::io::Error> {
425        match self.listener.accept() {
426            Ok((stream, _socket_addr)) => Ok(Some(OnionStream {
427                stream,
428                local_addr: Some(self.onion_addr.clone()),
429                peer_addr: None,
430            })),
431            Err(err) => {
432                if err.kind() == std::io::ErrorKind::WouldBlock {
433                    Ok(None)
434                } else {
435                    Err(err)
436                }
437            }
438        }
439    }
440}
441
442impl Drop for OnionListener {
443    fn drop(&mut self) {
444        if let (Some(data), Some(mut drop)) = (self.data.take(), self.drop.take()) {
445            drop(data)
446        }
447    }
448}
449
450/// The `TorProvider` trait allows for high-level Tor Network functionality. Implementations ay connect to the Tor Network, anonymously connect to both clearnet and onion-service endpoints, and host onion-services.
451pub trait TorProvider: Send {
452    /// Process and return `TorEvent`s handled by this `TorProvider`.
453    fn update(&mut self) -> Result<Vec<TorEvent>, Error>;
454    /// Begin connecting to the Tor Network.
455    fn bootstrap(&mut self) -> Result<(), Error>;
456    /// Add v3 onion-service authorisation credentials, allowing this `TorProvider` to connect to an onion-service whose service-descriptor is encrypted using the assocciated x25519 public key.
457    fn add_client_auth(
458        &mut self,
459        service_id: &V3OnionServiceId,
460        client_auth: &X25519PrivateKey,
461    ) -> Result<(), Error>;
462    /// Remove a previously added client authorisation credential. This `TorProvider` will be unable to connect to the onion-service associated with the removed credentail.
463    fn remove_client_auth(&mut self, service_id: &V3OnionServiceId) -> Result<(), Error>;
464    /// Anonymously connect to the address specified by `target` over the Tor Network and return the associated [`OnionStream`].
465    ///
466    /// When conecting to clearnet targets, an optional [`CircuitToken`] may be used to enforce usage of different circuits through the Tor Network. If `circuit` is `None`, the default circuit is used.
467    ///
468    ///Connections made with different `CircuitToken`s are required to use different circuits through the Tor Network. However, connections made with identical `CircuitToken`s are *not* required to use identical circuits through the Tor Network.
469    ///
470    /// Specifying a circuit token when connecting to an onion-service has no effect on the resulting circuit.
471    fn connect(
472        &mut self,
473        target: TargetAddr,
474        circuit: Option<CircuitToken>,
475    ) -> Result<OnionStream, Error>;
476    /// Anonymously connect to the address specified by `target` over the Tor Network. The resulting [`OnionStream`] (or failure) will be returned as a [`TorEvent`] from [`TorProvider::update()`].
477    ///
478    /// When conecting to clearnet targets, an optional [`CircuitToken`] may be used to enforce usage of different circuits through the Tor Network. If `circuit` is `None`, the default circuit is used.
479    ///
480    ///Connections made with different `CircuitToken`s are required to use different circuits through the Tor Network. However, connections made with identical `CircuitToken`s are *not* required to use identical circuits through the Tor Network.
481    ///
482    /// Specifying a circuit token when connecting to an onion-service has no effect on the resulting circuit.
483    fn connect_async(
484        &mut self,
485        _target: TargetAddr,
486        _circuit: Option<CircuitToken>,
487    ) -> Result<ConnectHandle, Error> {
488        Err(Error::NotImplemented)
489    }
490    /// Anonymously start an onion-service and return the associated [`OnionListener`].
491    ///
492    ///The resulting onion-service will not be reachable by clients until [`TorProvider::update()`] returns a [`TorEvent::OnionServicePublished`] event. The optional `authorised_clients` parameter may be used to require client authorisation keys to connect to resulting onion-service. For further information, see the Tor Project's onion-services [client-auth documentation](https://community.torproject.org/onion-services/advanced/client-auth).
493    fn listener(
494        &mut self,
495        private_key: &Ed25519PrivateKey,
496        virt_port: u16,
497        authorised_clients: Option<&[X25519PublicKey]>,
498    ) -> Result<OnionListener, Error>;
499    /// Create a new [`CircuitToken`].
500    fn generate_token(&mut self) -> CircuitToken;
501    /// Releaes a previously generated [`CircuitToken`].
502    fn release_token(&mut self, token: CircuitToken);
503}