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