├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── libtor-derive ├── Cargo.toml ├── README.md └── src │ └── lib.rs └── libtor ├── Cargo.toml ├── README.md └── src ├── hs.rs ├── lib.rs ├── log.rs ├── ports.rs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *.swp 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | # - 1.31.0 5 | # - 1.22.0 6 | before_script: 7 | - rustup component add rustfmt 8 | script: 9 | - cargo fmt -- --check --verbose 10 | - cd libtor-derive 11 | - cargo fmt -- --check --verbose 12 | - cd .. 13 | - cargo test -vv 14 | 15 | before_cache: 16 | - rm -rf "$TRAVIS_HOME/.cargo/registry/src" 17 | cache: cargo 18 | 19 | notifications: 20 | email: false 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "libtor", 5 | "libtor-derive", 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Magical Bitcoin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libtor 2 | 3 | `libtor` is a Rust crate for bundling inside your project a fully-running Tor daemon. 4 | It exposes a nicer interface to operate it compared to the bare-bones version, [libtor-sys](https://crates.io/crates/libtor-sys). 5 | If you need further instructions on how to build or cross-compile the project, you should refer to the [`libtor-sys` README.md](https://github.com/MagicalBitcoin/libtor-sys). 6 | 7 | `libtor` makes it easier for projects on multiple platforms to use Tor without depending on the user to configure complex proxy settings from other external software. 8 | 9 | ## Example 10 | 11 | ``` 12 | use libtor::{Tor, TorFlag, TorAddress, HiddenServiceVersion}; 13 | 14 | Tor::new() 15 | .flag(TorFlag::DataDirectory("/tmp/tor-rust".into())) 16 | .flag(TorFlag::SocksPort(19050)) 17 | .flag(TorFlag::HiddenServiceDir("/tmp/tor-rust/hs-dir".into())) 18 | .flag(TorFlag::HiddenServiceVersion(HiddenServiceVersion::V3)) 19 | .flag(TorFlag::HiddenServicePort(TorAddress::Port(8000), None.into())) 20 | .start()?; 21 | ``` 22 | 23 | Since Tor uses internally some static variables to keep its state, keep in mind that **you can't start more than one Tor instance per process**. 24 | 25 | ## Supported platforms 26 | 27 | The currently supported platforms are: 28 | 29 | * Linux (tested on Fedora 30 and Ubuntu Xenial) 30 | * Android through the NDK 31 | * MacOS 32 | * iOS 33 | * Windows cross-compiled from Linux with `mingw` 34 | 35 | Coming Soon :tm:: 36 | 37 | * Windows (natively built) 38 | 39 | ## Dependencies 40 | 41 | The following dependencies are needed: 42 | - `openssl` 43 | - `pkg-config` 44 | - `file` 45 | - the "usual" C build tools: a compiler, `automake`, `autoconf` 46 | -------------------------------------------------------------------------------- /libtor-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libtor-derive" 3 | version = "0.1.2" 4 | authors = ["Alekos Filini "] 5 | license = "MIT" 6 | homepage = "https://github.com/MagicalBitcoin/libtor" 7 | repository = "https://github.com/MagicalBitcoin/libtor" 8 | documentation = "https://docs.rs/libtor/" 9 | description = "Internal #[derive] macros for libtor" 10 | keywords = ["tor", "daemon"] 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | proc-macro2 = "1.0" 15 | syn = "1.0" 16 | quote = "1.0" 17 | 18 | [lib] 19 | proc-macro = true 20 | 21 | [features] 22 | debug = ["syn/extra-traits"] 23 | -------------------------------------------------------------------------------- /libtor-derive/README.md: -------------------------------------------------------------------------------- 1 | # libtor-derive 2 | 3 | Internal macros used by [libtor](https://crates.io/crates/libtor) 4 | -------------------------------------------------------------------------------- /libtor-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | extern crate proc_macro2; 3 | extern crate quote; 4 | extern crate syn; 5 | 6 | use proc_macro2::{Ident, Span, TokenStream}; 7 | use quote::{format_ident, quote, quote_spanned}; 8 | use syn::parse::{Parse, ParseStream}; 9 | use syn::spanned::Spanned; 10 | use syn::{braced, parenthesized, parse_macro_input, token, Data, DeriveInput, Fields, Token}; 11 | 12 | #[cfg_attr(feature = "debug", derive(Debug))] 13 | struct ExpandToArg { 14 | keyword: Ident, 15 | name: syn::Lit, 16 | } 17 | 18 | impl Parse for ExpandToArg { 19 | fn parse(input: ParseStream) -> Result { 20 | let keyword = input.parse()?; 21 | input.parse::()?; 22 | let name = input.parse()?; 23 | 24 | Ok(ExpandToArg { keyword, name }) 25 | } 26 | } 27 | 28 | #[cfg_attr(feature = "debug", derive(Debug))] 29 | struct TestStruct { 30 | args_group: Option, 31 | expected: syn::LitStr, 32 | } 33 | 34 | impl Parse for TestStruct { 35 | fn parse(input: ParseStream) -> Result { 36 | let keyword: Ident = input.parse()?; 37 | if keyword != "test" { 38 | return Err(syn::Error::new(keyword.span(), "expected `test`")); 39 | } 40 | input.parse::()?; 41 | 42 | let args_group: Option = if input.peek(token::Brace) { 43 | let content; 44 | braced!(content in input); 45 | let content: TokenStream = content.parse()?; 46 | 47 | Some(quote! { 48 | { #content } 49 | }) 50 | } else if input.peek(token::Paren) { 51 | let content; 52 | parenthesized!(content in input); 53 | let content: TokenStream = content.parse()?; 54 | 55 | Some(quote! { 56 | ( #content ) 57 | }) 58 | } else { 59 | None 60 | }; 61 | 62 | input.parse::]>()?; 63 | 64 | let expected = input.parse()?; 65 | if let syn::Lit::Str(expected) = expected { 66 | Ok(TestStruct { 67 | args_group, 68 | expected, 69 | }) 70 | } else { 71 | Err(syn::Error::new(keyword.span(), "expected a string literal")) 72 | } 73 | } 74 | } 75 | 76 | fn split_first_space_args(val: TokenStream) -> TokenStream { 77 | quote! { 78 | { 79 | let formatted = #val; 80 | let parts = formatted.splitn(2, " ").collect::>(); 81 | 82 | let mut answer = vec![parts[0].to_string()]; 83 | if let Some(part) = parts.get(1) { 84 | answer.push(part.to_string()); 85 | } 86 | 87 | answer 88 | } 89 | } 90 | } 91 | 92 | fn generate_test( 93 | parsed: TestStruct, 94 | test_count: usize, 95 | enum_name: &Ident, 96 | name: &Ident, 97 | span: Span, 98 | ) -> TokenStream { 99 | let test_name = format_ident!("TEST_{}_{}", name, test_count); 100 | let args_group = &parsed.args_group.unwrap_or_default(); 101 | let expected = &parsed.expected; 102 | 103 | quote_spanned! {span=> 104 | #[test] 105 | fn #test_name() { 106 | use Expand; 107 | 108 | let v = #enum_name::#name#args_group; 109 | println!("{:?} => {}", v, v.expand_cli()); 110 | assert_eq!(v.expand_cli(), #expected); 111 | } 112 | } 113 | } 114 | 115 | #[proc_macro_derive(Expand, attributes(expand_to))] 116 | pub fn derive_helper_attr(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 117 | let input = parse_macro_input!(input as DeriveInput); 118 | let enum_name = &input.ident; 119 | 120 | let (match_body, test_funcs) = match input.data { 121 | Data::Enum(data) => { 122 | let mut stream = TokenStream::new(); 123 | let mut test_stream = TokenStream::new(); 124 | 125 | for variant in data.variants { 126 | let span = &variant.span(); 127 | let name = &variant.ident; 128 | let mut name_string = name.to_string(); 129 | let mut test_count = 0; 130 | let mut implemented_with = false; 131 | 132 | let mut fmt_attr = None; 133 | 134 | for attr in &variant.attrs { 135 | if attr.path.get_ident() != Some(&format_ident!("expand_to")) { 136 | continue; 137 | } 138 | 139 | if attr.parse_args::().is_ok() { 140 | fmt_attr = Some(attr); 141 | } else if let Ok(arg) = attr.parse_args::() { 142 | if arg.keyword == "rename" { 143 | if let syn::Lit::Str(lit_str) = arg.name { 144 | name_string = lit_str.value(); 145 | } else { 146 | let tokens = quote_spanned! {*span=> 147 | #enum_name::#name{..} => compile_error!("`rename` must be followed by a string literal, eg #[expand_to(rename = \"example\")]"), 148 | }; 149 | stream.extend(tokens); 150 | } 151 | } else if arg.keyword == "with" { 152 | let tokens = if let syn::Lit::Str(lit_str) = arg.name { 153 | let ident = Ident::new(&lit_str.value(), *span); 154 | 155 | let matcher = match variant.fields { 156 | Fields::Unnamed(_) => quote! { 157 | #enum_name::#name(..) 158 | }, 159 | Fields::Named(_) => quote! { 160 | #enum_name::#name{..} 161 | }, 162 | Fields::Unit => quote! { 163 | #enum_name::#name() 164 | }, 165 | }; 166 | 167 | quote_spanned! {*span=> 168 | #matcher => #ident(self), 169 | } 170 | } else { 171 | quote_spanned! {*span=> 172 | #enum_name::#name{..} => compile_error!("`with` must be followed by a string literal, eg #[expand_to(with = \"my_custom_function\")]"), 173 | } 174 | }; 175 | 176 | stream.extend(tokens); 177 | implemented_with = true; 178 | } 179 | } else { 180 | // TODO: add those example as doc attributes 181 | if let Ok(parsed) = attr.parse_args::() { 182 | test_stream 183 | .extend(generate_test(parsed, test_count, enum_name, name, *span)); 184 | test_count += 1; 185 | } 186 | } 187 | } 188 | 189 | if implemented_with { 190 | continue; 191 | } 192 | 193 | let ignore_filter = |field: &&syn::Field| { 194 | !field.attrs.iter().any(|a| { 195 | a.parse_args::() 196 | .and_then(|ident| Ok(ident == "ignore")) 197 | .unwrap_or(false) 198 | }) 199 | }; 200 | let tokens = match (variant.fields, fmt_attr) { 201 | (Fields::Named(_), None) => { 202 | quote_spanned! {*span=> 203 | #enum_name::#name{..} => compile_error!("Named fields require an explicit expansion attribute"), 204 | } 205 | } 206 | (Fields::Named(fields), Some(attr)) => { 207 | let args: TokenStream = attr.parse_args().unwrap(); 208 | 209 | let fmt_params = fields.named.iter().filter(ignore_filter).map(|f| { 210 | let ident = &f.ident; 211 | quote_spanned! {f.span()=> #ident = #ident } 212 | }); 213 | let expand_params = fields.named.iter().map(|f| { 214 | let ident = &f.ident; 215 | quote_spanned! {f.span()=> #ident } 216 | }); 217 | 218 | let fmt_str_quoted = quote! { format!(#args, #(#fmt_params, )*) }; 219 | let content = split_first_space_args(fmt_str_quoted); 220 | quote_spanned! {attr.span()=> 221 | #enum_name::#name{#(#expand_params, )*} => { 222 | #content 223 | }, 224 | } 225 | } 226 | (Fields::Unnamed(fields), attr) => { 227 | let expand_params = (0..fields.unnamed.len()) 228 | .map(|i| Ident::new(&format!("p_{}", i), i.span())); 229 | let fmt_params = (0..fields.unnamed.len()) 230 | .filter(|i| ignore_filter(&&fields.unnamed[*i])) 231 | .map(|i| Ident::new(&format!("p_{}", i), i.span())); 232 | 233 | if let Some(attr) = attr { 234 | let args: TokenStream = attr.parse_args().unwrap(); 235 | let fmt_str_quoted = quote! { format!(#args, #(#fmt_params, )*) }; 236 | let content = split_first_space_args(fmt_str_quoted); 237 | quote_spanned! {*span=> 238 | #enum_name::#name(#(#expand_params, )*) => { 239 | #content 240 | }, 241 | } 242 | } else { 243 | let fmt_str = (0..fields.unnamed.len()) 244 | .map(|_| "{}") 245 | .collect::>() 246 | .join(" "); 247 | quote_spanned! {*span=> 248 | #enum_name::#name(#(#expand_params, )*) => vec![#name_string.to_string(), format!(#fmt_str, #(#fmt_params, )*)], 249 | } 250 | } 251 | } 252 | (Fields::Unit, None) => quote! { 253 | #enum_name::#name => vec![#name_string.to_string()], 254 | }, 255 | (Fields::Unit, Some(attr)) => { 256 | let args: TokenStream = attr.parse_args().unwrap(); 257 | let args_str_quoted = quote! { #args.to_string() }; 258 | let content = split_first_space_args(args_str_quoted); 259 | quote! { 260 | #enum_name::#name => #content, 261 | } 262 | } 263 | }; 264 | 265 | stream.extend(tokens); 266 | } 267 | 268 | (stream, test_stream) 269 | } 270 | _ => unimplemented!(), 271 | }; 272 | 273 | let test_mod_name = Ident::new( 274 | &format!("_GENERATED_TESTS_FOR_{}", enum_name), 275 | enum_name.span(), 276 | ); 277 | 278 | let name = input.ident; 279 | let expanded = quote! { 280 | impl Expand for #name { 281 | fn expand(&self) -> Vec { 282 | #[allow(unused)] 283 | #[allow(clippy::useless_format)] 284 | match self { 285 | #match_body 286 | } 287 | } 288 | } 289 | 290 | #[cfg(test)] 291 | #[allow(non_snake_case)] 292 | mod #test_mod_name { 293 | use super::*; 294 | 295 | #test_funcs 296 | } 297 | }; 298 | 299 | proc_macro::TokenStream::from(expanded) 300 | } 301 | -------------------------------------------------------------------------------- /libtor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libtor" 3 | version = "47.13.0+0.4.7.x" 4 | authors = ["Alekos Filini "] 5 | license = "MIT" 6 | homepage = "https://github.com/MagicalBitcoin/libtor" 7 | repository = "https://github.com/MagicalBitcoin/libtor" 8 | documentation = "https://docs.rs/libtor/" 9 | description = "Bundle and run Tor inside your own project" 10 | keywords = ["tor", "daemon"] 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | libtor-sys = "^47.13" 15 | libtor-derive = "^0.1.2" 16 | log = "^0.4" 17 | serde = { version = "1.0.130", features = ["derive"], optional = true } 18 | rand = "0.8" 19 | sha1 = "0.6" 20 | 21 | [features] 22 | vendored-openssl = ["libtor-sys/vendored-openssl"] 23 | vendored-lzma = ["libtor-sys/vendored-lzma"] 24 | vendored-zstd = ["libtor-sys/vendored-zstd"] 25 | with-lzma = ["libtor-sys/with-lzma"] 26 | with-zstd = ["libtor-sys/with-zstd"] 27 | -------------------------------------------------------------------------------- /libtor/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /libtor/src/hs.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Hidden service version 5 | #[derive(Debug, Clone, Copy)] 6 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 7 | pub enum HiddenServiceVersion { 8 | #[deprecated(note = "Please migrate to V3 hidden services")] 9 | V2 = 2, 10 | V3 = 3, 11 | } 12 | 13 | impl std::fmt::Display for HiddenServiceVersion { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | write!(f, "{}", *self as u8) 16 | } 17 | } 18 | 19 | /// Hidden service authorization type for authorized clients 20 | #[derive(Debug, Clone, Copy)] 21 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 22 | pub enum HiddenServiceAuthType { 23 | Basic, 24 | Stealth, 25 | } 26 | -------------------------------------------------------------------------------- /libtor/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # libtor 2 | //! 3 | //! Bundle and run Tor in your own project with this library! 4 | //! 5 | //! # Example 6 | //! 7 | //! ```no_run 8 | //! use libtor::{Tor, TorFlag, TorAddress, HiddenServiceVersion}; 9 | //! 10 | //! Tor::new() 11 | //! .flag(TorFlag::DataDirectory("/tmp/tor-rust".into())) 12 | //! .flag(TorFlag::SocksPort(19050)) 13 | //! .flag(TorFlag::HiddenServiceDir("/tmp/tor-rust/hs-dir".into())) 14 | //! .flag(TorFlag::HiddenServiceVersion(HiddenServiceVersion::V3)) 15 | //! .flag(TorFlag::HiddenServicePort(TorAddress::Port(8000), None.into())) 16 | //! .start()?; 17 | //! # Ok::<(), libtor::Error>(()) 18 | //! ``` 19 | 20 | #[macro_use] 21 | extern crate libtor_derive; 22 | extern crate log as log_crate; 23 | extern crate rand; 24 | extern crate sha1; 25 | extern crate tor_sys; 26 | 27 | #[cfg(feature = "serde")] 28 | extern crate serde; 29 | 30 | #[cfg(feature = "serde")] 31 | use serde::{Deserialize, Serialize}; 32 | 33 | use std::ffi::CString; 34 | use std::thread::{self, JoinHandle}; 35 | 36 | use rand::Rng; 37 | 38 | #[allow(unused_imports)] 39 | use log_crate::{debug, error, info, trace}; 40 | 41 | #[macro_use] 42 | mod utils; 43 | /// Hidden services related flags 44 | pub mod hs; 45 | /// Log related flags 46 | pub mod log; 47 | /// ControlPort and SocksPort related flags 48 | pub mod ports; 49 | 50 | pub use crate::hs::*; 51 | pub use crate::log::*; 52 | pub use crate::ports::*; 53 | use crate::utils::*; 54 | 55 | trait Expand: std::fmt::Debug { 56 | fn expand(&self) -> Vec; 57 | 58 | fn expand_cli(&self) -> String { 59 | let mut parts = self.expand(); 60 | if parts.len() > 1 { 61 | let args = parts.drain(1..).collect::>().join(" "); 62 | parts.push(format!("\"{}\"", args)); 63 | } 64 | 65 | parts.join(" ") 66 | } 67 | } 68 | 69 | /// Enum that represents the size unit both in bytes and bits 70 | #[derive(Debug, Clone, Copy)] 71 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 72 | pub enum SizeUnit { 73 | Bytes, 74 | KBytes, 75 | MBytes, 76 | GBytes, 77 | TBytes, 78 | Bits, 79 | KBits, 80 | MBits, 81 | GBits, 82 | TBits, 83 | } 84 | 85 | display_like_debug!(SizeUnit); 86 | 87 | /// Enum that represents a bool, rendered as `1` for true/enabled and `0` for false/disabled 88 | #[derive(Debug, Clone, Copy)] 89 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 90 | pub enum TorBool { 91 | True, 92 | False, 93 | Enabled, 94 | Disabled, 95 | } 96 | 97 | impl std::fmt::Display for TorBool { 98 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 99 | let val = match self { 100 | TorBool::True | TorBool::Enabled => 1, 101 | TorBool::False | TorBool::Disabled => 0, 102 | }; 103 | write!(f, "{}", val) 104 | } 105 | } 106 | 107 | impl From for TorBool { 108 | fn from(other: bool) -> TorBool { 109 | if other { 110 | TorBool::True 111 | } else { 112 | TorBool::False 113 | } 114 | } 115 | } 116 | 117 | fn log_expand(flag: &TorFlag) -> Vec { 118 | let (levels, dest) = match flag { 119 | TorFlag::Log(level) => (vec![(vec![], *level)], None), 120 | TorFlag::LogTo(level, dest) => (vec![(vec![], *level)], Some(dest)), 121 | TorFlag::LogExpanded(expanded_level, dest) => (expanded_level.clone(), Some(dest)), 122 | _ => unimplemented!(), 123 | }; 124 | 125 | let levels_str = levels 126 | .iter() 127 | .map(|(domains, level)| { 128 | let mut concat_str = domains 129 | .iter() 130 | .map(|(enabled, domain)| { 131 | let enabled_char = if *enabled { "" } else { "~" }; 132 | format!("{}{:?}", enabled_char, domain) 133 | }) 134 | .collect::>() 135 | .join(","); 136 | if !concat_str.is_empty() { 137 | concat_str = format!("[{}]", concat_str); 138 | } 139 | 140 | format!("{}{:?}", concat_str, level).to_lowercase() 141 | }) 142 | .collect::>() 143 | .join(" "); 144 | let dest_str = dest 145 | .map(|d| format!(" {}", d).to_lowercase()) 146 | .unwrap_or_default(); 147 | 148 | vec!["Log".into(), format!("{}{}", levels_str, dest_str)] 149 | } 150 | 151 | /// Enum used to represent the generic concept of an "Address" 152 | /// 153 | /// It can also represent Unix sockets on platforms that support them. 154 | #[derive(Debug, Clone)] 155 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 156 | pub enum TorAddress { 157 | /// Shorthand to only encode the port 158 | Port(u16), 159 | /// Shorthand to only encode the address 160 | Address(String), 161 | /// Explicit version that encodes both the address and the port 162 | AddressPort(String, u16), 163 | /// Path to a Unix socket 164 | #[cfg(target_family = "unix")] 165 | Unix(String), 166 | } 167 | 168 | impl std::fmt::Display for TorAddress { 169 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 170 | match self { 171 | TorAddress::Port(port) => write!(f, "{}", port), 172 | TorAddress::Address(addr) => write!(f, "{}", addr), 173 | TorAddress::AddressPort(addr, port) => write!(f, "{}:{}", addr, port), 174 | #[cfg(target_family = "unix")] 175 | TorAddress::Unix(path) => write!(f, "unix:{}", path), 176 | } 177 | } 178 | } 179 | 180 | /// Enum that represents a subset of the options supported by Tor 181 | /// 182 | /// Generally speaking, all the server-only features have not been mapped since this crate is 183 | /// targeted more to a client-like usage. Arbitrary flags can still be added using the 184 | /// `TorFlag::Custom(String)` variant. 185 | #[derive(Debug, Clone, Expand)] 186 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 187 | pub enum TorFlag { 188 | #[expand_to("-f {}")] 189 | #[expand_to(test = ("filename".into()) => "-f \"filename\"")] 190 | ConfigFile(String), 191 | #[expand_to("--passphrase-fd {}")] 192 | PassphraseFD(u32), 193 | 194 | #[expand_to(test = (256, SizeUnit::MBits) => "BandwidthRate \"256 MBits\"")] 195 | BandwidthRate(usize, SizeUnit), 196 | BandwidthBurst(usize, SizeUnit), 197 | #[expand_to(test = (true.into()) => "DisableNetwork \"1\"")] 198 | DisableNetwork(TorBool), 199 | 200 | ControlPort(u16), 201 | #[expand_to("ControlPort auto")] 202 | ControlPortAuto, 203 | #[expand_to("ControlPort {} {}")] 204 | #[cfg_attr(target_family = "unix", expand_to(test = (TorAddress::Unix("/tmp/tor-cp".into()), Some(vec![ControlPortFlag::GroupWritable].into()).into()) => "ControlPort \"unix:/tmp/tor-cp GroupWritable\""))] 205 | #[cfg_attr(target_family = "unix", expand_to(test = (TorAddress::Unix("/tmp/tor-cp".into()), Some(vec![ControlPortFlag::GroupWritable, ControlPortFlag::RelaxDirModeCheck].into()).into()) => "ControlPort \"unix:/tmp/tor-cp GroupWritable RelaxDirModeCheck\""))] 206 | ControlPortAddress( 207 | TorAddress, 208 | DisplayOption>, 209 | ), 210 | 211 | #[cfg(target_family = "unix")] 212 | ControlSocket(String), 213 | #[cfg(target_family = "unix")] 214 | ControlSocketsGroupWritable(TorBool), 215 | 216 | HashedControlPassword(String), 217 | CookieAuthentication(TorBool), 218 | CookieAuthFile(String), 219 | CookieAuthFileGroupReadable(TorBool), 220 | ControlPortWriteToFile(String), 221 | ControlPortFileGroupReadable(TorBool), 222 | 223 | DataDirectory(String), 224 | DataDirectoryGroupReadable(TorBool), 225 | CacheDirectory(String), 226 | CacheDirectoryGroupReadable(String), 227 | 228 | HTTPSProxy(String), 229 | #[expand_to("HTTPSProxyAuthenticator {}:{}")] 230 | #[expand_to(test = ("user".into(), "pass".into()) => "HTTPSProxyAuthenticator \"user:pass\"")] 231 | HTTPSProxyAuthenticator(String, String), 232 | Socks4Proxy(String), 233 | Socks5Proxy(String), 234 | Socks5ProxyUsername(String), 235 | Socks5ProxyPassword(String), 236 | 237 | UnixSocksGroupWritable(TorBool), 238 | 239 | KeepalivePeriod(usize), 240 | 241 | #[expand_to(with = "log_expand")] 242 | #[expand_to(test = (LogLevel::Notice) => "Log \"notice\"")] 243 | Log(LogLevel), 244 | #[expand_to(with = "log_expand")] 245 | #[expand_to(test = (LogLevel::Notice, LogDestination::File("/dev/null".into())) => "Log \"notice file /dev/null\"")] 246 | #[expand_to(test = (LogLevel::Notice, LogDestination::Stdout) => "Log \"notice stdout\"")] 247 | LogTo(LogLevel, LogDestination), 248 | #[expand_to(with = "log_expand")] 249 | #[expand_to(test = (vec![(vec![(true, LogDomain::Handshake)], LogLevel::Debug), (vec![(false, LogDomain::Net), (false, LogDomain::Mm)], LogLevel::Info), (vec![], LogLevel::Notice)], LogDestination::Stdout) => "Log \"[handshake]debug [~net,~mm]info notice stdout\"")] 250 | LogExpanded(Vec<(Vec<(bool, LogDomain)>, LogLevel)>, LogDestination), 251 | LogMessageDomains(TorBool), 252 | 253 | LogTimeGranularity(usize), 254 | TruncateLogFile(TorBool), 255 | SyslogIdentityTag(String), 256 | AndroidIdentityTag(String), 257 | SafeLogging(TorBool), // TODO: 'relay' unsupported at the moment 258 | 259 | PidFile(String), 260 | ProtocolWarnings(TorBool), 261 | 262 | User(String), 263 | NoExec(TorBool), 264 | 265 | Bridge(String, String, String), 266 | 267 | ConnectionPadding(TorBool), // TODO: 'auto' not supported at the moment 268 | ReducedConnectionPadding(TorBool), 269 | CircuitPadding(TorBool), 270 | ReducedCircuitPadding(TorBool), 271 | 272 | ExcludeNodes(DisplayVec), 273 | ExcludeExitNodes(DisplayVec), 274 | ExitNodes(DisplayVec), 275 | MiddleNodes(DisplayVec), 276 | EntryNodes(DisplayVec), 277 | StrictNodes(TorBool), 278 | 279 | FascistFirewall(TorBool), 280 | FirewallPorts(DisplayVec), 281 | 282 | MapAddress(String, String), 283 | NewCircuitPeriod(usize), 284 | 285 | SocksPort(u16), 286 | #[expand_to("SocksPort auto")] 287 | SocksPortAuto, 288 | #[expand_to(rename = "SocksPort")] 289 | SocksPortAddress( 290 | TorAddress, 291 | DisplayOption>, 292 | DisplayOption>, 293 | ), 294 | SocksTimeout(usize), 295 | SafeSocks(TorBool), 296 | TestSocks(TorBool), 297 | 298 | UpdateBridgesFromAuthority(TorBool), 299 | UseBridges(TorBool), 300 | 301 | HiddenServiceDir(String), 302 | HiddenServicePort(TorAddress, DisplayOption), 303 | HiddenServiceVersion(HiddenServiceVersion), 304 | #[expand_to("HiddenServiceAuthorizeClient {:?} {}")] 305 | HiddenServiceAuthorizeClient(HiddenServiceAuthType, DisplayVec), 306 | HiddenServiceAllowUnknownPorts(TorBool), 307 | HiddenServiceMaxStreams(usize), 308 | HiddenServiceMaxStreamsCloseCircuit(TorBool), 309 | 310 | /// Custom argument, expanded as ` " ..."` 311 | #[expand_to("{}")] 312 | Custom(String), 313 | 314 | /// Don't log anything to the console 315 | #[expand_to("--quiet")] 316 | Quiet(), 317 | /// Only log warnings and errors to the console 318 | #[expand_to("--hush")] 319 | Hush(), 320 | } 321 | 322 | /// Error enum 323 | #[derive(Debug, Clone)] 324 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 325 | pub enum Error { 326 | NotRunning, 327 | } 328 | 329 | impl std::fmt::Display for Error { 330 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 331 | match self { 332 | Error::NotRunning => write!(f, "Tor service is not running"), 333 | } 334 | } 335 | } 336 | 337 | impl std::error::Error for Error {} 338 | 339 | /// Configuration builder for a Tor daemon 340 | /// 341 | /// Offers the ability to set multiple flags and then start the daemon either in the current 342 | /// thread, or in a new one 343 | #[derive(Debug, Clone, Default)] 344 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 345 | pub struct Tor { 346 | flags: Vec, 347 | } 348 | 349 | impl Tor { 350 | /// Create a new instance 351 | pub fn new() -> Tor { 352 | Default::default() 353 | } 354 | 355 | /// Add a configuration flag 356 | pub fn flag(&mut self, flag: TorFlag) -> &mut Tor { 357 | self.flags.push(flag); 358 | self 359 | } 360 | 361 | /// Start the Tor daemon in the current thread 362 | pub fn start(&self) -> Result { 363 | unsafe { 364 | let config = tor_sys::tor_main_configuration_new(); 365 | let mut argv = vec![String::from("tor")]; 366 | argv.extend_from_slice( 367 | &self 368 | .flags 369 | .iter() 370 | .map(TorFlag::expand) 371 | .flatten() 372 | .collect::>(), 373 | ); 374 | 375 | debug!("Starting tor with args: {:#?}", argv); 376 | 377 | let argv: Vec<_> = argv.into_iter().map(|s| CString::new(s).unwrap()).collect(); 378 | let argv: Vec<_> = argv.iter().map(|s| s.as_ptr()).collect(); 379 | tor_sys::tor_main_configuration_set_command_line( 380 | config, 381 | argv.len() as i32, 382 | argv.as_ptr(), 383 | ); 384 | 385 | let result = tor_sys::tor_run_main(config); 386 | 387 | tor_sys::tor_main_configuration_free(config); 388 | 389 | Ok(result as u8) 390 | } 391 | } 392 | 393 | /// Starts the Tor daemon in a background detached thread and return its handle 394 | pub fn start_background(&self) -> JoinHandle> { 395 | let cloned = self.clone(); 396 | thread::spawn(move || cloned.start()) 397 | } 398 | } 399 | 400 | /// Generate a hashed password to use HashedControlPassword 401 | pub fn generate_hashed_password(secret: &str) -> String { 402 | // This code is rewrite of 403 | // https://gist.github.com/s4w3d0ff/9d65ec5866d78842547183601b2fa4d5 404 | // s4w3d0ff and jamesacampbell, Thank you! 405 | 406 | let c: usize = 96; 407 | const EXPBIAS: usize = 6; 408 | let mut count = (16_usize + (c & 15_usize)) << ((c >> 4_usize) + EXPBIAS); 409 | let mut d = sha1::Sha1::new(); 410 | 411 | let slen = 8 + (secret.as_bytes().len()); 412 | let mut tmp = Vec::with_capacity(slen); 413 | tmp.extend_from_slice(&rand::rngs::OsRng.gen::().to_ne_bytes()); 414 | tmp.extend_from_slice(secret.as_bytes()); 415 | 416 | while count != 0 { 417 | if count > slen { 418 | d.update(&tmp); 419 | count -= slen; 420 | } else { 421 | d.update(&tmp[..count]); 422 | break; 423 | } 424 | } 425 | let hashed = d.digest().to_string(); 426 | let tmp = tmp[..8] 427 | .iter() 428 | .map(|n| format!("{:02x}", n)) 429 | .collect::(); 430 | 431 | format!("16:{}{:02x}{}", tmp, c as u8, hashed) 432 | } 433 | 434 | #[cfg(test)] 435 | mod tests { 436 | use crate::*; 437 | 438 | #[test] 439 | #[ignore] 440 | fn test_run() { 441 | Tor::new() 442 | .flag(TorFlag::DataDirectory("/tmp/tor-rust".into())) 443 | .flag(TorFlag::HiddenServiceDir("/tmp/tor-rust/hs-dir".into())) 444 | .flag(TorFlag::HiddenServiceVersion(HiddenServiceVersion::V3)) 445 | .flag(TorFlag::HiddenServicePort( 446 | TorAddress::Port(80), 447 | Some(TorAddress::AddressPort("example.org".into(), 80)).into(), 448 | )) 449 | .flag(TorFlag::SocksPort(0)) 450 | .start_background(); 451 | 452 | std::thread::sleep(std::time::Duration::from_secs(10)); 453 | } 454 | 455 | #[test] 456 | fn test_generate_hashed_password() { 457 | for i in 0..0xff { 458 | let secret = i.to_string(); 459 | let hashed_pwd = generate_hashed_password(&secret); 460 | assert_eq!(hashed_pwd.len(), 61); 461 | } 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /libtor/src/log.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Log level 5 | #[derive(Debug, Clone, Copy)] 6 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 7 | pub enum LogLevel { 8 | Debug, 9 | Info, 10 | Notice, 11 | Warn, 12 | Err, 13 | } 14 | 15 | /// Log destination 16 | #[derive(Debug, Clone)] 17 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 18 | pub enum LogDestination { 19 | Stdout, 20 | Stderr, 21 | #[cfg(target_family = "unix")] 22 | Syslog, 23 | File(String), 24 | #[cfg(target_os = "android")] 25 | Android, 26 | } 27 | impl std::fmt::Display for LogDestination { 28 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 29 | match self { 30 | LogDestination::File(path) => write!(f, "file {}", path), 31 | _ => write!(f, "{:?}", self), 32 | } 33 | } 34 | } 35 | 36 | /// Log domain, for fine grained control 37 | #[derive(Debug, Clone, Copy)] 38 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 39 | pub enum LogDomain { 40 | General, 41 | Crypto, 42 | Net, 43 | Config, 44 | Fs, 45 | Protocol, 46 | Mm, 47 | Http, 48 | App, 49 | Control, 50 | Circ, 51 | Rend, 52 | Bug, 53 | Dir, 54 | Dirserv, 55 | Or, 56 | Edge, 57 | Acct, 58 | Hist, 59 | Handshake, 60 | Heartbeat, 61 | Channel, 62 | Sched, 63 | Guard, 64 | Consdiff, 65 | Dos, 66 | Process, 67 | Pt, 68 | Btrack, 69 | Mesg, 70 | } 71 | -------------------------------------------------------------------------------- /libtor/src/ports.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Flags to change the behavior of the control port 5 | /// 6 | /// Currently, all of the possible flags are only available on Unix systems since they only 7 | /// apply to the "unix" type of ControlPort 8 | #[derive(Debug, Clone, Copy)] 9 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 10 | pub enum ControlPortFlag { 11 | #[cfg(target_family = "unix")] 12 | GroupWritable, 13 | #[cfg(target_family = "unix")] 14 | WorldWritable, 15 | #[cfg(target_family = "unix")] 16 | RelaxDirModeCheck, 17 | } 18 | 19 | /// Flags to change the behavior of the socks port 20 | #[derive(Debug, Clone, Copy)] 21 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 22 | pub enum SocksPortFlag { 23 | NoIPv4Traffic, 24 | IPv6Traffic, 25 | PreferIPv6, 26 | NoDNSRequest, 27 | NoOnionTraffic, 28 | OnionTrafficOnly, 29 | CacheIPv4DNS, 30 | CacheIPv6DNS, 31 | GroupWritable, 32 | WorldWritable, 33 | CacheDNS, 34 | UseIPv4Cache, 35 | UseIPv6Cache, 36 | UseDNSCache, 37 | PreferIPv6Automap, 38 | PreferSOCKSNoAuth, 39 | } 40 | 41 | /// Flags to change the isolation of clients connected to the control port 42 | #[derive(Debug, Clone, Copy)] 43 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 44 | pub enum SocksPortIsolationFlag { 45 | IsolateClientAddr, 46 | IsolateSOCKSAuth, 47 | IsolateClientProtocol, 48 | IsolateDestPort, 49 | IsolateDestAddr, 50 | KeepAliveIsolateSOCKSAuth, 51 | } 52 | 53 | display_like_debug!(ControlPortFlag); 54 | display_like_debug!(SocksPortFlag); 55 | display_like_debug!(SocksPortIsolationFlag); 56 | -------------------------------------------------------------------------------- /libtor/src/utils.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | macro_rules! display_like_debug { 5 | ($type:ty) => { 6 | impl std::fmt::Display for $type { 7 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 8 | write!(f, "{:?}", self) 9 | } 10 | } 11 | }; 12 | } 13 | 14 | pub trait Joiner: std::fmt::Debug + std::clone::Clone { 15 | fn joiner(&self) -> String; 16 | fn new() -> Self; 17 | } 18 | 19 | #[derive(Debug, Clone)] 20 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 21 | pub struct CommaJoiner {} 22 | impl Joiner for CommaJoiner { 23 | fn joiner(&self) -> String { 24 | ",".to_string() 25 | } 26 | 27 | fn new() -> Self { 28 | CommaJoiner {} 29 | } 30 | } 31 | 32 | #[derive(Debug, Clone)] 33 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 34 | pub struct SpaceJoiner {} 35 | impl Joiner for SpaceJoiner { 36 | fn joiner(&self) -> String { 37 | " ".to_string() 38 | } 39 | 40 | fn new() -> Self { 41 | SpaceJoiner {} 42 | } 43 | } 44 | 45 | #[derive(Debug, Clone)] 46 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 47 | pub struct DisplayVec { 48 | vec: Vec, 49 | joiner: J, 50 | } 51 | 52 | impl std::fmt::Display for DisplayVec { 53 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 54 | let joined: String = self 55 | .vec 56 | .iter() 57 | .map(|v| format!("{}", v)) 58 | .collect::>() 59 | .join(&self.joiner.joiner()); 60 | write!(f, "{}", joined) 61 | } 62 | } 63 | 64 | impl From> for DisplayVec { 65 | fn from(vec: Vec) -> DisplayVec { 66 | DisplayVec { 67 | vec, 68 | joiner: J::new(), 69 | } 70 | } 71 | } 72 | 73 | #[derive(Debug, Clone)] 74 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 75 | pub struct DisplayOption { 76 | option: Option, 77 | } 78 | 79 | impl std::fmt::Display for DisplayOption { 80 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 81 | match &self.option { 82 | Some(val) => write!(f, "{}", val), 83 | None => Ok(()), 84 | } 85 | } 86 | } 87 | 88 | impl From> for DisplayOption { 89 | fn from(option: Option) -> DisplayOption { 90 | DisplayOption { option } 91 | } 92 | } 93 | --------------------------------------------------------------------------------