signaturepdf/vendor/fatfree/smtp.php

364 lines
9.3 KiB
PHP

<?php
/*
Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
This 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 3 of the License, or later.
Fat-Free Framework 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 Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
*/
//! SMTP plug-in
class SMTP extends Magic {
//@{ Locale-specific error/exception messages
const
E_Header='%s: header is required',
E_Blank='Message must not be blank',
E_Attach='Attachment %s not found',
E_DIALOG='SMTP dialog error: %s';
//@}
protected
//! Message properties
$headers,
//! E-mail attachments
$attachments,
//! SMTP host
$host,
//! SMTP port
$port,
//! TLS/SSL
$scheme,
//! User ID
$user,
//! Password
$pw,
//! TLS/SSL stream context
$context,
//! TCP/IP socket
$socket,
//! Server-client conversation
$log;
/**
* Fix header
* @return string
* @param $key string
**/
protected function fixheader($key) {
return str_replace(' ','-',
ucwords(preg_replace('/[_\-]/',' ',strtolower($key))));
}
/**
* Return TRUE if header exists
* @return bool
* @param $key
**/
function exists($key) {
$key=$this->fixheader($key);
return isset($this->headers[$key]);
}
/**
* Bind value to e-mail header
* @return string
* @param $key string
* @param $val string
**/
function set($key,$val) {
$key=$this->fixheader($key);
return $this->headers[$key]=$val;
}
/**
* Return value of e-mail header
* @return string|NULL
* @param $key string
**/
function &get($key) {
$key=$this->fixheader($key);
if (isset($this->headers[$key]))
$val=&$this->headers[$key];
else
$val=NULL;
return $val;
}
/**
* Remove header
* @return NULL
* @param $key string
**/
function clear($key) {
$key=$this->fixheader($key);
unset($this->headers[$key]);
}
/**
* Return client-server conversation history
* @return string
**/
function log() {
return str_replace("\n",PHP_EOL,$this->log);
}
/**
* Send SMTP command and record server response
* @return string
* @param $cmd string
* @param $log bool|string
* @param $mock bool
**/
protected function dialog($cmd=NULL,$log=TRUE,$mock=FALSE) {
$reply='';
if ($mock) {
$host=str_replace('ssl://','',$this->host);
switch ($cmd) {
case NULL:
$reply='220 '.$host.' ESMTP ready'."\n";
break;
case 'DATA':
$reply='354 Go ahead'."\n";
break;
case 'QUIT':
$reply='221 '.$host.' closing connection'."\n";
break;
default:
$reply='250 OK'."\n";
break;
}
}
else {
$socket=&$this->socket;
if ($cmd)
fputs($socket,$cmd."\r\n");
while (!feof($socket) && ($info=stream_get_meta_data($socket)) &&
!$info['timed_out'] && $str=fgets($socket,4096)) {
$reply.=$str;
if (preg_match('/(?:^|\n)\d{3} .+?\r\n/s',$reply))
break;
}
}
if ($log) {
if ($cmd)
$this->log.=$cmd."\n";
$this->log.=str_replace("\r",'',$reply);
}
if (preg_match('/^(4|5)\d{2}\s.*$/', $reply))
user_error(sprintf(self::E_DIALOG,$reply),E_USER_ERROR);
return $reply;
}
/**
* Add e-mail attachment
* @return NULL
* @param $file string
* @param $alias string
* @param $cid string
**/
function attach($file,$alias=NULL,$cid=NULL) {
if (!is_file($file))
user_error(sprintf(self::E_Attach,$file),E_USER_ERROR);
if ($alias)
$file=[$alias,$file];
$this->attachments[]=['filename'=>$file,'cid'=>$cid];
}
/**
* Transmit message
* @return bool
* @param $message string
* @param $log bool|string
* @param $mock bool
**/
function send($message,$log=TRUE,$mock=FALSE) {
if ($this->scheme=='ssl' && !extension_loaded('openssl'))
return FALSE;
// Message should not be blank
if (!$message)
user_error(self::E_Blank,E_USER_ERROR);
$fw=Base::instance();
// Retrieve headers
$headers=$this->headers;
// Connect to the server
if (!$mock) {
$socket=&$this->socket;
$socket=@stream_socket_client($this->host.':'.$this->port,
$errno,$errstr,ini_get('default_socket_timeout'),
STREAM_CLIENT_CONNECT,$this->context);
if (!$socket) {
$fw->error(500,$errstr);
return FALSE;
}
stream_set_blocking($socket,TRUE);
}
// Get server's initial response
$this->dialog(NULL,$log,$mock);
// Announce presence
$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock);
if (strtolower($this->scheme)=='tls') {
$this->dialog('STARTTLS',$log,$mock);
if (!$mock) {
$method=STREAM_CRYPTO_METHOD_TLS_CLIENT;
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
$method|=STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
$method|=STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
}
stream_socket_enable_crypto($socket,TRUE,$method);
}
$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock);
}
$message=wordwrap($message,998);
if (preg_match('/8BITMIME/',$reply))
$headers['Content-Transfer-Encoding']='8bit';
else {
$headers['Content-Transfer-Encoding']='quoted-printable';
$message=preg_replace('/^\.(.+)/m',
'..$1',quoted_printable_encode($message));
}
if ($this->user && $this->pw && preg_match('/AUTH/',$reply)) {
// Authenticate
$this->dialog('AUTH LOGIN',$log,$mock);
$this->dialog(base64_encode($this->user),$log,$mock);
$reply=$this->dialog(base64_encode($this->pw),$log,$mock);
if (!preg_match('/^235\s.*/',$reply)) {
$this->dialog('QUIT',$log,$mock);
if (!$mock && $socket)
fclose($socket);
return FALSE;
}
}
if (empty($headers['Message-Id']))
$headers['Message-Id']='<'.uniqid('',TRUE).'@'.$this->host.'>';
if (empty($headers['Date']))
$headers['Date']=date('r');
// Required headers
$reqd=['From','To','Subject'];
foreach ($reqd as $id)
if (empty($headers[$id]))
user_error(sprintf(self::E_Header,$id),E_USER_ERROR);
$eol="\r\n";
// Stringify headers
foreach ($headers as $key=>&$val) {
if (in_array($key,['From','To','Cc','Bcc'])) {
$email='';
preg_match_all('/(?:".+?" |=\?.+?\?= )?(?:<.+?>|[^ ,]+)/',
$val,$matches,PREG_SET_ORDER);
foreach ($matches as $raw)
$email.=($email?', ':'').
(preg_match('/<.+?>/',$raw[0])?
$raw[0]:
('<'.$raw[0].'>'));
$val=$email;
}
unset($val);
}
$from=isset($headers['Sender'])?$headers['Sender']:strstr($headers['From'],'<');
unset($headers['Sender']);
// Start message dialog
$this->dialog('MAIL FROM: '.$from,$log,$mock);
foreach ($fw->split($headers['To'].
(isset($headers['Cc'])?(';'.$headers['Cc']):'').
(isset($headers['Bcc'])?(';'.$headers['Bcc']):'')) as $dst) {
$this->dialog('RCPT TO: '.strstr($dst,'<'),$log,$mock);
}
$this->dialog('DATA',$log,$mock);
if ($this->attachments) {
// Replace Content-Type
$type=$headers['Content-Type'];
unset($headers['Content-Type']);
$enc=$headers['Content-Transfer-Encoding'];
unset($headers['Content-Transfer-Encoding']);
$hash=uniqid('',TRUE);
// Send mail headers
$out='Content-Type: multipart/mixed; boundary="'.$hash.'"'.$eol;
foreach ($headers as $key=>$val)
if ($key!='Bcc')
$out.=$key.': '.$val.$eol;
$out.=$eol;
$out.='This is a multi-part message in MIME format'.$eol;
$out.=$eol;
$out.='--'.$hash.$eol;
$out.='Content-Type: '.$type.$eol;
$out.='Content-Transfer-Encoding: '.$enc.$eol;
$out.=$eol;
$out.=$message.$eol;
foreach ($this->attachments as $attachment) {
if (is_array($attachment['filename']))
list($alias,$file)=$attachment['filename'];
else
$alias=basename($file=$attachment['filename']);
$out.='--'.$hash.$eol;
$out.='Content-Type: application/octet-stream'.$eol;
$out.='Content-Transfer-Encoding: base64'.$eol;
if ($attachment['cid'])
$out.='Content-Id: '.$attachment['cid'].$eol;
$out.='Content-Disposition: attachment; '.
'filename="'.$alias.'"'.$eol;
$out.=$eol;
$out.=chunk_split(base64_encode(
file_get_contents($file))).$eol;
}
$out.=$eol;
$out.='--'.$hash.'--'.$eol;
$out.='.';
$this->dialog($out,preg_match('/verbose/i',$log),$mock);
}
else {
// Send mail headers
$out='';
foreach ($headers as $key=>$val)
if ($key!='Bcc')
$out.=$key.': '.$val.$eol;
$out.=$eol;
$out.=$message.$eol;
$out.='.';
// Send message
$this->dialog($out,preg_match('/verbose/i',$log),$mock);
}
$this->dialog('QUIT',$log,$mock);
if (!$mock && $socket)
fclose($socket);
return TRUE;
}
/**
* Instantiate class
* @param $host string
* @param $port int
* @param $scheme string
* @param $user string
* @param $pw string
* @param $ctx resource
**/
function __construct(
$host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL,$ctx=NULL) {
$this->headers=[
'MIME-Version'=>'1.0',
'Content-Type'=>'text/plain; '.
'charset='.Base::instance()->ENCODING
];
$this->host=strtolower((($this->scheme=strtolower($scheme?:''))=='ssl'?
'ssl':'tcp').'://'.$host);
$this->port=$port;
$this->user=$user;
$this->pw=$pw;
$this->context=stream_context_create($ctx);
}
}