main.rs (8753B)
1 #![crate_name = "wala"] 2 3 use tiny_http::{ 4 Server, 5 ServerConfig, 6 Request, 7 Header, 8 Method, 9 }; 10 use mime::Mime; 11 use std::net::{Ipv4Addr, SocketAddrV4}; 12 use std::str::FromStr; 13 use std::path::{PathBuf, Path}; 14 use std::fs::{ 15 File, 16 create_dir_all, 17 }; 18 use std::error::Error; 19 use std::fmt; 20 use std::io::{ 21 copy as io_copy, 22 Read, 23 Seek, 24 empty, 25 }; 26 use std::time::Duration; 27 28 use std::sync::Arc; 29 use std::sync::atomic::{AtomicBool, Ordering}; 30 31 use env_logger; 32 use ascii::AsciiStr; 33 use signal_hook::flag; 34 use signal_hook::consts; 35 36 use wala::auth::{ 37 AuthSpec, 38 AuthResult, 39 }; 40 41 use wala::record::{ 42 RequestResult, 43 RequestResultType, 44 }; 45 46 use wala::request::process_method; 47 use wala::response::{ 48 exec_response, 49 preflight_response, 50 }; 51 52 #[cfg(feature = "trace")] 53 use wala::trace::trace_request; 54 55 mod arg; 56 use arg::Settings; 57 58 use log::{info, error, warn}; 59 60 use tempfile::tempfile; 61 62 #[cfg(feature = "dev")] 63 use wala::auth::mock::auth_check as mock_auth_check; 64 65 #[cfg(feature = "pgpauth")] 66 //use wala::auth::pgp::auth_check as pgp_auth_check; 67 use wala::auth::pgp_sequoia::auth_check as pgp_auth_check; 68 69 70 #[derive(Debug)] 71 pub struct NoAuthError; 72 73 impl Error for NoAuthError { 74 fn description(&self) -> &str{ 75 "no auth" 76 } 77 } 78 79 impl fmt::Display for NoAuthError { 80 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 81 fmt.write_str(self.description()) 82 } 83 } 84 85 fn exec_auth(auth_spec: AuthSpec, data: &File, data_length: usize) -> Option<AuthResult> { 86 #[cfg(feature = "dev")] 87 match mock_auth_check(&auth_spec, data, data_length) { 88 Ok(v) => { 89 return Some(v); 90 }, 91 Err(e) => { 92 }, 93 } 94 95 #[cfg(feature = "pgpauth")] 96 match pgp_auth_check(&auth_spec, data, data_length) { 97 Ok(v) => { 98 return Some(v); 99 }, 100 Err(e) => { 101 }, 102 } 103 104 None 105 } 106 107 108 fn process_auth(auth_spec: AuthSpec, data: &File, data_length: usize) -> Option<AuthResult> { 109 if !auth_spec.valid() { 110 let r = AuthResult{ 111 identity: vec!(), 112 error: true, 113 }; 114 return Some(r); 115 } 116 exec_auth(auth_spec, data, data_length) 117 } 118 119 120 fn auth_from_headers(headers: &[Header], method: &Method) -> Option<AuthSpec> { 121 for h in headers { 122 let k = &h.field; 123 if k.equiv("Authorization") { 124 let v = &h.value; 125 let r = AuthSpec::from_str(v.as_str()); 126 match r { 127 Ok(v) => { 128 return Some(v); 129 }, 130 Err(e) => { 131 error!("malformed auth string: {}", &h.value); 132 let r = AuthSpec{ 133 method: String::from(method.as_str()), 134 key: String::new(), 135 signature: String::new(), 136 }; 137 return Some(r); 138 } 139 } 140 } 141 } 142 None 143 } 144 145 146 fn process_request(req: &mut Request, f: &File) -> AuthResult { 147 let headers = req.headers(); 148 let method = req.method(); 149 150 let r: Option<AuthResult>; 151 152 r = match auth_from_headers(headers, method) { 153 Some(v) => { 154 process_auth(v, f, 0) 155 }, 156 _ => { 157 None 158 }, 159 }; 160 161 match r { 162 Some(v) => { 163 return v; 164 }, 165 _ => {}, 166 }; 167 168 // is not auth 169 AuthResult{ 170 identity: vec!(), 171 error: false, 172 } 173 } 174 175 fn process_meta(req: &Request, path: &Path, digest: Vec<u8>) -> Option<Mime> { 176 let headers = req.headers(); 177 let mut m: Option<mime::Mime> = None; 178 let mut n: Option<String> = None; 179 180 for h in headers { 181 let k = &h.field; 182 if k.equiv("Content-Type") { 183 let v = &h.value; 184 m = match Mime::from_str(v.as_str()) { 185 Err(e) => { 186 error!("invalid mime type"); 187 return None; 188 }, 189 Ok(v) => { 190 Some(v) 191 }, 192 }; 193 } else if k.equiv("X-Filename") { 194 let v = &h.value; 195 let p = Path::new(v.as_str()); 196 let fp = p.to_str().unwrap(); 197 n = Some(String::from(fp)); 198 } 199 } 200 201 #[cfg(feature = "meta")] 202 match m { 203 Some(v) => { 204 match wala::meta::register_type(path, &digest, v) { 205 Err(e) => { 206 error!("could not register content type: {}", &e); 207 }, 208 _ => {}, 209 }; 210 }, 211 _ => {}, 212 }; 213 214 #[cfg(feature = "meta")] 215 match n { 216 Some(v) => { 217 match wala::meta::register_filename(path, &digest, v) { 218 Err(e) => { 219 error!("could not register content type: {}", &e); 220 }, 221 _ => {}, 222 }; 223 }, 224 _ => {}, 225 }; 226 227 None 228 } 229 230 231 fn main() { 232 env_logger::init(); 233 234 let settings = Settings::from_args(); 235 let base_path = settings.dir.as_path(); 236 237 let spool_path = base_path.join("spool"); 238 let mut spool_ok = false; 239 240 #[cfg(feature = "trace")] 241 { 242 match create_dir_all(&spool_path) { 243 Ok(v) => { 244 spool_ok = true; 245 }, 246 Err(e) => { 247 warn!("spool directory could not be created: {:?}", e); 248 }, 249 }; 250 } 251 252 info!("Using data dir: {:?}", &base_path); 253 254 let ip_addr = Ipv4Addr::from_str(&settings.host).unwrap(); 255 let tcp_port: u16 = settings.port; 256 let sock_addr = SocketAddrV4::new(ip_addr, tcp_port); 257 let srv_cfg = ServerConfig{ 258 addr: sock_addr, 259 ssl: None, 260 }; 261 let srv = Server::new(srv_cfg).unwrap(); 262 263 let term = Arc::new(AtomicBool::new(false)); 264 signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term)).unwrap(); 265 266 #[cfg(feature = "docker")] 267 signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&term)).unwrap(); 268 269 const loop_timeout: Duration = Duration::new(1, 0); 270 271 while !term.load(Ordering::Relaxed) { 272 273 let b = srv.recv_timeout(loop_timeout); 274 let mut hasreq: Option<Request>; 275 match b { 276 Ok(v) => hasreq = v, 277 Err(e) => { 278 error!("{}", e); 279 break; 280 } 281 }; 282 let mut req: Request; 283 match hasreq { 284 Some(v) => { 285 req = v; 286 }, 287 None => { 288 continue 289 } 290 }; 291 292 let method = req.method().clone(); 293 match &method { 294 Method::Options => { 295 preflight_response(req); 296 continue; 297 }, 298 _ => {}, 299 } 300 301 let url = String::from(&req.url()[1..]); 302 let expected_size = match req.body_length() { 303 Some(v) => { 304 v 305 }, 306 None => { 307 0 308 }, 309 }; 310 let f = req.as_reader(); 311 let mut path = base_path.clone(); 312 let mut res: AuthResult = AuthResult{ 313 identity: vec!(), 314 error: false, 315 }; 316 let rw: Option<File> = match tempfile() { 317 Ok(mut v) => { 318 io_copy(f, &mut v); 319 v.rewind(); 320 res = process_request(&mut req, &mut v); 321 v.rewind(); 322 Some(v) 323 }, 324 Err(e) => { 325 None 326 }, 327 }; 328 329 let mut result: RequestResult; 330 match rw { 331 Some(v) => { 332 result = process_method(&method, url, v, expected_size, &path, res); 333 }, 334 None => { 335 let v = empty(); 336 result = process_method(&method, url, v, expected_size, &path, res); 337 }, 338 }; 339 340 match &result.typ { 341 RequestResultType::Changed => { 342 let digest_hex = result.v.clone().unwrap(); 343 let digest = hex::decode(&digest_hex).unwrap(); 344 process_meta(&req, &path, digest); 345 }, 346 RequestResultType::Found => { 347 348 }, 349 _ => {}, 350 } 351 352 #[cfg(feature="trace")] 353 { 354 for h in req.headers() { 355 if h.field.equiv("X-Wala-Trace") { 356 let mut identity = false; 357 if h.value.eq_ignore_ascii_case(AsciiStr::from_ascii("identity").unwrap()) { 358 identity = true; 359 } 360 trace_request(&spool_path, &result, identity); 361 } 362 } 363 } 364 365 exec_response(req, result); 366 367 } 368 }