// Copyright 2021-2022 Ian Jackson and contributors to Hippotat // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception // There is NO WARRANTY. use super::*; /// Sent from hyper worker pool task to client task #[derive(Debug)] pub struct WebRequest { // initial part of body // used up to and including first 2 lines of metadata // end delimiter for the metadata not yet located, but in here somewhere pub initial: Box<[u8]>, pub initial_remaining: usize, pub length_hint: usize, pub body: hyper::body::Body, pub boundary_finder: multipart::BoundaryFinder, pub reply_to: oneshot::Sender, pub warnings: Warnings, pub conn: Arc, pub may_route: MayRoute, } /// Reply from client task to hyper worker pool task #[derive(Debug)] pub struct WebResponse { pub warnings: Warnings, pub data: Result, } pub type WebResponseData = FrameQueueBuf; pub type WebResponseBody = BufBody; pub async fn handle( conn: Arc, global: Arc, req: hyper::Request ) -> Result, hyper::http::Error> { if req.method() == Method::GET { let mut resp = hyper::Response::new(BufBody::display("hippotat\r\n")); resp.headers_mut().insert( "Content-Type", "text/plain; charset=US-ASCII".try_into().unwrap() ); return Ok(resp) } let mut warnings: Warnings = default(); async { let get_header = |hn: &str| { let mut values = req.headers().get_all(hn).iter(); let v = values.next().ok_or_else(|| anyhow!("missing {}", hn))?; if values.next().is_some() { throw!(anyhow!("multiple {}!", hn)); } let v = v.to_str().context(anyhow!("interpret {} as UTF-8", hn))?; Ok::<_,AE>(v) }; let mkboundary = |b: &'_ _| format!("\n--{}", b).into_bytes(); let boundary = match (||{ let t = get_header("Content-Type")?; let t: mime::Mime = t.parse().context("parse Content-Type")?; if t.type_() != "multipart" { throw!(anyhow!("not multipart/")) } let b = mime::BOUNDARY; let b = t.get_param(b).ok_or_else(|| anyhow!("missing boundary=..."))?; if t.subtype() != "form-data" { warnings.add(&"Content-Type not /form-data")?; } let b = mkboundary(b.as_str()); Ok::<_,AE>(b) })() { Ok(y) => y, Err(e) => { warnings.add(&e.wrap_err("guessing boundary"))?; mkboundary("b") }, }; let length_hint: usize = (||{ let clength = get_header("Content-Length")?; let clength = clength.parse().context("parse Content-Length")?; Ok::<_,AE>(clength) })().unwrap_or_else( |e| { let _ = warnings.add(&e.wrap_err("parsing Content-Length")); 0 } ); let mut body = req.into_body(); let initial = match read_limited_bytes( METADATA_MAX_LEN, default(), length_hint, &mut body ).await { Ok(all) => all, Err(ReadLimitedError::Truncated { sofar,.. }) => sofar, Err(ReadLimitedError::Hyper(e)) => throw!(e), }; let boundary_finder = memmem::Finder::new(&boundary); let mut boundary_iter = boundary_finder.find_iter(&initial); let start = if initial.starts_with(&boundary[1..]) { boundary.len()-1 } else if let Some(start) = boundary_iter.next() { start + boundary.len() } else { throw!(anyhow!("initial boundary not found")) }; let comp = multipart::process_boundary (&mut warnings, &initial[start..], PartName::m)? .ok_or_else(|| anyhow!(r#"no "m" component"#))?; if comp.name != PartName::m { throw!(anyhow!( r#"first multipart component must be name="m""# )) } let mut meta = MetadataFieldIterator::new(comp.payload); let client: ClientName = meta.need_parse().context("client addr")?; let mut hmac_got = [0; HMAC_L]; let (client_time, hmac_got_l) = (||{ let token: &str = meta.need_next().context(r#"find in "m""#)?; let (time_t, hmac_b64) = token.split_once(' ') .ok_or_else(|| anyhow!("split"))?; let time_t = u64::from_str_radix(time_t, 16).context("parse time_t")?; let l = io::copy( &mut base64::read::DecoderReader::new(&mut hmac_b64.as_bytes(), &BASE64_CONFIG), &mut &mut hmac_got[..] ).context("parse b64 token")?; let l = l.try_into()?; Ok::<_,AE>((time_t, l)) })().context("token")?; let hmac_got = &hmac_got[0..hmac_got_l]; let client_name = client; let client = global.all_clients.get(&client_name); // We attempt to hide whether the client exists we don't try to // hide the hash lookup computationgs, but we do try to hide the // HMAC computation by always doing it. We hope that the compiler // doesn't produce a specialised implementation for the dummy // secret value. let client_exists = subtle::Choice::from(client.is_some() as u8); let secret = client.map(|c| c.ic.secret.0.as_bytes()); let secret = secret.unwrap_or(&[0x55; HMAC_B][..]); let client_time_s = format!("{:x}", client_time); let hmac_exp = token_hmac(secret, client_time_s.as_bytes()); // We also definitely want a consttime memeq for the hmac value let hmac_ok = hmac_got.ct_eq(&hmac_exp); //dbg!(DumpHex(&hmac_exp), client.is_some()); //dbg!(DumpHex(hmac_got), hmac_ok, client_exists); if ! bool::from(hmac_ok & client_exists) { debug!("{} rejected client {}", &conn, &client_name); let body = BufBody::display("Not authorised\r\n"); return Ok( hyper::Response::builder() .status(hyper::StatusCode::FORBIDDEN) .header("Content-Type", r#"text/plain; charset="utf-8""#) .body(body) ) } let client = client.unwrap(); let now = time_t_now(); let chk_skew = |a: u64, b: u64, c_ahead_behind| { if let Some(a_ahead) = a.checked_sub(b) { if a_ahead > client.ic.max_clock_skew.as_secs() { throw!(anyhow!("too much clock skew (client {} by {})", c_ahead_behind, a_ahead)); } } Ok::<_,AE>(()) }; chk_skew(client_time, now, "ahead")?; chk_skew(now, client_time, "behind")?; let initial_remaining = meta.remaining_bytes_len(); //eprintln!("boundary={:?} start={} name={:?} client={}", // boundary, start, &comp.name, &client.ic); let (reply_to, reply_recv) = oneshot::channel(); trace!("{} {} request, Content-Length={}", &conn, &client_name, length_hint); let wreq = WebRequest { initial, initial_remaining, length_hint, boundary_finder: boundary_finder.into_owned(), body, warnings: mem::take(&mut warnings), reply_to, conn: conn.clone(), may_route: MayRoute::came_from_outside_hippotatd(), }; client.web.try_send(wreq) .map_err(|_| anyhow!("client user task overloaded"))?; let reply: WebResponse = reply_recv.await?; warnings = reply.warnings; let data = reply.data?; if warnings.warnings.is_empty() { trace!("{} {} responding, {}", &conn, &client_name, data.len()); } else { debug!("{} {} responding, {} warnings={:?}", &conn, &client_name, data.len(), &warnings.warnings); } let data = BufBody::new(data); Ok::<_,AE>( hyper::Response::builder() .header("Content-Type", r#"application/octet-stream"#) .body(data) ) }.await.unwrap_or_else(|e| { debug!("{} error {}", &conn, &e); let mut errmsg = format!("ERROR\n\n{:?}\n\n", &e); for w in warnings.warnings { writeln!(errmsg, "warning: {}", w).unwrap(); } hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) .header("Content-Type", r#"text/plain; charset="utf-8""#) .body(BufBody::display(errmsg)) }) }