https://blog.csdn.net/rty426/article/details/88899069
通过socks5代理使用smtp发邮件
起因
因为使用smtp发邮件,点击查看邮件原文能看到发送的服务器ip,而一般使用smtp的服务器为后台服务器,为了防止ip暴露,所以需要通过代理来进行发邮件的动作。
为了完成这个需求,首先百度、谷歌了一遍没找到有能’参考‘的例子,因此只能自己想办法去实现。
思路1
之前使用过php的curl扩展中添加代理,因此很容易就想到用curl,而curl使用代理的相关例子也是比较容易找到,在curl中设置相关参数就可以。
curl_setopt($curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
curl_setopt($curl, CURLOPT_PROXY, "代理服务器ip");
curl_setopt($curl, CURLOPT_PROXYPORT, "代理端口");
curl_setopt($curl, CURLOPT_PROXYUSERPWD, "账号:密码");
而使用curl去发送smtp邮件的例子我也找到,参考这个帖子的例子php中通过curl smtp发送邮件(虽然我不知道他的代码自己是不是能实现的,反正在我的环境PHP Version 7.1.26;cURL Information 7.61.1是报错的,提示curl的url不合法)。然后花了不少时间去查出错的原因(因为在phpinfo中看到cURL支持的协议有smtp、telnet,我觉得还能抢救一下),但是相关的用法没找到文档或例子。百度能搜到的只是刚刚参考的例子,谷歌搜到的使用curl发smtp邮件的完全没有。唯一相关的帖子回答大概意思是curl虽然支持简单的SMTP连接,但是不适合进行这种持续发送应答的tcp连接,最好使用sockets去完成,于是我使用了思路二的方式。
思路2
没办法使用curl(目前的能力不足)完成就只能使用sockets去连接了。没有办法偷懒,只能先去看一下socks5的协议和smtp的协议。这里难点其实是如何让socks5服务器连接上smtp服务器,连上以后又要如何让socks5服务器发消息给smtp服务器。而使用sockets去完成smtp发邮件的一整套流程,有比较多可以参考的资料。
smtp协议
对smtp协议没什么概念的话,建议可以参考一下一些使用cmd的telnet发送邮件的例子(随便贴一个例子),然后尝试使用telnet去发一封邮件。以qq的smtp服务器为例:(为回复消息)
telnet smtp.qq.com 587(220 smtp.qq.com Esmtp QQ Mail Server)
EHLO smtp.qq.com(250-smtp.qq.com 250-PIPELINING 250-SIZE 73400320 250-STARTTLS 250-AUTH LOGIN PLAIN 250-AUTH=LOGIN 250-MAILCOMPRESS 250 8BITMIME)
STARTTLS auth login(220 Ready to start TLS)
AUTH LOGIN(334 VXNlcm5hbWU6)
{base64加密后的qq邮箱账号}(334 VXNlcm5hbWU6)
{base64加密后的qq邮箱密码}(235 Authentication successful)
mail from: <发件人邮箱地址>(250 OK)
rcpt to:<接收人邮箱地址>(250 OK)
data(354 END DATA WITH <CR><LF>.<CR><LF>)(意思是空行加一个"." 代表结束)
输入一些邮件的补充信息标题、日期、回复地址、版本 等等(其中标题如果是中文需要拼接一下成Subject:=?UTF-8?B?{base64加密标题}?= )
正文
/*空一行*/
.(250 OK queued as)
到这里就已经完成发送邮件了,可以看到自己的邮箱已经收到一封没有附件的邮件。我的代码中就是按照这种形式去完成的发送邮件。
socks5协议
接下来就是socks5协议,这里我找到一篇比较详细的帖子,对socks5服务器的一些应答有一定的解释说明socks5代理服务器协议说明。按照我需要的,我只需要完成几步:
连上socks5服务器
完成socks服务器认证
让socks服务器连上smtp服务器
而我需要做的是:
向代理服务器对应端口发送0x05 0x01 0x02 且服务器回复0x05 0x02 (5 1 2 说明是认证密码模式 还有不需要认证的模式 这里我就没有管这个 在上面的帖子有对不同的模式进行说明)
向代理服务器发送0x01 代理服务器账号长度 代理服务器账号 代理服务器密码长度 代理服务器密码 且服务器回复0x01 0x00(发送0x01 固定,然后指定之后的多少个字节为账号 然后是账号 然后指定之后多少个字节为密码 然后是密码 服务器回复1 0说明认证成功)
向代理服务器发送0x05 0x01 0x00 0x03 smtp服务器域名长度 smtp服务器域名 smtp服务器端口 且服务器回复0x05 0x00 0x00 0x01 再加上6位数字(发送5 1 0 3 说明让代理服务器 访问后面指定的域名和端口 如果是 ip 则发 5 1 0 1 回复的 前4位固定 后面6位表示代理服务器使用了哪个端口去进行连接)
测试时因为这个不能像smtp一样使用telnet去测试,所以这里我直接使用代码来测试
pfsockopen($this->proxyHost,$this->proxyPort,$errno,$errbuf, 60);//创建一个socket句柄
fwrite($this->sock, pack("C3", 0x05, 0x01, 0x02));//发送5 1 2
$res=fread($this->sock,512);//代理服务器回复的内容 这里直接打印出来是乱码,通过打印unpack('C'.strlen($res),$res) 可以查看返回值
到这里其实就已经完成了代理服务器的部分(就是这么简单),后面就是按smtp的协议直接发送,接收。
testSMTP.php例子代码
<?php
use Mail\ProxySMTP;
require('ProxySMTP.php');
$SMTP=new ProxySMTP;
$SMTP->proxyHost="代理服务器ip";
$SMTP->proxyPort="代理服务器端口";
$SMTP->proxyUsername="代理服务器用户名";
$SMTP->proxyPassword= "代理服务器密码";
$SMTP->smtpHost='smtp.qq.com';//smtp服务器域名
$SMTP->smtpPort='587';//smtp服务器端口
$SMTP->smtpUsername='2592515244@qq.com';//填邮箱
$SMTP->smtpPassword='smtp服务器的账号对应的密码';//填设置里面的授权码 不是qq密码
$SMTP->title='测试标题';
$SMTP->content='测试内容';
$SMTP->from='2592515244@qq.com';//发信人 和账号一样
$SMTP->to='2592515244@qq.com';//接收人
$SMTP->attachment='TIM图片20190322105750.gif';//加附件路径
$return=$SMTP->send();
echo $return['msg'];
?>
ProxySMTP.php类的代码
<?php
namespace Mail;
class ProxySMTP{
//代理服务器ip
public $proxyHost;
//代理服务器端口
public $proxyPort;
//代理服务器用户名
public $proxyUsername;
//代理服务器密码
public $proxyPassword;
//smtp 域名(不能是ip)ip需要改代理的报文前缀 5 1 0 3改成 5 1 0 1
public $smtpHost;
//smtp 端口
public $smtpPort;
//smtp用户名
public $smtpUsername;
//smtp授权码
public $smtpPassword;
//邮件标题
public $title;
//发件人邮箱
public $from;
//收件人邮箱
public $to;
//邮件正文
public $content;
//附件有效访问路径
public $attachment='';
//附件的相关变量
protected $file_name;
protected $file_type;
protected $file_ext;
//smtp判断指令结束符号 回车符+换行符
protected $CRLF="\r\n";
//socket句柄
private $sock;
//socks服务器连上smtp服务器状态
private $connectSMTP=false;
/**
* 连接代理服务器,让代理服务器连接smtp服务器
*
*
* */
protected function connect()
{
$this->sock = pfsockopen($this->proxyHost,$this->proxyPort,$errno,$errbuf, 60);
if(!$this->sock){
//连接失败
return ['status'=>false,'errno'=>$errno,'errstr'=>$errbuf,'msg'=>'socks服务器连接失败'];
}
fwrite($this->sock, pack("C3", 0x05, 0x01, 0x02));
if(fread($this->sock,512)!==pack('C2',0x05,0x02)){
//socks服务器不支持密码认证模式
return ['status'=>false,'errno'=>$errno,'errstr'=>$errbuf,'msg'=>'socks服务器不支持密码认证模式'];
}
fwrite($this->sock, pack('C2',0x01,strlen($this->proxyUsername)).$this->proxyUsername.pack('C1',strlen($this->proxyPassword)).$this->proxyPassword);
if(fread($this->sock,512)!==pack('C2',0x01,0x00)){
//socks服务器账号密码认证失败
return ['status'=>false,'errno'=>$errno,'errstr'=>$errbuf,'msg'=>'socks服务器账号密码认证失败'];
}
fwrite($this->sock, pack("C5", 0x05 , 0x01 , 0x00 , 0x03, strlen($this->smtpHost)).$this->smtpHost.pack("n", $this->smtpPort));
if(substr(fread($this->sock,512),0,4)!==pack('C4',0x05,0x00,0x00,0x01)){
//socks服务器连接smtp服务器失败
return ['status'=>false,'errno'=>$errno,'errstr'=>$errbuf,'msg'=>'socks服务器连接smtp服务器失败'];
}else{
$return=fread($this->sock,512);
if(substr($return,0,3)!='220'){
//smtp连接错误
return ['status'=>false,'errno'=>$errno,'errstr'=>$errbuf,'msg'=>$return];
}
$this->connectSMTP=true;
return ['status'=>true];
}
}
/**
*检查必传字段是否设置好
*
*
* */
protected function beforeSend(){
$reflect =new \ReflectionClass($this);
$arg=$reflect->getProperties(\ReflectionProperty::IS_PUBLIC);
foreach($arg as $v){
if($v->getName()!='attachment'){
if(!$v->getValue($this)){
return ['status'=>false,'msg'=>$v->getName().'不能为空'];
}
}
}
return ['status'=>true];
}
/**
* 发邮件方法,连接代理服务器,让代理服务器连接smtp服务器并验证权限后发送
*
*
* */
public function send()
{
$beforeSendCheck=$this->beforeSend();
if(!$beforeSendCheck['status']){
return $beforeSendCheck;
}
$connectStatus=$this->connect();
if(!$connectStatus['status']){
return $connectStatus;
}
$authStatus=$this->smtpAuth();
if(!$authStatus['status']){
return $authStatus;
}
$headerStatus=$this->smtpHeader();
if(!$headerStatus['status']){
return $headerStatus;
}
$bodyStatus=$this->smtpBody();
if(!$bodyStatus['status']){
return $bodyStatus;
}
return ['status'=>true,'msg'=>'发送成功'];
}
/**
* 邮箱账号密码认证
*
*
* */
protected function smtpAuth()
{
fputs($this->sock,'EHLO '.$this->smtpHost.$this->CRLF);
fread($this->sock,512);
fputs($this->sock,'STARTTLS auth login'.$this->CRLF);
$return=fread($this->sock,512);
if(substr($return,0,3)!='220'){
//不支持TLS
return ['status'=>false,'msg'=>$return];
}
fputs($this->sock,'AUTH LOGIN'.$this->CRLF);
$return=fread($this->sock,512);
if(substr($return,0,3)!='334'){
//不支持TLS
return ['status'=>false,'msg'=>$return];
}
fputs($this->sock,base64_encode($this->smtpUsername).$this->CRLF);
$return=fread($this->sock,512);
if(substr($return,0,3)!='334'){
//不支持TLS
return ['status'=>false,'msg'=>$return];
}
fputs($this->sock,base64_encode($this->smtpPassword).$this->CRLF);
$return=fread($this->sock,512);
if(substr($return,0,3)!='235'){
//账号密码认证不成功
return ['status'=>false,'msg'=>'账号密码认证不成功'];
}
return ['status'=>true];
}
/**
* 设置邮件发送人和收件人
*
*
* */
protected function smtpHeader()
{
fputs($this->sock,'mail from:<'.$this->from.'>'.$this->CRLF);
$return=fread($this->sock,512);
if(substr($return,0,3)!='250'){
//发送不成功
return ['status'=>false,'msg'=>'发件人邮箱发送不成功'];
}
fputs($this->sock,'rcpt to:<'.$this->to.'>'.$this->CRLF);
$return=fread($this->sock,512);
if(substr($return,0,3)!='250'){
//发送不成功
return ['status'=>false,'msg'=>'收件人邮箱发送不成功'];
}
return ['status'=>true];
}
/**
* 设置邮件正文
*
*
* */
protected function smtpBody()
{
fputs($this->sock,'data'.$this->CRLF);
$return=fread($this->sock,512);
if(substr($return,0,3)!='354'){
//发送不成功
return ['status'=>false,'msg'=>'正文发送不成功'];
}
fputs($this->sock,'Return-path:<'.$this->from.'>'.$this->CRLF);
fputs($this->sock,'Date:'.date('r').$this->CRLF);
fputs($this->sock,'From:<'.$this->from.'>'.$this->CRLF);
fputs($this->sock,'MIME-Version:1.0'.$this->CRLF);
fputs($this->sock,'Subject:=?UTF-8?B?'.base64_encode(trim($this->title)).'?= '.$this->CRLF);
fputs($this->sock,'To:<'.$this->to.'>'.$this->CRLF);
if($this->attachment){
//有附件
$this->file_name=basename($this->attachment);
$this->file_ext=$this->mb_pathinfo($this->file_name, PATHINFO_EXTENSION);
$this->file_type=$this->_mime_types($this->file_ext);
$boundary=$this->generateId();
fputs($this->sock,'Content-type: multipart/mixed; boundary="'.$boundary.'"'.$this->CRLF.$this->CRLF);
fputs($this->sock,'This is a multi-part message in MIME format.'.$this->CRLF);
fputs($this->sock,'--'.$boundary.$this->CRLF);
fputs($this->sock,'Content-type:text/plain; charset=UTF-8'.$this->CRLF);
fputs($this->sock,'Content-Transfer-Encoding:base64'.$this->CRLF.$this->CRLF);
fputs($this->sock,base64_encode($this->content).$this->CRLF.$this->CRLF);
//附件
fputs($this->sock,'--'.$boundary.$this->CRLF);
fputs($this->sock,'Content-type:'.$this->file_type.'; name==?UTF-8?B?'.base64_encode($this->file_name).'?='.$this->CRLF);
fputs($this->sock,'Content-Transfer-Encoding:base64'.$this->CRLF);
fputs($this->sock,'Content-Disposition: attachment; filename==?UTF-8?B?'.base64_encode($this->file_name).'?='.$this->CRLF.$this->CRLF);
$file_string=chunk_split(base64_encode(file_get_contents($this->attachment)),76,$this->CRLF);
fputs($this->sock,$file_string.$this->CRLF);
fputs($this->sock,'--'.$boundary.$this->CRLF);
}else{
//没附件
fputs($this->sock,'Content-Type:text/html; charset=UTF-8; format=flowed'.$this->CRLF);
fputs($this->sock,'Content-Transfer-Encoding:base64'.$this->CRLF.$this->CRLF);
fputs($this->sock,base64_encode($this->content).$this->CRLF.$this->CRLF);
}
//结束符号 换行+制表+.+换行+制表
fputs($this->sock,'.'.$this->CRLF);
$return=fread($this->sock,512);
if(substr($return,0,3)!='250'){
//发送不成功
return ['status'=>false,'msg'=>'正文发送不成功'];
}
return ['status'=>true];
}
protected function mb_pathinfo($path, $options = null)
{
$ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
$pathinfo = [];
if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$#im', $path, $pathinfo)) {
if (array_key_exists(1, $pathinfo)) {
$ret['dirname'] = $pathinfo[1];
}
if (array_key_exists(2, $pathinfo)) {
$ret['basename'] = $pathinfo[2];
}
if (array_key_exists(5, $pathinfo)) {
$ret['extension'] = $pathinfo[5];
}
if (array_key_exists(3, $pathinfo)) {
$ret['filename'] = $pathinfo[3];
}
}
switch ($options) {
case PATHINFO_DIRNAME:
case 'dirname':
return $ret['dirname'];
case PATHINFO_BASENAME:
case 'basename':
return $ret['basename'];
case PATHINFO_EXTENSION:
case 'extension':
return $ret['extension'];
case PATHINFO_FILENAME:
case 'filename':
return $ret['filename'];
default:
return $ret;
}
}
/**
* 根据后缀判断mime type
* @param $ext 后缀
*
* */
protected function _mime_types($ext = '')
{
$mimes = [
'xl' => 'application/excel',
'js' => 'application/javascript',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'bin' => 'application/macbinary',
'doc' => 'application/msword',
'word' => 'application/msword',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'class' => 'application/octet-stream',
'dll' => 'application/octet-stream',
'dms' => 'application/octet-stream',
'exe' => 'application/octet-stream',
'lha' => 'application/octet-stream',
'lzh' => 'application/octet-stream',
'psd' => 'application/octet-stream',
'sea' => 'application/octet-stream',
'so' => 'application/octet-stream',
'oda' => 'application/oda',
'pdf' => 'application/pdf',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'smi' => 'application/smil',
'smil' => 'application/smil',
'mif' => 'application/vnd.mif',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dvi' => 'application/x-dvi',
'gtar' => 'application/x-gtar',
'php3' => 'application/x-httpd-php',
'php4' => 'application/x-httpd-php',
'php' => 'application/x-httpd-php',
'phtml' => 'application/x-httpd-php',
'phps' => 'application/x-httpd-php-source',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'tar' => 'application/x-tar',
'tgz' => 'application/x-tar',
'xht' => 'application/xhtml+xml',
'xhtml' => 'application/xhtml+xml',
'zip' => 'application/zip',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'm4a' => 'audio/mp4',
'mpga' => 'audio/mpeg',
'aif' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'ram' => 'audio/x-pn-realaudio',
'rm' => 'audio/x-pn-realaudio',
'rpm' => 'audio/x-pn-realaudio-plugin',
'ra' => 'audio/x-realaudio',
'wav' => 'audio/x-wav',
'mka' => 'audio/x-matroska',
'bmp' => 'image/bmp',
'gif' => 'image/gif',
'jpeg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'jpg' => 'image/jpeg',
'png' => 'image/png',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'webp' => 'image/webp',
'heif' => 'image/heif',
'heifs' => 'image/heif-sequence',
'heic' => 'image/heic',
'heics' => 'image/heic-sequence',
'eml' => 'message/rfc822',
'css' => 'text/css',
'html' => 'text/html',
'htm' => 'text/html',
'shtml' => 'text/html',
'log' => 'text/plain',
'text' => 'text/plain',
'txt' => 'text/plain',
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'vcf' => 'text/vcard',
'vcard' => 'text/vcard',
'ics' => 'text/calendar',
'xml' => 'text/xml',
'xsl' => 'text/xml',
'wmv' => 'video/x-ms-wmv',
'mpeg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mp4' => 'video/mp4',
'm4v' => 'video/mp4',
'mov' => 'video/quicktime',
'qt' => 'video/quicktime',
'rv' => 'video/vnd.rn-realvideo',
'avi' => 'video/x-msvideo',
'movie' => 'video/x-sgi-movie',
'webm' => 'video/webm',
'mkv' => 'video/x-matroska',
];
$ext = strtolower($ext);
if (array_key_exists($ext, $mimes)) {
return $mimes[$ext];
}
return 'application/octet-stream';
}
/**
* 生成随机字符串 用于有附件时 邮件boundary 区分多个部分
*
*
* */
protected function generateId()
{
$len = 32; //256 bits
if(function_exists('random_bytes')){
$bytes = random_bytes($len);
}else if(function_exists('openssl_random_pseudo_bytes')) {
$bytes = openssl_random_pseudo_bytes($len);
}else{
$bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
}
return 'b1_'.str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
}
/**
*断开smtp服务器连接 释放句柄
*
*
* */
protected function close(){
if($this->sock!==null){
if($this->connectSMTP){
fputs($this->sock,'QUIT'.$this->CRLF);
if(substr(fread($this->sock,512),0,3)!='221'){
//发送不成功
return ['status'=>false,'msg'=>'退出指令发送失败'];
}
}
if(is_resource($this->sock)){
fclose($this->sock);
}
}
return ['status'=>true];
}
/**
* 析构函数 断开连接
*
*
*/
public function __destruct()
{
$this->close();
}
}
?>
本文链接:https://www.kinber.cn/post/3775.html 转载需授权!
推荐本站淘宝优惠价购买喜欢的宝贝: