From 517e8d42f04cb55b1265257683ae613586e921c3 Mon Sep 17 00:00:00 2001 From: wrongecho Date: Tue, 10 Sep 2024 22:34:20 +0100 Subject: [PATCH] Email Send - Tidy - Enhance error logging in cron_mail_queue.php - Prevent invalid sender addresses - Prevent potential SQL injections in the sender name (admin settings and should be sanitized before being sent to queue anyway) --- cron_mail_queue.php | 105 +++++++++++++++++++++++++++----------------- functions.php | 6 +-- post/setting.php | 16 +++---- 3 files changed, 75 insertions(+), 52 deletions(-) diff --git a/cron_mail_queue.php b/cron_mail_queue.php index f6fb6088a..ba0e12f65 100644 --- a/cron_mail_queue.php +++ b/cron_mail_queue.php @@ -24,11 +24,13 @@ // Check cron is enabled if ($config_enable_cron == 0) { + error_log("Mail queue error - Cron is not enabled"); exit("Cron: is not enabled -- Quitting.."); } // Check Cron Key if ($argv[1] !== $config_cron_key) { + error_log("Mail queue error - Invalid cron key supplied"); exit("Cron Key invalid -- Quitting.."); } @@ -63,8 +65,12 @@ // 2 Failed // 3 Sent +/* + * ############################################################################################################### + * Initial email send + * ############################################################################################################### + */ // Get Mail Queue that has status of Queued and send it to the function sendSingleEmail() located in functions.php - $sql_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 0 AND email_queued_at <= NOW()"); if (mysqli_num_rows($sql_queue) > 0) { @@ -80,47 +86,68 @@ $email_sent_at = $row['email_sent_at']; $email_ics_str = $row['email_cal_str']; - // Sanitized Input - $email_recipient_logging = sanitizeInput($row['email_recipient']); - $email_subject_logging = sanitizeInput($row['email_subject']); - - // Update the status to sending - mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id"); - - // Verify contact email is valid - if (filter_var($email_recipient, FILTER_VALIDATE_EMAIL)) { - - $mail = sendSingleEmail( - $config_smtp_host, - $config_smtp_username, - $config_smtp_password, - $config_smtp_encryption, - $config_smtp_port, - $email_from, - $email_from_name, - $email_recipient, - $email_recipient_name, - $email_subject, - $email_content, - $email_ics_str - ); - - if ($mail !== true) { - // Update Message - Failure - mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = 1 WHERE email_id = $email_id"); - - mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Mail', notification = 'Failed to send email to $email_recipient_logging'"); - mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Mail', log_action = 'Error', log_description = 'Failed to send email to $email_recipient_logging regarding $email_subject_logging. $mail'"); + // First, validate the sender email address + if (filter_var($email_from, FILTER_VALIDATE_EMAIL)) { + + // Sanitized Input + $email_recipient_logging = sanitizeInput($row['email_recipient']); + $email_subject_logging = sanitizeInput($row['email_subject']); + + // Update the status to sending + mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id"); + + // Next, verify recipient email is valid + if (filter_var($email_recipient, FILTER_VALIDATE_EMAIL)) { + + $mail = sendSingleEmail( + $config_smtp_host, + $config_smtp_username, + $config_smtp_password, + $config_smtp_encryption, + $config_smtp_port, + $email_from, + $email_from_name, + $email_recipient, + $email_recipient_name, + $email_subject, + $email_content, + $email_ics_str + ); + + if ($mail !== true) { + // Update Message - Failure + mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = 1 WHERE email_id = $email_id"); + + mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Cron-Mail-Queue', notification = 'Failed to send email #$email_id to $email_recipient_logging'"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Cron-Mail-Queue', log_action = 'Error', log_description = 'Failed to send email #$email_id to $email_recipient_logging regarding $email_subject_logging. $mail'"); + } else { + // Update Message - Success + mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = 1 WHERE email_id = $email_id"); + } } else { - // Update Message - Success - mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = 1 WHERE email_id = $email_id"); + // Recipient email isn't valid, mark as failed and log the error + mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Cron-Mail-Queue', log_action = 'Error', log_description = 'Failed to send email #$email_id due to invalid recipient address. Email subject was: $email_subject_logging.'"); } + + } else { + error_log("Failed to send email due to invalid sender address (' $email_from ') - check configuration in settings."); + + $email_from_logging = sanitizeInput($row['email_from']); + mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Cron-Mail-Queue', log_action = 'Error', log_description = 'Failed to send email #$email_id due to invalid sender address: $email_from_logging - check configuration in settings.'"); + mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Mail', notification = 'Failed to send email #$email_id due to invalid sender address'"); } + } } -// +/* + * ############################################################################################################### + * Retries + * ############################################################################################################### + */ // Get Mail that failed to send and attempt to send Failed Mail up to 4 times every 30 mins $sql_failed_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 2 AND email_attempts < 4 AND email_failed_at < NOW() + INTERVAL 30 MINUTE"); @@ -146,7 +173,7 @@ // Update the status to sending before actually sending mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id"); - // Verify contact email is valid + // Verify recipient email is valid if (filter_var($email_recipient, FILTER_VALIDATE_EMAIL)) { $mail = sendSingleEmail( @@ -167,9 +194,7 @@ if ($mail !== true) { // Update Message mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id"); - - mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Mail', notification = 'Failed to send email to $email_recipient_logging'"); - mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Mail', log_action = 'Error', log_description = 'Failed to send email to $email_recipient_logging regarding $email_subject_logging. $mail'"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Cron-Mail-Queue', log_action = 'Error', log_description = 'Failed to re-send email #$email_id to $email_recipient_logging regarding $email_subject_logging. $mail'"); } else { // Update Message mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id"); @@ -178,5 +203,5 @@ } } -// Remove the lock file once mail has finished processing so it doesnt get overun causing possible duplicates +// Remove the lock file once mail has finished processing unlink($lock_file_path); diff --git a/functions.php b/functions.php index cbd021a18..55a0f151f 100644 --- a/functions.php +++ b/functions.php @@ -480,7 +480,6 @@ function getSSL($full_name) function strtoAZaz09($string) { - // Gets rid of non-alphanumerics return preg_replace('/[^A-Za-z0-9_-]/', '', $string); } @@ -547,7 +546,6 @@ function sendSingleEmail($config_smtp_host, $config_smtp_username, $config_smtp_ if (empty($config_smtp_username)) { $smtp_auth = false; } else { - $smtp_auth = true; } @@ -635,7 +633,7 @@ function sendSingleEmail($config_smtp_host, $config_smtp_username, $config_smtp_ } catch (Exception $e) { // If we couldn't send the message return the error, so we can log it in the database (truncated) error_log("ITFlow - Failed to send email: " . $mail->ErrorInfo); - return substr("Mailer Error: $mail->ErrorInfo", 0, 150) . "..."; + return substr("Mailer Error: $mail->ErrorInfo", 0, 100) . "..."; } } @@ -1050,7 +1048,7 @@ function addToMailQueue($mysqli, $data) { $cal_str = ''; if (isset($email['cal_str'])) { - $cal_str = mysqli_escape_string($mysqli,$email['cal_str']); + $cal_str = mysqli_escape_string($mysqli, $email['cal_str']); } // Check if 'email_queued_at' is set and not empty diff --git a/post/setting.php b/post/setting.php index 9b5620431..013b731af 100644 --- a/post/setting.php +++ b/post/setting.php @@ -124,17 +124,17 @@ validateCSRFToken($_POST['csrf_token']); validateAdminRole(); - $config_mail_from_email = sanitizeInput($_POST['config_mail_from_email']); - $config_mail_from_name = sanitizeInput($_POST['config_mail_from_name']); + $config_mail_from_email = sanitizeInput(filter_var($_POST['config_mail_from_email'], FILTER_VALIDATE_EMAIL)); + $config_mail_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_mail_from_name'])); - $config_invoice_from_email = sanitizeInput($_POST['config_invoice_from_email']); - $config_invoice_from_name = sanitizeInput($_POST['config_invoice_from_name']); + $config_invoice_from_email = sanitizeInput(filter_var($_POST['config_invoice_from_email'], FILTER_VALIDATE_EMAIL)); + $config_invoice_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_invoice_from_name'])); - $config_quote_from_email = sanitizeInput($_POST['config_quote_from_email']); - $config_quote_from_name = sanitizeInput($_POST['config_quote_from_name']); + $config_quote_from_email = sanitizeInput(filter_var($_POST['config_quote_from_email'], FILTER_VALIDATE_EMAIL)); + $config_quote_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_quote_from_name'])); - $config_ticket_from_email = sanitizeInput($_POST['config_ticket_from_email']); - $config_ticket_from_name = sanitizeInput($_POST['config_ticket_from_name']); + $config_ticket_from_email = sanitizeInput(filter_var($_POST['config_ticket_from_email'], FILTER_VALIDATE_EMAIL)); + $config_ticket_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_ticket_from_name'])); mysqli_query($mysqli,"UPDATE settings SET config_mail_from_email = '$config_mail_from_email', config_mail_from_name = '$config_mail_from_name', config_invoice_from_email = '$config_invoice_from_email', config_invoice_from_name = '$config_invoice_from_name', config_quote_from_email = '$config_quote_from_email', config_quote_from_name = '$config_quote_from_name', config_ticket_from_email = '$config_ticket_from_email', config_ticket_from_name = '$config_ticket_from_name' WHERE company_id = 1");