From 89e31e52ab2be102dc1cd7d0c57e81163fde9d30 Mon Sep 17 00:00:00 2001 From: Marius Burkard Date: Fri, 14 Dec 2018 12:23:48 +0100 Subject: [PATCH] - improved mail sending library, contributed by Timme Hosting --- helper_scripts/test_mailqueue.php | 45 ++++++ install/sql/incremental/upd_0085.sql | 2 +- .../sql/incremental/upd_dev_collection.sql | 9 ++ install/sql/ispconfig3.sql | 15 ++ interface/lib/classes/ispcmail.inc.php | 137 ++++++++++++++---- .../cron.d/100-send_sys_mailqueue.inc.php | 77 ++++++++++ server/lib/classes/ispcmail.inc.php | 137 ++++++++++++++---- 7 files changed, 371 insertions(+), 51 deletions(-) create mode 100644 helper_scripts/test_mailqueue.php create mode 100644 server/lib/classes/cron.d/100-send_sys_mailqueue.inc.php diff --git a/helper_scripts/test_mailqueue.php b/helper_scripts/test_mailqueue.php new file mode 100644 index 0000000000..0d5600b390 --- /dev/null +++ b/helper_scripts/test_mailqueue.php @@ -0,0 +1,45 @@ +auth->check_module_permissions('admin'); + +$app->uses('getconf,ispcmail'); +$mail_config = $app->getconf->get_global_config('mail'); +if($mail_config['smtp_enabled'] == 'y') { + $mail_config['use_smtp'] = true; + $app->ispcmail->setOptions($mail_config); +} + +$to = 't.heller@timmehosting.de'; +$subject = 'Test von ISPConfig-Mailqueue'; +$text = '123'."\n\n".date(DATE_RFC822)."\n\n".'SMTP: '.$mail_config['use_smtp']; +$from = 'ispconfig@thomas.timmeserver.de'; +$filepath = ''; +$filetype = 'application/pdf'; +$filename = ''; +$cc = ''; +$bcc = ''; +$from_name = 'ISPConfig'; + +$app->ispcmail->setSender($from, $from_name); +$app->ispcmail->setSubject($subject); +$app->ispcmail->setMailText($text); + +if($filepath != '') { + if(!file_exists($filepath)) $app->error("Mail attachement does not exist ".$filepath); + $app->ispcmail->readAttachFile($filepath); +} + +if($cc != '') $app->ispcmail->setHeader('Cc', $cc); +if($bcc != '') $app->ispcmail->setHeader('Bcc', $bcc); + +$app->ispcmail->send($to); +$app->ispcmail->finish(); + +echo $text; + diff --git a/install/sql/incremental/upd_0085.sql b/install/sql/incremental/upd_0085.sql index 1291262ee7..9676436833 100644 --- a/install/sql/incremental/upd_0085.sql +++ b/install/sql/incremental/upd_0085.sql @@ -18,4 +18,4 @@ ALTER TABLE `dns_rr` CHANGE `data` `data` TEXT NOT NULL; ALTER TABLE `web_database` CHANGE `database_quota` `database_quota` INT(11) NULL DEFAULT NULL; ALTER TABLE `web_domain` ADD `log_retention` INT NOT NULL DEFAULT '30' ; ALTER TABLE spamfilter_policy CHANGE spam_tag_level spam_tag_level DECIMAL(5,2) NULL DEFAULT NULL, CHANGE spam_tag2_level spam_tag2_level DECIMAL(5,2) NULL DEFAULT NULL, CHANGE spam_kill_level spam_kill_level DECIMAL(5,2) NULL DEFAULT NULL, CHANGE spam_dsn_cutoff_level spam_dsn_cutoff_level DECIMAL(5,2) NULL DEFAULT NULL, CHANGE spam_quarantine_cutoff_level spam_quarantine_cutoff_level DECIMAL(5,2) NULL DEFAULT NULL; -UPDATE `web_database` as d LEFT JOIN `web_domain` as w ON (w.domain_id = d.parent_domain_id) SET d.parent_domain_id = 0 WHERE w.domain_id IS NULL AND d.parent_domain_id != 0 AND (SELECT EXISTS(SELECT * FROM web_domain)); \ No newline at end of file +UPDATE `web_database` as d LEFT JOIN `web_domain` as w ON (w.domain_id = d.parent_domain_id) SET d.parent_domain_id = 0 WHERE w.domain_id IS NULL AND d.parent_domain_id != 0 AND (SELECT EXISTS(SELECT * FROM web_domain)); diff --git a/install/sql/incremental/upd_dev_collection.sql b/install/sql/incremental/upd_dev_collection.sql index dc5d4bb45c..68228ca52d 100644 --- a/install/sql/incremental/upd_dev_collection.sql +++ b/install/sql/incremental/upd_dev_collection.sql @@ -151,3 +151,12 @@ CREATE TABLE IF NOT EXISTS `addons` ( PRIMARY KEY (`addon_id`), UNIQUE KEY `ident` (`addon_ident`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +CREATE TABLE IF NOT EXISTS `sys_mailqueue` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `from` varchar(255) NOT NULL DEFAULT '', + `recipients` text NOT NULL, + `mail_content` mediumblob NOT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; diff --git a/install/sql/ispconfig3.sql b/install/sql/ispconfig3.sql index 1b168b44bf..9f30013469 100644 --- a/install/sql/ispconfig3.sql +++ b/install/sql/ispconfig3.sql @@ -1585,6 +1585,21 @@ CREATE TABLE `sys_log` ( -- -------------------------------------------------------- +-- +-- Table structure for table `sys_mailqueue` +-- + +CREATE TABLE IF NOT EXISTS `sys_mailqueue` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `from` varchar(255) NOT NULL DEFAULT '', + `recipients` text NOT NULL, + `mail_content` mediumblob NOT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; + +-- -------------------------------------------------------- + -- -- Table structure for table `sys_remoteaction` -- diff --git a/interface/lib/classes/ispcmail.inc.php b/interface/lib/classes/ispcmail.inc.php index b818e1e44a..a3256b5ff6 100644 --- a/interface/lib/classes/ispcmail.inc.php +++ b/interface/lib/classes/ispcmail.inc.php @@ -744,32 +744,22 @@ class ispcmail { } if($this->use_smtp == true) { - if(!$this->_logged_in || !$this->_smtp_conn) { - $result = $this->_smtp_login(); - if(!$result) return false; - } $bcc_cc_sent = false; foreach($recipients as $recipname => $recip) { - if($this->_sent_mails >= $this->smtp_max_mails) { - // close connection to smtp and reconnect - $this->_sent_mails = 0; - $this->_smtp_close(); - $result = $this->_smtp_login(); - if(!$result) return false; - } - $this->_sent_mails += 1; + // Build mail headers, content etc. + + $m_recipients = array(); + $m_mail_content = ''; $recipname = trim(str_replace('"', '', $recipname)); $recip = $this->_encodeHeader($recip, $this->mail_charset); $recipname = $this->_encodeHeader($recipname, $this->mail_charset); //Email From - fputs($this->_smtp_conn, 'MAIL FROM: <' . $this->_mail_sender . '>' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); + $m_from = $this->_mail_sender; //Email To - fputs($this->_smtp_conn, 'RCPT TO: <' . $recip . '>' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); + $m_recipients[] = $recip; if($bcc_cc_sent == false) { $add_recips = array(); @@ -777,17 +767,12 @@ class ispcmail { if($this->getHeader('Bcc') != '') $add_recips = array_merge($add_recips, $this->_extract_names($this->getHeader('Bcc'))); foreach($add_recips as $add_recip) { if(!$add_recip['mail']) continue; - fputs($this->_smtp_conn, 'RCPT TO: <' . $this->_encodeHeader($add_recip['mail'], $this->mail_charset) . '>' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); + $m_recipients[] = $this->_encodeHeader($add_recip['mail'], $this->mail_charset); } unset($add_recips); $bcc_cc_sent = true; } - //The Email - fputs($this->_smtp_conn, 'DATA' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); - //Construct Headers if($recipname && !is_numeric($recipname)) $this->setHeader('To', $recipname . ' <' . $recip . '>'); else $this->setHeader('To', $recip); @@ -797,10 +782,32 @@ class ispcmail { if($this->getHeader('Cc') != '') $mail_content .= 'Cc: ' . $this->_encodeHeader($this->getHeader('Cc'), $this->mail_charset) . $this->_crlf; $mail_content .= implode($this->_crlf, $headers) . $this->_crlf . ($this->_is_signed == false ? $this->_crlf : '') . $this->body; - fputs($this->_smtp_conn, $mail_content . $this->_crlf . '.' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); + $m_mail_content = $mail_content; + + // Attempt SMTP login: + + if(!$this->_logged_in || !$this->_smtp_conn) { + $result = $this->_smtp_login(); + } + + if($this->_sent_mails >= $this->smtp_max_mails) { + // close connection to smtp and reconnect + $this->_sent_mails = 0; + $this->_smtp_close(); + $result = $this->_smtp_login(); + } + $this->_sent_mails += 1; + + // Send mail or queue it + + if ($result) { + $this->send_smtp($m_from, $m_recipients, $m_mail_content); + } else { + $this->add_to_queue($m_from, $m_recipients, $m_mail_content); + } + + // hopefully message was correctly sent or queued now - // hopefully message was correctly sent now $result = true; } } else { @@ -828,7 +835,87 @@ class ispcmail { return $result; } + /** + * Send all mails in queue (usually called from cron) + */ + public function send_queue() { + global $app; + + $mails = $app->db->queryAllRecords('SELECT * FROM sys_mailqueue'); + + if (is_array($mails)) { + foreach ($mails as $mail) { + // Open SMTP connections if not open: + + if(!$this->_logged_in || !$this->_smtp_conn) { + $result = $this->_smtp_login(); + } + + if($this->_sent_mails >= $this->smtp_max_mails) { + // close connection to smtp and reconnect + $this->_sent_mails = 0; + $this->_smtp_close(); + $result = $this->_smtp_login(); + } + $this->_sent_mails += 1; + + if (!$result) { + return false; + } + + // Send mail: + + $id = $mail['id']; + $from = $mail['from']; + $recipients = explode("\n", $mail['recipients']); + $mail_content = $mail['mail_content']; + + $this->send_smtp($from, $recipients, $mail_content); + + // Delete from queue: + + $app->db->query('DELETE FROM sys_mailqueue WHERE id = ?', $id); + } + } + } + + /** + * Send mail via SMTP + */ + private function send_smtp($from, $recipients, $mail_content) { + // from: + + fputs($this->_smtp_conn, 'MAIL FROM: <' . $from . '>' . $this->_crlf); + $response = fgets($this->_smtp_conn, 515); + + // recipients (To, Cc or Bcc): + + foreach ($recipients as $recipient) { + fputs($this->_smtp_conn, 'RCPT TO: <' . $recipient . '>' . $this->_crlf); + $response = fgets($this->_smtp_conn, 515); + } + + // mail content: + fputs($this->_smtp_conn, 'DATA' . $this->_crlf); + $response = fgets($this->_smtp_conn, 515); + + fputs($this->_smtp_conn, $mail_content . $this->_crlf . '.' . $this->_crlf); + $response = fgets($this->_smtp_conn, 515); + + // hopefully message was correctly sent now + $result = true; + + return $result; + } + + /** + * Add mail to queue for sending later + */ + private function add_to_queue($from, $recipients, $mail_content) { + global $app; + $app->db->query('INSERT INTO `sys_mailqueue` (`from`, `recipients`, `mail_content`) VALUES (?,?,?)', $from, implode("\n", $recipients), $mail_content); + } /** * Close mail connections diff --git a/server/lib/classes/cron.d/100-send_sys_mailqueue.inc.php b/server/lib/classes/cron.d/100-send_sys_mailqueue.inc.php new file mode 100644 index 0000000000..2ab46dfc81 --- /dev/null +++ b/server/lib/classes/cron.d/100-send_sys_mailqueue.inc.php @@ -0,0 +1,77 @@ +uses('getconf,ispcmail'); + + $mail_config = $app->getconf->get_global_config('mail'); + if($mail_config['smtp_enabled'] == 'y') { + $mail_config['use_smtp'] = true; + $app->ispcmail->setOptions($mail_config); + + // Mail queue is supported only with SMTP... + $app->ispcmail->send_queue(); + } + + parent::onRunJob(); + } + + /* this function is optional if it contains no custom code */ + public function onAfterRun() { + global $app; + + parent::onAfterRun(); + } + +} + +?> diff --git a/server/lib/classes/ispcmail.inc.php b/server/lib/classes/ispcmail.inc.php index 305b39f35b..93d7b6069c 100644 --- a/server/lib/classes/ispcmail.inc.php +++ b/server/lib/classes/ispcmail.inc.php @@ -740,32 +740,22 @@ class ispcmail { } if($this->use_smtp == true) { - if(!$this->_logged_in || !$this->_smtp_conn) { - $result = $this->_smtp_login(); - if(!$result) return false; - } $bcc_cc_sent = false; foreach($recipients as $recipname => $recip) { - if($this->_sent_mails >= $this->smtp_max_mails) { - // close connection to smtp and reconnect - $this->_sent_mails = 0; - $this->_smtp_close(); - $result = $this->_smtp_login(); - if(!$result) return false; - } - $this->_sent_mails += 1; + // Build mail headers, content etc. + + $m_recipients = array(); + $m_mail_content = ''; $recipname = trim(str_replace('"', '', $recipname)); $recip = $this->_encodeHeader($recip, $this->mail_charset); $recipname = $this->_encodeHeader($recipname, $this->mail_charset); //Email From - fputs($this->_smtp_conn, 'MAIL FROM: <' . $this->_mail_sender . '>' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); + $m_from = $this->_mail_sender; //Email To - fputs($this->_smtp_conn, 'RCPT TO: <' . $recip . '>' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); + $m_recipients[] = $recip; if($bcc_cc_sent == false) { $add_recips = array(); @@ -773,17 +763,12 @@ class ispcmail { if($this->getHeader('Bcc') != '') $add_recips = array_merge($add_recips, $this->_extract_names($this->getHeader('Bcc'))); foreach($add_recips as $add_recip) { if(!$add_recip['mail']) continue; - fputs($this->_smtp_conn, 'RCPT TO: <' . $this->_encodeHeader($add_recip['mail'], $this->mail_charset) . '>' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); + $m_recipients[] = $this->_encodeHeader($add_recip['mail'], $this->mail_charset); } unset($add_recips); $bcc_cc_sent = true; } - //The Email - fputs($this->_smtp_conn, 'DATA' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); - //Construct Headers if($recipname && !is_numeric($recipname)) $this->setHeader('To', $recipname . ' <' . $recip . '>'); else $this->setHeader('To', $recip); @@ -793,10 +778,32 @@ class ispcmail { if($this->getHeader('Cc') != '') $mail_content .= 'Cc: ' . $this->_encodeHeader($this->getHeader('Cc'), $this->mail_charset) . $this->_crlf; $mail_content .= implode($this->_crlf, $headers) . $this->_crlf . ($this->_is_signed == false ? $this->_crlf : '') . $this->body; - fputs($this->_smtp_conn, $mail_content . $this->_crlf . '.' . $this->_crlf); - $response = fgets($this->_smtp_conn, 515); + $m_mail_content = $mail_content; + + // Attempt SMTP login: + + if(!$this->_logged_in || !$this->_smtp_conn) { + $result = $this->_smtp_login(); + } + + if($this->_sent_mails >= $this->smtp_max_mails) { + // close connection to smtp and reconnect + $this->_sent_mails = 0; + $this->_smtp_close(); + $result = $this->_smtp_login(); + } + $this->_sent_mails += 1; + + // Send mail or queue it + + if ($result) { + $this->send_smtp($m_from, $m_recipients, $m_mail_content); + } else { + $this->add_to_queue($m_from, $m_recipients, $m_mail_content); + } + + // hopefully message was correctly sent or queued now - // hopefully message was correctly sent now $result = true; } } else { @@ -824,7 +831,87 @@ class ispcmail { return $result; } + /** + * Send all mails in queue (usually called from cron) + */ + public function send_queue() { + global $app; + + $mails = $app->db->queryAllRecords('SELECT * FROM sys_mailqueue'); + + if (is_array($mails)) { + foreach ($mails as $mail) { + // Open SMTP connections if not open: + + if(!$this->_logged_in || !$this->_smtp_conn) { + $result = $this->_smtp_login(); + } + + if($this->_sent_mails >= $this->smtp_max_mails) { + // close connection to smtp and reconnect + $this->_sent_mails = 0; + $this->_smtp_close(); + $result = $this->_smtp_login(); + } + $this->_sent_mails += 1; + + if (!$result) { + return false; + } + + // Send mail: + + $id = $mail['id']; + $from = $mail['from']; + $recipients = explode("\n", $mail['recipients']); + $mail_content = $mail['mail_content']; + + $this->send_smtp($from, $recipients, $mail_content); + + // Delete from queue: + + $app->db->query('DELETE FROM sys_mailqueue WHERE id = ?', $id); + } + } + } + + /** + * Send mail via SMTP + */ + private function send_smtp($from, $recipients, $mail_content) { + // from: + + fputs($this->_smtp_conn, 'MAIL FROM: <' . $from . '>' . $this->_crlf); + $response = fgets($this->_smtp_conn, 515); + + // recipients (To, Cc or Bcc): + + foreach ($recipients as $recipient) { + fputs($this->_smtp_conn, 'RCPT TO: <' . $recipient . '>' . $this->_crlf); + $response = fgets($this->_smtp_conn, 515); + } + + // mail content: + fputs($this->_smtp_conn, 'DATA' . $this->_crlf); + $response = fgets($this->_smtp_conn, 515); + + fputs($this->_smtp_conn, $mail_content . $this->_crlf . '.' . $this->_crlf); + $response = fgets($this->_smtp_conn, 515); + + // hopefully message was correctly sent now + $result = true; + + return $result; + } + + /** + * Add mail to queue for sending later + */ + private function add_to_queue($from, $recipients, $mail_content) { + global $app; + $app->db->query('INSERT INTO `sys_mailqueue` (`from`, `recipients`, `mail_content`) VALUES (?,?,?)', $from, implode("\n", $recipients), $mail_content); + } /** * Close mail connections -- GitLab