28
2023
03
09:42:25

通过socks5代理使用smtp发邮件

https://blog.csdn.net/rty426/article/details/88899069


通过socks5代理使用smtp发邮件

起因

因为使用smtp发邮件,点击查看邮件原文能看到发送的服务器ip,而一般使用smtp的服务器为后台服务器,为了防止ip暴露,所以需要通过代理来进行发邮件的动作。

cca3edfdd28dac9d0e4efc748ef3c346_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3J0eTQyNg==,size_16,color_FFFFFF,t_70.png

为了完成这个需求,首先百度、谷歌了一遍没找到有能’参考‘的例子,因此只能自己想办法去实现。


思路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去完成,于是我使用了思路二的方式。

image.png


思路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();

    }

}

?>





推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

本文链接:https://hqyman.cn/post/3775.html 非本站原创文章欢迎转载,原创文章需保留本站地址!

分享到:
打赏





休息一下~~


« 上一篇 下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

请先 登录 再评论,若不是会员请先 注册

您的IP地址是: