use std::io; use std::net::SocketAddrV4; use tokio::net::{TcpListener, TcpStream}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::oneshot; use tauri::{AppHandle, Manager}; use crate::clientinfo; use crate::errors::RequestError; use crate::ipc::{Request, Approval}; pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()> { let listener = TcpListener::bind(&addr).await?; println!("Listening on {addr}"); loop { let new_handle = app_handle.app_handle(); match listener.accept().await { Ok((stream, _)) => { tokio::spawn(async { if let Err(e) = handle(stream, new_handle).await { eprintln!("{e}"); } }); }, Err(e) => { eprintln!("Error accepting connection: {e}"); } } } } // it doesn't really return Approval, we just need to placate the compiler async fn stall(stream: &mut TcpStream) -> Result { let delay = std::time::Duration::from_secs(1); loop { tokio::time::sleep(delay).await; stream.write(b"x").await?; } } async fn handle(mut stream: TcpStream, app_handle: AppHandle) -> Result<(), RequestError> { let (chan_send, chan_recv) = oneshot::channel(); let app_state = app_handle.state::(); let request_id = app_state.register_request(chan_send); let peer_addr = match stream.peer_addr()? { std::net::SocketAddr::V4(addr) => addr, _ => unreachable!(), // we only listen on IPv4 }; let clients = clientinfo::get_clients(peer_addr.port())?; // Do we want to panic if this fails? Does that mean the frontend is dead? let req = Request {id: request_id, clients}; app_handle.emit_all("credentials-request", req).unwrap(); let mut buf = [0; 8192]; // it's what tokio's BufReader uses let mut n = 0; loop { n += stream.read(&mut buf[n..]).await?; if &buf[(n - 4)..n] == b"\r\n\r\n" {break;} if n == buf.len() {return Err(RequestError::RequestTooLarge);} } println!("{}", std::str::from_utf8(&buf).unwrap()); stream.write(b"HTTP/1.0 200 OK\r\n").await?; stream.write(b"Content-Type: application/json\r\n").await?; stream.write(b"X-Creddy-delaying-tactic: ").await?; let approval = tokio::select!{ e = stall(&mut stream) => e?, // this will never return Ok, just Err if it can't write to the stream r = chan_recv => r.unwrap(), // only panics if the sender is dropped without sending, which shouldn't happen }; if matches!(approval, Approval::Denied) { // because we own the stream, it gets closed when we return. // Unfortunately we've already signaled 200 OK, there's no way around this - // we have to write the status code first thing, and we have to assume that the user // might need more time than that gives us (especially if entering the passphrase). // Fortunately most AWS libs automatically retry if the request dies uncompleted, allowing // us to respond with a proper error status. return Ok(()); } let creds = app_state.get_creds_serialized()?; stream.write(b"\r\nContent-Length: ").await?; stream.write(creds.as_bytes().len().to_string().as_bytes()).await?; stream.write(b"\r\n\r\n").await?; stream.write(creds.as_bytes()).await?; stream.write(b"\r\n\r\n").await?; Ok(()) }