diff options
author | Yorhel <git@yorhel.nl> | 2019-04-19 12:46:49 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2019-04-19 12:46:51 +0200 |
commit | adf4bcb6aeb460e0f243fb0cec2bf4bfea11fc29 (patch) | |
tree | 8d077c76499e85a73342aab5d5a0d68186ea7433 /src/sock.rs | |
parent | 56c77e94185ed35bdbc474a0377eba0ac8117ba9 (diff) |
Add bootstrap event support + change log event API
The message parsing code is getting pretty ugly. Hmm.
Diffstat (limited to 'src/sock.rs')
-rw-r--r-- | src/sock.rs | 216 |
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"); } } |