Files
DomFramework/smtp.php
2020-09-07 14:08:26 +00:00

277 lines
9.0 KiB
PHP

<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
*/
/** Allow to send mails by smtp
*/
class smtp
{
/** Debug mode
*/
public $debug = 0;
/** The debug file used to store all the communication between the client and
* the server. The file contains the passwords if used !
*/
public $debugFile = "/tmp/debugSMTP";
/** The authentication user allow to send SMTP mails
*/
public $user = null;
/** The authentication password allow to send SMTP mails
*/
public $password = null;
/** The SMTP server name or IP
*/
public $server = "127.0.0.1";
/** The SMTP port to use (if false, use 25 if no SSL or 465 if SSL,
* if user defined, use the user value)
*/
public $port = false;
/** The SMTPS support by tunnelling the session in SSL transport
*/
public $ssl = false;
/** Check the certification chain in SSL mode
*/
public $sslCheck = true;
/** The Timeout between the answer of the SMTP server. If the server don't
* answer in this time, an exception is raised
*/
public $timeout = 10;
/** Activate STARTTLS if needed. Allowed values : none, may, encrypt
*/
public $starttls = "may";
/** Check the certificate in STARTTLS
*/
public $starttlsCheck = false;
/** The authentication methods in an array. Allowed : plain, login
*/
public $authmethods = array ("plain", "login");
/** The socket of the connection
*/
private $smtpStream = null;
/** Connect to the SMTP server */
public function connect ()
{
$context = array ();
if ($this->ssl)
{
$this->server = "tls://$this->server";
$context["ssl"]["verify_peer_name"] = $this->sslCheck;
$context["ssl"]["verify_peer"] = $this->sslCheck;
if ($this->port === false)
$this->port = 465;
}
else
{
if ($this->port === false)
$this->port = 25;
}
$mainContext = stream_context_create ($context);
$this->debug ("####SMTP Connection to $this->server:$this->port (".
date ("Y/m/d H:i:s").")\n");
ini_set('track_errors', 1);
$this->smtpStream = @stream_socket_client ("$this->server:$this->port",
$errno, $errstr,
$this->timeout, STREAM_CLIENT_CONNECT,
$mainContext);
ini_set('track_errors', 0);
if ($this->smtpStream === false)
{
if ($errstr === "" && $php_errormsg !== "")
$errstr = $php_errormsg;
throw new \Exception (sprintf (dgettext ("domframework",
"Can't connect to SMTP server : %s"), $errstr), 500);
}
stream_set_timeout ($this->smtpStream, $this->timeout);
// Wait for banner
$banner = $this->getLine ("SMTP Banner");
// Send EHLO
$features = $this->putLine ("EHLO ".gethostname ()."\r\n");
$features = explode ("\r\n", $features);
if (in_array ("250-STARTTLS", $features))
{
// Server supports STARTTLS
if ($this->starttls === "may" || $this->starttls === "encrypt")
{
$this->putLine ("STARTTLS\r\n");
$context["ssl"]["verify_peer_name"] = $this->starttlsCheck;
$context["ssl"]["verify_peer"] = $this->starttlsCheck;
stream_context_set_option ($this->smtpStream, $context);
// The track_errors permit to create the $php_errormsg in case of
// warning
ini_set('track_errors', 1);
if (@stream_socket_enable_crypto ($this->smtpStream, true,
STREAM_CRYPTO_METHOD_TLS_CLIENT) ===
false)
throw new \Exception (sprintf (dgettext ("domframework",
"Can't activate STARTTLS %s"), strstr ($php_errormsg, ": ")), 500);
ini_set('track_errors', 0);
$this->debug ("STARTTLS ACTIVATED\n");
}
}
elseif ($this->starttls === "encrypt")
throw new \Exception (dgettext ("domframework",
"Server doesn't supports STARTTLS"), 500);
if ($this->user !== null && $this->password !== null)
{
$auths = preg_grep ("#^250-AUTH #", $features);
$auths = reset ($auths);
if (strpos ($auths, "PLAIN") && in_array ("plain", $this->authmethods))
{
// Send User and password AUTH PLAIN
$this->putLine ("AUTH PLAIN ".
base64_encode ($this->user.chr(0).$this->user.chr(0).
$this->password)."\r\n");
}
elseif (strpos ($auths, "LOGIN") &&
in_array ("login", $this->authmethods))
{
// Send User and password AUTH LOGIN
$this->putLine ("AUTH LOGIN ".base64_encode ($this->user)."\r\n");
$this->putLine (base64_encode ($this->password)."\r\n");
}
else
throw new \Exception (
dgettext ("domframework",
"No authentication method available for the server"), 500);
}
}
/** Clean the provided mail and add the <> signs arround it
* @param string $mail The mail to clean
* @return string The cleaned mail
*/
private function cleanMail ($mail)
{
if (! is_string ($mail))
throw new \Exception ("SMTP: Invalid mail provided: not a string", 500);
$mail = trim ($mail);
$pos = strpos ($mail, "<");
if ($pos !== false)
$mail = substr ($mail, $pos);
else
$mail = "<$mail";
if (substr ($mail, -1) !== ">")
$mail .= ">";
return $mail;
}
/** Send the mail to the users
* @param string $from The email address used for the from enveloppe
* @param string|array $to the recipient of the email. Not displayed in the
* email content
* @param string $completeMail The content of the email
* @return the message from the server (where is stored the message queue
* identifier
*/
public function send ($from, $to, $completeMail)
{
if ($this->smtpStream === null)
throw new \Exception (
dgettext ("domframework",
"Can't send email : not connected to SMTP server"), 500);
$from = $this->cleanMail ($from);
if (is_string ($to))
$to = explode (",", $to);
if (! is_array ($to) || count ($to) === 0)
throw new \Exception ("Can't send mail: no valid recipient provided",
500);
$this->putLine ("MAIL FROM: $from\r\n");
foreach ($to as $t)
{
$t = $this->cleanMail ($t);
$this->putLine ("RCPT TO: $t\r\n");
}
$this->putLine ("DATA\r\n");
if (substr ($completeMail, -2) !== "\r\n")
$completeMail .= "\r\n";
// Dot on first column : must be doubled to not be detected as the end
// of SMTP transaction. The SMTP server will remove it before distribute
// http://tools.ietf.org/html/rfc5321#section-4.5.2
$completeMail = preg_replace ("#^\.#m", "..", $completeMail);
return $this->putLine ("$completeMail.\r\n");
}
/** Disconnect from the SMTP server
*/
public function disconnect ()
{
if ($this->smtpStream === null)
throw new \Exception (
dgettext ("domframework",
"Can't send email : not connected to SMTP server"), 500);
$this->putLine ("QUIT\r\n");
fclose ($this->smtpStream);
}
/** Reset the session to start a new mail in the same SMTP connection
*/
public function reset ()
{
if ($this->smtpStream === null)
throw new \Exception (
dgettext ("domframework",
"Can't send email : not connected to SMTP server"), 500);
$this->putLine ("RSET\r\n");
}
/** Send something to the SMTP server. Wait the acknoledgement line
* @param string $data The line to send to server
*/
private function putLine ($data)
{
$this->debug ("> $data");
fwrite ($this->smtpStream, $data);
return $this->getLine ($data);
}
/** Wait something from the server
* @param string $message The answer to wait from server
*/
private function getLine ($message = "")
{
$this->debug ("Waiting for ".rtrim ($message)." answer\n", 2);
$content = "";
while (1)
{
$line = stream_get_line ($this->smtpStream, 1024, "\r\n");
if ($line === false)
break;
$meta = stream_get_meta_data ($this->smtpStream);
if ($meta["timed_out"] !== FALSE)
{
$this->debug ("Timeout when waiting $message\n");
throw new \Exception ("Timeout when waiting $message", 500);
}
$content .= $line."\r\n";
if (substr ($line, 3, 1) === " ")
break;
}
$this->debug ("< $content");
if (substr ($line, 0, 1) !== "2" && substr ($line, 0, 1) !== "3")
{
$this->debug ("Can't send mail : server answer : $line\n");
$this->putLine ("QUIT\r\n");
fclose ($this->smtpStream);
throw new \Exception ("Can't send mail : server answer : $line", 500);
}
return $content;
}
/** Save the connection debug in file
* @param string $message The message to save in debug
* @param integer|null $priority The priority to use
*/
private function debug ($message, $priority = 1)
{
if ($this->debug == false)
return;
if ($priority > $this->debug)
return;
file_put_contents ($this->debugFile, $message, FILE_APPEND);
}
}