<?php
/*
OpenRat Content Management System
Copyright (C) Jan Dankert
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
or 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/**
* Erzeugen und Versender einer E-Mail gemaess RFC 822.<br>
* <br>
* Die E-Mail kann entweder �ber
* - die interne PHP-Funktion "mail()" versendet werden oder
* - direkt per SMTP-Protokoll an einen SMTP-Server.<br>
* Welcher Weg gew�hlt wird, kann konfiguriert werden.<br>
* <br>
* Prinzipiell spricht nichts gegen die interne PHP-Funktion mail(), wenn diese
* aber nicht zu Verf�gung steht oder PHP ungeeignet konfiguriert ist, so kann
* SMTP direkt verwendet werden. Hierbei sollte wenn m�glich ein Relay-Host
* eingesetzt werden. Die Mail kann zwar auch direkt an Mail-Exchanger (MX) des
* Empf�ngers geschickt werden, falls dieser aber Greylisting einsetzt ist eine
* Zustellung nicht m�glich.<br>
* <br>
*
* @author Jan Dankert
* @version $Id$
* @package serviceClasses
*/
class Mail
{
var $from = '';
var $to = '';
var $bcc = '';
var $cc = '';
var $subject = '';
var $text = '';
var $header = array();
var $nl = '';
/**
* Falls beim Versendern der E-Mail etwas schiefgeht, steht hier drin
* die technische Fehlermeldung.
*
* @var String Fehler
*/
var $error = array();
/**
* Set to true for debugging.
* If true, All SMTP-Commands are written to error log.
*
* @var unknown_type
*/
var $debug = true;
/**
* Konstruktor.
* Es werden folgende Parameter erwartet
* @param String $to Empf�nger
* @param String der Textschl�ssel
* @param String unbenutzt.
* @return Mail
*/
function Mail( $to,$text,$xy='' )
{
global $conf;
// Zeilenumbruch CR/LF gem. RFC 822.
$this->nl = chr(13).chr(10);
if ( !empty($conf['mail']['from']) )
$this->from = $this->header_encode($conf['mail']['from']);
// Priorit�t definieren (sofern konfiguriert)
if ( !empty($conf['mail']['priority']) )
$this->header[] = 'X-Priority: '.$conf['mail']['priority'];
$this->header[] = 'X-Mailer: '.$this->header_encode(OR_TITLE.' '.OR_VERSION);
$this->header[] = 'Content-Type: text/plain; charset='.lang( 'CHARSET' );
$this->subject = $this->header_encode(lang( 'mail_subject_'.$text ));
$this->to = $this->header_encode($to);
$this->text = $this->nl.wordwrap(str_replace(';',$this->nl,lang('mail_text_'.$text)),70,$this->nl).$this->nl;
// Signatur anhaengen (sofern konfiguriert)
if ( !empty($conf['mail']['signature']) )
{
$this->text .= $this->nl.'-- '.$this->nl;
$this->text .= str_replace(';',$this->nl,$conf['mail']['signature']);
$this->text .= $this->nl;
}
// Kopie-Empf�nger
if ( !empty($conf['mail']['cc']) )
$this->cc = $this->header_encode($conf['mail']['cc']);
// Blindkopie-Empf�nger
if ( !empty($conf['mail']['bcc']) )
$this->bcc = $this->header_encode($conf['mail']['bcc']);
}
/**
* Kodiert einen Text in das Format "Quoted-printable".<br>
* See RFC 2045.
* @param String $text Eingabe
* @return Text im quoted-printable-Format
*/
function quoted_printable_encode( $text )
{
$text = str_replace(' ','=20',$text);
for( $i=128; $i<=255; $i++ )
{
$text = str_replace( chr($i),'='.dechex($i),$text );
}
return $text;
}
/**
* Setzen einer Variablen in den Mail-Inhalt.
*/
function setVar( $varName,$varInhalt)
{
$this->text = str_replace( '{'.$varName.'}', $varInhalt, $this->text );
}
/**
* Mail absenden.
* Die E-Mail wird versendet.
*
* @return boolean Erfolg
*/
function send()
{
global $conf;
$to_domain = array_pop( explode('@',$this->to) );
// Prüfen gegen die Whitelist
$white = @$conf['mail']['whitelist'];
if ( !empty($white) )
{
if ( ! $this->containsDomain($to_domain,$white) )
{
// Wenn Domain nicht in Whitelist gefunden, dann Mail nicht verschicken.
$this->error[] = 'Mail-Domain is not whitelisted';
return false;
}
}
// Prüfen gegen die Blacklist
$black = @$conf['mail']['blacklist'];
if ( !empty($black) )
{
if ( $this->containsDomain($to_domain,$black) )
{
// Wenn Domain in Blacklist gefunden, dann Mail nicht verschicken.
$this->error[] = 'Mail-Domain is blacklisted';
return false;
}
}
// Header um Adressangaben erg�nzen.
if ( !empty($this->from ) )
$this->header[] = 'From: '.$this->from;
if ( !empty($this->cc ) )
$this->header[] = 'Cc: '.$this->cc;
if ( !empty($this->bcc ) )
$this->header[] = 'Bcc: '.$this->bcc;
// Mail versenden
if ( strtolower(@$conf['mail']['client']) == 'php' )
{
// PHP-interne Mailfunktion verwenden.
$result = @mail( $this->to, // Empf�nger
$this->subject, // Betreff
$this->text, // Inhalt
// Lt. RFC822 müssen Header mit CRLF enden.
// ABER: Der Parameter "additional headers" verlangt offenbar \n am Zeilenende.
implode("\n",$this->header) );
if ( !$result )
// Die E-Mail wurde nicht akzeptiert.
// Genauer geht es leider nicht, da mail() nur einen boolean-Wert
// zur�ck liefert.
$this->error[] = 'Mail was NOT accepted by mail()';
return $result;
}
else
{
// eigenen SMTP-Dialog verwenden.
$smtpConf = $conf['mail']['smtp'];
if ( !empty($smtpConf['host']))
{
// Eigenen Relay-Host verwenden.
$mxHost = $smtpConf['host'];
$mxPort = intval($smtpConf['port']);
}
else
{
// Mail direkt zustellen.
$mxHost = $this->getMxHost($this->to);
if ( empty($mxHost) )
{
$this->error[] = "No MX-Entry found. Mail could not be sent.";
return false;
}
if ($smtpConf['ssl'])
$mxPort = 465;
else
$mxPort = 25;
}
if ( !empty($smtpConf['localhost']))
{
$myHost = $smtpConf['localhost'];
}
else
{
$myHost = gethostbyaddr(getenv('REMOTE_ADDR'));
}
if ( $smtpConf['ssl'])
$proto = 'ssl';
else
$proto = 'tcp';
//connect to the host and port
$smtpSocket = fsockopen($proto.'://'.$mxHost,$mxPort, $errno, $errstr, intval($smtpConf['timeout']));
if ( !is_resource($smtpSocket) )
{
$this->error[] = 'Connection failed to: '.$proto.'://'.$mxHost.':'.$mxPort.' ('.$errstr.'/'.$errno.')';
return false;
}
$smtpResponse = fgets($smtpSocket, 4096);
if ( $this->debug)
$this->error[] = trim($smtpResponse);
if ( substr($smtpResponse,0,3) != '220' )
{
$this->error[] = 'No 220: '.trim($smtpResponse);
return false;
}
if ( !is_resource($smtpSocket) )
{
$this->error[] = 'Connection failed to: '.$smtpConf['host'].':'.$smtpConf['port'].' ('.$smtpResponse.')';
return false;
}
//you have to say HELO again after TLS is started
$smtpResponse = $this->sendSmtpCommand($smtpSocket,'HELO '.$myHost);
if ( substr($smtpResponse,0,3) != '250' )
{
$this->error[] = "No 2xx after HELO, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
if ( $smtpConf['tls'] )
{
$smtpResponse = $this->sendSmtpCommand($smtpSocket,'STARTTLS');
if ( substr($smtpResponse,0,3) == '220' )
{
// STARTTLS ist gelungen.
//you have to say HELO again after TLS is started
$smtpResponse = $this->sendSmtpCommand($smtpSocket,'HELO '.$myHost);
if ( substr($smtpResponse,0,3) != '250' )
{
$this->error[] = "No 2xx after HELO, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
}
else
{
// STARTTLS ging in die Hose. Einfach weitermachen.
}
}
// request for auth login
if ( isset($smtpConf['auth_username']) && !empty($smtpConf['host']) && !empty($smtpConf['auth_username']))
{
$smtpResponse = $this->sendSmtpCommand($smtpSocket,"AUTH LOGIN");
if ( substr($smtpResponse,0,3) != '334' )
{
$this->error[] = "No 334 after AUTH_LOGIN, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
if ( $this->debug)
$this->error[] = 'Login for '.$smtpConf['auth_username'];
//send the username
$smtpResponse = $this->sendSmtpCommand($smtpSocket, base64_encode($smtpConf['auth_username']));
if ( substr($smtpResponse,0,3) != '334' )
{
$this->error[] = "No 3xx after setting username, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
//send the password
$smtpResponse = $this->sendSmtpCommand($smtpSocket, base64_encode($smtpConf['auth_password']));
if ( substr($smtpResponse,0,3) != '235' )
{
$this->error[] = "No 235 after sending password, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
}
//email from
$smtpResponse = $this->sendSmtpCommand($smtpSocket, 'MAIL FROM: <'.$conf['mail']['from'].'>');
if ( substr($smtpResponse,0,3) != '250' )
{
$this->error[] = "No 2xx after MAIL_FROM, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
//email to
$smtpResponse = $this->sendSmtpCommand($smtpSocket, 'RCPT TO: <'.$this->to.'>');
if ( substr($smtpResponse,0,3) != '250' )
{
$this->error[] = "No 2xx after RCPT_TO, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
//the email
$smtpResponse = $this->sendSmtpCommand($smtpSocket, "DATA");
if ( substr($smtpResponse,0,3) != '354' )
{
$this->error[] = "No 354 after DATA, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
$this->header[] = 'To: '.$this->to;
$this->header[] = 'Subject: '.$this->subject;
$this->header[] = 'Date: '.date('r');
$this->header[] = 'Message-Id: '.'<'.getenv('REMOTE_ADDR').'.'.time().'.openrat@'.getenv('SERVER_NAME').'.'.getenv('HOSTNAME').'>';
//observe the . after the newline, it signals the end of message
$smtpResponse = $this->sendSmtpCommand($smtpSocket, implode($this->nl,$this->header).$this->nl.$this->nl.$this->text.$this->nl.'.');
if ( substr($smtpResponse,0,3) != '250' )
{
$this->error[] = "No 2xx after putting DATA, server says: ".$smtpResponse;
$this->sendSmtpQuit($smtpSocket);
return false;
}
// say goodbye
$this->sendSmtpQuit($smtpSocket);
return true;
}
}
/**
* Sendet ein SMTP-Kommando zum SMTP-Server.
*
* @access private
* @param Resource $socket TCP/IP-Socket zum SMTP-Server
* @param unknown_type $cmd SMTP-Kommando
* @return Server-Antwort
*/
function sendSmtpCommand( $socket,$cmd )
{
if ( $this->debug )
$this->error[] = 'CLIENT: >>> '.trim($cmd);
if ( !is_resource($socket) )
{
// Die Verbindung ist geschlossen. Dies kann bei dieser
// Implementierung eigentlich nur dann passieren, wenn
// der Server die Verbindung schlie�t.
// Dieser Client trennt die Verbindung nur nach einem "QUIT".
$this->error[] = "Connection lost";
return;
}
fputs($socket,$cmd.$this->nl);
$response = trim(fgets($socket, 4096));
if ( $this->debug )
$this->error[] = 'SERVER: <<< '.$response;
return $response;
}
/**
* Sendet ein QUIT zum SMTP-Server, wartet die Antwort ab und
* schlie�t danach die Verbindung.
*
* @param Resource Socket
*/
function sendSmtpQuit( $socket )
{
if ( $this->debug )
$this->error[] = "CLIENT: >>> QUIT";
if ( !is_resource($socket) )
return;
// Wenn die Verbindung nicht mehr da ist, brauchen wir
// auch kein QUIT mehr :)
fputs($socket,'QUIT'.$this->nl);
$response = trim(fgets($socket, 4096));
if ( $this->debug )
$this->error[] = 'SERVER: <<< '.$response;
if ( substr($response,0,3) != '221' )
$this->error[] = 'QUIT FAILED: '.$response;
fclose($socket);
}
/**
* Umwandlung von 8-bit-Zeichen in MIME-Header gemaess RFC 2047.<br>
* Header d�rfen nur 7-bit-Zeichen enthalten. 8-bit-Zeichen m�ssen kodiert werden.
*
* @param String $text
* @return String
*/
function header_encode( $text )
{
global $conf;
if ( empty($conf['mail']['header_encoding']) )
return $text;
$woerter = explode(' ',$text);
$neu = array();
foreach( $woerter as $wort )
{
$type = strtolower(substr($conf['mail']['header_encoding'],0,1));
$neu_wort = '';
if ( $type == 'b' )
$neu_wort = base64_encode($wort);
elseif ( $type == 'q' )
$neu_wort = $this->quoted_printable_encode($wort);
else
Logger::error( 'Mail-Configuratin broken: UNKNOWN Header-Encoding type: '.$type);
if ( strlen($wort)==strlen($neu_wort) )
$neu[] = $wort;
else
$neu[] = '=?'.lang('CHARSET').'?'.$type.'?'.$neu_wort.'?=';
}
return implode(' ',$neu);
}
/**
* Ermittelt den MX-Eintrag zu einer E-Mail-Adresse.<br>
* Es wird der Eintrag mit der h�chsten Priorit�t ermittelt.
*
* @param String E-Mail-Adresse des Empf�ngers.
* @return MX-Eintrag
*/
function getMxHost( $to )
{
list($user,$host) = explode('@',$to.'@');
if ( empty($host) )
{
$this->error[] = 'Illegal mail address - No hostname found.';
return "";
}
list($host) = explode('>',$host);
$mxHostsName = array();
$mxHostsPrio = array();
getmxrr($host,$mxHostsName,$mxHostsPrio);
$mxList = array();
foreach( $mxHostsName as $id=>$mxHostName )
{
$mxList[$mxHostName] = $mxHostsPrio[$id];
}
asort($mxList);
return key($mxList);
}
/**
* Stellt fest, ob die E-Mail-Adresse eine gueltige Syntax besitzt.
*
* Es wird nur die Syntax geprüft. Ob die Adresse wirklich existiert, steht dadurch noch lange
* nicht fest. Dazu müsste man die MX-Records auflösen und einen Zustellversuch unternehmen.
*
* @param $email_address Adresse
* @return true, falls Adresse OK, sonst false
*/
function checkAddress( $email_address )
{
// Source: de.php.net/ereg
return ereg("^[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[@]{1}[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[.]{1}[A-Za-z]{2,5}$", $email_address);
}
/**
* Prüft, ob eine Domain in einer List von Domains enthalten ist.
*
* @param $checkDomain zu prüfende Domain
* @param $domain_list Liste von Domains als kommaseparierte Liste
* @return true, falls vorhanden, sonst false
*/
function containsDomain($checkDomain, $domain_list)
{
$domains = explode(',',$domain_list);
foreach( $domains as $domain )
{
$domain = trim($domain);
if (empty($domain))
continue;
if ($domain == substr($checkDomain,-strlen($domain)))
{
return true;
}
}
return false;
}
}
?>