openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

SmtpClient.class.php (8671B)


      1 <?php
      2 // OpenRat Content Management System
      3 // Copyright (C) 2002-2012 Jan Dankert, cms@jandankert.de
      4 //
      5 // This program is free software; you can redistribute it and/or
      6 // modify it under the terms of the GNU General Public License
      7 // as published by the Free Software Foundation; either version 2
      8 // of the License, or (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU General Public License
     16 // along with this program; if not, write to the Free Software
     17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 namespace util\mail\client;
     20 
     21 use cms\base\Configuration;
     22 use cms\base\Startup;
     23 use LogicException;
     24 
     25 /**
     26  * Erzeugen und Versender einer E-Mail gemaess RFC 822.<br>
     27  * <br>
     28  * Die E-Mail kann entweder �ber
     29  * - die interne PHP-Funktion "mail()" versendet werden oder
     30  * - direkt per SMTP-Protokoll an einen SMTP-Server.<br>
     31  * Welcher Weg gew�hlt wird, kann konfiguriert werden.<br>
     32  * <br>
     33  * Prinzipiell spricht nichts gegen die interne PHP-Funktion mail(), wenn diese
     34  * aber nicht zu Verf�gung steht oder PHP ungeeignet konfiguriert ist, so kann
     35  * SMTP direkt verwendet werden. Hierbei sollte wenn m�glich ein Relay-Host
     36  * eingesetzt werden. Die Mail kann zwar auch direkt an Mail-Exchanger (MX) des
     37  * Empf�ngers geschickt werden, falls dieser aber Greylisting einsetzt ist eine
     38  * Zustellung nicht m�glich.<br>
     39  * <br>
     40  *
     41  * @author Jan Dankert
     42  */
     43 class SmtpClient implements Client
     44 {
     45 	/**
     46 	 * Newline characters as defined in RFC 822.
     47 	 */
     48 	const NL = "\r\n";
     49 
     50 	/**
     51 	 * Mail absenden.
     52 	 * Die E-Mail wird versendet.
     53 	 */
     54 	public function send( $to, $subject, $body, $headers)
     55 	{
     56 		$mailConfig = Configuration::subset('mail');
     57 
     58 		// Mail versenden
     59 		// eigenen SMTP-Dialog verwenden.
     60 		$smtpConf = $mailConfig->subset('smtp');
     61 
     62 		$relayConfig = $smtpConf->subset('relay');
     63 
     64 		if ( $relayConfig->has('host')) {
     65 			// Eigenen Relay-Host verwenden.
     66 			$mxHost = $relayConfig->get('host');
     67 			$mxPort = $relayConfig->get('port',25);
     68 		} else {
     69 			// Mail direkt zustellen.
     70 			$mxHost = $this->getMxHost($to);
     71 
     72 			if (empty($mxHost))
     73 				throw new LogicException( TextMessage::create('No MX-Entry found for ${0}',[$to]));
     74 
     75 			if ($smtpConf->is('ssl',false))
     76 				$mxPort = 465;
     77 			else
     78 				$mxPort = 25;
     79 		}
     80 
     81 
     82 		// gets the server hostname (necessary for the HELO command)
     83 		$myHost = $smtpConf->get('hostname', gethostname() );
     84 
     85 		if ($smtpConf->is('ssl',false))
     86 			$proto = 'ssl';
     87 		else
     88 			$proto = 'tcp';
     89 
     90 		//connect to the host and port
     91 		$smtpSocket = fsockopen($proto . '://' . $mxHost, $mxPort, $errno, $errstr, $smtpConf->get('timeout',30 ) );
     92 
     93 		if (!is_resource($smtpSocket)) {
     94 			throw new LogicException('Connection failed to: ' . $proto . '://' . $mxHost . ':' . $mxPort . ' (' . $errstr . '/' . $errno . ')');
     95 		}
     96 
     97 		$smtpResponse = fgets($smtpSocket, 4096);
     98 
     99 		if (substr($smtpResponse, 0, 3) != '220') {
    100 			throw new LogicException('No 220: ' . trim($smtpResponse) );
    101 		}
    102 
    103 		if (!is_resource($smtpSocket)) {
    104 			throw new LogicException('Connection failed to: ' . $smtpConf['host'] . ':' . $smtpConf['port'] . ' (' . $smtpResponse . ')' );
    105 		}
    106 
    107 		//you have to say HELO again after TLS is started
    108 		$smtpResponse = $this->sendSmtpCommand($smtpSocket, 'HELO ' . $myHost);
    109 
    110 		if (substr($smtpResponse, 0, 3) != '250') {
    111 			$this->sendSmtpQuit($smtpSocket);
    112 			throw new LogicException('No 2xx after HELO as host "'.$myHost.'", server says: ' . $smtpResponse);
    113 		}
    114 
    115 		if ($smtpConf->is('tls')) {
    116 			$smtpResponse = $this->sendSmtpCommand($smtpSocket, 'STARTTLS');
    117 			if (substr($smtpResponse, 0, 3) == '220') {
    118 				// STARTTLS ist gelungen.
    119 				//you have to say HELO again after TLS is started
    120 				$smtpResponse = $this->sendSmtpCommand($smtpSocket, 'HELO ' . $myHost);
    121 
    122 				if (substr($smtpResponse, 0, 3) != '250') {
    123 					$this->sendSmtpQuit($smtpSocket);
    124 					throw new LogicException("No 2xx after HELO, server says: " . $smtpResponse );
    125 				}
    126 			} else {
    127 				// STARTTLS ging in die Hose. Einfach weitermachen.
    128 			}
    129 		}
    130 
    131 		// request for auth login
    132 		if ( $smtpConf->has('auth_username') && $relayConfig->has('host') ) {
    133 			$smtpResponse = $this->sendSmtpCommand($smtpSocket, "AUTH LOGIN");
    134 			if (substr($smtpResponse, 0, 3) != '334') {
    135 				$this->sendSmtpQuit($smtpSocket);
    136 				throw new LogicException("No 334 after AUTH_LOGIN, server says: " . $smtpResponse);
    137 			}
    138 
    139 			//send the username
    140 			$smtpResponse = $this->sendSmtpCommand($smtpSocket, base64_encode($smtpConf->get('auth_username')));
    141 			if (substr($smtpResponse, 0, 3) != '334') {
    142 				$this->sendSmtpQuit($smtpSocket);
    143 				throw new LogicException("No 3xx after setting username, server says: " . $smtpResponse);
    144 			}
    145 
    146 			//send the password
    147 			$smtpResponse = $this->sendSmtpCommand($smtpSocket, base64_encode($smtpConf->get('auth_password')));
    148 			if (substr($smtpResponse, 0, 3) != '235') {
    149 				$this->sendSmtpQuit($smtpSocket);
    150 				throw new LogicException("No 235 after sending password, server says: " . $smtpResponse );
    151 			}
    152 		}
    153 
    154 		//email from
    155 		$smtpResponse = $this->sendSmtpCommand($smtpSocket, 'MAIL FROM: <' . $mailConfig->get('from') . '>');
    156 		if (substr($smtpResponse, 0, 3) != '250') {
    157 			$this->sendSmtpQuit($smtpSocket);
    158 			throw new LogicException("No 2xx after MAIL_FROM, server says: " . $smtpResponse);
    159 		}
    160 
    161 		//email to
    162 		$smtpResponse = $this->sendSmtpCommand($smtpSocket, 'RCPT TO: <' . $to . '>');
    163 		if (substr($smtpResponse, 0, 3) != '250') {
    164 			$this->sendSmtpQuit($smtpSocket);
    165 			throw new LogicException("No 2xx after RCPT_TO, server says: " . $smtpResponse);
    166 		}
    167 
    168 		//the email
    169 		$smtpResponse = $this->sendSmtpCommand($smtpSocket, "DATA");
    170 		if (substr($smtpResponse, 0, 3) != '354') {
    171 			$this->sendSmtpQuit($smtpSocket);
    172 			throw new LogicException("No 354 after DATA, server says: " . $smtpResponse);
    173 		}
    174 
    175 		$headers[] = 'To: ' . $to;
    176 		$headers[] = 'Subject: ' . $subject;
    177 		$headers[] = 'Date: ' . date('r');
    178 		$headers[] = 'Message-Id: ' . '<' . getenv('REMOTE_ADDR') . '.' . time() . '.openrat@' . getenv('SERVER_NAME') . '.' . getenv('HOSTNAME') . '>';
    179 
    180 		//observe the . after the newline, it signals the end of message
    181 		$smtpResponse = $this->sendSmtpCommand($smtpSocket, implode(self::NL, $headers) . self::NL . self::NL . $body . self::NL . '.');
    182 		if (substr($smtpResponse, 0, 3) != '250') {
    183 			$this->sendSmtpQuit($smtpSocket);
    184 			throw new LogicException("No 2xx after putting DATA, server says: " . $smtpResponse);
    185 		}
    186 
    187 		// say goodbye
    188 		$this->sendSmtpQuit($smtpSocket);
    189 	}
    190 
    191 
    192 	/**
    193 	 * Sendet ein SMTP-Kommando zum SMTP-Server.
    194 	 *
    195 	 * @access private
    196 	 * @param Resource $socket TCP/IP-Socket zum SMTP-Server
    197 	 * @param string $cmd SMTP-Kommando
    198 	 * @return Server-Antwort
    199 	 */
    200 	private function sendSmtpCommand($socket, $cmd)
    201 	{
    202 		if (!is_resource($socket))
    203 			// Die Verbindung ist geschlossen. Dies kann bei dieser
    204 			// Implementierung eigentlich nur dann passieren, wenn
    205 			// der Server die Verbindung schlie�t.
    206 			// Dieser Client trennt die Verbindung nur nach einem "QUIT".
    207 			throw new LogicException("Connection lost");
    208 
    209 		fputs($socket, $cmd . self::NL);
    210 		$response = trim(fgets($socket, 4096));
    211 		return $response;
    212 	}
    213 
    214 
    215 	/**
    216 	 * Sendet ein QUIT zum SMTP-Server, wartet die Antwort ab und
    217 	 * schlie�t danach die Verbindung.
    218 	 *
    219 	 * @param Resource Socket
    220 	 */
    221 	private function sendSmtpQuit($socket)
    222 	{
    223 
    224 		if (!is_resource($socket))
    225 			return;
    226 		// Wenn die Verbindung nicht mehr da ist, brauchen wir
    227 		// auch kein QUIT mehr :)
    228 
    229 
    230 		fputs($socket, 'QUIT' . self::NL);
    231 		$response = trim(fgets($socket, 4096));
    232 
    233 		if (substr($response, 0, 3) != '221')
    234 			throw new LogicException("No 221 after QUIT, server says: " . $response);
    235 
    236 		fclose($socket);
    237 	}
    238 
    239 
    240 
    241 	/**
    242 	 * Ermittelt den MX-Eintrag zu einer E-Mail-Adresse.<br>
    243 	 * Es wird der Eintrag mit der h�chsten Priorit�t ermittelt.
    244 	 *
    245 	 * @param String E-Mail-Adresse des Empf�ngers.
    246 	 * @return MX-Eintrag
    247 	 */
    248 	private function getMxHost($to)
    249 	{
    250 		list($user, $host) = explode('@', $to . '@');
    251 
    252 		if (empty($host)) {
    253 			throw new LogicException( TextMessage::create('Illegal mail address ${0}: No hostname found',[$to]) );
    254 		}
    255 
    256 		list($host) = explode('>', $host);
    257 
    258 		$mxHostsName = array();
    259 		$mxHostsPrio = array();
    260 		getmxrr($host, $mxHostsName, $mxHostsPrio);
    261 
    262 		$mxList = array();
    263 		foreach ($mxHostsName as $id => $mxHostName) {
    264 			$mxList[$mxHostName] = $mxHostsPrio[$id];
    265 		}
    266 		asort($mxList);
    267 		return key($mxList);
    268 	}
    269 }