diff options
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | examples/cli.rs | 11 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/process.rs | 14 | ||||
-rw-r--r-- | src/sock.rs | 165 |
5 files changed, 67 insertions, 135 deletions
@@ -1,5 +1,9 @@ # torctl -An unfinished high-level Rust crate for spawning a Tor process and interacting -with it. The main goal of this crate is to enable Rust programs to connect with -the Tor network. +An unfinished and half-assed high-level Rust crate for spawning a Tor process +and interacting with the Tor network. + +The goal of this crate is to make it easy to write programs that interact with +the Tor network. This includes using Tor to connect to the rest of the internet +or to create and interact with Onion services. It is *not* a goal to provide a +generic implementation of the Tor control socket API. diff --git a/examples/cli.rs b/examples/cli.rs index 18a7747..59ed330 100644 --- a/examples/cli.rs +++ b/examples/cli.rs @@ -15,14 +15,15 @@ fn main() { } } }); - s.setevents( torctl::EventList::default().log(torctl::Severity::Notice).bootstrap() ).unwrap(); + s.set_events( torctl::EventList::default().log(torctl::Severity::Notice).bootstrap() ).unwrap(); - s.setconf(&[("SocksPort", Some("10245"))]).unwrap(); - dbg!(s.getconf(&["ContactInfo", "DataDirectory", "HiddenServiceOptions"]).unwrap()); - dbg!(s.getinfo(&["version", "config-text"]).unwrap()); + dbg!(s.socks_addr()); + //s.setconf(&[("SocksPort", Some("10245"))]).unwrap(); + //dbg!(s.getconf(&["ContactInfo", "DataDirectory", "HiddenServiceOptions"]).unwrap()); + //dbg!(s.getinfo(&["version", "config-text", "net/listeners/socks"]).unwrap()); std::thread::sleep(std::time::Duration::from_secs(5)); - s.quit().unwrap(); + //s.quit().unwrap(); std::thread::sleep(std::time::Duration::from_secs(500)); } @@ -3,5 +3,5 @@ mod process; mod sock; pub use err::Error; -pub use sock::{Sock,Auth,Event,EventList,Severity,BootstrapStatus}; +pub use sock::{Tor,Event,EventList,Severity,BootstrapStatus}; pub use process::{genpass,hashpass,spawn}; diff --git a/src/process.rs b/src/process.rs index 2ef37cb..a6f63fd 100644 --- a/src/process.rs +++ b/src/process.rs @@ -3,7 +3,7 @@ use std::net::SocketAddr; use std::path::Path; use std::process::{Child,Command,Stdio}; -use crate::sock::{Sock,Auth}; +use crate::sock::Tor; use crate::err::Result; @@ -103,7 +103,9 @@ fn wait(child: &mut Child, control_path: &Path) -> Result<SocketAddr> { /// Spawn a new Tor process and return its control socket. The Tor process will automatically shut /// down when the socket is closed. -pub fn spawn<S: AsRef<std::ffi::OsStr>, P: AsRef<Path>>(bin: S, data_dir: P, conf_add: &str) -> Result<Sock> { +/// +/// It is an error to call this function if a Tor process is already running with the same `data_dir`. +pub fn spawn<S: AsRef<std::ffi::OsStr>, P: AsRef<Path>>(bin: S, data_dir: P, conf_add: &str) -> Result<Tor> { // We don't really have to create the directory, Tor can do that itself; But we should have a // canonical path to pass to Tor, otherwise it will (rightfully) complain. std::fs::create_dir_all(&data_dir)?; @@ -125,9 +127,7 @@ pub fn spawn<S: AsRef<std::ffi::OsStr>, P: AsRef<Path>>(bin: S, data_dir: P, con .arg("--ControlPort").arg("auto") .arg("--ControlPortWriteToFile").arg(&control_path) .arg("--__OwningControllerProcess").arg(std::process::id().to_string()) - // The SocksPort is configured through the control socket, if needed. This approach may - // trigger a bug in 0.3.4: https://trac.torproject.org/projects/tor/ticket/27849 - .arg("--SocksPort").arg("0") + .arg("--SocksPort").arg("auto") .arg("-f").arg("-") // TODO: Capture stderr/stdout? That'd be useful if tor exits immediately with an error, // but may cause tor to block on writing to the pipes if we don't keep reading from them. @@ -144,9 +144,7 @@ pub fn spawn<S: AsRef<std::ffi::OsStr>, P: AsRef<Path>>(bin: S, data_dir: P, con child.stdin.take().unwrap().write_all(to_stdin.as_bytes())?; let addr = wait(&mut child, &control_path)?; - let sock = Sock::connect_child(&addr, &Auth::HashedPassword(pass), Some(child))?; - sock.takeownership()?; - sock.resetconf(&[("__OwningControllerProcess", None)])?; + let sock = Tor::connect_child(&addr, &pass, child)?; Ok(sock) } diff --git a/src/sock.rs b/src/sock.rs index 42a5d90..15311db 100644 --- a/src/sock.rs +++ b/src/sock.rs @@ -137,18 +137,6 @@ impl ReplyLine { } -/// Authentication method and credentials used to authenticate to the Tor control socket. -/// The `COOKIE` and `SAFECOOKIE` authentication methods are not supported. -pub enum Auth { - /// Authenticate without credentials. - Null, - /// Authenticate with a password, this corresponds to the 'HashedControlPassword' configuration - /// option in torrc. See `genpass()` and `hashpass()` for functions to generate a suitable - /// password and `HashedControlPassword` combination. - HashedPassword(String) -} - - /// Status event of Tor's bootstrapping process. #[derive(Debug)] pub struct BootstrapStatus { @@ -294,28 +282,24 @@ impl<'a> QuotedString<'a> { -/// Tor control socket. +/// Tor process and control socket. Use `spawn()` to create an instance of this struct. /// -/// A socket is created either by connecting to an existing Tor process using `Sock::connect()`, or -/// by calling `spawn()` to spawn a new Tor process. -/// -/// A socket can be used from multiple threads, with the following limitations: -/// - It is possible to run commands simultaneously from multiple threads, but the order in which -/// they are executed is, of course, not deterministic. Running a `setconf()` and `getconf()` on -/// the same keys at the same time may not give reliable results. -/// - You should only read asynchronous events from a single thread at a time. The API allows for +/// A Tor struct can be used from multiple threads, with the following limitations: +/// - It is possible to call methods simultaneously from multiple threads, but the order in which +/// they are executed is, of course, not deterministic. +/// - You should only call `read_event()` from a single thread at a time. The API allows for /// multiple threads to read events, but an event will only be received by one thread. -pub struct Sock { +pub struct Tor { sock: TcpStream, - // Should be set when tor is running as a child process and this socket has "ownership" of the process. - child: Option<Child>, + child: Child, + socks: SocketAddr, // Read buffer - only one thread can be reading from the socket at a time. rdbuf: Mutex<Vec<u8>>, // Read queue - if multiple threads are interested in reading from the socket, then this // structure is used to coordinate which thread gets to read from the socket. If that thread // happens to read a reply that it isn't interested in (e.g. it expected a sync reply and got // an async one), then that reply is pushed to the queue. - queue: Mutex<SockQueue>, + queue: Mutex<TorQueue>, queuecv: Condvar, // Write mutex - this ensures only a single command can be sent at a time. This mutex is // currently also held while reading the response of a command, because the read queue itself @@ -325,28 +309,26 @@ pub struct Sock { writer: Mutex<bool>, } -struct SockQueue { +struct TorQueue { queue: Vec<Reply>, reading: bool, } -impl Drop for Sock { +impl Drop for Tor { fn drop(&mut self) { // We have to wait() for the child to shut down, otherwise we get a zombie process. This is // a quick-and-dirty approach which doesn't really give Tor the time to do a clean // shutdown. In fact, we kill the process before even shutting down the socket. // A cleaner "shut down socket and wait a bit" approach should probably be implemented as a // separate method. - if let Some(mut c) = self.child.take() { - c.kill().is_ok(); - c.wait().is_ok(); - } + self.child.kill().is_ok(); + self.child.wait().is_ok(); } } -impl Sock { +impl Tor { fn read_line(buf: &mut Vec<u8>, sock: &TcpStream) -> Result<ReplyLine> { // This buffer handling isn't very efficient, but that's probably fine. loop { @@ -425,60 +407,30 @@ impl Sock { } } - pub(crate) fn connect_child(s: &SocketAddr, auth: &Auth, child: Option<Child>) -> Result<Sock> { - let sock = Sock { + pub(crate) fn connect_child(s: &SocketAddr, pass: &str, child: Child) -> Result<Tor> { + let mut tor = Tor { sock: TcpStream::connect(s)?, child: child, + socks: "0.0.0.0:0".parse().unwrap(), rdbuf: Mutex::new(Vec::new()), - queue: Mutex::new(SockQueue { + queue: Mutex::new(TorQueue { queue: Vec::new(), reading: false, }), queuecv: Condvar::new(), writer: Mutex::new(true), }; + tor.cmd(format!("AUTHENTICATE {}\r\n", QuotedString(pass)))?; + tor.cmd("TAKEOWNERSHIP\r\n")?; + tor.resetconf(&[("__OwningControllerProcess", None)])?; - match auth { - Auth::Null => sock.cmd("AUTHENTICATE\r\n")?, - Auth::HashedPassword(s) => sock.cmd(format!("AUTHENTICATE {}\r\n", QuotedString(s)))? - }; - Ok(sock) - } - - /// Connect to a running Tor process and authenticate using the given authentication method. - pub fn connect(s: &SocketAddr, auth: &Auth) -> Result<Sock> { - Self::connect_child(s, auth, None) - } - - /// Send a TAKEOWNERSHIP command. If this command has been acknowledged, the Tor process - /// will automatically shut down when this control socket is closed. - /// - /// Ownership is already implied if this socket has been created with the `spawn()` function, - /// so in that case you do not have to call this method. - pub fn takeownership(&self) -> Result<()> { - self.cmd("TAKEOWNERSHIP\r\n").map(|_|()) - } - - /// Send a DROPOWNERSHIP command. This reverses any earlier TAKEOWNERSHIP command and tells - /// the Tor process to keep running even after this control socket is closed. - /// - /// Note that, if this socket has been created with the `spawn()` function, then the Tor - /// process will still be killed when the socket is dropped. This method is only useful when - /// connected to an external Tor process. - pub fn dropownership(&self) -> Result<()> { - self.cmd("DROPOWNERSHIP\r\n").map(|_|()) - } + let ret = tor.getinfo(&["net/listeners/socks"])?; + let mut buf = &ret[0].1[..]; + tor.socks = QuotedString::parse(&mut buf)?.parse().map_err(|_| err!(Parse))?; - /// Send a QUIT command. This tells Tor to close this control socket. Any further commands will - /// likely result in an error. - /// - /// The Tor process will exit if this control socket has called `takeownership()` before. - // TODO: Get rid of this function and implement a more thorough shutdown() instead? - pub fn quit(&self) -> Result<()> { - self.cmd("QUIT\r\n").map(|_|()) + Ok(tor) } - // XXX: This IntoIterator works with &[..] and &vec![..]. Haven't tested HashMap/BTreeMap yet, // but I suspect their signature doesn't match. fn setresetconf<'a,T>(&self, mut msg: String, settings: T) -> Result<()> @@ -497,56 +449,27 @@ impl Sock { self.cmd(msg).map(|_|()) } - /// Send a SETCONF command. - pub fn setconf<'a,T>(&self, settings: T) -> Result<()> + /* Send a SETCONF command. + fn setconf<'a,T>(&self, settings: T) -> Result<()> where T: IntoIterator<Item = &'a (&'a str, Option<&'a str>)> { self.setresetconf("SETCONF".to_string(), settings) - } + }*/ - /// Send a RESETCONF command. - pub fn resetconf<'a,T>(&self, settings: T) -> Result<()> + // Send a RESETCONF command. + fn resetconf<'a,T>(&self, settings: T) -> Result<()> where T: IntoIterator<Item = &'a (&'a str, Option<&'a str>)> { self.setresetconf("RESETCONF".to_string(), settings) } - /// Returns the configuration variables for the requested keys. The values are returned in the - /// order they were requested. Some keys may return multiple values, these will be listed - /// multiple times. Some keys may return `None` when are unset or default. An error is - /// returned if any of the requested keys does not exist. - /// - /// This method corresponds to the GETCONF command. - pub fn getconf<'a,T: IntoIterator<Item = &'a &'a str>>(&self, keys: T) -> Result<Vec<(String, Option<String>)>> { - let mut msg = "GETCONF".to_string(); - for k in keys { - is_keyword(k)?; - msg.push(' '); - msg.push_str(k); - } - msg.push_str("\r\n"); - - let mut res = Vec::new(); - for line in self.cmd(msg)? { - if let Some(is) = line.text.find('=') { - let mut val = &line.text[is+1..]; - let val = if val.starts_with('"') { QuotedString::parse(&mut val)? } else { val.to_string() }; - res.push(( (&line.text[..is]).to_string(), Some(val) )); - } else { - res.push((line.text, None)); - } - } - Ok(res) - } - - /// Similar to the `getconf()` method, except this is used to get run-time variables that are - /// not saved in the configuration. An error is returned if any of the requested keys does not - /// exist. - /// - /// Corresponds to the GETINFO command. Refer to the GETINFO documentation in the [tor-control - /// specification](https://gitweb.torproject.org/torspec.git/blob/control-spec.txt) for the - /// list of accepted keys. - pub fn getinfo<'a,T: IntoIterator<Item = &'a &'a str>>(&self, keys: T) -> Result<Vec<(String, String)>> { + // Get run-time Tor variables. An error is returned if any of the requested keys does not + // exist. + // + // Corresponds to the GETINFO command. Refer to the GETINFO documentation in the [tor-control + // specification](https://gitweb.torproject.org/torspec.git/blob/control-spec.txt) for the + // list of accepted keys. + fn getinfo<'a,T: IntoIterator<Item = &'a &'a str>>(&self, keys: T) -> Result<Vec<(String, String)>> { let mut msg = "GETINFO".to_string(); for k in keys { is_keyword(k)?; @@ -574,8 +497,12 @@ impl Sock { Ok(res) } - /// Returns the latest bootstrap status. This is a convenience wrapper around - /// `getinfo(&["status/bootstrap-phase"])`. + /// Returns the address of the Tor SOCKS proxy. + pub fn socks_addr(&self) -> SocketAddr { + self.socks + } + + /// Returns the most recent bootstrap status. pub fn bootstrap_status(&self) -> Result<Box<BootstrapStatus>> { let ret = self.getinfo(&["status/bootstrap-phase"])?; let mut buf = &ret[0].1[..]; @@ -584,12 +511,14 @@ impl Sock { BootstrapStatus::parse(sev, &mut buf) } - pub fn setevents(&self, events: EventList) -> Result<()> { + /// Configure which events to receive with `read_events()`. + pub fn set_events(&self, events: EventList) -> Result<()> { self.cmd(events.cmd()).map(|_|()) } - /// Read an event from the socket. This method blocks until an event has been received. + /// Read the next event from the socket. This method blocks until an event has been received. // TODO: Differentiate between fatal and temporary errors. + // TODO: Some way to wake up a blocked read_event (e.g. to shut down the socket). pub fn read_event(&self) -> Result<Event> { loop { let mut ret = self.get_reply(true)?; |