summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--examples/cli.rs11
-rw-r--r--src/lib.rs2
-rw-r--r--src/process.rs14
-rw-r--r--src/sock.rs165
5 files changed, 67 insertions, 135 deletions
diff --git a/README.md b/README.md
index 3d5c62c..c353176 100644
--- a/README.md
+++ b/README.md
@@ -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));
}
diff --git a/src/lib.rs b/src/lib.rs
index 75814a2..371bb09 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)?;