File modules/util/mail/client/SmtpClient.class.php
Last commit: Wed Oct 27 02:27:59 2021 +0200 Jan Dankert Refactoring: Splitted the mail client into a.) sendmail and b.) smtp.
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 }
Downloadmodules/util/mail/client/SmtpClient.class.php
History Wed, 27 Oct 2021 02:27:59 +0200 Jan Dankert Refactoring: Splitted the mail client into a.) sendmail and b.) smtp.