torcc_rs/controller/
mod.rs

1use std::collections::HashMap;
2use std::io::prelude::*;
3use std::fmt::{Debug, Write as FmtWrite};
4use std::io::{BufReader, Write};
5use std::net::{TcpStream, ToSocketAddrs};
6use std::str::FromStr;
7
8use nom::IResult;
9
10use crate::parsers;
11
12mod error;
13use error::Error;
14
15const DEFAULT_API: &'static str = "127.0.0.1:9051";
16
17#[derive(Debug, PartialEq, Eq)]
18pub struct ProtocolInfo {
19	pub auth_methods: Vec<AuthMethod>,
20	pub version:      String,
21	pub cookiefile: String,
22}
23
24#[derive(Debug, PartialEq, Eq)]
25pub enum AuthMethod {
26	Cookie,
27	SafeCookie,
28	HashedPassword,
29}
30
31impl FromStr for AuthMethod {
32	type Err = Error;
33
34	fn from_str(s: &str) -> Result<Self, Self::Err> {
35		match s {
36			"COOKIE" => Ok(AuthMethod::Cookie),
37			"SAFECOOKIE" => Ok(AuthMethod::SafeCookie),
38			"HASHEDPASSWORD" => Ok(AuthMethod::HashedPassword),
39			_ => Err(Error::UnknownAuthMethod),
40		}
41	}
42}
43
44pub enum Signal {
45	Reload,
46	Shutdown,
47	Dump,
48	Debug,
49	Halt,
50	ClearDNSCache,
51	Newnym,
52	Heartbeat,
53	Dormant,
54	Active,
55}
56
57impl ToString for Signal {
58	fn to_string(&self) -> String {
59		match self {
60			Signal::Reload => "RELOAD".to_string(),
61			Signal::Shutdown => "SHUTDOWN".to_string(),
62			Signal::Dump => "DUMP".to_string(),
63			Signal::Debug => "DEBUG".to_string(),
64			Signal::Halt => "HALT".to_string(),
65			Signal::ClearDNSCache => "CLEARDNSCACHE".to_string(),
66			Signal::Newnym => "NEWNYM".to_string(),
67			Signal::Heartbeat => "HEARTBEAT".to_string(),
68			Signal::Dormant => "DORMANT".to_string(),
69			Signal::Active => "ACTIVE".to_string(),
70		}
71	}
72}
73
74pub enum AddOnionFlag {
75	DiscardPK,
76	Detach,
77	BasicAuth,
78}
79
80#[derive(Debug, PartialEq, Eq)]
81pub enum KeyType {
82	Best,
83	RSA1024,
84	ED25519V3,
85}
86
87impl Default for KeyType {
88	fn default() -> Self {
89		KeyType::Best
90	}
91}
92
93impl ToString for KeyType {
94	fn to_string(&self) -> String {
95		match &self {
96			KeyType::ED25519V3 => String::from("ED25519-V3"),
97			KeyType::RSA1024 => String::from("RSA1024"),
98			KeyType::Best => String::from("BEST"),
99		}
100	}
101}
102
103impl FromStr for KeyType {
104	type Err = Error;
105
106	fn from_str(s: &str) -> Result<Self, Self::Err> {
107		return match s {
108			"ED25519-V3" => Ok(KeyType::ED25519V3),
109			"RSA1024" => Ok(KeyType::RSA1024),
110			_ => Err(Error::UnknownKeyType),
111		};
112	}
113}
114
115#[derive(Debug, PartialEq, Eq)]
116pub struct ServiceID(String);
117
118impl From<&str> for ServiceID {
119	fn from(service_id: &str) -> Self {
120		ServiceID(service_id.to_string())
121	}
122}
123
124impl From<String> for ServiceID {
125	fn from(service_id: String) -> Self {
126		ServiceID(service_id)
127	}
128}
129
130pub struct HiddenService {
131	pub service_id:  ServiceID,
132	pub key_type:    KeyType,
133	pub private_key: String,
134}
135
136pub struct TorController {
137	conn: TcpStream,
138}
139
140impl TorController {
141	fn send<F, T>(&mut self, msg: String, reply_parser: F) -> Result<T, Error>
142	where
143		T: Debug,
144		F: Fn(&str) -> IResult<&str, T>,
145	{
146		debug!("-> {}", &msg);
147		let bytes = format!("{}\r\n", msg).into_bytes();
148		self.conn.write_all(&bytes)?;
149
150		let mut reader = BufReader::new(&self.conn);
151		let mut buffer = String::new();
152		loop {
153			let mut line = String::new();
154			reader.read_line(&mut line)?;
155			buffer.push_str(&line);
156			if parsers::is_final_line(&line) {
157				break;
158			}
159		}
160
161		debug!("<- {}", &buffer);
162		let comparison = "250-PROTOCOLINFO 1\n250-AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE=\"/var/run/tor/control.authcookie\"\n250-VERSION Tor=\"0.1.2.3\"\n250 OK".to_string();
163		debug!("<- {}", &comparison);
164
165		return match reply_parser(&buffer) {
166			Ok((_, response)) => Ok(response),
167			Err(_) => Err(Error::InternalError),
168		};
169	}
170
171	fn authenticate(&mut self, password: String) -> Result<(), Error> {
172		let authentication_string = format!("AUTHENTICATE \"{}\"", password.replace("\"", "\\\""));
173		self.send(authentication_string, parsers::is_ok)?;
174
175		Ok(())
176	}
177
178	// pub fn authenticate_authcookie(&mut self, password: String) -> Result<(), Error> {
179	// 	let protocol_info = self.protocol_info()?;
180	// 	unimplemented!();
181	// 	Ok(())
182	// }
183
184	pub fn protocol_info(&mut self) -> Result<ProtocolInfo, Error> {
185		let response = self.send(String::from("PROTOCOLINFO"), parsers::protocol_info)?;
186
187		Ok(response)
188	}
189
190	fn connect<A: ToSocketAddrs>(addr: A) -> Result<TorController, Error> {
191		let conn = TcpStream::connect(addr)?;
192		let controller = Self { conn };
193		Ok(controller)
194	}
195
196	pub fn connect_default_with_authcookie() -> Result<TorController, Error> {
197		TorController::connect_with_authcookie(DEFAULT_API)
198	}
199
200	pub fn connect_with_authcookie<A: ToSocketAddrs>(addr: A) -> Result<TorController, Error> {
201		let mut controller = TorController::connect(addr)?;
202		let protocol_info = controller.protocol_info()?;
203
204		let contents = std::fs::read(&protocol_info.cookiefile).unwrap();
205		let mut cookie_string = String::new();
206		contents.into_iter().for_each(|b| write!(cookie_string, "{:02X}", b).unwrap());
207
208		let msg = format!("AUTHENTICATE {}", cookie_string);
209		controller.send(msg, parsers::is_ok)?;
210
211		Ok(controller)
212	}
213
214	pub fn connect_default_with_password(password: String) -> Result<TorController, Error> {
215		TorController::connect_with_password(DEFAULT_API, password)
216	}
217
218	pub fn connect_with_password<A: ToSocketAddrs>(
219		addr: A,
220		password: String,
221	) -> Result<TorController, Error> {
222		let mut controller = TorController::connect(addr)?;
223
224		let protocol_info = controller.protocol_info()?;
225
226		if !protocol_info
227			.auth_methods
228			.contains(&AuthMethod::HashedPassword)
229		{
230			return Err(Error::AuthMethodDisabled);
231		}
232
233		controller.authenticate(password)?;
234
235		Ok(controller)
236	}
237
238	pub fn add_onion(&mut self, key_type: KeyType, port: u16) -> Result<HiddenService, Error> {
239		let add_onion_command = format!("ADD_ONION NEW:{} port={}", key_type.to_string(), port);
240		let (service_id, key) = self.send(add_onion_command, parsers::add_onion)?;
241		let (key_type, private_key) = key.unwrap();
242		let hidden_service = HiddenService {
243			service_id,
244			key_type,
245			private_key,
246		};
247
248		Ok(hidden_service)
249	}
250
251	pub fn add_onion_default(&mut self, port: u16) -> Result<HiddenService, Error> {
252		self.add_onion(KeyType::default(), port)
253	}
254
255	pub fn add_onion_with_key(
256		&mut self,
257		key_type: KeyType,
258		key: String,
259		port: u16,
260	) -> Result<HiddenService, Error> {
261		let add_onion_command = format!("ADD_ONION {}:{} port={}", key_type.to_string(), key, port);
262		let (service_id, optional_key) = self.send(add_onion_command, parsers::add_onion)?;
263		let (key_type, private_key) = optional_key.unwrap();
264		let hidden_service = HiddenService {
265			service_id,
266			private_key,
267			key_type,
268		};
269
270		Ok(hidden_service)
271	}
272
273	// Delete an onion with the given service id
274	pub fn delete_onion(&mut self, service_id: ServiceID) -> Result<(), Error> {
275		let del_onion_command = format!("DEL_ONION {}", service_id.0);
276		self.send(del_onion_command, parsers::is_ok)?;
277
278		Ok(())
279	}
280
281	pub fn get_info(&mut self, info_fields: Vec<&str>) -> Result<HashMap<String, String>, Error> {
282		let get_info_command = format!("GETINFO {}", info_fields.join(" "));
283		let response = self.send(get_info_command, parsers::get_info)?;
284
285		Ok(response)
286	}
287
288	pub fn signal(&mut self, signal: Signal) -> Result<(), Error> {
289		let signal_command = format!("SIGNAL {}", signal.to_string());
290		self.send(signal_command, parsers::is_ok)?;
291
292		Ok(())
293	}
294}
295
296#[cfg(test)]
297mod tests {
298	use super::*;
299
300	fn init() {
301		let _ = env_logger::builder().is_test(true).try_init();
302	}
303
304	fn get_controller() -> TorController {
305		TorController::connect_default_with_authcookie().unwrap()
306	}
307
308	#[test]
309	fn establish_connection() {
310		init();
311
312		let password =
313			std::env::var("TOR_CONTROLLER_PASSWORD").expect("TOR_CONTROLLER_PASSWORD is not set");
314		let result = TorController::connect_default_with_password(password);
315		assert!(result.is_ok());
316	}
317
318	#[test]
319	fn create_onion() {
320		let mut controller = get_controller();
321		let hidden_service = controller.add_onion(KeyType::Best, 80);
322		assert!(hidden_service.is_ok());
323	}
324
325	#[test]
326	fn delete_onion() {
327		let mut controller = get_controller();
328		let hidden_service = controller
329			.add_onion_default(80)
330			.expect("Error adding onion");
331		assert!(controller.delete_onion(hidden_service.service_id).is_ok());
332	}
333
334	#[test]
335	fn delete_fake_onion() {
336		let mut controller = get_controller();
337		let service_id = ServiceID::from("does not exist".to_string());
338		assert!(controller.delete_onion(service_id).is_err());
339	}
340
341	#[test]
342	fn get_info() {
343		let mut controller = get_controller();
344		let info = controller.get_info(vec!["version"]).unwrap();
345		assert!(info.contains_key("version"));
346	}
347}