wala-rust

Content-adressed HTTP file server
Info | Log | Files | Refs | README | LICENSE

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 }