tor_interface/
legacy_tor_process.rs1use std::default::Default;
3use std::fs;
4use std::fs::File;
5use std::io::{BufRead, BufReader, Read, Write};
6use std::net::SocketAddr;
7use std::ops::Drop;
8use std::path::Path;
9use std::process;
10use std::process::{Child, ChildStdout, Command, Stdio};
11use std::str::FromStr;
12use std::string::ToString;
13use std::sync::{Arc, Mutex};
14use std::time::{Duration, Instant};
15
16use data_encoding::HEXUPPER;
18use rand::RngCore;
19use sha1::{Digest, Sha1};
20
21use crate::tor_crypto::generate_password;
23
24#[derive(thiserror::Error, Debug)]
25pub enum Error {
26 #[error("failed to read control port file")]
27 ControlPortFileReadFailed(#[source] std::io::Error),
28
29 #[error("provided control port file '{0}' larger than expected ({1} bytes)")]
30 ControlPortFileTooLarge(String, u64),
31
32 #[error("failed to parse '{0}' as control port file")]
33 ControlPortFileContentsInvalid(String),
34
35 #[error("provided tor bin path '{0}' must be an absolute path")]
36 TorBinPathNotAbsolute(String),
37
38 #[error("provided data directory '{0}' must be an absolute path")]
39 TorDataDirectoryPathNotAbsolute(String),
40
41 #[error("failed to create data directory")]
42 DataDirectoryCreationFailed(#[source] std::io::Error),
43
44 #[error("file exists in provided data directory path '{0}'")]
45 DataDirectoryPathExistsAsFile(String),
46
47 #[error("failed to create default_torrc file")]
48 DefaultTorrcFileCreationFailed(#[source] std::io::Error),
49
50 #[error("failed to write default_torrc file")]
51 DefaultTorrcFileWriteFailed(#[source] std::io::Error),
52
53 #[error("failed to create torrc file")]
54 TorrcFileCreationFailed(#[source] std::io::Error),
55
56 #[error("failed to remove control_port file")]
57 ControlPortFileDeleteFailed(#[source] std::io::Error),
58
59 #[error("failed to start legacy tor process")]
60 LegacyTorProcessStartFailed(#[source] std::io::Error),
61
62 #[error("failed to read control addr from control_file '{0}'")]
63 ControlPortFileMissing(String),
64
65 #[error("unable to take legacy tor process stdout")]
66 LegacyTorProcessStdoutTakeFailed(),
67
68 #[error("failed to spawn tor process stdout read thread")]
69 StdoutReadThreadSpawnFailed(#[source] std::io::Error),
70}
71
72fn read_control_port_file(control_port_file: &Path) -> Result<SocketAddr, Error> {
73 let mut file = File::open(control_port_file).map_err(Error::ControlPortFileReadFailed)?;
75
76 let metadata = file.metadata().map_err(Error::ControlPortFileReadFailed)?;
78 if metadata.len() >= 1024 {
79 return Err(Error::ControlPortFileTooLarge(
80 format!("{}", control_port_file.display()),
81 metadata.len(),
82 ));
83 }
84
85 let mut contents = String::new();
87 file.read_to_string(&mut contents)
88 .map_err(Error::ControlPortFileReadFailed)?;
89
90 if contents.starts_with("PORT=") {
91 let addr_string = &contents.trim_end()["PORT=".len()..];
92 if let Ok(addr) = SocketAddr::from_str(addr_string) {
93 return Ok(addr);
94 }
95 }
96 Err(Error::ControlPortFileContentsInvalid(format!(
97 "{}",
98 control_port_file.display()
99 )))
100}
101
102pub(crate) struct LegacyTorProcess {
104 control_addr: SocketAddr,
105 process: Child,
106 password: String,
107 stdout_lines: Arc<Mutex<Vec<String>>>,
109}
110
111impl LegacyTorProcess {
112 const S2K_RFC2440_SPECIFIER_LEN: usize = 9;
113
114 fn hash_tor_password_with_salt(
115 salt: &[u8; Self::S2K_RFC2440_SPECIFIER_LEN],
116 password: &str,
117 ) -> String {
118 assert_eq!(salt[Self::S2K_RFC2440_SPECIFIER_LEN - 1], 0x60);
119
120 const EXPBIAS: u8 = 6u8;
122 const C: u8 = 0x60; const COUNT: usize = (16usize + ((C & 15u8) as usize)) << ((C >> 4) + EXPBIAS);
124
125 let mut input: Vec<u8> = Default::default();
127 input.extend_from_slice(&salt[0..Self::S2K_RFC2440_SPECIFIER_LEN - 1]);
129 input.extend_from_slice(password.as_bytes());
131
132 let input = input.as_slice();
133 let input_len = input.len();
134
135 let mut sha1 = Sha1::new();
136 let mut count = COUNT;
137 while count > 0 {
138 if count > input_len {
139 sha1.update(input);
140 count -= input_len;
141 } else {
142 sha1.update(&input[0..count]);
143 break;
144 }
145 }
146
147 let key = sha1.finalize();
148
149 let mut hash = "16:".to_string();
150 HEXUPPER.encode_append(salt, &mut hash);
151 HEXUPPER.encode_append(&key, &mut hash);
152
153 hash
154 }
155
156 fn hash_tor_password(password: &str) -> String {
157 let mut salt = [0x00u8; Self::S2K_RFC2440_SPECIFIER_LEN];
158 let csprng = &mut tor_llcrypto::rng::CautiousRng;
159 csprng.fill_bytes(&mut salt);
160 salt[Self::S2K_RFC2440_SPECIFIER_LEN - 1] = 0x60u8;
161
162 Self::hash_tor_password_with_salt(&salt, password)
163 }
164
165 pub fn get_control_addr(&self) -> &SocketAddr {
166 &self.control_addr
167 }
168
169 pub fn get_password(&self) -> &String {
170 &self.password
171 }
172
173 pub fn new(tor_bin_path: &Path, data_directory: &Path) -> Result<LegacyTorProcess, Error> {
174 if tor_bin_path.is_relative() {
175 return Err(Error::TorBinPathNotAbsolute(format!(
176 "{}",
177 tor_bin_path.display()
178 )));
179 }
180 if data_directory.is_relative() {
181 return Err(Error::TorDataDirectoryPathNotAbsolute(format!(
182 "{}",
183 data_directory.display()
184 )));
185 }
186
187 if !data_directory.exists() {
189 fs::create_dir_all(data_directory).map_err(Error::DataDirectoryCreationFailed)?;
190 } else if data_directory.is_file() {
191 return Err(Error::DataDirectoryPathExistsAsFile(format!(
192 "{}",
193 data_directory.display()
194 )));
195 }
196
197 let default_torrc = data_directory.join("default_torrc");
199 let torrc = data_directory.join("torrc");
200 let control_port_file = data_directory.join("control_port");
201
202 if !default_torrc.exists() {
209 const DEFAULT_TORRC_CONTENT: &str = "SocksPort auto\n\
210 AvoidDiskWrites 1\n\
211 DisableNetwork 1\n";
212
213 let mut default_torrc_file =
214 File::create(&default_torrc).map_err(Error::DefaultTorrcFileCreationFailed)?;
215 default_torrc_file
216 .write_all(DEFAULT_TORRC_CONTENT.as_bytes())
217 .map_err(Error::DefaultTorrcFileWriteFailed)?;
218 }
219
220 if !torrc.exists() {
222 let _ = File::create(&torrc).map_err(Error::TorrcFileCreationFailed)?;
223 }
224
225 if control_port_file.exists() {
227 fs::remove_file(&control_port_file).map_err(Error::ControlPortFileDeleteFailed)?;
228 }
229
230 const CONTROL_PORT_PASSWORD_LENGTH: usize = 32usize;
231 let password = generate_password(CONTROL_PORT_PASSWORD_LENGTH);
232 let password_hash = Self::hash_tor_password(&password);
233
234 let mut process = Command::new(tor_bin_path.as_os_str())
235 .stdout(Stdio::piped())
236 .stdin(Stdio::null())
237 .stderr(Stdio::null())
238 .current_dir(data_directory)
240 .arg("--defaults-torrc")
242 .arg(default_torrc)
243 .arg("--torrc-file")
245 .arg(torrc)
246 .arg("DataDirectory")
248 .arg(data_directory)
249 .arg("ControlPort")
252 .arg("auto")
253 .arg("ControlPortWriteToFile")
255 .arg(control_port_file.clone())
256 .arg("HashedControlPassword")
259 .arg(password_hash)
260 .arg("__OwningControllerProcess")
263 .arg(process::id().to_string())
264 .spawn()
265 .map_err(Error::LegacyTorProcessStartFailed)?;
266
267 let mut control_addr = None;
268 let start = Instant::now();
269
270 while control_addr.is_none() && start.elapsed() < Duration::from_secs(5) {
274 if control_port_file.exists() {
275 control_addr = Some(read_control_port_file(control_port_file.as_path())?);
276 fs::remove_file(&control_port_file).map_err(Error::ControlPortFileDeleteFailed)?;
277 }
278 }
279
280 let control_addr = match control_addr {
281 Some(control_addr) => control_addr,
282 None => {
283 return Err(Error::ControlPortFileMissing(format!(
284 "{}",
285 control_port_file.display()
286 )))
287 }
288 };
289
290 let stdout_lines: Arc<Mutex<Vec<String>>> = Default::default();
291
292 {
293 let stdout_lines = Arc::downgrade(&stdout_lines);
294 let stdout = BufReader::new(match process.stdout.take() {
295 Some(stdout) => stdout,
296 None => return Err(Error::LegacyTorProcessStdoutTakeFailed()),
297 });
298
299 std::thread::Builder::new()
300 .name("tor_stdout_reader".to_string())
301 .spawn(move || {
302 LegacyTorProcess::read_stdout_task(&stdout_lines, stdout);
303 })
304 .map_err(Error::StdoutReadThreadSpawnFailed)?;
305 }
306
307 Ok(LegacyTorProcess {
308 control_addr,
309 process,
310 password,
311 stdout_lines,
312 })
313 }
314
315 fn read_stdout_task(
316 stdout_lines: &std::sync::Weak<Mutex<Vec<String>>>,
317 mut stdout: BufReader<ChildStdout>,
318 ) {
319 while let Some(stdout_lines) = stdout_lines.upgrade() {
320 let mut line = String::default();
321 if stdout.read_line(&mut line).is_ok() {
323 line.pop();
325 let mut stdout_lines = match stdout_lines.lock() {
327 Ok(stdout_lines) => stdout_lines,
328 Err(_) => unreachable!(),
329 };
330 stdout_lines.push(line);
331 }
332 }
333 }
334
335 pub fn wait_log_lines(&mut self) -> Vec<String> {
336 let mut lines = match self.stdout_lines.lock() {
337 Ok(lines) => lines,
338 Err(_) => unreachable!(),
339 };
340 std::mem::take(&mut lines)
341 }
342}
343
344impl Drop for LegacyTorProcess {
345 fn drop(&mut self) {
346 let _ = self.process.kill();
347 }
348}
349
350#[test]
351fn test_password_hash() -> Result<(), anyhow::Error> {
352 let salt1: [u8; LegacyTorProcess::S2K_RFC2440_SPECIFIER_LEN] = [
353 0xbeu8, 0x2au8, 0x25u8, 0x1du8, 0xe6u8, 0x2cu8, 0xb2u8, 0x7au8, 0x60u8,
354 ];
355 let hash1 = LegacyTorProcess::hash_tor_password_with_salt(&salt1, "abcdefghijklmnopqrstuvwxyz");
356 assert_eq!(
357 hash1,
358 "16:BE2A251DE62CB27A60AC9178A937990E8ED0AB662FA82A5C7DE3EBB23A"
359 );
360
361 let salt2: [u8; LegacyTorProcess::S2K_RFC2440_SPECIFIER_LEN] = [
362 0x36u8, 0x73u8, 0x0eu8, 0xefu8, 0xd1u8, 0x8cu8, 0x60u8, 0xd6u8, 0x60u8,
363 ];
364 let hash2 = LegacyTorProcess::hash_tor_password_with_salt(&salt2, "password");
365 assert_eq!(
366 hash2,
367 "16:36730EEFD18C60D66052E7EA535438761C0928D316EEA56A190C99B50A"
368 );
369
370 assert_ne!(
372 LegacyTorProcess::hash_tor_password("password"),
373 LegacyTorProcess::hash_tor_password("password")
374 );
375
376 Ok(())
377}