. */ require_once("Mail/mimePart.php"); class Mail { private $host; private $port; private $domain; private $from; private $rcpt; private $header; private $message; private $boundary; private $attachment; private $dlv_msg; private $dlv_ret_nr; private $addfooter = true; const MAIL_FAIL = 0; const MAIL_OK = 1; const MAIL_QUEUE = 2; public function __construct($from, $rcpt, $domain="", $host="127.0.0.1", $port=25) { if( self::checkEmail($from) ) $this->from = $from; else { debug("Invalid from: ".$from); return false; } if( DEBUG ) { $this->rcpt = DEBUG_MAILTO; }else { if( is_array($rcpt) ) { for( $i=0; $ircpt = $rcpt; }else { if( self::checkEmail($rcpt) ) $this->rcpt = $rcpt; else { debug("Invalid rcpt: ".$rcpt); return false; } } } $this->host = $host; $this->port = $port; if( strlen($domain)>0 ) $this->domain = $domain; else $this->domain = $_SERVER['SERVER_NAME']; $this->header = array(); $this->message = ""; $this->boundary = date('YmdHis') . '_' . mt_rand(10000, 99999) . "/" . $this->domain; $this->attachment = array(); $this->dlv_msg = ""; $this->dlv_ret_nr = 0; return true; } public function __destruct() { } public static function encodeHeader($value) { $encoder = new Mail_mimePart(); return $encoder->encodeHeader("Subject", $value, "UTF-8", "base64"); } public function setaddfooter($value) { $this->addfooter = ($value == true); } public function setMessage($msg) { $this->message = $msg; } public function addHeader($name, $value) { $this->header[ ucwords($name) ] = $value; } public function addAttachment($msg, $ContentType, $Encoding="") { $c = count($this->attachment); $this->attachment[$c]['contenttype'] = $ContentType; $this->attachment[$c]['encoding'] = $Encoding; $this->attachment[$c]['message'] = $msg; } public function send($sendLaterOnError=false) { $this->checkHeader(); if( is_array($this->rcpt) ) { $to = $this->rcpt[0]; for( $i=1; $ircpt); $i++ ) { $to .= " ,".$this->rcpt[$i]; } }elseif( strlen($this->rcpt)>0 ) { $to = $this->rcpt; }else { return self::MAIL_FAIL; } if( $stream=$this->initStream($this->from, $to) ) { // headers $this->sendHeaders($stream); // message $this->sendMessage($stream); // attachment if( count($this->attachment)>0 ) $this->sendAttachment($stream); // close mail if( $this->finalizeStream($stream) ) return self::MAIL_OK; else return self::MAIL_FAIL; }else { // failed debug("Failed initStream()"); debug("Error ".$this->dlv_ret_nr.": ".$this->dlv_msg); if( $sendLaterOnError ) { if( $this->queue() ) return self::MAIL_QUEUE; else return self::MAIL_FAIL; } return self::MAIL_FAIL; } } public function setSubject($subject) { $this->addHeader("Subject", $subject); } public function get_delivery_msg() { return $this->dlv_msg; } public function get_delivery_ret_nr() { return $this->dlv_ret_nr; } /** * Check if the supplied e-mail address is a valid e-mail address. If yes, return true, else return false */ public static function checkEmail($email) { $re = "/(^(\w|\.|-|\+)+@(\w|-)+(\.(\w|-)+)*\.[a-zA-Z]{2,}$)/"; if( preg_match($re, $email) ) { //Regex matches, now check MX $host = substr($email, strpos($email, "@") + 1); if( getmxrr($host, $mxhosts) || checkdnsrr($host, "A") || checkdnsrr($host, "AAAA") ) { // Accept MX, A and AAAA records return true; }else { return false; } }else { return false; } } private function checkHeader() { $k = array_keys($this->header); if( !in_array("From", $k) ) $this->header['From'] = $this->from; if( !in_array("To", $k) ) { if( is_array($this->rcpt) ) { $this->header['To'] = $this->rcpt[0]; for( $i=1; $ircpt); $i++ ) { $this->header['To'] .= $this->rcpt[$i]; } }else { $this->header['To'] = $this->rcpt; } } if( !in_array("Date", $k) ) $this->header['Date'] = date("r"); if( !in_array("Message-ID", $k) ) $this->header['Message-ID'] = "<" . date('YmdHis') . '.' . mt_rand(10000, 99999) . "@" . $this->domain . ">"; if( !in_array("Content-Type", $k) ) { if( count($this->attachment)>0 ) { $this->header['Content-Type'] = "multipart/mixed; boundary=\"".$this->boundary."\""; }else { $this->header['Content-Type'] = "text/plain; charset=UTF-8; format=flowed"; $this->header['Content-Transfer-Encoding'] = "8bit"; } } if( !in_array("Mime-Version", $k) ) { $this->header['Mime-Version'] = "1.0"; } $this->header['X-Mailer'] = "Speelotheek De Speelakker"; } public function queue() { $data = array(); $data['date'] = date("Y-m-d H:i:s"); if( is_array($this->rcpt) ) { $data['to'] = implode(", ", $this->rcpt); }else { $data['to'] = $this->rcpt; } $data['content'] = $this->header['X-Content']; $data['mail'] = serialize($this); if( $GLOBALS['DB']->insert("mailout", $data) ) { return true; }else { return false; } } private function sendHeaders($stream) { $headerorder = array("Message-ID", "Date", "From", "To", "Cc", "Subject", "Content-Type", "Content-Transfer-Encoding", "Mime-Version"); $k = array_keys($this->header); for( $i=0; $iwriteHeader($stream, $headerorder[$i], $this->header[ $headerorder[$i] ]); } } for( $i=0; $iwriteHeader($stream, $k[$i], $this->header[ $k[$i] ]); } } $this->writeToStream($stream, "\r\n"); } private function writeHeader($stream, $key, $value) { $header = $key . ": " . str_replace("\r\n", "", $value); $header = wordwrap($header, 80, "\r\n\t"); $this->writeToStream($stream, $header . "\r\n"); } private function sendMessage($stream) { // Wrap lines, but keep in mind existing line breaks $message = ""; foreach( explode("\n", $this->message) as $line ) { $message .= wordwrap($line, 80, " \r\n") . "\r\n"; } // Add footer if( $this->addfooter && (substr($this->header['Content-Type'], 0, 10) == "text/plain" || substr($this->header['Content-Type'], 0, 15) == "multipart/mixed") ) { $message .= "\r\n".str_replace("\\n", "\n", MAIL_FOOTER)."\r\n"; } $this->writeToStream($stream, $message); } private function sendAttachment($stream) { ########## } private function preWriteToStream(&$s) { if( $s ) { if( substr($s, 0, 1) == '.' ) $s = '.' . $s; $s = str_replace("\n.","\n..",$s); } } private function initStream($from, $to) { $to.=","; $to_list=array(); $a=0; $b=strpos($to, ",", $a); while( !($b===false) ) { $to_list[]=substr($to, $a, $b-$a); $a=$b+1; $b=strpos($to, ",", $a); } $stream = @fsockopen($this->host, $this->port, $errorNumber, $errorString); if( !$stream ) { $this->dlv_msg = $errorString; $this->dlv_ret_nr = $errorNumber; return false; } $tmp = fgets($stream, 1024); if( $this->errorCheck($tmp, $stream) ) { return false; } if( $this->host=="localhost" ) $helodomain = "localhost"; else $helodomain = $this->domain; /* Lets introduce ourselves */ fwrite($stream, "EHLO ".$helodomain."\r\n"); $tmp = fgets($stream,1024); if( $this->errorCheck($tmp,$stream) ) { // fall back to HELO if EHLO is not supported if( $this->dlv_ret_nr == '500' ) { fwrite($stream, "HELO ".$helodomain."\r\n"); $tmp = fgets($stream,1024); if( $this->errorCheck($tmp,$stream) ) { return false; } }else { return false; } } /* Ok, who is sending the message? */ fwrite($stream, "MAIL FROM: <".$from.">\r\n"); $tmp = fgets($stream, 1024); if( $this->errorCheck($tmp, $stream) ) { return false; } /* send who the recipients are */ for( $i = 0, $cnt = count($to_list); $i < $cnt; $i++ ) { fwrite($stream, "RCPT TO: <".$to_list[$i].">\r\n"); $tmp = fgets($stream, 1024); if( $this->errorCheck($tmp, $stream) ) { return false; } } /* Lets start sending the actual message */ fwrite($stream, "DATA\r\n"); $tmp = fgets($stream, 1024); if( $this->errorCheck($tmp, $stream) ) { return false; } return $stream; } private function writeToStream($stream, $data) { $this->preWriteToStream($data); fwrite($stream, $data); } private function finalizeStream($stream) { fwrite($stream, "\r\n.\r\n"); /* end the DATA part */ stream_set_timeout($stream, 50); $tmp = fgets($stream, 1024); $this->errorCheck($tmp, $stream); if( $this->dlv_ret_nr != 250 ) { return false; } fwrite($stream, "QUIT\r\n"); /* log off */ fclose($stream); return true; } /* check if an SMTP reply is an error and set an error message) */ private function errorCheck($line, $smtpConnection) { $err_num = substr($line, 0, 3); $this->dlv_ret_nr = $err_num; $server_msg = substr($line, 4); while( substr($line, 0, 4) == ($err_num.'-') ) { $line = fgets($smtpConnection, 1024); $server_msg .= substr($line, 4); } if( ((int) substr($err_num, 0, 1)) < 4 ) { return false; } switch( $err_num ) { case '421': $message = _("Service not available, closing channel"); break; case '432': $message = _("A password transition is needed"); break; case '450': $message = _("Requested mail action not taken: mailbox unavailable"); break; case '451': $message = _("Requested action aborted: error in processing"); break; case '452': $message = _("Requested action not taken: insufficient system storage"); break; case '454': $message = _("Temporary authentication failure"); break; case '500': $message = _("Syntax error; command not recognized"); break; case '501': $message = _("Syntax error in parameters or arguments"); break; case '502': $message = _("Command not implemented"); break; case '503': $message = _("Bad sequence of commands"); break; case '504': $message = _("Command parameter not implemented"); break; case '530': $message = _("Authentication required"); break; case '534': $message = _("Authentication mechanism is too weak"); break; case '535': $message = _("Authentication failed"); break; case '538': $message = _("Encryption required for requested authentication mechanism"); break; case '550': $message = _("Requested action not taken: mailbox unavailable"); break; case '551': $message = _("User not local; please try forwarding"); break; case '552': $message = _("Requested mail action aborted: exceeding storage allocation"); break; case '553': $message = _("Requested action not taken: mailbox name not allowed"); break; case '554': $message = _("Transaction failed"); break; default: $message = _("Unknown response"); break; } $this->dlv_msg = $message; $this->dlv_server_msg = nl2br(htmlspecialchars($server_msg)); return true; } } ?>