Skip to main content

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