diff --git a/src/confirmation.rs b/src/confirmation.rs index 7360381..f1182bd 100644 --- a/src/confirmation.rs +++ b/src/confirmation.rs @@ -1,42 +1,67 @@ use chrono::Utc; +use log::{debug, error, trace, warn}; use crate::errors::Error; use crate::management::{delete_key, Action, Pending}; use crate::pending_path; use crate::settings::{MAILER, SETTINGS}; -use crate::utils::{get_email_from_cert, parse_pem}; +use crate::utils::{get_email_from_cert, get_filename, parse_pem}; use crate::PENDING_FOLDER; use lettre::{Message, Transport}; use std::fs; use std::path::Path; -pub fn confirm_action(token: &str) -> Result<(), Error> { +pub fn confirm_action(token: &str) -> Result<(Action, String), Error> { + trace!("Handling token {}", token); let pending_path = pending_path!().join(token); let content = if pending_path.is_file() { match fs::read_to_string(&pending_path) { Ok(content) => content, - Err(_) => return Err(Error::Inaccessible), + Err(_) => { + warn!( + "Token {} was requested, but can't be read to string!", + token + ); + return Err(Error::Inaccessible); + } } } else { + trace!("Requested token {} isn't a file", token); return Err(Error::MissingPending); }; let key = match serde_json::from_str::(&content) { Ok(key) => key, - Err(_) => return Err(Error::DeserializeData), + Err(_) => { + warn!("Error while deserializing token {}!", token); + return Err(Error::DeserializeData); + } }; if Utc::now().timestamp() - key.timestamp() > SETTINGS.max_age { - match fs::remove_file(pending_path) { - Ok(_) => Err(Error::MissingPending), - Err(_) => Err(Error::Inaccessible), + match fs::remove_file(&pending_path) { + Ok(_) => { + debug!( + "Deleted stale token {}", + get_filename(&pending_path).unwrap() + ); + Err(Error::MissingPending) + } + Err(_) => { + warn!("Stale token {} can't be deleted!", token); + Err(Error::Inaccessible) + } } } else { - match key.action() { + let address = match key.action() { Action::Add => { let cert = parse_pem(key.data())?; - let domain = match get_email_from_cert(&cert)?.split('@').last() { + let email = get_email_from_cert(&cert)?; + let domain = match email.split('@').last() { Some(domain) => domain.to_string(), - None => return Err(Error::ParseEmail), + None => { + warn!("Error while parsing email's domain in token {}", token); + return Err(Error::ParseEmail); + } }; match sequoia_net::wkd::insert( &SETTINGS.root_folder, @@ -44,28 +69,54 @@ pub fn confirm_action(token: &str) -> Result<(), Error> { SETTINGS.variant, &cert, ) { - Ok(_) => (), - Err(_) => return Err(Error::AddingKey), + Ok(_) => email, + Err(_) => { + warn!("Unable to create a wkd entry for token {}", token); + return Err(Error::AddingKey); + } } } - Action::Delete => delete_key(key.data())?, - } + Action::Delete => match delete_key(key.data()) { + Ok(_) => key.data().to_owned(), + Err(error) => { + warn!("Unable to delete key for user {}", key.data()); + return Err(error); + } + }, + }; + debug!("Token {} was confirmed", token); match fs::remove_file(&pending_path) { - Ok(_) => Ok(()), - Err(_) => Err(Error::Inaccessible), + Ok(_) => { + trace!( + "Deleted confirmed token {}", + pending_path.file_name().unwrap().to_str().unwrap() + ); + Ok((*key.action(), address)) + } + Err(_) => { + warn!("Unable to delete confirmed token {}", token); + Err(Error::Inaccessible) + } } } } -pub fn send_confirmation_email(email: &str, action: &Action, token: &str) -> Result<(), Error> { +pub fn send_confirmation_email(address: &str, action: &Action, token: &str) -> Result<(), Error> { + debug!("Sending email to {}", address); let email = Message::builder() .from(match SETTINGS.mail_settings.mail_from.parse() { Ok(mailbox) => mailbox, - Err(_) => panic!("Unable to parse the email in the settings!"), + Err(_) => { + error!("Unable to parse the email in the settings!"); + panic!("Unable to parse the email in the settings!") + } }) - .to(match email.parse() { + .to(match address.parse() { Ok(mailbox) => mailbox, - Err(_) => return Err(Error::ParseEmail), + Err(_) => { + warn!("Error while parsing destination email for token {}", token); + return Err(Error::ParseEmail); + } }) .subject( SETTINGS @@ -87,11 +138,20 @@ pub fn send_confirmation_email(email: &str, action: &Action, token: &str) -> Res let message = match email { Ok(message) => message, - Err(_) => return Err(Error::MailGeneration), + Err(_) => { + warn!("Unable to build email for token {}", token); + return Err(Error::MailGeneration); + } }; match MAILER.send(&message) { - Ok(_) => Ok(()), - Err(_) => Err(Error::SendMail), + Ok(_) => { + debug!("successfully sent email to {}", address); + Ok(()) + } + Err(_) => { + warn!("Unable to send email to {}", address); + Err(Error::SendMail) + } } } diff --git a/src/main.rs b/src/main.rs index 7c053ec..5073882 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use self::management::{clean_stale, store_pending_addition, store_pending_deleti use self::utils::{gen_random_token, get_email_from_cert, parse_pem}; use actix_web::{get, post, web, App, HttpServer, Result}; -use log::error; +use log::{error, info}; use serde::Deserialize; use std::fs; use std::path::Path; @@ -39,6 +39,7 @@ struct Email { #[actix_web::main] async fn main() -> std::io::Result<()> { if init_logger().is_err() { + error!("Could not set up logger!"); panic!("Could not set up logger!") }; fs::create_dir_all(pending_path!())?; @@ -46,9 +47,9 @@ async fn main() -> std::io::Result<()> { let mut metronome = time::interval(time::Duration::from_secs(SETTINGS.cleanup_interval)); loop { metronome.tick().await; - if clean_stale(SETTINGS.max_age).is_err() { - error!("Error while cleaning stale requests!"); - } + info!("Running cleanup..."); + clean_stale(SETTINGS.max_age); + info!("Cleanup completed!"); } }); HttpServer::new(|| App::new().service(submit).service(confirm).service(delete)) @@ -62,14 +63,19 @@ async fn submit(pem: web::Form) -> Result { let cert = parse_pem(&pem.key)?; let email = get_email_from_cert(&cert)?; let token = gen_random_token(); - store_pending_addition(pem.key.clone(), &token)?; + store_pending_addition(pem.key.clone(), &email, &token)?; send_confirmation_email(&email, &Action::Add, &token)?; + info!("User {} submitted a key!", &email); Ok(String::from("Key submitted successfully!")) } #[get("/api/confirm/{value}")] async fn confirm(token: web::Path) -> Result { - confirm_action(&token.value)?; + let (action, email) = confirm_action(&token.value)?; + match action { + Action::Add => info!("Key for user {} was added successfully!", email), + Action::Delete => info!("Key for user {} was deleted successfully!", email), + } Ok(String::from("Confirmation successful!")) } @@ -79,5 +85,6 @@ async fn delete(email: web::Path) -> Result { let token = gen_random_token(); store_pending_deletion(email.address.clone(), &token)?; send_confirmation_email(&email.address, &Action::Delete, &token)?; + info!("User {} requested the deletion of his key!", email.address); Ok(String::from("Deletion request submitted successfully!")) } diff --git a/src/management.rs b/src/management.rs index 6158774..d809418 100644 --- a/src/management.rs +++ b/src/management.rs @@ -1,14 +1,15 @@ -use crate::errors::Error; use crate::pending_path; use crate::settings::SETTINGS; use crate::utils::get_user_file_path; use crate::PENDING_FOLDER; +use crate::{errors::Error, utils::get_filename}; use chrono::Utc; +use log::{debug, warn}; use serde::{Deserialize, Serialize}; use std::{fmt::Display, fs, path::Path}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub enum Action { Add, Delete, @@ -65,40 +66,62 @@ fn store_pending(pending: &Pending, token: &str) -> Result<(), Error> { } } -pub fn store_pending_addition(pem: String, token: &str) -> Result<(), Error> { +pub fn store_pending_addition(pem: String, email: &str, token: &str) -> Result<(), Error> { let pending = Pending::build_add(pem); store_pending(&pending, token)?; + debug!("Stored submission from {} with token {}", email, token); Ok(()) } pub fn store_pending_deletion(email: String, token: &str) -> Result<(), Error> { - let pending = Pending::build_delete(email); + let pending = Pending::build_delete(email.clone()); store_pending(&pending, token)?; + debug!( + "Stored deletion request from {} with token {}", + email, token + ); Ok(()) } -pub fn clean_stale(max_age: i64) -> Result<(), Error> { +pub fn clean_stale(max_age: i64) { for path in fs::read_dir(pending_path!()).unwrap().flatten() { let file_path = path.path(); if file_path.is_file() { let content = match fs::read_to_string(&file_path) { Ok(content) => content, - Err(_) => return Err(Error::Inaccessible), + Err(_) => { + warn!( + "Could not read contents of token {} to string", + get_filename(&file_path).unwrap() + ); + continue; + } }; let key = match serde_json::from_str::(&content) { Ok(key) => key, - Err(_) => return Err(Error::DeserializeData), + Err(_) => { + warn!( + "Could not deserialize token {}", + get_filename(&file_path).unwrap() + ); + continue; + } }; let now = Utc::now().timestamp(); if now - key.timestamp() > max_age { - let err = fs::remove_file(&file_path).is_err(); - if err { - return Err(Error::Inaccessible); + if fs::remove_file(&file_path).is_err() { + { + warn!( + "Could not delete stale token {}", + get_filename(&file_path).unwrap() + ); + continue; + }; } + debug!("Deleted stale token {}", get_filename(&file_path).unwrap()) } } } - Ok(()) } pub fn delete_key(email: &str) -> Result<(), Error> { diff --git a/src/settings.rs b/src/settings.rs index ed403d3..1b8b88c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,4 +1,5 @@ use lettre::{transport::smtp::authentication::Credentials, SmtpTransport}; +use log::{debug, error, warn}; use once_cell::sync::Lazy; use sequoia_net::wkd::Variant; use serde::{Deserialize, Serialize}; @@ -42,18 +43,27 @@ pub enum SMTPEncryption { } fn get_settings() -> Settings { + debug!("Reading settings..."); let content = match fs::read_to_string("wkd.toml") { Ok(content) => content, - Err(_) => panic!("Unable to access settings file!"), + Err(_) => { + error!("Unable to access settings file!"); + panic!("Unable to access settings file!") + } }; let settings = match toml::from_str(&content) { Ok(settings) => settings, - Err(_) => panic!("Unable to parse settings from file!"), + Err(_) => { + error!("Unable to parse settings from file!"); + panic!("Unable to parse settings from file!") + } }; + debug!("Successfully read setting!"); settings } fn get_mailer() -> SmtpTransport { + debug!("Setting up SMTP..."); let creds = Credentials::new( SETTINGS.mail_settings.smtp_username.to_owned(), SETTINGS.mail_settings.smtp_password.to_owned(), @@ -66,11 +76,18 @@ fn get_mailer() -> SmtpTransport { }; let mailer = match builder { Ok(builder) => builder, - Err(_) => panic!("Unable to set up smtp"), + Err(_) => { + error!("Unable to set up smtp"); + panic!("Unable to set up smtp") + } } .credentials(creds) .port(SETTINGS.mail_settings.smtp_port) .build(); + if mailer.test_connection().is_err() { + warn!("Connection test to smtp host failed!"); + } + debug!("SMTP setup successful!"); mailer } diff --git a/src/utils.rs b/src/utils.rs index 4cb46da..b4daae6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -60,6 +60,10 @@ pub fn key_exists(email: &str) -> Result { Ok(true) } +pub fn get_filename(path: &Path) -> Option<&str> { + path.file_name()?.to_str() +} + pub fn custom_format( w: &mut dyn std::io::Write, now: &mut DeferredNow,