tor_interface/
legacy_tor_client.rs

1// standard
2use std::collections::BTreeMap;
3use std::convert::From;
4use std::default::Default;
5use std::net::{IpAddr, SocketAddr, TcpListener};
6use std::option::Option;
7use std::path::PathBuf;
8use std::str::FromStr;
9use std::string::ToString;
10use std::sync::{atomic, Arc, Mutex};
11use std::time::Duration;
12
13// extern crates
14use socks::Socks5Stream;
15use zeroize::ZeroizeOnDrop;
16
17// internal crates
18use crate::censorship_circumvention::*;
19use crate::legacy_tor_control_stream::*;
20use crate::legacy_tor_controller::*;
21use crate::legacy_tor_process::*;
22use crate::legacy_tor_version::*;
23use crate::proxy::*;
24use crate::tor_crypto::*;
25use crate::tor_provider;
26use crate::tor_provider::*;
27
28/// [`LegacyTorClient`]-specific error type
29#[derive(thiserror::Error, Debug)]
30pub enum Error {
31    #[error("failed to create LegacyTorProcess object")]
32    LegacyTorProcessCreationFailed(#[source] crate::legacy_tor_process::Error),
33
34    #[error("failed to create LegacyControlStream object")]
35    LegacyControlStreamCreationFailed(#[source] crate::legacy_tor_control_stream::Error),
36
37    #[error("failed to create LegacyTorController object")]
38    LegacyTorControllerCreationFailed(#[source] crate::legacy_tor_controller::Error),
39
40    #[error("failed to authenticate with the tor process")]
41    LegacyTorProcessAuthenticationFailed(#[source] crate::legacy_tor_controller::Error),
42
43    #[error("failed to determine the tor process version")]
44    GetInfoVersionFailed(#[source] crate::legacy_tor_controller::Error),
45
46    #[error("tor process version to old; found {0} but must be at least {1}")]
47    LegacyTorProcessTooOld(String, String),
48
49    #[error("failed to register for STATUS_CLIENT and HS_DESC events")]
50    SetEventsFailed(#[source] crate::legacy_tor_controller::Error),
51
52    #[error("failed to delete unused onion service")]
53    DelOnionFailed(#[source] crate::legacy_tor_controller::Error),
54
55    #[error("failed waiting for async events: {0}")]
56    WaitAsyncEventsFailed(#[source] crate::legacy_tor_controller::Error),
57
58    #[error("failed to begin bootstrap")]
59    SetConfDisableNetwork0Failed(#[source] crate::legacy_tor_controller::Error),
60
61    #[error("failed to setconf")]
62    SetConfFailed(#[source] crate::legacy_tor_controller::Error),
63
64    #[error("failed to add client auth for onion service")]
65    OnionClientAuthAddFailed(#[source] crate::legacy_tor_controller::Error),
66
67    #[error("failed to remove client auth from onion service")]
68    OnionClientAuthRemoveFailed(#[source] crate::legacy_tor_controller::Error),
69
70    #[error("failed to get socks listener")]
71    GetInfoNetListenersSocksFailed(#[source] crate::legacy_tor_controller::Error),
72
73    #[error("no socks listeners available to connect through")]
74    NoSocksListenersFound(),
75
76    #[error("invalid circuit token")]
77    CircuitTokenInvalid(),
78
79    #[error("unable to connect to socks listener")]
80    Socks5ConnectionFailed(#[source] std::io::Error),
81
82    #[error("failed to spawn connect_async thread")]
83    ConnectAsyncThreadSpawnFailed(#[source] std::io::Error),
84
85    #[error("unable to bind TCP listener")]
86    TcpListenerBindFailed(#[source] std::io::Error),
87
88    #[error("unable to get TCP listener's local address")]
89    TcpListenerLocalAddrFailed(#[source] std::io::Error),
90
91    #[error("failed to create onion service")]
92    AddOnionFailed(#[source] crate::legacy_tor_controller::Error),
93
94    #[error("tor not bootstrapped")]
95    LegacyTorNotBootstrapped(),
96
97    #[error("{0}")]
98    PluggableTransportConfigDirectoryCreationFailed(#[source] std::io::Error),
99
100    #[error("unable to create pluggable-transport directory because file with same name already exists: {0:?}")]
101    PluggableTransportDirectoryNameCollision(PathBuf),
102
103    #[error("{0}")]
104    PluggableTransportSymlinkRemovalFailed(#[source] std::io::Error),
105
106    #[error("{0}")]
107    PluggableTransportSymlinkCreationFailed(#[source] std::io::Error),
108
109    #[error("pluggable transport binary name not representable as utf8: {0:?}")]
110    PluggableTransportBinaryNameNotUtf8Representnable(std::ffi::OsString),
111
112    #[error("{0}")]
113    PluggableTransportConfigError(#[source] crate::censorship_circumvention::PluggableTransportConfigError),
114
115    #[error("pluggable transport multiply defines '{0}' bridge transport type")]
116    BridgeTransportTypeMultiplyDefined(String),
117
118    #[error("bridge transport '{0}' not supported by pluggable transport configuration")]
119    BridgeTransportNotSupported(String),
120
121    #[error("invalid environment variable configuration: {0}")]
122    EnvironmentConfigurationInvalid(String),
123
124    #[error("not implemented")]
125    NotImplemented(),
126}
127
128impl From<Error> for crate::tor_provider::Error {
129    fn from(error: Error) -> Self {
130        crate::tor_provider::Error::Generic(error.to_string())
131    }
132}
133
134//
135// CircuitToken Implementation
136//
137struct LegacyCircuitToken {
138    username: String,
139    password: String,
140}
141
142impl LegacyCircuitToken {
143    fn new() -> LegacyCircuitToken {
144        const CIRCUIT_TOKEN_USERNAME_LENGTH: usize = 32usize;
145        const CIRCUIT_TOKEN_PASSWORD_LENGTH: usize = 32usize;
146        let username = generate_password(CIRCUIT_TOKEN_USERNAME_LENGTH);
147        let password = generate_password(CIRCUIT_TOKEN_PASSWORD_LENGTH);
148
149        LegacyCircuitToken { username, password }
150    }
151}
152
153impl Default for LegacyCircuitToken {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159//
160// LegacyTorClientConfig
161//
162
163#[derive(Clone, Debug)]
164pub enum LegacyTorClientConfig {
165    BundledTor {
166        tor_bin_path: PathBuf,
167        data_directory: PathBuf,
168        proxy_settings: Option<ProxyConfig>,
169        allowed_ports: Option<Vec<u16>>,
170        pluggable_transports: Option<Vec<PluggableTransportConfig>>,
171        bridge_lines: Option<Vec<BridgeLine>>,
172    },
173    SystemTor {
174        tor_socks_addr: SocketAddr,
175        tor_control_addr: SocketAddr,
176        tor_control_auth: TorAuth,
177    },
178}
179
180impl LegacyTorClientConfig {
181    // Construct a LegacyTorClientConfig::SystemTor struct from environment variables
182    // see: https://gitlab.torproject.org/tpo/applications/wiki/-/wikis/Environment-variables-and-related-preferences
183    pub fn try_from_environment() -> Result<Self, Error> {
184        use std::env::{var, var_os};
185
186        // get socks proxy address
187        const TOR_SOCKS_HOST: &str = "TOR_SOCKS_HOST";
188        const TOR_SOCKS_PORT: &str = "TOR_SOCKS_PORT";
189        let tor_socks_addr = match (var(TOR_SOCKS_HOST), var(TOR_SOCKS_PORT)) {
190            (Ok(host), Ok(port)) => {
191                let ip = IpAddr::from_str(host.as_str()).map_err(|_| Error::EnvironmentConfigurationInvalid(format!("cannot parse TOR_SOCKS_HOST value '{host}' as ip address")))?;
192                let port = u16::from_str(port.as_str()).map_err(|_| Error::EnvironmentConfigurationInvalid(format!("cannot parse TOR_SOCKS_PORT value '{port}' as port")))?;
193                SocketAddr::new(ip, port)
194            },
195            _ => return Err(Error::EnvironmentConfigurationInvalid("environment variables TOR_SOCKS_HOST and TOR_SOCKS_PORT must be defined".to_string())),
196        };
197
198        // get control port address
199        const TOR_CONTROL_HOST: &str = "TOR_CONTROL_HOST";
200        const TOR_CONTROL_PORT: &str = "TOR_CONTROL_PORT";
201        let tor_control_addr = match (var(TOR_CONTROL_HOST), var(TOR_CONTROL_PORT)) {
202            (Ok(host), Ok(port)) => {
203                let ip = IpAddr::from_str(host.as_str()).map_err(|_| Error::EnvironmentConfigurationInvalid(format!("cannot parse TOR_CONTROL_HOST value '{host}' as ip address")))?;
204                let port = u16::from_str(port.as_str()).map_err(|_| Error::EnvironmentConfigurationInvalid(format!("cannot parse TOR_CONTROL_PORT value '{port}' as port")))?;
205                SocketAddr::new(ip, port)
206            },
207            _ => return Err(Error::EnvironmentConfigurationInvalid("environment variables TOR_CONTROL_HOST and TOR_CONTROL_PORT must be defined".to_string())),
208        };
209
210        // get control auth (prefer cookie file)
211        const TOR_CONTROL_COOKIE_AUTH_FILE: &str = "TOR_CONTROL_COOKIE_AUTH_FILE";
212        const TOR_CONTROL_PASSWD: &str = "TOR_CONTROL_PASSWD";
213        let tor_control_auth = if let Some(cookie_file) = var_os(TOR_CONTROL_COOKIE_AUTH_FILE) {
214            let cookie_file: PathBuf = cookie_file.clone().try_into().map_err(|_| Error::EnvironmentConfigurationInvalid(format!("Cannot parse TOR_CONTROL_COOKIE_AUTH_FILE value '{cookie_file:?} as file path")))?;
215            TorAuth::CookieFile(cookie_file)
216        } else {
217            match var(TOR_CONTROL_PASSWD) {
218                Ok(control_password) => TorAuth::Password(control_password),
219                Err(std::env::VarError::NotPresent) => TorAuth::Null,
220                _ => return Err(Error::EnvironmentConfigurationInvalid("Failed to read TOR_CONTROL_PASSWD".to_string())),
221            }
222        };
223
224        Ok(Self::SystemTor{tor_socks_addr, tor_control_addr, tor_control_auth})
225    }
226}
227
228#[derive(Clone, Debug, Default, Eq, PartialEq, ZeroizeOnDrop)]
229pub enum TorAuth {
230    #[default]
231    #[zeroize(skip)]
232    Null,
233    Password(String),
234    #[zeroize(skip)]
235    CookieFile(PathBuf),
236}
237
238//
239// LegacyTorClient
240//
241
242/// A `LegacyTorClient` implements the [`TorProvider`] trait using a legacy c-tor daemon backend.
243///
244/// The tor process can either be launched and owned by `LegacyTorClient`, or it can use an already running tor-daemon. When using an already runnng tor-daemon, the [`TorProvider::bootstrap()`] automatically succeeds, presuming the connected tor-daemon has successfully bootstrapped.
245///
246/// The minimum supported c-tor is version 0.4.6.1.
247pub struct LegacyTorClient {
248    daemon: Option<LegacyTorProcess>,
249    version: LegacyTorVersion,
250    controller: LegacyTorController,
251    bootstrapped: bool,
252    socks_listener: Option<SocketAddr>,
253    async_events: Arc<Mutex<Vec<TorEvent>>>,
254    next_connect_handle: ConnectHandle,
255    // list of open onion services and their is_active flag
256    onion_services: Vec<(V3OnionServiceId, Arc<atomic::AtomicBool>)>,
257    // our list of circuit tokens for the tor daemon
258    circuit_token_counter: usize,
259    circuit_tokens: BTreeMap<CircuitToken, LegacyCircuitToken>,
260}
261
262impl LegacyTorClient {
263    /// Construct a new `LegacyTorClient` from a [`LegacyTorClientConfig`].
264    pub fn new(mut config: LegacyTorClientConfig) -> Result<LegacyTorClient, Error> {
265        let (daemon, mut controller, mut auth, socks_listener) = match &mut config {
266            LegacyTorClientConfig::BundledTor {
267                tor_bin_path,
268                data_directory,
269                ..
270            } => {
271                // launch tor
272                let daemon =
273                    LegacyTorProcess::new(tor_bin_path.as_path(), data_directory.as_path())
274                        .map_err(Error::LegacyTorProcessCreationFailed)?;
275                // open a control stream
276                let control_stream =
277                    LegacyControlStream::new(daemon.get_control_addr(), Duration::from_millis(16))
278                        .map_err(Error::LegacyControlStreamCreationFailed)?;
279
280                // create a controler
281                let controller = LegacyTorController::new(control_stream)
282                    .map_err(Error::LegacyTorControllerCreationFailed)?;
283
284                let password = daemon.get_password().to_string();
285                (Some(daemon), controller, TorAuth::Password(password), None)
286            }
287            LegacyTorClientConfig::SystemTor {
288                tor_socks_addr,
289                tor_control_addr,
290                tor_control_auth,
291            } => {
292                // open a control stream
293                let control_stream =
294                    LegacyControlStream::new(&tor_control_addr, Duration::from_millis(16))
295                        .map_err(Error::LegacyControlStreamCreationFailed)?;
296
297                // create a controler
298                let controller = LegacyTorController::new(control_stream)
299                    .map_err(Error::LegacyTorControllerCreationFailed)?;
300
301                (
302                    None,
303                    controller,
304                    std::mem::take(tor_control_auth),
305                    Some(tor_socks_addr.clone()),
306                )
307            }
308        };
309
310        // authenticate
311        match &mut auth {
312            TorAuth::Null => controller.authenticate(),
313            TorAuth::Password(pass) => controller.authenticate_password(std::mem::take(pass)),
314            TorAuth::CookieFile(file) => controller.authenticate_safecookie(std::mem::take(file)),
315        }.map_err(Error::LegacyTorProcessAuthenticationFailed)?;
316
317        // min required version for v3 client auth (see control-spec.txt)
318        let min_required_version = LegacyTorVersion {
319            major: 0u32,
320            minor: 4u32,
321            micro: 6u32,
322            patch_level: 1u32,
323            status_tag: None,
324        };
325
326        // verify version is recent enough
327        let version = controller
328            .getinfo_version()
329            .map_err(Error::GetInfoVersionFailed)?;
330
331        if version < min_required_version {
332            return Err(Error::LegacyTorProcessTooOld(
333                version.to_string(),
334                min_required_version.to_string(),
335            ));
336        }
337
338        // configure tor client
339        if let LegacyTorClientConfig::BundledTor {
340            data_directory,
341            proxy_settings,
342            allowed_ports,
343            pluggable_transports,
344            bridge_lines,
345            ..
346        } = config
347        {
348            // configure proxy
349            match proxy_settings {
350                Some(ProxyConfig::Socks4(Socks4ProxyConfig { address })) => {
351                    controller
352                        .setconf(&[("Socks4Proxy", address.to_string())])
353                        .map_err(Error::SetConfFailed)?;
354                }
355                Some(ProxyConfig::Socks5(Socks5ProxyConfig {
356                    address,
357                    username,
358                    password,
359                })) => {
360                    controller
361                        .setconf(&[("Socks5Proxy", address.to_string())])
362                        .map_err(Error::SetConfFailed)?;
363                    let username = username.unwrap_or("".to_string());
364                    if !username.is_empty() {
365                        controller
366                            .setconf(&[("Socks5ProxyUsername", username.to_string())])
367                            .map_err(Error::SetConfFailed)?;
368                    }
369                    let password = password.unwrap_or("".to_string());
370                    if !password.is_empty() {
371                        controller
372                            .setconf(&[("Socks5ProxyPassword", password.to_string())])
373                            .map_err(Error::SetConfFailed)?;
374                    }
375                }
376                Some(ProxyConfig::Https(HttpsProxyConfig {
377                    address,
378                    username,
379                    password,
380                })) => {
381                    controller
382                        .setconf(&[("HTTPSProxy", address.to_string())])
383                        .map_err(Error::SetConfFailed)?;
384                    let username = username.unwrap_or("".to_string());
385                    let password = password.unwrap_or("".to_string());
386                    if !username.is_empty() || !password.is_empty() {
387                        let authenticator = format!("{}:{}", username, password);
388                        controller
389                            .setconf(&[("HTTPSProxyAuthenticator", authenticator)])
390                            .map_err(Error::SetConfFailed)?;
391                    }
392                }
393                None => (),
394            }
395            // configure firewall
396            if let Some(allowed_ports) = allowed_ports {
397                let allowed_addresses: Vec<String> = allowed_ports
398                    .iter()
399                    .map(|port| format!("*{{}}:{port}"))
400                    .collect();
401                let allowed_addresses = allowed_addresses.join(", ");
402                controller
403                    .setconf(&[("ReachableAddresses", allowed_addresses)])
404                    .map_err(Error::SetConfFailed)?;
405            }
406            // configure pluggable transports
407            let mut supported_transports: std::collections::BTreeSet<String> = Default::default();
408            if let Some(pluggable_transports) = pluggable_transports {
409                // Legacy tor daemon cannot be configured to use pluggable-transports which
410                // exist in paths containing spaces. To work around this, we create a known, safe
411                // path in the tor daemon's working directory, and soft-link the provided
412                // binary path to this safe location. Finally, we configure tor to use the soft-linked
413                // binary in the ClientTransportPlugin setconf call.
414
415                // create pluggable-transport directory
416                let mut pt_directory = data_directory.clone();
417                pt_directory.push("pluggable-transports");
418                if !std::path::Path::exists(&pt_directory) {
419                    // path does not exist so create it
420                    std::fs::create_dir(&pt_directory)
421                        .map_err(Error::PluggableTransportConfigDirectoryCreationFailed)?;
422                } else if !std::path::Path::is_dir(&pt_directory) {
423                    // path exists but it is not a directory
424                    return Err(Error::PluggableTransportDirectoryNameCollision(
425                        pt_directory,
426                    ));
427                }
428
429                // symlink all our pts and configure tor
430                let mut conf: Vec<(&str, String)> = Default::default();
431                for pt_settings in &pluggable_transports {
432                    // symlink absolute path of pt binary to pt_directory in tor's working
433                    // directory
434                    let path_to_binary = pt_settings.path_to_binary();
435                    let binary_name = path_to_binary
436                        .file_name()
437                        .expect("file_name should be absolute path");
438                    let mut pt_symlink = pt_directory.clone();
439                    pt_symlink.push(binary_name);
440                    let binary_name = if let Some(binary_name) = binary_name.to_str() {
441                        binary_name
442                    } else {
443                        return Err(Error::PluggableTransportBinaryNameNotUtf8Representnable(
444                            binary_name.to_os_string(),
445                        ));
446                    };
447
448                    // remove any file that may exist with the same name
449                    if std::path::Path::exists(&pt_symlink) {
450                        std::fs::remove_file(&pt_symlink)
451                            .map_err(Error::PluggableTransportSymlinkRemovalFailed)?;
452                    }
453
454                    // create new symlink
455                    #[cfg(windows)]
456                    std::os::windows::fs::symlink_file(path_to_binary, &pt_symlink)
457                        .map_err(Error::PluggableTransportSymlinkCreationFailed)?;
458                    #[cfg(unix)]
459                    std::os::unix::fs::symlink(path_to_binary, &pt_symlink)
460                        .map_err(Error::PluggableTransportSymlinkCreationFailed)?;
461
462                    // verify a bridge-type support has not been defined for multiple pluggable-transports
463                    for transport in pt_settings.transports() {
464                        if supported_transports.contains(transport) {
465                            return Err(Error::BridgeTransportTypeMultiplyDefined(
466                                transport.to_string(),
467                            ));
468                        }
469                        supported_transports.insert(transport.to_string());
470                    }
471
472                    // finally construct our setconf value
473                    let transports = pt_settings.transports().join(",");
474                    use std::path::MAIN_SEPARATOR;
475                    let path_to_binary =
476                        format!("pluggable-transports{MAIN_SEPARATOR}{binary_name}");
477                    let options = pt_settings.options().join(" ");
478
479                    let value = format!("{transports} exec {path_to_binary} {options}");
480                    conf.push(("ClientTransportPlugin", value));
481                }
482                controller
483                    .setconf(conf.as_slice())
484                    .map_err(Error::SetConfFailed)?;
485            }
486            // configure bridge lines
487            if let Some(bridge_lines) = bridge_lines {
488                let mut conf: Vec<(&str, String)> = Default::default();
489                for bridge_line in &bridge_lines {
490                    if !supported_transports.contains(bridge_line.transport()) {
491                        return Err(Error::BridgeTransportNotSupported(
492                            bridge_line.transport().to_string(),
493                        ));
494                    }
495                    let value = bridge_line.as_legacy_tor_setconf_value();
496                    conf.push(("Bridge", value));
497                }
498                conf.push(("UseBridges", "1".to_string()));
499                controller
500                    .setconf(conf.as_slice())
501                    .map_err(Error::SetConfFailed)?;
502            }
503        }
504
505        // register for STATUS_CLIENT async events
506        controller
507            .setevents(&["STATUS_CLIENT", "HS_DESC"])
508            .map_err(Error::SetEventsFailed)?;
509
510        Ok(LegacyTorClient {
511            daemon,
512            version,
513            controller,
514            bootstrapped: false,
515            socks_listener,
516            onion_services: Default::default(),
517            async_events: Default::default(),
518            next_connect_handle: Default::default(),
519            circuit_token_counter: 0usize,
520            circuit_tokens: Default::default(),
521        })
522    }
523
524    /// Get the version of the connected c-tor daemon.
525    pub fn version(&mut self) -> LegacyTorVersion {
526        self.version.clone()
527    }
528
529    fn socks_listener(&mut self) -> Result<SocketAddr, Error> {
530        match self.socks_listener {
531            Some(socks_listener) => Ok(socks_listener.clone()),
532            None => {
533                let mut listeners = self
534                    .controller
535                    .getinfo_net_listeners_socks()
536                    .map_err(Error::GetInfoNetListenersSocksFailed)?;
537                if listeners.is_empty() {
538                    return Err(Error::NoSocksListenersFound())?;
539                }
540                let socks_listener = listeners.swap_remove(0);
541                self.socks_listener = Some(socks_listener.clone());
542                Ok(socks_listener)
543            }
544        }
545    }
546
547    fn connect_impl(
548        target_addr: TargetAddr,
549        socks_listener: SocketAddr,
550        socks_credentials: Option<(String, String)>,
551    ) -> Result<Socks5Stream, tor_provider::Error> {
552        // our target
553        let socks_target = match target_addr {
554            TargetAddr::Socket(socket_addr) => socks::TargetAddr::Ip(socket_addr),
555            TargetAddr::Domain(domain_addr) => {
556                socks::TargetAddr::Domain(domain_addr.domain().to_string(), domain_addr.port())
557            }
558            TargetAddr::OnionService(OnionAddr::V3(OnionAddrV3 {
559                service_id,
560                virt_port,
561            })) => socks::TargetAddr::Domain(format!("{}.onion", service_id), virt_port),
562        };
563
564        // readwrite stream
565        let stream = match socks_credentials {
566            None => Socks5Stream::connect(socks_listener, socks_target),
567            Some((username, password)) => Socks5Stream::connect_with_password(
568                socks_listener,
569                socks_target,
570                &username,
571                &password,
572            ),
573        }.map_err(Error::Socks5ConnectionFailed)?;
574        Ok(stream)
575    }
576}
577
578impl TorProvider for LegacyTorClient {
579    fn update(&mut self) -> Result<Vec<TorEvent>, tor_provider::Error> {
580        let mut i = 0;
581        while i < self.onion_services.len() {
582            // remove onion services with no active listeners
583            if !self.onion_services[i].1.load(atomic::Ordering::Relaxed) {
584                let entry = self.onion_services.swap_remove(i);
585                let service_id = entry.0;
586
587                self.controller
588                    .del_onion(&service_id)
589                    .map_err(Error::DelOnionFailed)?;
590            } else {
591                i += 1;
592            }
593        }
594
595        let mut events: Vec<TorEvent> = Default::default();
596        for async_event in self
597            .controller
598            .wait_async_events()
599            .map_err(Error::WaitAsyncEventsFailed)?
600        {
601            match async_event {
602                AsyncEvent::StatusClient {
603                    severity,
604                    action,
605                    arguments,
606                } => {
607                    if severity == "NOTICE" && action == "BOOTSTRAP" {
608                        let mut progress: u32 = 0;
609                        let mut tag: String = Default::default();
610                        let mut summary: String = Default::default();
611                        for (key, val) in arguments {
612                            match key.as_str() {
613                                "PROGRESS" => progress = val.parse().unwrap_or(0u32),
614                                "TAG" => tag = val,
615                                "SUMMARY" => summary = val,
616                                _ => {} // ignore unexpected arguments
617                            }
618                        }
619                        events.push(TorEvent::BootstrapStatus {
620                            progress,
621                            tag,
622                            summary,
623                        });
624                        if progress == 100u32 {
625                            events.push(TorEvent::BootstrapComplete);
626                            self.bootstrapped = true;
627                        }
628                    }
629                }
630                AsyncEvent::HsDesc { action, hs_address } => {
631                    if action == "UPLOADED" {
632                        events.push(TorEvent::OnionServicePublished {
633                            service_id: hs_address,
634                        });
635                    }
636                }
637                AsyncEvent::Unknown { lines } => {
638                    println!("Received Unknown Event:");
639                    for line in lines.iter() {
640                        println!(" {}", line);
641                    }
642                }
643            }
644        }
645
646        if let Some(daemon) = &mut self.daemon {
647            // bundled tor gives us log-lines
648            for log_line in daemon.wait_log_lines().iter_mut() {
649                events.push(TorEvent::LogReceived {
650                    line: std::mem::take(log_line),
651                });
652            }
653        } else if !self.bootstrapped {
654            // system tor needs to send a bootstrap complete event *once*
655            events.push(TorEvent::BootstrapComplete);
656            self.bootstrapped = true;
657        }
658
659        // append any new async events
660        let mut async_events = self.async_events.lock().expect("async_events mutex poisoned");
661        if !async_events.is_empty() {
662            events.append(&mut std::mem::take(&mut *async_events));
663        }
664
665        Ok(events)
666    }
667
668    fn bootstrap(&mut self) -> Result<(), tor_provider::Error> {
669        if !self.bootstrapped {
670            self.controller
671                .setconf(&[("DisableNetwork", "0".to_string())])
672                .map_err(Error::SetConfDisableNetwork0Failed)?;
673        }
674        Ok(())
675    }
676
677    fn add_client_auth(
678        &mut self,
679        service_id: &V3OnionServiceId,
680        client_auth: &X25519PrivateKey,
681    ) -> Result<(), tor_provider::Error> {
682        Ok(self
683            .controller
684            .onion_client_auth_add(service_id, client_auth, None, &Default::default())
685            .map_err(Error::OnionClientAuthAddFailed)?)
686    }
687
688    fn remove_client_auth(
689        &mut self,
690        service_id: &V3OnionServiceId,
691    ) -> Result<(), tor_provider::Error> {
692        Ok(self
693            .controller
694            .onion_client_auth_remove(service_id)
695            .map_err(Error::OnionClientAuthRemoveFailed)?)
696    }
697
698    // connect to an onion service and returns OnionStream
699    fn connect(
700        &mut self,
701        target: TargetAddr,
702        circuit: Option<CircuitToken>,
703    ) -> Result<OnionStream, tor_provider::Error> {
704        if !self.bootstrapped {
705            return Err(Error::LegacyTorNotBootstrapped().into());
706        }
707
708        let socks_listener = self.socks_listener()?;
709        let socks_credentials = match circuit {
710            Some(circuit) => if let Some(circuit) = self.circuit_tokens.get(&circuit) {
711                Some((circuit.username.clone(), circuit.password.clone()))
712            } else {
713                return Err(Error::CircuitTokenInvalid())?;
714            },
715            None => None,
716        };
717
718        let stream = Self::connect_impl(target.clone(), socks_listener, socks_credentials)?;
719
720        Ok(OnionStream {
721            stream: stream.into_inner(),
722            local_addr: None,
723            peer_addr: Some(target),
724        })
725    }
726
727    fn connect_async(
728        &mut self,
729        target: TargetAddr,
730        circuit: Option<CircuitToken>,
731    ) -> Result<ConnectHandle, tor_provider::Error> {
732
733        let socks_listener = self.socks_listener()?;
734        let socks_credentials = match circuit {
735            Some(circuit) => if let Some(circuit) = self.circuit_tokens.get(&circuit) {
736                Some((circuit.username.clone(), circuit.password.clone()))
737            } else {
738                return Err(Error::CircuitTokenInvalid())?;
739            },
740            None => None,
741        };
742
743        let handle = self.next_connect_handle;
744        self.next_connect_handle += 1usize;
745
746        let async_events = Arc::downgrade(&self.async_events);
747
748        // connect to socks listener on background thread
749        std::thread::Builder::new()
750            .spawn(move || {
751                let stream = Self::connect_impl(target.clone(), socks_listener, socks_credentials);
752                if let Some(async_events) = async_events.upgrade() {
753                    let event = match stream {
754                        Ok(stream) => {
755                            let stream = OnionStream {
756                                stream: stream.into_inner(),
757                                local_addr: None,
758                                peer_addr: Some(target),
759                            };
760                            TorEvent::ConnectComplete{
761                                handle,
762                                stream,
763                            }
764                        },
765                        Err(error) => TorEvent::ConnectFailed{
766                            handle,
767                            error,
768                        },
769                    };
770                    let mut async_events = async_events.lock().expect("async_events mutex poisoned");
771                    async_events.push(event);
772                }
773            }).map_err(Error::ConnectAsyncThreadSpawnFailed)?;
774
775        Ok(handle)
776    }
777
778    // stand up an onion service and return an OnionListener
779    fn listener(
780        &mut self,
781        private_key: &Ed25519PrivateKey,
782        virt_port: u16,
783        authorized_clients: Option<&[X25519PublicKey]>,
784    ) -> Result<OnionListener, tor_provider::Error> {
785        if !self.bootstrapped {
786            return Err(Error::LegacyTorNotBootstrapped().into());
787        }
788
789        // try to bind to a local address, let OS pick our port
790        let socket_addr = SocketAddr::from(([127, 0, 0, 1], 0u16));
791        let listener = TcpListener::bind(socket_addr).map_err(Error::TcpListenerBindFailed)?;
792        let socket_addr = listener
793            .local_addr()
794            .map_err(Error::TcpListenerLocalAddrFailed)?;
795
796        let flags = AddOnionFlags {
797            discard_pk: true,
798            v3_auth: authorized_clients.is_some(),
799            ..Default::default()
800        };
801
802        let onion_addr = OnionAddr::V3(OnionAddrV3::new(
803            V3OnionServiceId::from_private_key(private_key),
804            virt_port,
805        ));
806
807        // start onion service
808        let (_, service_id) = self
809            .controller
810            .add_onion(
811                Some(private_key),
812                &flags,
813                None,
814                virt_port,
815                Some(socket_addr),
816                authorized_clients,
817            )
818            .map_err(Error::AddOnionFailed)?;
819
820        let is_active = Arc::new(atomic::AtomicBool::new(true));
821        self.onion_services
822            .push((service_id, Arc::clone(&is_active)));
823
824        Ok(OnionListener::new(listener, onion_addr, is_active, |is_active| {
825            is_active.store(false, atomic::Ordering::Relaxed);
826        }))
827    }
828
829    fn generate_token(&mut self) -> CircuitToken {
830        let new_token = self.circuit_token_counter;
831        self.circuit_token_counter += 1;
832        self.circuit_tokens
833            .insert(new_token, LegacyCircuitToken::new());
834        new_token
835    }
836
837    fn release_token(&mut self, circuit_token: CircuitToken) {
838        self.circuit_tokens.remove(&circuit_token);
839    }
840}