summaryrefslogtreecommitdiff
path: root/src/sock.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sock.rs')
-rw-r--r--src/sock.rs216
1 files changed, 144 insertions, 72 deletions
diff --git a/src/sock.rs b/src/sock.rs
index c4c8e8f..faccb46 100644
--- a/src/sock.rs
+++ b/src/sock.rs
@@ -12,13 +12,38 @@ use crate::err::Result;
// input in order to prevent protocol injection. It is therefore lenient enough in that it allows
// for the different "keyword" contexts, but should still be strict enough to prevent the string
// from being interpreted as something other than a keyword.
+fn is_keyword_char(c: char) -> bool { c.is_ascii_alphanumeric() || c == '/' || c == '-' || c == '_' || c == '.' }
fn is_keyword(s: &str) -> Result<()> {
- if s.contains(|c: char| !c.is_ascii_alphanumeric() && c != '/' && c != '-' && c != '_' && c != '.') {
+ if s.contains(|c| !is_keyword_char(c)) {
return Err(err!(Keyword, s.to_string()))
}
Ok(())
}
+// Consume a prefix from a buffer. Handy utility function when parsing.
+#[inline]
+fn cons(buf: &mut &str, prefix: &str) -> Result<()> {
+ if buf.starts_with(prefix) { *buf = &buf[prefix.len()..]; Ok(()) } else { Err(err!(Parse)) }
+}
+
+fn cons_keyword<'a>(buf: &mut &'a str) -> Result<&'a str> {
+ let i = buf.find(|c| !is_keyword_char(c)).unwrap_or(buf.len());
+ if i == 0 {
+ Err(err!(Parse))
+ } else {
+ let s = &buf[..i];
+ *buf = &buf[i..];
+ Ok(s)
+ }
+}
+
+fn cons_u8(buf: &mut &str) -> Result<u8> {
+ let i = buf.find(|c:char| !c.is_ascii_digit()).unwrap_or(buf.len());
+ let v = (&buf[..i]).parse().map_err(|_| err!(Parse))?;
+ *buf = &buf[i..];
+ Ok(v)
+}
+
// A Reply (Sync or Async) consists of any number of intermediate lines (Mid/Data) followed by an
// End line.
@@ -118,20 +143,46 @@ pub enum Auth {
/// Authenticate without credentials.
Null,
/// Authenticate with a password, this corresponds to the 'HashedControlPassword' configuration
- /// option in torrc.
+ /// 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 {
+ /// Severity of this event. Typically `Notice` if all is going well, `Warn` if there are some
+ /// problems.
+ pub severity: Severity,
+ /// Bootstrap progress in percent.
+ pub progress: u8,
+ /// Tor identifier for the current action.
+ pub tag: String, // TODO: Enum?
+ /// Human readable description of the next action to take (e.g. "Handshaking with a relay").
+ pub summary: String,
+ // TODO: warning/reason/count/recommendation
+}
+
+
+/// Tor log message severity.
+#[derive(Debug,PartialEq,Eq,PartialOrd,Ord,Clone,Copy)]
+pub enum Severity {
+ Debug,
+ Info,
+ Notice,
+ Warn,
+ Err
+}
+
+
/// An asynchronous event received from Tor. These are returned by `read_event()`.
#[derive(Debug)]
pub enum Event {
- // Log messages
- Debug(String),
- Info(String),
- Notice(String),
- Warn(String),
- Err(String),
+ /// Log messages
+ Log(Severity, String),
+ /// Bootstrap status event
+ Bootstrap(Box<BootstrapStatus>),
}
@@ -139,37 +190,33 @@ pub enum Event {
/// default (empty) event list.
///
/// ```
-/// # use torctl::EventList;
-/// let log_warn_and_error = EventList::default().warn();
+/// # use torctl::{EventList,Severity};
+/// let log_warn_and_error = EventList::default().log(Severity::Notice);
/// ```
#[derive(Debug,Default,Clone,Copy)]
pub struct EventList {
- debug: bool,
- info: bool,
- notice: bool,
- warn: bool,
- err: bool,
+ log: Option<Severity>,
+ bootstrap: bool,
}
impl EventList {
- /// Enable all log events.
- pub fn debug (mut self) -> Self { self.debug = true; self.info = true; self.notice = true; self.warn = true; self.err = true; self }
- /// Enable all log events of level *INFO* and higher.
- pub fn info (mut self) -> Self { self.debug = false; self.info = true; self.notice = true; self.warn = true; self.err = true; self }
- /// Enable all log events of level *NOTICE* and higher.
- pub fn notice(mut self) -> Self { self.debug = false; self.info = false; self.notice = true; self.warn = true; self.err = true; self }
- /// Enable all log events of level *WARN* and higher.
- pub fn warn (mut self) -> Self { self.debug = false; self.info = false; self.notice = false; self.warn = true; self.err = true; self }
- /// Only enable logging for log level *ERR*.
- pub fn err (mut self) -> Self { self.debug = false; self.info = false; self.notice = false; self.warn = false; self.err = true; self }
+ /// Receive log messages matching the given severity or higher.
+ pub fn log(mut self, lvl: Severity) -> Self { self.log = Some(lvl); self }
+
+ /// Receive `Bootstrap` events.
+ pub fn bootstrap(mut self) -> Self { self.bootstrap = true; self }
fn cmd(&self) -> String {
let mut s = "SETEVENTS".to_string();
- if self.debug { s.push_str(" DEBUG" ) }
- if self.info { s.push_str(" INFO" ) }
- if self.notice { s.push_str(" NOTICE") }
- if self.warn { s.push_str(" WARN" ) }
- if self.err { s.push_str(" ERR" ) }
+ match self.log {
+ Some(Severity::Debug) => s.push_str(" ERR WARN NOTICE INFO DEBUG"),
+ Some(Severity::Info) => s.push_str(" ERR WARN NOTICE INFO"),
+ Some(Severity::Notice) => s.push_str(" ERR WARN NOTICE"),
+ Some(Severity::Warn) => s.push_str(" ERR WARN"),
+ Some(Severity::Err) => s.push_str(" ERR"),
+ None => (),
+ }
+ if self.bootstrap { s.push_str(" STATUS_CLIENT") }
s.push_str("\r\n");
s
}
@@ -197,23 +244,19 @@ impl<'a> std::fmt::Display for QuotedString<'a> {
}
impl<'a> QuotedString<'a> {
- fn parse(s: &str) -> Result<String> {
- if s.len() < 2 || !s.starts_with('"') || !s.ends_with('"') {
- return Err(err!(Parse))
- }
- let mut out = String::with_capacity(s.len());
+ fn parse(s: &mut &str) -> Result<String> {
+ cons(s, "\"")?;
+ let mut out = String::new();
let mut q = false;
- for c in (&s[1..s.len()-1]).chars() {
+ for (i, c) in s.char_indices() {
match (q, c) {
(true , _ ) => { q = false; out.push(c) },
(false, '\\') => q = true,
+ (false, '"' ) => { *s = &s[i+1..]; return Ok(out) },
(false, _ ) => out.push(c),
}
}
- if q {
- return Err(err!(Parse))
- }
- Ok(out)
+ Err(err!(Parse))
}
}
@@ -341,7 +384,6 @@ impl Sock {
fn cmd<S: AsRef<[u8]>>(&self, cmd: S) -> Result<Reply> {
let _guard = self.writer.lock().unwrap();
- print!("C: {}", std::str::from_utf8(cmd.as_ref()).unwrap());
(&self.sock).write_all(cmd.as_ref())?;
let mut ret = self.get_reply(false)?;
@@ -456,8 +498,8 @@ impl Sock {
let mut res = Vec::new();
for line in self.cmd(msg)? {
if let Some(is) = line.text.find('=') {
- let val = &line.text[is+1..];
- let val = if val.starts_with('"') { QuotedString::parse(val)? } else { val.to_string() };
+ 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));
@@ -507,29 +549,47 @@ impl Sock {
/// Read an event from the socket. This method blocks until an event has been received.
pub fn read_event(&self) -> Result<Event> {
- let mut ret = self.get_reply(true)?;
- let ev = ret.remove(0);
-
- fn logmsg(r: ReplyLine, skip: usize) -> String {
- if r.data.is_empty() {
- (&r.text[skip..]).trim().to_owned()
- } else {
- String::from_utf8_lossy(&r.data).trim().to_owned()
- }
+ fn extract_sev(buf: &mut &str) -> Result<Severity> {
+ if cons(buf, "DEBUG " ).is_ok() { Ok(Severity::Debug ) }
+ else if cons(buf, "INFO " ).is_ok() { Ok(Severity::Info ) }
+ else if cons(buf, "NOTICE ").is_ok() { Ok(Severity::Notice) }
+ else if cons(buf, "WARN " ).is_ok() { Ok(Severity::Warn ) }
+ else if cons(buf, "ERR " ).is_ok() { Ok(Severity::Err ) }
+ else { Err(err!(Parse)) }
}
- if ev.status == 650 && ev.text.starts_with("DEBUG") {
- Ok(Event::Debug(logmsg(ev, 6)))
- } else if ev.status == 650 && ev.text.starts_with("NOTICE") {
- Ok(Event::Notice(logmsg(ev, 7)))
- } else if ev.status == 650 && ev.text.starts_with("INFO") {
- Ok(Event::Info(logmsg(ev, 5)))
- } else if ev.status == 650 && ev.text.starts_with("WARN") {
- Ok(Event::Warn(logmsg(ev, 5)))
- } else if ev.status == 650 && ev.text.starts_with("ERR") {
- Ok(Event::Err(logmsg(ev, 4)))
- } else {
- Err(err!(UnknownEvent, ev.text))
+ loop {
+ let mut ret = self.get_reply(true)?;
+ let ev = ret.remove(0);
+ let mut buf = &ev.text[..];
+
+ if let Ok(sev) = extract_sev(&mut buf) {
+ let s = if ev.data.is_empty() {
+ buf.trim().to_owned()
+ } else {
+ String::from_utf8_lossy(&ev.data).trim().to_owned()
+ };
+ return Ok(Event::Log(sev, s))
+
+ } else if cons(&mut buf, "STATUS_CLIENT ").is_ok() {
+ let sev = extract_sev(&mut buf)?;
+ if cons(&mut buf, "BOOTSTRAP ").is_ok() {
+ let mut bs = Box::new(BootstrapStatus {
+ severity: sev,
+ progress: 0,
+ tag: String::new(),
+ summary: String::new(),
+ });
+ // TODO: Can the order of these arguments change?
+ cons(&mut buf, "PROGRESS=")?;
+ bs.progress = cons_u8(&mut buf)?;
+ cons(&mut buf, " TAG=")?;
+ bs.tag = cons_keyword(&mut buf)?.to_owned();
+ cons(&mut buf, " SUMMARY=")?;
+ bs.summary = QuotedString::parse(&mut buf)?;
+ return Ok(Event::Bootstrap(bs))
+ }
+ }
}
}
}
@@ -568,13 +628,25 @@ mod tests {
#[test]
fn qs_parse() {
- assert!(QuotedString::parse("").is_err());
- assert!(QuotedString::parse("\"").is_err());
- assert!(QuotedString::parse("\" \\\"").is_err());
- assert!(QuotedString::parse("abc").is_err());
-
- assert_eq!(QuotedString::parse("\"\"").unwrap(), "".to_string());
- assert_eq!(QuotedString::parse("\" \\\\a\\\"b \x02\"").unwrap(), " \\a\"b \x02".to_string());
- assert_eq!(QuotedString::parse("\"\\r\\n\"").unwrap(), "rn".to_string());
+ let mut s = "";
+ assert!(QuotedString::parse(&mut s).is_err());
+ let mut s = "\"";
+ assert!(QuotedString::parse(&mut s).is_err());
+ let mut s = "\" \\\"";
+ assert!(QuotedString::parse(&mut s).is_err());
+ let mut s = "abc";
+ assert!(QuotedString::parse(&mut s).is_err());
+
+ let mut s = "\"\"";
+ assert_eq!(QuotedString::parse(&mut s).unwrap(), "".to_string());
+ assert_eq!(s, "");
+
+ let mut s = "\" \\\\a\\\"b \x02\"";
+ assert_eq!(QuotedString::parse(&mut s).unwrap(), " \\a\"b \x02".to_string());
+ assert_eq!(s, "");
+
+ let mut s = "\"\\r\\n\" hey";
+ assert_eq!(QuotedString::parse(&mut s).unwrap(), "rn".to_string());
+ assert_eq!(s, " hey");
}
}