1
1
//! A minimal command line program for connecting to the tor network
2
//!
3
//! (If you want a more general Tor client library interface, use [`arti_client`].)
4
//!
5
//! This crate is the primary command-line interface for
6
//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
7
//! implement [Tor](https://www.torproject.org/) in Rust.
8
//! Many other crates in Arti depend on it.
9
//!
10
//! Note that Arti is a work in progress; although we've tried to
11
//! write all the critical security components, you probably shouldn't
12
//! use Arti in production until it's a bit more mature.
13
//!
14
//! More documentation will follow as this program improves.  For now,
15
//! just know that it can run as a simple SOCKS proxy over the Tor network.
16
//! It will listen on port 9150 by default, but you can override this in
17
//! the configuration.
18
//!
19
//! # Command-line interface
20
//!
21
//! (This is not stable; future versions will break this.)
22
//!
23
//! `arti` uses the [`clap`](https://docs.rs/clap/) crate for command-line
24
//! argument parsing; run `arti help` to get it to print its documentation.
25
//!
26
//! The only currently implemented subcommand is `arti proxy`; try
27
//! `arti help proxy` for a list of options you can pass to it.
28
//!
29
//! # Configuration
30
//!
31
//! By default, `arti` looks for its configuration files in a
32
//! platform-dependent location.
33
//!
34
//! | OS      | Configuration File                                 |
35
//! |---------|----------------------------------------------------|
36
//! | Unix    | `~/.config/arti/arti.toml`                         |
37
//! | macOS   | `~/Library/Application Support/arti/arti.toml`     |
38
//! | Windows | `\Users\<USERNAME>\AppData\Roaming\arti\arti.toml` |
39
//!
40
//! The configuration file is TOML.  (We do not guarantee its stability.)
41
//! For an example see [`arti_defaults.toml`](./arti_defaults.toml).
42
//!
43
//! # Compile-time features
44
//!
45
//! `tokio` (default): Use the tokio runtime library as our backend.
46
//!
47
//! `async-std`: Use the async-std runtime library as our backend.
48
//! This feature has no effect unless building with `--no-default-features`
49
//! to disable tokio.
50
//!
51
//! `native-tls` -- Build with support for the `native_tls` TLS
52
//! backend. (default)
53
//!
54
//! `rustls` -- Build with support for the `rustls` TLS backend.
55
//!
56
//! `static` -- Link with static versions of your system dependencies,
57
//! including sqlite and/or openssl.  (⚠ Warning ⚠: this feature will
58
//! include a dependency on native-tls, even if you weren't planning
59
//! to use native-tls.  If you only want to build with a static sqlite
60
//! library, enable the `static-sqlite` feature.  We'll look for
61
//! better solutions here in the future.)
62
//!
63
//! `static-sqlite` -- Link with a static version of sqlite.
64
//!
65
//! `static-native-tls` -- Link with a static version of `native-tls`.
66
//! Enables `native-tls`.
67
//!
68
//! # Limitations
69
//!
70
//! There are many missing features.  Among them: there's no onion
71
//! service support yet. There's no anti-censorship support.  You
72
//! can't be a relay.  There isn't any kind of proxy besides SOCKS.
73
//!
74
//! See the [README
75
//! file](https://gitlab.torproject.org/tpo/core/arti/-/blob/main/README.md)
76
//! for a more complete list of missing features.
77
//!
78
//! # Library for building command-line client
79
//!
80
//! This library crate contains code useful for making
81
//! a command line program similar to `arti`.
82
//! The API should not be considered stable.
83

            
84
#![warn(missing_docs)]
85
#![deny(missing_docs)]
86
#![warn(noop_method_call)]
87
#![deny(unreachable_pub)]
88
#![warn(clippy::all)]
89
#![deny(clippy::await_holding_lock)]
90
#![deny(clippy::cargo_common_metadata)]
91
#![deny(clippy::cast_lossless)]
92
#![deny(clippy::checked_conversions)]
93
#![warn(clippy::cognitive_complexity)]
94
#![deny(clippy::debug_assert_with_mut_call)]
95
#![deny(clippy::exhaustive_enums)]
96
#![deny(clippy::exhaustive_structs)]
97
#![deny(clippy::expl_impl_clone_on_copy)]
98
#![deny(clippy::fallible_impl_from)]
99
#![deny(clippy::implicit_clone)]
100
#![deny(clippy::large_stack_arrays)]
101
#![warn(clippy::manual_ok_or)]
102
#![deny(clippy::missing_docs_in_private_items)]
103
#![deny(clippy::missing_panics_doc)]
104
#![warn(clippy::needless_borrow)]
105
#![warn(clippy::needless_pass_by_value)]
106
#![warn(clippy::option_option)]
107
#![allow(clippy::print_stderr)] // Allowed in this crate only.
108
#![allow(clippy::print_stdout)] // Allowed in this crate only.
109
#![warn(clippy::rc_buffer)]
110
#![deny(clippy::ref_option_ref)]
111
#![warn(clippy::semicolon_if_nothing_returned)]
112
#![warn(clippy::trait_duplication_in_bounds)]
113
#![deny(clippy::unnecessary_wraps)]
114
#![warn(clippy::unseparated_literal_suffix)]
115
#![deny(clippy::unwrap_used)]
116

            
117
pub mod exit;
118
pub mod process;
119
pub mod proxy;
120
pub mod trace;
121
pub mod watch_cfg;
122

            
123
use arti_client::{TorClient, TorClientConfig};
124
use arti_config::{default_config_file, ArtiConfig};
125
use tor_rtcompat::{BlockOn, Runtime};
126

            
127
use anyhow::{Context, Result};
128
use clap::{App, AppSettings, Arg, SubCommand};
129
use tracing::{info, warn};
130

            
131
use std::convert::TryInto;
132

            
133
/// Run the main loop of the proxy.
134
///
135
/// # Panics
136
///
137
/// Currently, might panic if things go badly enough wrong
138
pub async fn run<R: Runtime>(
139
    runtime: R,
140
    socks_port: u16,
141
    config_sources: arti_config::ConfigurationSources,
142
    arti_config: arti_config::ArtiConfig,
143
    client_config: TorClientConfig,
144
) -> Result<()> {
145
    // Using OnDemand arranges that, while we are bootstrapping, incoming connections wait
146
    // for bootstrap to complete, rather than getting errors.
147
    use arti_client::BootstrapBehavior::OnDemand;
148
    use futures::FutureExt;
149
    let client = TorClient::with_runtime(runtime.clone())
150
        .config(client_config)
151
        .bootstrap_behavior(OnDemand)
152
        .create_unbootstrapped()?;
153
    if arti_config.application().watch_configuration() {
154
        watch_cfg::watch_for_config_changes(config_sources, arti_config, client.clone())?;
155
    }
156
    futures::select!(
157
        r = exit::wait_for_ctrl_c().fuse()
158
            => r.context("waiting for termination signal"),
159
        r = proxy::run_socks_proxy(runtime, client.clone(), socks_port).fuse()
160
            => r.context("SOCKS proxy failure"),
161
        r = async {
162
            client.bootstrap().await?;
163
            info!("Sufficiently bootstrapped; system SOCKS now functional.");
164
            futures::future::pending::<Result<()>>().await
165
        }.fuse()
166
            => r.context("bootstrap"),
167
    )
168
}
169

            
170
/// Inner function to allow convenient error handling
171
///
172
/// # Panics
173
///
174
/// Currently, might panic if wrong arguments are specified.
175
pub fn main_main() -> Result<()> {
176
    // We describe a default here, rather than using `default()`, because the
177
    // correct behavior is different depending on whether the filename is given
178
    // explicitly or not.
179
    let mut config_file_help = "Specify which config file(s) to read.".to_string();
180
    if let Some(default) = arti_config::default_config_file() {
181
        config_file_help.push_str(&format!(" Defaults to {:?}", default));
182
    }
183

            
184
    let matches =
185
        App::new("Arti")
186
            .version(env!("CARGO_PKG_VERSION"))
187
            .author("The Tor Project Developers")
188
            .about("A Rust Tor implementation.")
189
            // HACK(eta): clap generates "arti [OPTIONS] <SUBCOMMAND>" for this usage string by
190
            //            default, but then fails to parse options properly if you do put them
191
            //            before the subcommand.
192
            //            We just declare all options as `global` and then require them to be
193
            //            put after the subcommand, hence this new usage string.
194
            .usage("arti <SUBCOMMAND> [OPTIONS]")
195
            .arg(
196
                Arg::with_name("config-files")
197
                    .short("c")
198
                    .long("config")
199
                    .takes_value(true)
200
                    .value_name("FILE")
201
                    .multiple(true)
202
                    // NOTE: don't forget the `global` flag on all arguments declared at this level!
203
                    .global(true)
204
                    .help(&config_file_help),
205
            )
206
            .arg(
207
                Arg::with_name("option")
208
                    .short("o")
209
                    .takes_value(true)
210
                    .value_name("KEY=VALUE")
211
                    .multiple(true)
212
                    .global(true)
213
                    .help("Override config file parameters, using TOML-like syntax."),
214
            )
215
            .arg(
216
                Arg::with_name("loglevel")
217
                    .short("l")
218
                    .long("log-level")
219
                    .global(true)
220
                    .takes_value(true)
221
                    .value_name("LEVEL")
222
                    .help("Override the log level (usually one of 'trace', 'debug', 'info', 'warn', 'error')."),
223
            )
224
            .subcommand(
225
                SubCommand::with_name("proxy")
226
                    .about(
227
                        "Run Arti in SOCKS proxy mode, proxying connections through the Tor network.",
228
                    )
229
                    .arg(
230
                        Arg::with_name("socks-port")
231
                            .short("p")
232
                            .takes_value(true)
233
                            .value_name("PORT")
234
                            .help("Port to listen on for SOCKS connections (overrides the port in the config if specified).")
235
                    )
236
            )
237
            .setting(AppSettings::SubcommandRequiredElseHelp)
238
            .get_matches();
239

            
240
    let cfg_sources = {
241
        let mut cfg_sources = arti_config::ConfigurationSources::new();
242

            
243
        let config_files = matches.values_of_os("config-files").unwrap_or_default();
244
        if config_files.len() == 0 {
245
            if let Some(default) = default_config_file() {
246
                cfg_sources.push_optional_file(default);
247
            }
248
        } else {
249
            config_files.for_each(|f| cfg_sources.push_file(f));
250
        }
251

            
252
        matches
253
            .values_of("option")
254
            .unwrap_or_default()
255
            .for_each(|s| cfg_sources.push_option(s));
256

            
257
        cfg_sources
258
    };
259

            
260
    let cfg = cfg_sources.load()?;
261

            
262
    let config: ArtiConfig = cfg.try_into().context("read configuration")?;
263

            
264
    let _log_guards = trace::setup_logging(config.logging(), matches.value_of("loglevel"))?;
265

            
266
    if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
267
        let socks_port = match (
268
            proxy_matches.value_of("socks-port"),
269
            config.proxy().socks_port(),
270
        ) {
271
            (Some(p), _) => p.parse().expect("Invalid port specified"),
272
            (None, Some(s)) => s,
273
            (None, None) => {
274
                warn!(
275
                "No SOCKS port set; specify -p PORT or use the `socks_port` configuration option."
276
            );
277
                return Ok(());
278
            }
279
        };
280

            
281
        let client_config = config.tor_client_config()?;
282

            
283
        info!(
284
            "Starting Arti {} in SOCKS proxy mode on port {}...",
285
            env!("CARGO_PKG_VERSION"),
286
            socks_port
287
        );
288

            
289
        process::use_max_file_limit(&client_config);
290

            
291
        cfg_if::cfg_if! {
292
            if #[cfg(all(feature="tokio", feature="native-tls"))] {
293
            use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
294
            } else if #[cfg(all(feature="tokio", feature="rustls"))] {
295
                use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
296
            } else if #[cfg(all(feature="async-std", feature="native-tls"))] {
297
                use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
298
            } else if #[cfg(all(feature="async-std", feature="rustls"))] {
299
                use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
300
            }
301
        }
302

            
303
        let runtime = ChosenRuntime::create()?;
304

            
305
        let rt_copy = runtime.clone();
306
        rt_copy.block_on(run(runtime, socks_port, cfg_sources, config, client_config))?;
307
        Ok(())
308
    } else {
309
        panic!("Subcommand added to clap subcommand list, but not yet implemented")
310
    }
311
}
312

            
313
/// Main program, callable directly from a binary crate's `main`
314
pub fn main() {
315
    main_main().unwrap_or_else(tor_error::report_and_exit);
316
}