Example #1
0
function sendmail($to, $subject, $message, $from = null, $message_id = null, $send_html = false, $priority = 1, $hotmail_fix = null)
{
    global $webmaster_email, $context, $modSettings, $txt, $scripturl;
    // Use sendmail if it's set or if no SMTP server is set.
    $use_sendmail = empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '';
    // Line breaks need to be \r\n only in windows or for SMTP.
    $line_break = $context['server']['is_windows'] || !$use_sendmail ? "\r\n" : "\n";
    // So far so good.
    $mail_result = true;
    // If the recipient list isn't an array, make it one.
    $to_array = is_array($to) ? $to : array($to);
    // Sadly Hotmail & Yahoomail don't support character sets properly.
    if ($hotmail_fix === null) {
        $hotmail_to = array();
        foreach ($to_array as $i => $to_address) {
            if (preg_match('~@(yahoo|hotmail)\\.[a-zA-Z\\.]{2,6}$~i', $to_address) === 1) {
                $hotmail_to[] = $to_address;
                $to_array = array_diff($to_array, array($to_address));
            }
        }
        // Call this function recursively for the hotmail addresses.
        if (!empty($hotmail_to)) {
            $mail_result = sendmail($hotmail_to, $subject, $message, $from, $message_id, $send_html, $priority, true);
        }
        // The remaining addresses no longer need the fix.
        $hotmail_fix = false;
        // No other addresses left? Return instantly.
        if (empty($to_array)) {
            return $mail_result;
        }
    }
    // Get rid of slashes and entities.
    $subject = un_htmlspecialchars(stripslashes($subject));
    // Make the message use the proper line breaks.
    $message = str_replace(array("\r", "\n"), array('', $line_break), stripslashes($message));
    // Make sure hotmail mails are sent as HTML so that HTML entities work.
    if ($hotmail_fix && !$send_html) {
        $send_html = true;
        $message = strtr($message, array($line_break => '<br />' . $line_break));
        $message = preg_replace('~(' . preg_quote($scripturl, '~') . '([?/][\\w\\-_%\\.,\\?&;=#]+)?)~', '<a href="$1">$1</a>', $message);
    }
    list(, $from_name) = mimespecialchars(addcslashes($from !== null ? $from : $context['forum_name'], '<>()\'\\"'), true, $hotmail_fix, $line_break);
    list(, $subject) = mimespecialchars($subject, true, $hotmail_fix, $line_break);
    // Construct the mail headers...
    $headers = 'From: "' . $from_name . '" <' . (empty($modSettings['mail_from']) ? $webmaster_email : $modSettings['mail_from']) . '>' . $line_break;
    $headers .= $from !== null ? 'Reply-To: <' . $from . '>' . $line_break : '';
    $headers .= 'Return-Path: ' . (empty($modSettings['mail_from']) ? $webmaster_email : $modSettings['mail_from']) . $line_break;
    $headers .= 'Date: ' . gmdate('D, d M Y H:i:s') . ' -0000' . $line_break;
    if ($message_id !== null && empty($modSettings['mail_no_message_id'])) {
        $headers .= 'Message-ID: <' . md5($scripturl . microtime()) . '-' . $message_id . strstr(empty($modSettings['mail_from']) ? $webmaster_email : $modSettings['mail_from'], '@') . '>' . $line_break;
    }
    $headers .= 'X-Mailer: SMF' . $line_break;
    // pass this to the integration before we start modifying the output -- it'll make it easier later
    if (isset($modSettings['integrate_outgoing_email']) && function_exists($modSettings['integrate_outgoing_email'])) {
        if ($modSettings['integrate_outgoing_email']($subject, $message, $headers) === false) {
            return false;
        }
    }
    // Save the original message...
    $orig_message = $message;
    // The mime boundary separates the different alternative versions.
    $mime_boundary = 'SMF-' . md5($message . time());
    // Sending HTML?  Let's plop in some basic stuff, then.
    if ($send_html) {
        // This should send a text message with MIME multipart/alternative stuff.
        $headers .= 'Mime-Version: 1.0' . $line_break;
        $headers .= 'Content-Type: multipart/alternative; boundary="' . $mime_boundary . '"' . $line_break;
        $headers .= 'Content-Transfer-Encoding: 7bit' . $line_break;
        $no_html_message = un_htmlspecialchars(strip_tags(strtr($orig_message, array('</title>' => $line_break))));
        // But, then, dump it and use a plain one for dinosaur clients.
        list(, $plain_message) = mimespecialchars($no_html_message, false, true, $line_break);
        $message = $plain_message . $line_break . '--' . $mime_boundary . $line_break;
        // This is the plain text version.  Even if no one sees it, we need it for spam checkers.
        list($charset, $plain_charset_message, $encoding) = mimespecialchars($no_html_message, false, false, $line_break);
        $message .= 'Content-Type: text/plain; charset=' . $charset . $line_break;
        $message .= 'Content-Transfer-Encoding: ' . $encoding . $line_break . $line_break;
        $message .= $plain_charset_message . $line_break . '--' . $mime_boundary . $line_break;
        // This is the actual HTML message, prim and proper.  If we wanted images, they could be inlined here (with multipart/related, etc.)
        list($charset, $html_message, $encoding) = mimespecialchars($orig_message, false, $hotmail_fix, $line_break);
        $message .= 'Content-Type: text/html; charset=' . $charset . $line_break;
        $message .= 'Content-Transfer-Encoding: ' . ($encoding == '' ? '7bit' : $encoding) . $line_break . $line_break;
        $message .= $html_message . $line_break . '--' . $mime_boundary . '--';
    } else {
        // Using mime, as it allows to send a plain unencoded alternative.
        $headers .= 'Mime-Version: 1.0' . $line_break;
        $headers .= 'Content-Type: multipart/alternative; boundary="' . $mime_boundary . '"' . $line_break;
        $headers .= 'Content-Transfer-Encoding: 7bit' . $line_break;
        // Send a plain message first, for the older web clients.
        list(, $plain_message) = mimespecialchars($orig_message, false, true, $line_break);
        $message = $plain_message . $line_break . '--' . $mime_boundary . $line_break;
        // Now add an encoded message using the forum's character set.
        list($charset, $encoded_message, $encoding) = mimespecialchars($orig_message, false, false, $line_break);
        $message .= 'Content-Type: text/plain; charset=' . $charset . $line_break;
        $message .= 'Content-Transfer-Encoding: ' . $encoding . $line_break . $line_break;
        $message .= $encoded_message . $line_break . '--' . $mime_boundary . '--';
    }
    // SMTP or sendmail?
    if ($use_sendmail) {
        $subject = strtr($subject, array("\r" => '', "\n" => ''));
        if (!empty($modSettings['mail_strip_carriage'])) {
            $message = strtr($message, array("\r" => ''));
            $headers = strtr($headers, array("\r" => ''));
        }
        foreach ($to_array as $to) {
            if (!mail(strtr($to, array("\r" => '', "\n" => '')), $subject, $message, $headers)) {
                log_error(sprintf($txt['mail_send_unable'], $to));
                $mail_result = false;
            }
            // Wait, wait, I'm still sending here!
            @set_time_limit(300);
            if (function_exists('apache_reset_timeout')) {
                apache_reset_timeout();
            }
        }
    } else {
        $mail_result = $mail_result && smtp_mail($to_array, $subject, $message, $send_html ? $headers : "Mime-Version: 1.0" . $line_break . $headers);
    }
    // Everything go smoothly?
    return $mail_result;
}
Example #2
0
/**
 * This function sends an email to the specified recipient(s).
 *
 * It uses the mail_type settings and webmaster_email variable.
 *
 * @package Mail
 * @param string[]|string $to - the email(s) to send to
 * @param string $subject - email subject, expected to have entities, and slashes, but not be parsed
 * @param string $message - email body, expected to have slashes, no htmlentities
 * @param string|null $from = null - the address to use for replies
 * @param string|null $message_id = null - if specified, it will be used as local part of the Message-ID header.
 * @param bool $send_html = false, whether or not the message is HTML vs. plain text
 * @param int $priority = 3
 * @param bool|null $hotmail_fix = null
 * @param bool $is_private
 * @param string|null $from_wrapper - used to provide envelope from wrapper based on if we sharing a users display name
 * @param int|null $reference - The parent topic id for use in a References header
 * @return boolean whether or not the email was accepted properly.
 */
