diff --git a/Cargo.lock b/Cargo.lock index 2c71f1f..e95f9c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1961,6 +1961,7 @@ dependencies = [ "sequoia-openpgp", "serde", "serde_json", + "thiserror", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 6236f36..7725023 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ sequoia-net = "0.27.0" sequoia-openpgp = "1.14.0" serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0.96" +thiserror = "1.0.40" tokio = { version = "1.27.0", features = ["time"] } diff --git a/src/confirmation.rs b/src/confirmation.rs index 968194f..646dfaf 100644 --- a/src/confirmation.rs +++ b/src/confirmation.rs @@ -1,3 +1,4 @@ +use crate::errors::Error; use crate::management::{delete_key, Action, Pending}; use crate::utils::{get_email_from_cert, parse_pem}; use crate::PENDING; @@ -6,26 +7,42 @@ use crate::{pending_path, PATH, VARIANT}; use std::fs; use std::path::Path; -pub fn confirm_action(token: &str) { +pub fn confirm_action(token: &str) -> Result<(), Error> { let pending_path = pending_path!().join(token); - let key: Pending = serde_json::from_str(&fs::read_to_string(&pending_path).unwrap()).unwrap(); + let data = if pending_path.exists() { + match fs::read_to_string(&pending_path) { + Ok(data) => data, + Err(_) => return Err(Error::Inaccessible), + } + } else { + return Err(Error::MissingPath); + }; + let key = match serde_json::from_str::(&data) { + Ok(key) => key, + Err(_) => return Err(Error::ParseStored), + }; match key.action() { Action::Add => { - let cert = parse_pem(key.data()); - let domain = get_email_from_cert(&cert) - .split('@') - .last() - .unwrap() - .to_owned(); - sequoia_net::wkd::insert(PATH, domain, VARIANT, &cert).unwrap(); + let cert = parse_pem(key.data())?; + let domain = match get_email_from_cert(&cert)?.split('@').last() { + Some(domain) => domain.to_string(), + None => return Err(Error::MalformedMail), + }; + match sequoia_net::wkd::insert(PATH, domain, VARIANT, &cert) { + Ok(_) => (), + Err(_) => return Err(Error::AddingKey), + } } - Action::Delete => delete_key(key.data()), + Action::Delete => delete_key(key.data())?, + } + match fs::remove_file(&pending_path) { + Ok(_) => Ok(()), + Err(_) => Err(Error::Inaccessible), } - fs::remove_file(&pending_path).unwrap(); } -pub fn send_confirmation_email(email: &str, action: Action, token: &str) { - println!("Email sent to {}", email); - println!("Action: {:?}", action); - println!("Token: {}", token); +pub fn send_confirmation_email(email: &str, action: &Action, token: &str) { + println!("Email sent to {email}"); + println!("Action: {action:?}"); + println!("Token: {token}"); } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..8c1b1c9 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,35 @@ +use actix_web::http::StatusCode; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Error while parsing cert")] + ParseCert, + #[error("Error while parsing E-Mail")] + ParseMail, + #[error("Error while parsing stored data")] + ParseStored, + #[error("No E-Mail found in the certificate")] + MissingMail, + #[error("The E-Mail is malformed")] + MalformedMail, + #[error("Error while serializing data")] + SerializeData, + #[error("Error while deserializing data")] + DeserializeData, + #[error("File or directory does not exist")] + MissingPath, + #[error("The file is inaccessible")] + Inaccessible, + #[error("Error while adding a key to the wkd")] + AddingKey, +} + +impl actix_web::ResponseError for Error { + fn status_code(&self) -> actix_web::http::StatusCode { + match self { + Self::MissingPath => StatusCode::from_u16(404).unwrap(), + _ => StatusCode::from_u16(500).unwrap(), + } + } +} diff --git a/src/main.rs b/src/main.rs index 0072cee..c211808 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod confirmation; +mod errors; mod management; mod utils; @@ -40,7 +41,7 @@ async fn main() -> std::io::Result<()> { let mut metronome = time::interval(time::Duration::from_secs(60 * 60 * 3)); loop { metronome.tick().await; - clean_stale(MAX_AGE); + clean_stale(MAX_AGE).unwrap(); } }); HttpServer::new(|| App::new().service(submit).service(confirm).service(delete)) @@ -51,24 +52,24 @@ async fn main() -> std::io::Result<()> { #[post("/api/submit")] async fn submit(pem: web::Form) -> Result { - let cert = parse_pem(&pem.key); - let email = get_email_from_cert(&cert); + let cert = parse_pem(&pem.key)?; + let email = get_email_from_cert(&cert)?; let token = gen_random_token(); - store_pending_addition(pem.key.to_owned(), &token); - send_confirmation_email(&email, Action::Add, &token); + store_pending_addition(pem.key.clone(), &token)?; + send_confirmation_email(&email, &Action::Add, &token); Ok(String::from("OK!")) } #[get("/api/confirm/{data}")] async fn confirm(token: web::Path) -> Result { - confirm_action(&token.data); + confirm_action(&token.data)?; Ok(String::from("OK!")) } #[get("/api/delete/{address}")] async fn delete(email: web::Path) -> Result { let token = gen_random_token(); - store_pending_deletion(email.address.to_owned(), &token); - send_confirmation_email(&email.address, Action::Delete, &token); + store_pending_deletion(email.address.clone(), &token)?; + send_confirmation_email(&email.address, &Action::Delete, &token); Ok(String::from("OK!")) } diff --git a/src/management.rs b/src/management.rs index 880db7d..13d65e2 100644 --- a/src/management.rs +++ b/src/management.rs @@ -1,3 +1,4 @@ +use crate::errors::Error; use crate::utils::get_user_file_path; use crate::PENDING; use crate::{pending_path, PATH}; @@ -19,65 +20,86 @@ pub struct Pending { timestamp: i64, } impl Pending { - pub fn build_add(pem: String) -> Pending { + pub fn build_add(pem: String) -> Self { let timestamp = Utc::now().timestamp(); - Pending { + Self { action: Action::Add, data: pem, timestamp, } } - pub fn build_delete(email: String) -> Pending { + pub fn build_delete(email: String) -> Self { let timestamp = Utc::now().timestamp(); - Pending { + Self { action: Action::Delete, data: email, timestamp, } } - pub fn action(&self) -> &Action { + pub const fn action(&self) -> &Action { &self.action } pub fn data(&self) -> &str { &self.data } - pub fn timestamp(&self) -> i64 { + pub const fn timestamp(&self) -> i64 { self.timestamp } } -fn store_pending(pending: Pending, token: &str) { - let serialized = serde_json::to_string(&pending).unwrap(); - fs::create_dir_all(pending_path!()).unwrap(); - fs::write(pending_path!().join(token), serialized).unwrap(); -} - -pub fn store_pending_addition(pem: String, token: &str) { - let data = Pending::build_add(pem); - store_pending(data, token); -} - -pub fn store_pending_deletion(email: String, token: &str) { - let data = Pending::build_delete(email); - store_pending(data, token); -} - -pub fn clean_stale(max_age: i64) { - for path in fs::read_dir(pending_path!()).unwrap() { - let file_path = path.unwrap().path(); - let key: Pending = serde_json::from_str(&fs::read_to_string(&file_path).unwrap()).unwrap(); - let now = Utc::now().timestamp(); - if now - key.timestamp() > max_age { - fs::remove_file(&file_path).unwrap(); - println!( - "Deleted {}, since it was stale", - &file_path.to_str().unwrap() - ); - } +fn store_pending(pending: &Pending, token: &str) -> Result<(), Error> { + match serde_json::to_string(pending) { + Ok(serialized) => match fs::write(pending_path!().join(token), serialized) { + Ok(_) => Ok(()), + Err(_) => Err(Error::Inaccessible), + }, + Err(_) => Err(Error::SerializeData), } } -pub fn delete_key(email: &str) { - let path = Path::new(PATH).join(get_user_file_path(email)); - fs::remove_file(path).unwrap(); +pub fn store_pending_addition(pem: String, token: &str) -> Result<(), Error> { + let data = Pending::build_add(pem); + store_pending(&data, token)?; + Ok(()) +} + +pub fn store_pending_deletion(email: String, token: &str) -> Result<(), Error> { + let data = Pending::build_delete(email); + store_pending(&data, token)?; + Ok(()) +} + +pub fn clean_stale(max_age: i64) -> Result<(), Error> { + for path in fs::read_dir(pending_path!()).unwrap().flatten() { + let file_path = path.path(); + if file_path.exists() { + match fs::read_to_string(&file_path) { + Ok(data) => match serde_json::from_str::(&data) { + Ok(key) => { + let now = Utc::now().timestamp(); + if now - key.timestamp() > max_age { + if fs::remove_file(&file_path).is_err() { + return Err(Error::Inaccessible); + } + println!( + "Deleted {}, since it was stale", + &file_path.to_str().unwrap() + ); + } + } + Err(_) => return Err(Error::DeserializeData), + }, + Err(_) => return Err(Error::Inaccessible), + } + } + } + Ok(()) +} + +pub fn delete_key(email: &str) -> Result<(), Error> { + let path = Path::new(PATH).join(get_user_file_path(email)?); + match fs::remove_file(path) { + Ok(_) => Ok(()), + Err(_) => Err(Error::Inaccessible), + } } diff --git a/src/utils.rs b/src/utils.rs index 5ab07a2..0e3103f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +use crate::errors::Error; use crate::VARIANT; use rand::{distributions::Alphanumeric, thread_rng, Rng}; @@ -12,8 +13,11 @@ macro_rules! pending_path { }; } -pub fn parse_pem(data: &str) -> Cert { - sequoia_openpgp::Cert::from_bytes(data.as_bytes()).unwrap() +pub fn parse_pem(data: &str) -> Result { + match sequoia_openpgp::Cert::from_bytes(data.as_bytes()) { + Ok(data) => Ok(data), + Err(_) => Err(Error::ParseCert), + } } pub fn gen_random_token() -> String { @@ -21,10 +25,25 @@ pub fn gen_random_token() -> String { (0..10).map(|_| rng.sample(Alphanumeric) as char).collect() } -pub fn get_email_from_cert(cert: &Cert) -> String { - cert.userids().next().unwrap().email().unwrap().unwrap() +pub fn get_email_from_cert(cert: &Cert) -> Result { + match cert.userids().next() { + Some(data) => match data.email() { + Ok(data) => match data { + Some(data) => Ok(data), + None => Err(Error::MissingMail), + }, + Err(_) => Err(Error::ParseMail), + }, + None => Err(Error::ParseCert), + } } -pub fn get_user_file_path(email: &str) -> PathBuf { - Url::from(email).unwrap().to_file_path(VARIANT).unwrap() +pub fn get_user_file_path(email: &str) -> Result { + match Url::from(email) { + Ok(data) => match data.to_file_path(VARIANT) { + Ok(data) => Ok(data), + Err(_) => Err(Error::ParseMail), + }, + Err(_) => Err(Error::ParseMail), + } }