diff options
author | Yves Fischer <yvesf-git@xapek.org> | 2016-09-04 00:05:26 +0200 |
---|---|---|
committer | Yves Fischer <yvesf-git@xapek.org> | 2016-09-14 21:15:14 +0200 |
commit | 9416cd3d62f15f5d59f3bff94299f690e69a230d (patch) | |
tree | 4972cacce2dcbaa58d4a434f3ea7bce0616e11bc | |
parent | 53b5c00e625bf700a21be1a3e2070d3b23f1bef4 (diff) | |
download | auth-xmppmessage-9416cd3d62f15f5d59f3bff94299f690e69a230d.tar.gz auth-xmppmessage-9416cd3d62f15f5d59f3bff94299f690e69a230d.zip |
switch to using tiny_http
civet fails on FreeBSD because the mongoose Makefile requires gmake.
-rw-r--r-- | rust/Cargo.toml | 11 | ||||
-rw-r--r-- | rust/build.rs | 21 | ||||
-rw-r--r-- | rust/src/apachelog.rs | 24 | ||||
-rw-r--r-- | rust/src/handler.rs | 101 | ||||
-rw-r--r-- | rust/src/main.rs | 43 |
5 files changed, 123 insertions, 77 deletions
diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 50dfdaf..708256b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -5,13 +5,14 @@ authors = ["Yves Fischer <yvesf+git@xapek.org>"] build = "build.rs" [dependencies] -conduit = "0.7.4" -civet = "0.8.3" -time = "0.1.35" -base64 = "0.2.1" -rust-crypto = "0.2.36" +tiny_http = "0.5" +rustc-serialize = "0.3" +time = "0.1" +rust-crypto = "0.2" rand = "0.3" getopts = "0.2" +env_logger = "0.3" +log = "0.3" [build-dependencies] gcc = "0.3.35"
\ No newline at end of file diff --git a/rust/build.rs b/rust/build.rs index 3e9c14f..10c0d21 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -1,6 +1,21 @@ extern crate gcc; +use std::env; + fn main() { - gcc::compile_library("libsendxmpp.a", &["clib/sendxmpp.c"]); - println!("cargo:rustc-link-lib=strophe") -}
\ No newline at end of file + let target = env::var("TARGET").unwrap(); + println!("cargo:rustc-link-lib=strophe"); + + let mut config = gcc::Config::new(); + config.file("clib/sendxmpp.c"); + if target.contains("freebsd") { + println!("cargo:rustc-link-search=native=/usr/local/lib"); + config.include("/usr/local/include"); + } else if target.contains("linux") { + // ok pass + } else { + println!("Unknown OS, need to adapt build.rs"); + std::process::exit(1); + } + config.compile("libsendxmpp.a"); +} diff --git a/rust/src/apachelog.rs b/rust/src/apachelog.rs index 2517979..f502d99 100644 --- a/rust/src/apachelog.rs +++ b/rust/src/apachelog.rs @@ -1,7 +1,9 @@ ///! Prints logging similar to apache http access.log use std::net::IpAddr; -use conduit::{Request, Response}; +use std::io::Read; + use time; +use tiny_http::{Request, Response}; pub struct LogEntry { remote_ip_address: IpAddr, @@ -17,7 +19,7 @@ impl LogEntry { let entry = LogEntry { remote_ip_address: req.remote_addr().ip(), remote_user: String::new(), - request_path: String::from(req.path()), + request_path: String::from(req.url()), time: time::now(), status: 0, response_size: 0, @@ -25,21 +27,19 @@ impl LogEntry { return entry } - pub fn done(&mut self, response: Response) -> Response { - let (status_code, _) = response.status; - self.status = status_code; + pub fn done<R>(&mut self, _: &Response<R>) where R: Read { + self.status = 0; // not accessible self.print(); - return response; } #[inline(always)] fn print(&self) { println!("{} - {} - [{}] \"{}\" {} {}", - self.remote_ip_address, - self.remote_user, - time::strftime("%d/%b/%Y %T %z", &self.time).unwrap(), - self.request_path, - self.status, - self.response_size); + self.remote_ip_address, + self.remote_user, + time::strftime("%d/%b/%Y %T %z", &self.time).unwrap(), + self.request_path, + self.status, + self.response_size); } } diff --git a/rust/src/handler.rs b/rust/src/handler.rs index e16899b..5fb7a03 100644 --- a/rust/src/handler.rs +++ b/rust/src/handler.rs @@ -1,22 +1,19 @@ -use std::thread; -use std::collections::{HashMap, HashSet}; -use std::sync::{Arc, RwLock}; -use std::io::empty; -use std::error::Error; use std::cell::Cell; +use std::collections::{HashMap, HashSet}; +use std::io; use std::marker::Sync; use std::ops::Add; +use std::str::from_utf8; +use std::sync::{Arc, RwLock}; +use std::thread; use std::time::Duration; +use sendxmpp; use time; -use base64; -use civet::response; -use conduit::{Request, Response, Handler}; - use token; -use sendxmpp; -use apachelog; +use tiny_http::{Request, Response, StatusCode, Header}; +use rustc_serialize::base64::FromBase64; pub struct AuthHandler { bot_jid: String, @@ -24,10 +21,18 @@ pub struct AuthHandler { usernames: HashSet<String>, valid_tokens_cache: Arc<RwLock<HashMap<String, i64>>>, tg: token::TokenGenerator, - last_interactive_request: Cell<i64>, - headers_authenticate: HashMap<String, Vec<String>>, + last_interactive_request: Cell<i64> } +type EmptyResponse = Response<io::Empty>; + +macro_rules! get_header { + ($headers:expr, $name:expr) => ($headers.iter() + .filter(|h| h.field.equiv($name)) + .next().ok_or(stringify!(Header not found: $name))); +} + + impl AuthHandler { pub fn make(bot_jid: String, bot_password: String, usernames: HashSet<String>, validity: time::Duration, secret: Vec<u8>) -> AuthHandler { @@ -38,9 +43,6 @@ impl AuthHandler { valid_tokens_cache: Arc::new(RwLock::new(HashMap::new())), tg: token::TokenGenerator::new(validity.num_seconds(), secret), last_interactive_request: Cell::new(0), - headers_authenticate: vec!(("WWW-Authenticate".to_string(), - vec!("Basic realm=\"xmppmessage auth\"".to_string()))) - .into_iter().collect(), } } @@ -55,24 +57,37 @@ impl AuthHandler { } // Result<(method, username, password), error-message> - fn _get_username_password(request: &Request) -> Result<(String, String, String), &'static str> { + #[inline(always)] + fn _get_username_password<'a>(request: &'a Request) -> Result<(String, String, String), &'static str> { let headers = request.headers(); - let mut auth_header = try!(headers.find("Authorization").ok_or("No Authorization header found")); - let authorization = try!(auth_header.pop().ok_or("No Authorization header value")); + let auth_header: &Header = { try!(get_header!(headers, "Authorization")) }; + let authorization: &str = auth_header.value.as_str(); let mut authorization_split = authorization.split(' '); let method_value = try!(authorization_split.next().ok_or("No method in header value")); let value = try!(authorization_split.next().ok_or("No username/password value in header value")); - let decoded_value = try!(base64::decode(value).or(Err("Fail base64 decode"))); - let utf8_decoded_value = try!(String::from_utf8(decoded_value).or(Err("Failed to utf-8 decode username/password"))); + let decoded_value = try!(value.from_base64().or(Err("Fail base64 decode"))); + let utf8_decoded_value = try!(from_utf8(&decoded_value).or(Err("fail to decode utf-8"))); let mut username_password_split = utf8_decoded_value.split(':'); let username = try!(username_password_split.next().ok_or("No username in header")); let password = try!(username_password_split.next().ok_or("No password in header")); - Ok((method_value.to_string(), username.to_string(), password.to_string())) + Ok((String::from(method_value), String::from(username), String::from(password))) } - fn _call_internal(&self, req: &Request) -> Result<(), (u32, &'static str)> { + fn authenticate_response(status_code: u16) -> io::Result<EmptyResponse> { + Ok(Response::new( + StatusCode(status_code), + vec![ + Header::from_bytes(&b"WWW-Authenticate"[..], &b"Basic realm=\"xmppmessage auth\""[..]).unwrap() + ], + io::empty(), + Some(0), + None, + )) + } + + fn _call_internal(&self, request: &Request) -> io::Result<EmptyResponse> { let current_time = time::now().to_timespec().sec; - return match AuthHandler::_get_username_password(req) { + return match AuthHandler::_get_username_password(request) { Ok((_, username, password)) => { let is_known_user = self.usernames.contains(&username); if username.len() > 0 && password.len() == 0 { @@ -80,29 +95,29 @@ impl AuthHandler { if current_time - self.last_interactive_request.get() < 2 { // If last error was not longer then 2 second ago then sleep thread::sleep(Duration::from_secs(5)); - return Err((429, "Too many requests")) + return AuthHandler::authenticate_response(429) // Too many requests } else { self.last_interactive_request.set(current_time); if is_known_user { self.send_message(&username); } - return Err((401, "Token sent, retry now")) + return AuthHandler::authenticate_response(401) //Token sent, retry now } } else { match self.verify(&username, &password) { Ok(true) => { if is_known_user { - return Ok(()); + return Ok(Response::empty(200)) // Ok } else { self.last_interactive_request.set(current_time); - Err((401, "Token sent, retry")) + return AuthHandler::authenticate_response(401) // invalid password } }, Ok(false) => { if current_time - self.last_interactive_request.get() < 2 { // If last error was not longer then 2 seconds ago then sleep 5 seconds thread::sleep(Duration::from_secs(5)); - return Err((429, "Too Many Requests")) + return Ok(Response::empty(429)) // Too Many Requests } else { self.last_interactive_request.set(current_time); // in this case we use the chance to delete outdated cache entries @@ -110,19 +125,19 @@ impl AuthHandler { Ok(num) => println!("Removed {} cache entries", num), Err(e) => println!("{}", e), }; - return Err((401, "Authentication failed, username or password wrong")); + return AuthHandler::authenticate_response(401) // Authentication failed, username or password wrong } }, Err(msg) => { println!("verify failed: {}", msg); - Err((500, "Server Error")) + return Err(io::Error::new(io::ErrorKind::Other, "Server Error")) // Server Error } } } }, Err(e) => { - println!("Failed: {}", e); - return Err((401, e)) + info!("{}. Request Authentication", e); + return AuthHandler::authenticate_response(401) // No Authorization header }, }; } @@ -181,19 +196,15 @@ impl AuthHandler { } } } -} -unsafe impl Sync for AuthHandler { + #[inline(always)] + pub fn call(&self, request: &Request) -> Response<io::Empty> { + self._call_internal(request).unwrap_or_else(|err: io::Error| { + error!("{}", err); + Response::empty(500) + }) + } } -impl Handler for AuthHandler { - fn call(&self, req: &mut Request) -> Result<Response, Box<Error + Send>> { - let mut logentry = apachelog::LogEntry::start(req); - return match self._call_internal(req) { - Ok(_) => Ok(response((200, "OK, go ahead"), HashMap::new(), empty())), - Err((code, message)) => { - Ok(response((code, message), self.headers_authenticate.clone(), empty())) - } - }.map(|r| logentry.done(r)) - } +unsafe impl Sync for AuthHandler { }
\ No newline at end of file diff --git a/rust/src/main.rs b/rust/src/main.rs index 9537bd7..6338557 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,17 +1,18 @@ use std::env; use std::collections::HashSet; use std::iter::repeat; -use std::sync::mpsc::channel; +use std::sync::Arc; +use std::thread; -extern crate base64; extern crate crypto; -extern crate civet; -extern crate conduit; +extern crate env_logger; extern crate getopts; +#[macro_use] extern crate log; +extern crate tiny_http; extern crate time; extern crate rand; +extern crate rustc_serialize; -use civet::{Config, Server}; use crypto::digest::Digest; use crypto::sha1::Sha1; use getopts::Options; @@ -28,6 +29,8 @@ fn print_usage(program: &str, opts: Options) { } fn main() { + env_logger::init().unwrap(); + let args: Vec<String> = env::args().collect(); let program = args[0].clone(); let mut opts = Options::new(); @@ -50,7 +53,6 @@ fn main() { panic!("Missing jid or password"); } - let mut server_config = Config::new(); let usernames = matches.opt_strs("u").into_iter().collect::<HashSet<String>>(); let mut hasher = Sha1::new(); let mut secret: Vec<u8> = repeat(0).take((hasher.output_bits() + 7) / 8).collect(); @@ -62,18 +64,35 @@ fn main() { println!("No secret (-s/--secret) given, using random value"); thread_rng().fill_bytes(&mut secret); }); + let secret = secret.into_iter().take(16).collect::<Vec<u8>>(); let validity: i64 = matches.opt_str("t").unwrap_or(String::from("48")).parse() .unwrap_or_else(|_| { panic!("Failed to parse time") }); - server_config.port(matches.opt_str("o").unwrap_or(String::from("8080")).parse() - .unwrap_or_else(|_| { panic!("Failed to parse port number") })); - + let port = matches.opt_str("o").unwrap_or(String::from("8080")).parse() + .unwrap_or_else(|_| { panic!("Failed to parse port number") }); let handler = handler::AuthHandler::make(matches.opt_str("j").unwrap(), matches.opt_str("p").unwrap(), usernames, time::Duration::hours(validity), secret); - let _a = Server::start(server_config, handler); - let (_tx, rx) = channel::<()>(); - rx.recv().unwrap(); + let handler = Arc::new(handler); + let server = Arc::new(tiny_http::Server::http(("0.0.0.0", port)).unwrap()); + + let mut handles = Vec::new(); + + for _ in 0..2 { + let server = server.clone(); + let handler = handler.clone(); + handles.push(thread::spawn(move || { + for request in server.incoming_requests() { + let mut log = apachelog::LogEntry::start(&request); + let response = handler.call(&request); + log.done(&response); + let _ = request.respond(response); + } + })); + } + for h in handles { + h.join().unwrap(); + } }
\ No newline at end of file |