function sendmail($to, $subject, $message, $from = null, $message_id = null, $send_html = false, $priority = 3, $hotmail_fix = null, $is_private = false, $from_wrapper = null, $reference = null)
{
    global $webmaster_email, $context, $modSettings, $txt, $scripturl, $boardurl;
    // Use sendmail if it's set or if no SMTP server is set.
    $use_sendmail = empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '';
    // Using maillist styles and this message qualifies (priority 3 and below only (4 = digest, 5 = newsletter))
    $maillist = !empty($modSettings['maillist_enabled']) && $from_wrapper !== null && $message_id !== null && $priority < 4 && empty($modSettings['mail_no_message_id']);
    // Line breaks need to be \r\n only in windows or for SMTP.
    $line_break = !empty($context['server']['is_windows']) || !$use_sendmail ? "\r\n" : "\n";
    // So far so good.
    $mail_result = true;
    // If the recipient list isn't an array, make it one.
    $to_array = is_array($to) ? $to : array($to);
    // Once upon a time, Hotmail could not interpret non-ASCII mails.
    // In honour of those days, it's still called the 'hotmail fix'.
    if ($hotmail_fix === null) {
        $hotmail_to = array();
        foreach ($to_array as $i => $to_address) {
            if (preg_match('~@(att|comcast|bellsouth)\\.[a-zA-Z\\.]{2,6}$~i', $to_address) === 1) {
                $hotmail_to[] = $to_address;
                $to_array = array_diff($to_array, array($to_address));
            }
        }
        // Call this function recursively for the hotmail addresses.
        if (!empty($hotmail_to)) {
            $mail_result = sendmail($hotmail_to, $subject, $message, $from, $message_id, $send_html, $priority, true, $is_private, $from_wrapper, $reference);
        }
        // The remaining addresses no longer need the fix.
        $hotmail_fix = false;
        // No other addresses left? Return instantly.
        if (empty($to_array)) {
            return $mail_result;
        }
    }
    // Get rid of entities.
    $subject = un_htmlspecialchars($subject);
    // Make the message use the proper line breaks.
    $message = str_replace(array("\r", "\n"), array('', $line_break), $message);
    // Make sure hotmail mails are sent as HTML so that HTML entities work.
    if ($hotmail_fix && !$send_html) {
        $send_html = true;
        $message = strtr($message, array($line_break => '<br />' . $line_break));
        $message = preg_replace('~(' . preg_quote($scripturl, '~') . '(?:[?/][\\w\\-_%\\.,\\?&;=#]+)?)~', '<a href="$1">$1</a>', $message);
    }
    // Requirements (draft) for MLM to Support Basic DMARC Compliance
    // http://www.dmarc.org/supplemental/mailman-project-mlm-dmarc-reqs.html
    if ($maillist && $from !== null && $from_wrapper !== null) {
        // Be sure there is never an email in the from name if using maillist styles
        $dmarc_from = $from;
        if (filter_var($dmarc_from, FILTER_VALIDATE_EMAIL)) {
            $dmarc_from = str_replace(strstr($dmarc_from, '@'), '', $dmarc_from);
        }
        // Add in the 'via' if desired, helps prevent email clients from learning/replacing legit names/emails
        if (!empty($modSettings['maillist_sitename']) && empty($modSettings['dmarc_spec_standard'])) {
            // @memo (2014) "via" is still a draft, and it's not yet clear if it will be localized or not.
            // To play safe, we are keeping it hard-coded, but the string is available for translation.
            $from = $dmarc_from . ' ' . 'via' . ' ' . $modSettings['maillist_sitename'];
        } else {
            $from = $dmarc_from;
        }
    }
    // Take care of from / subject encodings
    list(, $from_name, $from_encoding) = mimespecialchars(addcslashes($from !== null ? $from : (!empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $context['forum_name']), '<>()\'\\"'), true, $hotmail_fix, $line_break);
    list(, $subject) = mimespecialchars($subject, true, $hotmail_fix, $line_break);
    if ($from_encoding !== 'base64') {
        $from_name = '"' . $from_name . '"';
    }
    // Construct the from / replyTo mail headers, based on if we showing a users name
    if ($from_wrapper != null) {
        $headers = 'From: ' . $from_name . ' <' . $from_wrapper . '>' . $line_break;
        // If they reply where is it going to be sent?
        $headers .= 'Reply-To: "' . (!empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $context['forum_name']) . '" <' . (!empty($modSettings['maillist_sitename_address']) ? $modSettings['maillist_sitename_address'] : (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'])) . '>' . $line_break;
        if ($reference !== null) {
            $headers .= 'References: <' . $reference . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>' . $line_break;
        }
    } else {
        // Standard ElkArte headers
        $headers = 'From: ' . $from_name . ' <' . (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from']) . '>' . $line_break;
        $headers .= $from !== null && strpos($from, '@') !== false ? 'Reply-To: <' . $from . '>' . $line_break : '';
    }
    // Return path, date, mailer
    $headers .= 'Return-Path: ' . (!empty($modSettings['maillist_sitename_address']) ? $modSettings['maillist_sitename_address'] : (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'])) . $line_break;
    $headers .= 'Date: ' . gmdate('D, d M Y H:i:s') . ' -0000' . $line_break;
    $headers .= 'X-Mailer: ELK' . $line_break;
    // If Using the maillist we include a few more headers for compliance
    if ($maillist) {
        // Lets try to avoid auto replies
        $headers .= 'X-Auto-Response-Suppress: All' . $line_break;
        $headers .= 'Auto-Submitted: auto-generated' . $line_break;
        // Indicate its a list server to avoid spam tagging and to help client filters
        // http://www.ietf.org/rfc/rfc2369.txt
        $headers .= 'List-Id: <' . (!empty($modSettings['maillist_sitename_address']) ? $modSettings['maillist_sitename_address'] : (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'])) . '>' . $line_break;
        $headers .= 'List-Unsubscribe: <' . $boardurl . '/index.php?action=profile;area=notification>' . $line_break;
        $headers .= 'List-Owner: <mailto:' . (!empty($modSettings['maillist_sitename_help']) ? $modSettings['maillist_sitename_help'] : (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'])) . '> (' . (!empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $context['forum_name']) . ')' . $line_break;
    }
    // Pass this to the integration before we start modifying the output -- it'll make it easier later.
    if (in_array(false, call_integration_hook('integrate_outgoing_email', array(&$subject, &$message, &$headers)), true)) {
        return false;
    }
    // Save the original message...
    $orig_message = $message;
    // The mime boundary separates the different alternative versions.
    $mime_boundary = 'ELK-' . md5($message . time());
    // Using mime, as it allows to send a plain unencoded alternative.
    $headers .= 'Mime-Version: 1.0' . $line_break;
    $headers .= 'Content-Type: multipart/alternative; boundary="' . $mime_boundary . '"' . $line_break;
    $headers .= 'Content-Transfer-Encoding: 7bit' . $line_break;
    // Sending HTML?  Let's plop in some basic stuff, then.
    if ($send_html) {
        $no_html_message = un_htmlspecialchars(strip_tags(strtr($orig_message, array('</title>' => $line_break))));
        // But, then, dump it and use a plain one for dinosaur clients.
        list(, $plain_message) = mimespecialchars($no_html_message, false, true, $line_break);
        $message = $plain_message . $line_break . '--' . $mime_boundary . $line_break;
        // This is the plain text version.  Even if no one sees it, we need it for spam checkers.
        list($charset, $plain_charset_message, $encoding) = mimespecialchars($no_html_message, false, false, $line_break);
        $message .= 'Content-Type: text/plain; charset=' . $charset . $line_break;
        $message .= 'Content-Transfer-Encoding: ' . $encoding . $line_break . $line_break;
        $message .= $plain_charset_message . $line_break . '--' . $mime_boundary . $line_break;
        // This is the actual HTML message, prim and proper.  If we wanted images, they could be inlined here (with multipart/related, etc.)
        list($charset, $html_message, $encoding) = mimespecialchars($orig_message, false, $hotmail_fix, $line_break);
        $message .= 'Content-Type: text/html; charset=' . $charset . $line_break;
        $message .= 'Content-Transfer-Encoding: ' . ($encoding == '' ? '7bit' : $encoding) . $line_break . $line_break;
        $message .= $html_message . $line_break . '--' . $mime_boundary . '--';
    } else {
        // Send a plain message first, for the older web clients.
        list(, $plain_message) = mimespecialchars($orig_message, false, true, $line_break);
        $message = $plain_message . $line_break . '--' . $mime_boundary . $line_break;
        // Now add an encoded message using the forum's character set.
        list($charset, $encoded_message, $encoding) = mimespecialchars($orig_message, false, false, $line_break);
        $message .= 'Content-Type: text/plain; charset=' . $charset . $line_break;
        $message .= 'Content-Transfer-Encoding: ' . $encoding . $line_break . $line_break;
        $message .= $encoded_message . $line_break . '--' . $mime_boundary . '--';
    }
    // Are we using the mail queue, if so this is where we butt in...
    if (!empty($modSettings['mail_queue']) && $priority != 0) {
        return AddMailQueue(false, $to_array, $subject, $message, $headers, $send_html, $priority, $is_private, $message_id);
    } elseif (!empty($modSettings['mail_queue']) && !empty($modSettings['mail_period_limit'])) {
        list($last_mail_time, $mails_this_minute) = @explode('|', $modSettings['mail_recent']);
        if (empty($mails_this_minute) || time() > $last_mail_time + 60) {
            $new_queue_stat = time() . '|' . 1;
        } else {
            $new_queue_stat = $last_mail_time . '|' . ((int) $mails_this_minute + 1);
        }
        updateSettings(array('mail_recent' => $new_queue_stat));
    }
    // SMTP or sendmail?
    if ($use_sendmail) {
        $subject = strtr($subject, array("\r" => '', "\n" => ''));
        if (!empty($modSettings['mail_strip_carriage'])) {
            $message = strtr($message, array("\r" => ''));
            $headers = strtr($headers, array("\r" => ''));
        }
        $sent = array();
        $need_break = substr($headers, -1) === "\n" || substr($headers, -1) === "\r" ? false : true;
        foreach ($to_array as $key => $to) {
            $unq_id = '';
            $unq_head = '';
            // If we are using the post by email functions, then we generate "reply to mail" security keys
            if ($maillist) {
                $unq_head = md5($boardurl . microtime() . rand()) . '-' . $message_id;
                $encoded_unq_head = base64_encode($line_break . $line_break . '[' . $unq_head . ']' . $line_break);
                $unq_id = ($need_break ? $line_break : '') . 'Message-ID: <' . $unq_head . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>';
                $message = mail_insert_key($message, $unq_head, $encoded_unq_head, $line_break);
            } elseif (empty($modSettings['mail_no_message_id'])) {
                $unq_id = ($need_break ? $line_break : '') . 'Message-ID: <' . md5($boardurl . microtime()) . '-' . $message_id . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>';
            }
            if (!mail(strtr($to, array("\r" => '', "\n" => '')), $subject, $message, $headers . $unq_id)) {
                log_error(sprintf($txt['mail_send_unable'], $to));
                $mail_result = false;
            } else {
                // keep our post via email log
                if (!empty($unq_head)) {
                    $sent[] = array($unq_head, time(), $to);
                }
                // track total emails sent
                if (!empty($modSettings['trackStats'])) {
                    trackStats(array('email' => '+'));
                }
            }
            // Wait, wait, I'm still sending here!
            @set_time_limit(300);
            if (function_exists('apache_reset_timeout')) {
                @apache_reset_timeout();
            }
        }
        // Log each email that we sent so they can be replied to
        if (!empty($sent)) {
            require_once SUBSDIR . '/Maillist.subs.php';
            log_email($sent);
        }
    } else {
        // SMTP protocol it is
        $mail_result = $mail_result && smtp_mail($to_array, $subject, $message, $headers, $priority, $message_id);
    }
    // Clear out the stat cache.
    trackStats();
    // Everything go smoothly?
    return $mail_result;
}