// Copyright: Copyright (C) 2002-2009 Pristine Communications // Set the include path if (!defined("INCPATH_SET")) { require_once dirname(__FILE__) . "/incpath.inc.php"; } // Referenced subroutines require_once "monica/cgiemu.inc.php"; require_once "monica/curtime.inc.php"; require_once "monica/errhndl.inc.php"; require_once "monica/geoip.inc.php"; require_once "monica/getlang.inc.php"; require_once "monica/guest.inc.php"; require_once "monica/https.inc.php"; require_once "monica/lninfo.inc.php"; require_once "monica/login.inc.php"; require_once "monica/md5.inc.php"; require_once "monica/mimeenc.inc.php"; require_once "monica/mimetype.inc.php"; require_once "monica/rfc1521.inc.php"; require_once "monica/rfc822.inc.php"; require_once "monica/server.inc.php"; require_once "monica/unicode.inc.php"; require_once "monica/upload.inc.php"; require_once "monica/xfileio.inc.php"; // References: // RFC 821 SIMPLE MAIL TRANSFER PROTOCOL // RFC 822 STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES // RFC 1521 MIME (Multipurpose Internet Mail Extensions) Part One: Mechanisms for Specifying and Describing the Format of Internet Message Bodies // RFC 1766 Tags for the Identification of Languages // RFC 1806 Communicating Presentation Information in Internet Messages: The Content-Disposition Header // RFC 1864 The Content-MD5 Header Field // RFC 2183 Communicating Presentation Information in Internet Messages: The Content-Disposition Header Field // RFC 2369 The Use of URLs as Meta-Syntax for Core Mail List Commands and their Transport through Message Header Fields // RFC 2919 List-Id: A Structured Field and Namespace for the Identification of Mailing Lists // Mail: An RFC-822 e-mail message // 2005-05-06: All input information must be in UTF-8 class Mail { protected $_body = null; protected $_parts = array(); protected $_charset = null; protected $_modified = false; protected $_output = null; protected $_attaches = array(); // RFC 821 headers/properties protected $_mail_from = null; protected $_rcpt_to = null; // RFC 822 headers/properties protected $_from = null; protected $_subject = null; protected $_sender = null; protected $_to = null; protected $_cc = null; protected $_bcc = null; protected $_reply_to = null; // RFC 1521 MIME headers/properties protected $_type = "text/plain"; protected $_id = null; // RFC 1766 Content-Language protected $_lang = null; // RFC 1806/2183 Content-Disposition protected $_disposition = null; protected $_filename = null; protected $_filemtime = null; protected $_filesize = null; // RFC 1864 Content-MD5 protected $_md5 = false; // RFC 2387 MIME multipart/related headers/properties protected $_related_type = null; protected $_related_start = null; // RFC 2369/2919 Mailing Lists headers/properties protected $_list_id = null; protected $_list_help = null; protected $_list_subscribe = null; protected $_list_unsubscribe = null; protected $_list_post = null; protected $_list_owner = null; protected $_list_archive = null; // Non-standard headers protected $_errors_to = null; protected $_precedence = null; protected $_mailer = "Monica mail.inc.php http://www.pristine.com.tw/"; protected $_any_header = array(); protected $_boundary_len = 32; // __construct: Initialize the e-mail message function __construct() { // Nothing to initialize } // RFC 821 headers/properties // mail_from: Set/retrieve the MAIL FROM: sender function mail_from($email = null) { if (func_num_args() > 0) { $this->_mail_from = $email; $this->_modified = true; } return $this->_mail_from; } // rcpt_to: Set/retrieve the RCPT TO: recipients (to be used in send()) function rcpt_to($email = null) { if (func_num_args() > 0) { // Reset it if (is_null($email)) { $this->_rcpt_to = null; // Add it } else { if (!is_array($this->_rcpt_to)) { $this->_rcpt_to = array(); } if (!in_array($email, $this->_rcpt_to)) { $this->_rcpt_to[] = $email; } } } return is_null($this->_rcpt_to)? null: implode(",", $this->_rcpt_to); } // RFC 822 headers/properties // from: Set/retrieve the From: header function from($email = null, $name = null) { if (func_num_args() > 0) { $this->_add_addr($this->_from, $email, $name); } return $this->_ret_addrs($this->_from); } // subject: Set/retrieve the subject function subject($subject = null) { if (func_num_args() > 0) { $this->_subject = $subject; $this->_modified = true; } return $this->_subject; } // sender: Set/retrieve the Sender: header function sender($email = null, $name = null) { if (func_num_args() > 0) { $this->_set_addr($this->_sender, $email, $name); } return $this->_ret_addr($this->_sender); } // to: Set/retrieve the To: header function to($email = null, $name = null) { if (func_num_args() > 0) { $this->_add_addr($this->_to, $email, $name); } return $this->_ret_addrs($this->_to); } // cc: Set/retrieve the Cc: header function cc($email = null, $name = null) { if (func_num_args() > 0) { $this->_add_addr($this->_cc, $email, $name); } return $this->_ret_addrs($this->_cc); } // bcc: Set/retrieve the Bcc: header function bcc($email = null, $name = null) { if (func_num_args() > 0) { $this->_add_addr($this->_bcc, $email, $name); } return $this->_ret_addrs($this->_bcc); } // reply_to: Set/retrieve the Reply-To: header function reply_to($email = null, $name = null) { if (func_num_args() > 0) { $this->_add_addr($this->_reply_to, $email, $name); } return $this->_ret_addrs($this->_reply_to); } // RFC 1521 MIME headers/properties // charset: Set/retrieve the desired character set // Note that this is the desired character set. The input information // should still in UTF-8 function charset($charset = null) { if (func_num_args() > 0) { $this->_charset = $charset; $this->_modified = true; } return $this->_charset; } // type: Set/retrieve the MIME content type function type($type = null) { if (func_num_args() > 0) { $this->_type = $type; $this->_modified = true; } return $this->_type; } // id: Set/retrieve the content ID. function id($id = true) { if (func_num_args() > 0) { // Set a content ID. if ($id) { if (is_null($this->_id)) { $this->_id = $this->_new_msgid(); $this->_modified = true; } // Unset the content ID. } else { if (!is_null($this->_id)) { $this->_id = null; $this->_modified = true; } } } return $this->_id; } // RFC 1766 Content-Language // lang: Set/retrieve the language function lang($lang = null) { if (func_num_args() > 0) { // Reset it if (is_null($lang)) { $this->_lang = null; // Add it } else { if (!is_array($this->_lang)) { $this->_lang = array(); } $this->_lang[] = $lang; } $this->_modified = true; } if (is_null($this->_lang)) { return null; } else { $langs = array(); for ($i = 0; $i < count($this->_lang); $i++) { $langs[] = ln($this->_lang[$i], LN_NAME); } return implode(", ", $this->_lang); } } // RFC 1806/2183 Content-Disposition // disposition: Set/retrieve the MIME content disposition // "inline" or "attachment" function disposition($disposition = null) { if (func_num_args() > 0) { if (is_null($disposition)) { $this->_disposition = null; } else { switch ($disposition) { case "inline": case "attachment": $this->_disposition = $disposition; break; default: trigger_error("bad content disposition $disposition", E_USER_ERROR); break; } } $this->_modified = true; } return $this->_disposition; } // filename: Set/retrieve the MIME content disposition filename function filename($filename = null) { if (func_num_args() > 0) { $this->_filename = $filename; $this->_modified = true; } return $this->_filename; } // filemtime: Set/retrieve the MIME content disposition modification time function filemtime($mtime = null) { if (func_num_args() > 0) { $this->_filemtime = $mtime; $this->_modified = true; } return $this->_filemtime; } // filesize: Set/retrieve the MIME content disposition size function filesize($size = null) { if (func_num_args() > 0) { $this->_filesize = $size; $this->_modified = true; } return $this->_filesize; } // RFC 1864 Content-MD5 // md5: Set/retrieve the MD5 digest flag function md5($md5 = true) { if (func_num_args() > 0) { $this->_md5 = $md5? true: false; $this->_modified = true; } return $this->_md5; } // RFC 2387 MIME multipart/related headers/properties // related_type: Set/retrieve the multipart/related type function related_type($reltype = null) { if (func_num_args() > 0) { $this->_related_type = $reltype; $this->_modified = true; } return $this->_related_type; } // related_start: Set/retrieve the multipart/related start function related_start($relstart = null) { if (func_num_args() > 0) { $this->_related_start = $relstart; $this->_modified = true; } return $this->_related_start; } // RFC 2369/2919 Mailing Lists headers/properties // list_id: Set/retrieve the list ID. function list_id($id = null, $phrase = null) { if (func_num_args() > 0) { // Reset it if (is_null($id)) { $this->_list_id = null; // Set it } else { $this->_list_id = array( "id" => $id, "phrase" => $phrase, ); } $this->_modified = true; } if (is_null($this->_list_id)) { return null; } $ret = "<" . $this->_list_id["id"] . ">"; if (!is_null($this->_list_id["phrase"])) { $phrase = $this->_list_id["phrase"]; if (rfc822_phrase_need_quoting($phrase)) { $phrase = "\"" . addslashes($phrase) . "\""; } $ret = "$phrase $ret"; } return $ret; } // list_help: Set/retrieve the list help address function list_help($url = null) { if (func_num_args() > 0) { $this->_add_list_url($this->_list_help, $url); } return $this->_ret_list_urls($this->_list_help); } // list_subscribe: Set/retrieve the list subscribe address function list_subscribe($url = null) { if (func_num_args() > 0) { $this->_add_list_url($this->_list_subscribe, $url); } return $this->_ret_list_urls($this->_list_subscribe); } // list_unsubscribe: Set/retrieve the list unsubscribe address function list_unsubscribe($url = null) { if (func_num_args() > 0) { $this->_add_list_url($this->_list_unsubscribe, $url); } return $this->_ret_list_urls($this->_list_unsubscribe); } // list_post: Set/retrieve the list post address // False means "List-Post: NO" function list_post($url = null) { if (func_num_args() > 0) { if ($url === false) { $this->_list_post = false; } else { $this->_add_list_url($this->_list_post, $url); } } if ($this->_list_post === false) { return "NO"; } else { return $this->_ret_list_urls($this->_list_post); } } // list_owner: Set/retrieve the list owner address function list_owner($url = null) { if (func_num_args() > 0) { $this->_add_list_url($this->_list_owner, $url); } return $this->_ret_list_urls($this->_list_owner); } // list_archive: Set/retrieve the list archive address function list_archive($url = null) { if (func_num_args() > 0) { $this->_add_list_url($this->_list_archive, $url); } return $this->_ret_list_urls($this->_list_archive); } // Non-standard headers // errors_to: Set/retrieve the Errors-To: header function errors_to($email = null) { if (func_num_args() > 0) { if (is_null($email)) { $this->_errors_to = null; } else { if (!is_array($this->_errors_to)) { $this->_errors_to = array(); } $this->_errors_to[] = $email; } $this->_modified = true; } return is_null($this->_errors_to)? null: implode(", ", $this->_errors_to); } // precedence: Set/retrieve the precedence, mentioned in RFC 2076 // "bulk" or "first-class" function precedence($precedence = null) { if (func_num_args() > 0) { if (is_null($precedence)) { $this->_precedence = null; } else { switch ($precedence) { case "bulk": case "first-class": $this->_precedence = $precedence; break; default: trigger_error("bad precedence $precedence", E_USER_ERROR); break; } } $this->_modified = true; } return $this->_precedence; } // mailer: Set/retrieve the mailer function mailer($mailer = null) { if (func_num_args() > 0) { $this->_mailer = $mailer; $this->_modified = true; } return $this->_mailer; } // any_header: Set/retrieve any header function any_header($name, $value = null) { // Find the index of that header unset($ndx); for ($i = 0; $i < count($this->_any_header); $i++) { if ($this->_any_header[$i]["name"] = $name) { $ndx = $i; break; } } // Set the value if (func_num_args() > 1) { if (isset($ndx)) { $this->_any_header[$ndx]["value"] = $value; } else { // Only add a non-null value if (!is_null($value)) { $this->_any_header[] = array( "name" => $name, "value" => $value, ); } } $this->_modified = true; return $value; // Retrieve the value } else { if (isset($ndx)) { return $this->_any_header[$ndx]["value"]; } else { return null; } } } // body: Set/retrieve the body function body($body = null) { if (func_num_args() > 0) { $this->_body = $body; $this->_modified = true; } return $this->_body; } // addpart: Add an MIME part // $mail is a Mail object function addpart($mail) { if (is_null($mail)) { $this->_parts = array(); } else { $this->_parts[] = $mail; } $this->_modified = true; return $mail; } // from_file: Set the mail from a file function from_file($file) { $this->_type = check_mime_type($file); $this->_body = xfread($file); $this->_modified = true; return; } // as_attach: Set the mail as an attachment function as_attach($file, $filename = null) { $this->_type = check_mime_type($file); $this->_disposition = "attachment"; $this->_filename = !is_null($filename)? $filename: basename($file); $this->_filemtime = filemtime($file); $this->_filesize = filesize($file); $this->_body = xfread($file); $this->_modified = true; return $this->_body; } // as_attach_upload: Set the an attachment from $_SESSION function as_attach_upload($sn) { $FILES =& file_deposit(); $file =& $FILES[$sn]; $this->_charset = getlang(LN_CHARSET); $this->_type = $file["type"]; $this->_disposition = "attachment"; $this->_filename = $file["name"]; $this->_filesize = $file["size"]; $this->_body = $file["content"]; $this->_modified = true; return $this->_body; } // add_attach: Add an attachment function add_attach($file, $filename = null) { if (is_null($file)) { $this->_attaches = array(); } else { // Create the attachment $attach = new Mail(); $attach->as_attach($file, $filename); $this->_attaches[] = $attach; } $this->_modified = true; return; } // add_attach_upload: Add an attachment from $_SESSION function add_attach_upload($sn) { if (is_null($sn)) { $this->_attaches = array(); } else { // Create the attachment $attach = new Mail(); $attach->as_attach_upload($sn); $this->_attaches[] = $attach; } $this->_modified = true; return; } // output: Output the mail message function output($is_full_msg = true) { // Output before if (!is_null($this->_output) && !$this->_modified) { return $this->_output; } // Preserve the original timeout $timeout = ini_get("max_execution_time"); ini_set("max_execution_time", 0); // Set the attachment $mail = $this->_get_attached_mail(); // Check the message if ($is_full_msg) { $mail->_check(); } // Set the mail body // Set the body first, since we need to know Content-Transfer-Encoding // and MIME multipart boundary first $body = ""; unset($content_transfer_encoding); // A single content if ( is_null($mail->_type) || substr($mail->_type, 0, 10) != "multipart/") { if (!is_null($mail->_body)) { $body = $mail->_body; // A text message if (substr($mail->_type, 0, 5) == "text/") { // Convert to the desired character set // This piece of code shall be refined if ( $mail->_type == "text/plain" && !is_usascii_printable($body) && !is_null($this->_charset) && $this->_charset != "UTF-8") { $body = iconv("UTF-8", $this->_charset, $body); } $body = str_replace("\r\n", "\n", $body); $body = str_replace("\n", "\r\n", $body); if ($mail->_md5) { $md5 = md5_base64($body); } // Not in US-ASCII, containing long lines, or MD5 is in use if ( !is_usascii_printable_text($body) || preg_match("/[^\r\n]{76}/", $body) || $mail->_md5) { $body = qpencode($body); $content_transfer_encoding = "quoted-printable"; } // A piece of RFC-822 e-mail message } elseif (substr($mail->_type, 0, 8) == "message/") { // Do nothing // A piece of binary data } else { if ($mail->_md5) { $md5 = md5_base64($body); } $body = base64_encode($body); $body = preg_replace("/(.{76})/", "$1\r\n", $body); $content_transfer_encoding = "base64"; } } // A multipart MIME content } else { $parts = array(); for ($i = 0; $i < count($mail->_parts); $i++) { $parts[] = $mail->_parts[$i]->output(false); } $everything = implode("", $parts); // Create a boundary do { $boundary = "=_"; while (strlen($boundary) < $mail->_boundary_len) { switch (mt_rand(0, 2)) { case 0: $boundary .= chr(mt_rand(0, 9) + ord("0")); break; case 1: $boundary .= chr(mt_rand(0, 25) + ord("A")); break; case 2: $boundary .= chr(mt_rand(0, 25) + ord("a")); break; } } } while (strpos($everything, $boundary) !== false); // Add the boundary for ($i = 0; $i < count($parts); $i++) { $parts[$i] = "--$boundary\r\n" . $parts[$i] . "\r\n"; } // Compose the body $body = implode("", $parts) . "--$boundary--\r\n"; } // Ensure a CRLF is in the end if (substr($body, -2) != "\r\n") { $body .= "\r\n"; } $headers = array(); // RFC 822 headers // RFC-822 suggests the header order as: // "Return-Path", "Received", "Date", "From", "Subject", "Sender", // "To", "cc", etc. // We do not set the Received: header. It is added before mail is sent. // Set the Date: header if ($is_full_msg) { $headers[] = "Date: " . date("r", NOW) . "\r\n"; } // Set the From: header if (!is_null($mail->_from)) { $headers[] = "From: " . $mail->_out_addrs($mail->_from) . "\r\n"; } elseif ($is_full_msg) { $pwent = posix_getpwuid(posix_geteuid()); $addr = array( "name" => $pwent["gecos"], "email" => $pwent["name"] . "@" . fqdn(), ); $headers[] = "From: " . $mail->_out_addr($addr) . "\r\n"; } // Set the Subject: header if (!is_null($mail->_subject)) { $subject = $this->_subject; $subject = b64hdr_encode($subject, $this->_charset); $headers[] = "Subject: $subject\r\n"; } // Set the Sender: header if (!is_null($mail->_sender)) { $need = false; // Multiple From: if (count($mail->_from) > 1) { $need = true; // Different than the only From: } elseif ( $mail->_from[0]["email"] !== $mail->_sender["email"] || $mail->_from[0]["name"] !== $mail->_sender["name"]) { $need = true; } if ($need) { $headers[] = "Sender: " . $mail->_out_addr($mail->_sender) . "\r\n"; } } // Set the To: header if (!is_null($mail->_to)) { $headers[] = "To: " . $mail->_out_addrs($mail->_to) . "\r\n"; } // Set the Cc: header if (!is_null($mail->_cc)) { $headers[] = "Cc: " . $mail->_out_addrs($mail->_cc) . "\r\n"; } // Set the Bcc: header if (!is_null($mail->_bcc)) { $headers[] = "Bcc: " . $mail->_out_addrs($mail->_bcc) . "\r\n"; } // Destination must exist (by RFC 822 4.1) /* if ( $is_full_msg && is_null($mail->_to) && is_null($mail->_cc) && is_null($mail->_bcc)) { $headers[] = "Bcc: \r\n"; } */ // Set the Reply-To: header if (!is_null($mail->_reply_to)) { $headers[] = "Reply-To: " . $mail->_out_addrs($mail->_reply_to) . "\r\n"; } // Set the Message-ID: header if ($is_full_msg) { $id = !is_null($mail->_id)? $mail->_id: $mail->_new_msgid(); $headers[] = "Message-ID: <$id>\r\n"; } // RFC 1521 MIME headers // Set the MIME-Version: header if ($is_full_msg && !is_null($mail->_type)) { $headers[] = "MIME-Version: 1.0\r\n"; } // Set the Content-Type: header if (!is_null($mail->_type)) { $type = $mail->_type; if (substr($type, 0, 5) == "text/" && !is_null($mail->_charset)) { $type .= "; charset=" . $mail->_charset; } // Attachment filename -- deprecated /* if (!is_null($mail->_filename)) { $filename = $mail->_filename; $filename = b64hdr_encode($filename, $this->_charset); if (rfc1521_value_need_quoting($filename)) { $filename = "\"" . addslashes($filename) . "\""; } $type .= ";\r\n name=$filename"; } */ if (substr($type, 0, 10) == "multipart/") { // Add type and start parameter to multipart/related if ($type == "multipart/related") { $reltype = $mail->_related_type; if (is_null($reltype)) { $reltype = $mail->_parts[0]->type(); } $type .= ";\r\n type=\"$reltype\""; // "start" parameter is broken in most e-mail client (??? why?) /* $relstart = $mail->_related_start; if (is_null($relstart)) { $relstart = $mail->_parts[0]->id(true); } $type .= ";\r\n start=\"$relstart\""; */ } $type .= ";\r\n boundary=\"$boundary\""; } $headers[] = "Content-Type: $type\r\n"; } // Set the Content-ID: header // Do not generate Content-ID for complete messages. Complete // messages has Message-ID instead. if (!$is_full_msg && !is_null($mail->_id)) { $headers[] = "Content-ID: <" . $mail->_id . ">\r\n"; } // Set the Content-Transfer-Encoding: header if (isset($content_transfer_encoding)) { $headers[] = "Content-Transfer-Encoding: $content_transfer_encoding\r\n"; } // RFC 1766 Content-Language if (!is_null($mail->_lang)) { $langs = array(); for ($i = 0; $i < count($mail->_lang); $i++) { $langs[] = ln($mail->_lang[$i], LN_NAME); } $headers[] = "Content-Language: " . implode(", ", $langs) . "\r\n"; } // RFC 1806/2183 Content-Disposition if (!is_null($mail->_disposition)) { $disposition = $mail->_disposition; if (!is_null($mail->_filename)) { $filename = $mail->_filename; // Note: Eudora cannot handle encoded MIME parameter values $filename = b64hdr_encode($filename, $this->_charset); if (rfc1521_value_need_quoting($filename)) { $filename = "\"" . addslashes($filename) . "\""; } $disposition .= ";\r\n filename=$filename"; } if (!is_null($mail->_filemtime)) { $disposition .= ";\r\n modification-date=\"" . date("r", $mail->_filemtime) . "\""; } if (!is_null($mail->_filesize)) { $disposition .= ";\r\n size=" . $mail->_filesize; } $headers[] = "Content-Disposition: $disposition\r\n"; } // RFC 1864 Content-MD5 if ($mail->_md5) { $headers[] = "Content-MD5: $md5\r\n"; } // RFC 2369/2919 Mailing Lists headers // Set the List-ID: header if (!is_null($mail->_list_id)) { $list_id = "<" . $mail->_list_id["id"] . ">"; if (!is_null($mail->_list_id["phrase"])) { $phrase = $mail->_list_id["phrase"]; if (rfc822_phrase_need_quoting($phrase)) { $phrase = "\"" . addslashes($phrase) . "\""; } $phrase = b64hdr_encode($phrase, $this->_charset); $list_id = "$phrase $list_id"; } $headers[] = "List-ID: $list_id\r\n"; } // Set the List-Help: header if (!is_null($mail->_list_help)) { $headers[] = "List-Help: " . $mail->_out_list_urls($mail->_list_help) . "\r\n"; } // Set the List-Subscribe: header if (!is_null($mail->_list_subscribe)) { $headers[] = "List-Subscribe: " . $mail->_out_list_urls($mail->_list_subscribe) . "\r\n"; } // Set the List-Unsubscribe: header if (!is_null($mail->_list_unsubscribe)) { $headers[] = "List-Unsubscribe: " . $mail->_out_list_urls($mail->_list_unsubscribe) . "\r\n"; } // Set the List-Post: header if (!is_null($mail->_list_post)) { $list_post = ($mail->_list_post !== false)? $mail->_out_list_urls($mail->_list_post): "NO"; $headers[] = "List-Post: $list_post\r\n"; } // Set the List-Owner: header if (!is_null($mail->_list_owner)) { $headers[] = "List-Owner: " . $mail->_out_list_urls($mail->_list_owner) . "\r\n"; } // Set the List-Archive: header if (!is_null($mail->_list_archive)) { $headers[] = "List-Archive: " . $mail->_out_list_urls($mail->_list_archive) . "\r\n"; } // Set the Errors-To: header if ($is_full_msg && !is_null($mail->_errors_to)) { $headers[] = "Errors-To: " . implode(", ", $mail->_errors_to) . "\r\n"; } // Set the Precedence: header if ($is_full_msg && !is_null($mail->_precedence)) { $headers[] = "Precedence: " . $mail->_precedence . "\r\n"; } // Set the X-Mailer: header if ($is_full_msg && !is_null($mail->_mailer)) { $headers[] = "X-Mailer: " . $mail->_mailer . "\r\n"; } // Compose it $this->_output = implode("", $headers) . "\r\n" . $body; $this->_modified = false; // Restore the timeout ini_set("max_execution_time", $timeout); return $this->_output; } // send: Send the mail message function send() { // Send the mail with sendmail return $this->_send_with_sendmail(); } // gsend: Send the mail message if the user is not a guest function gsend() { if (!is_guest()) { return $this->send(); } } // _new_msgid: Obtain a new message ID. function _new_msgid() { static $MSGIDS = array(); // Epoch time and minisecond list($msec, $sec) = explode(" ", microtime()); settype($sec, "integer"); $msec *= 1000000; settype($msec, "integer"); // A random serial number do { $sn = mt_rand(0, 9999); } while (in_array($sn, $MSGIDS)); $MSGIDS[] = $sn; // Compose it return sprintf("%10d.%06d.%05d.%04d.monica@%s", $sec, $msec, getmypid(), $sn, fqdn()); } // _get_attached_mail: Get the mail with its attachment // Actually, this will convert the mail to multipart/mixed // and add the attachments function _get_attached_mail() { // Make a copy of myself $mail = $this; // Return myself if there is no attachment if (count($mail->_attaches) == 0) { return $mail; } // Not multipart/mixed -- convert to multipart/mixed if (is_null($mail->_type) || $mail->_type != "multipart/mixed") { // Pass the content to the body part $body = new Mail(); $body->_charset = $mail->_charset; $body->_type = $mail->_type; $body->_id = $mail->_id; $body->_lang = $mail->_lang; $body->_md5 = $mail->_md5; // Set the body // The origin is multipart/xxx if (!is_null($mail->_type) && substr($mail->_type, 0, 10) == "multipart/") { $body->_parts = $mail->_parts; } else { $body->_body = $mail->_body; } // Reset these values $mail->_parts = array(); $mail->_body = null; $mail->_type = "multipart/mixed"; $mail->_id = null; $mail->_lang = null; $mail->_md5 = false; $mail->addpart($body); } // Add each attachment foreach ($mail->_attaches as $attach) { $mail->addpart($attach); } return $mail; } // _set_addr: Set an address function _set_addr(&$addr, $email, $name) { // Reset it if (is_null($email)) { $addr = null; // Set it } else { $addr = array( "name" => $name, "email" => $email, ); } $this->_modified = true; } // _add_addr: Add an address to an address list function _add_addr(&$list, $email, $name) { // Reset it if (is_null($email)) { $list = null; // Add it } else { if (!is_array($list)) { $list = array(); } $list[] = array( "name" => $name, "email" => $email, ); } $this->_modified = true; } // _ret_addr: Return an address function _ret_addr($addr) { if (is_null($addr)) { return null; } elseif (is_null($addr["name"])) { return $addr["email"]; } else { $name = $addr["name"]; if (rfc822_phrase_need_quoting($name)) { $name = "\"" . addslashes($name) . "\""; } return sprintf("%s <%s>", $name, $addr["email"]); } } // _ret_addrs: Return a list of addresses function _ret_addrs(&$list) { if (is_null($list)) { return null; } else { $addrs = array(); for ($i = 0; $i < count($list); $i++) { $addrs[] = $this->_ret_addr($list[$i]); } return implode(", ", $addrs); } } // _out_addr: Output an address function _out_addr($addr) { $out = $addr["email"]; if (!is_null($addr["name"])) { $name = $addr["name"]; if (rfc822_phrase_need_quoting($name)) { $name = "\"" . addslashes($name) . "\""; } $name = b64hdr_encode($name, $this->_charset); $out = "$name <$out>"; } return $out; } // _out_addrs: Output a list of addresses function _out_addrs(&$list) { $addrs = array(); for ($i = 0; $i < count($list); $i++) { $addrs[] = $this->_out_addr($list[$i]); } return implode(",\r\n ", $addrs); } // _add_list_url: Add a list URL to a list URL list function _add_list_url(&$list, $url) { // Reset it if (is_null($url)) { $list = null; // Add it } else { if (!is_array($list)) { $list = array(); } $list[] = $url; } $this->_modified = true; } // _ret_list_urls: Return a list of list URLs function _ret_list_urls(&$list) { if (is_null($list)) { return null; } else { $urls = array(); for ($i = 0; $i < count($list); $i++) { $urls[] = "<" . $list[$i] . ">"; } return implode(", ", $urls); } } // _out_list_urls: Output a list of list URLs function _out_list_urls(&$list) { $urls = array(); for ($i = 0; $i < count($list); $i++) { $urls[] = "<" . $list[$i] . ">"; } return implode(",\r\n ", $urls); } // _out_trace: Output the trace information (the Received: header) // See RFC 2821 4.4 Trace Information function _out_trace() { // The trace information $trace_info = array(); // From-domain $from_tcpinfo_domain = gethostbyaddr($_SERVER["REMOTE_ADDR"]); if ($from_tcpinfo_domain == $_SERVER["REMOTE_ADDR"]) { $from_tcpinfo_domain = null; } if (is_null($from_tcpinfo_domain)) { $from_tcp_info = sprintf("[%s]", $_SERVER["REMOTE_ADDR"]); } else { $from_tcp_info = sprintf("%s [%s]", $from_tcpinfo_domain, $_SERVER["REMOTE_ADDR"]); } $trace_info[] = "from webclient ($from_tcp_info)"; // The invoking user, as a from comment if (is_null(get_login_sn())) { $from_comment = "invoked by anonymous web user" . " country " . geoiplookup(); } else { $from_comment = "invoked by web user " . get_login_id() . " S/N " . get_login_sn() . " country " . geoiplookup(); } $trace_info[] = "($from_comment)"; // By-domain // Apache implementation if (is_apache()) { $by_address_literal = $_SERVER["SERVER_ADDR"]; // Microsoft IIS implementation } elseif (is_iis()) { $by_address_literal = $_SERVER["LOCAL_ADDR"]; // Else, do DNS query } else { $by_address_literal = gethostbyname($_SERVER["SERVER_NAME"]); } if (array_key_exists("HTTP_HOST", $_SERVER)) { $by_domain = preg_replace("/:\d+$/", "", $_SERVER["HTTP_HOST"]); } else { $by_domain = $by_address_literal; } $by_tcpinfo_domain = gethostbyaddr($by_address_literal); if ($by_tcpinfo_domain == $by_address_literal) { $by_tcpinfo_domain = null; } if (is_null($by_tcpinfo_domain)) { $by_tcp_info = sprintf("[%s]", $by_address_literal); } else { $by_tcp_info = sprintf("%s [%s]", $by_tcpinfo_domain, $by_address_literal); } $trace_info[] = sprintf("by %s (%s)", $by_domain, $by_tcp_info); // VIA Link and WITH Protocol $trace_info[] = "via TCP with HTTP"; // For recipients $rcpts = $this->_get_rcpts(); if (count($rcpts) > 0) { for ($i = 0; $i < count($rcpts); $i++) { $rcpts[$i] = "<" . $rcpts[$i] . ">"; } $trace_info[] = "for " . implode(" ", $rcpts); } return sprintf("Received: %s ;\r\n\t%s\r\n", implode("\r\n\t", $trace_info), date("r", NOW)); } // _check: Check if it is ready to be output // Refer to RFC 822 function _check() { // When no valid From: exists, default to the current user of the running process // Check if a valid Sender: exists with multiple From: if ( !is_null($this->_from) && count($this->_from) > 1 && is_null($this->_sender)) { trigger_error("Multiple From: without a valid Sender: (RFC-822)", E_USER_ERROR); } // Check if valid contents exist if ( !is_null($this->_type) && substr($this->_type, 0, 10) == "multipart/" && count($this->_parts) == 0) { trigger_error("MIME Multipart without any valid part", E_USER_ERROR); } } // _get_sender: Obtain the sender function _get_sender($fallback = false) { // MAIL FROM: specified if (!is_null($this->_mail_from)) { return $this->_mail_from; // Sender specified } elseif (!is_null($this->_sender)) { return $this->_sender["email"]; // Obtain the sender from From: } elseif (!is_null($this->_from)) { return $this->_from[0]["email"]; // Nothing left } elseif (!$fallback) { return null; // Use the current user } else { $pwent = posix_getpwuid(posix_geteuid()); return $pwent["name"] . "@" . fqdn(); } } // _get_rcpts: Collect the recipients list function _get_rcpts() { // Recipients specified if (!is_null($this->_rcpt_to)) { return $this->_rcpt_to; } // Obtain the recipients from To:, Cc: and Bcc: $rcpts = array(); for ($i = 0; $i < count($this->_to); $i++) { if (!in_array($this->_to[$i]["email"], $rcpts)) { $rcpts[] = $this->_to[$i]["email"]; } } for ($i = 0; $i < count($this->_cc); $i++) { if (!in_array($this->_cc[$i]["email"], $rcpts)) { $rcpts[] = $this->_cc[$i]["email"]; } } for ($i = 0; $i < count($this->_bcc); $i++) { if (!in_array($this->_bcc[$i]["email"], $rcpts)) { $rcpts[] = $this->_bcc[$i]["email"]; } } return $rcpts; } // _send_with_sendmail: Send the mail with Sendmail function _send_with_sendmail() { // Obtain the sender $sender = $this->_get_sender(); // Collect the Recipients list $rcpts = $this->_get_rcpts(); // No recipients found if (count($rcpts) == 0) { trigger_error("No recipients found", E_USER_ERROR); } // Send with Sendmail $sendmail = array("/usr/sbin/sendmail", "-odb"); if (!is_null($sender)) { $sendmail = array_merge($sendmail, array("-f", $sender)); } $sendmail = array_merge($sendmail, $rcpts); $mail = $this->_out_trace() . $this->output(); // Sendmail must escape "." on a line. See RFC 821 SMTP 4.5.2 $mail = preg_replace("/^(\.+)\r$/m", ".$1", $mail); return gxruncmd($sendmail, $mail); } } // b64hdr_encode: Encode a piece of header text with Base-64 // Refer to RFC-1522 4.1 function b64hdr_encode($text, $charset) { // US-ASCII printable -- no need to encode it if (is_usascii_printable($text)) { return $text; } // No desired character set available if (is_null($charset)) { return $text; } // Desired character set not UTF-8 if ($charset != "UTF-8") { // Try to convert into the desired character set $GLOBALS["php_errormsg"] = null; set_error_handler("null_error_handler"); $converted = iconv("UTF-8", $charset, $text); restore_error_handler(); // Conversion OK -- in the desired character set if (is_null($GLOBALS["php_errormsg"])) { return "=?$charset?B?" . base64_encode($converted) . "?="; } } // Else -- send in UTF-8 return "=?UTF-8?B?" . base64_encode($text) . "?="; } ?>