PHP利用socket发送邮件

 转 https://www.jianshu.com/p/14c014e55587

学习背景:

在实际工作中遇到了发邮件需求。现有代码是通过访问JSP页面调用一个java的邮件类来实现。
据说一开始也尝试过PHP的类,但好像用了一段时间就出问题了,所以就这样开始了“曲线救国”。
按现有方式完成自己工作后,出于好奇,想要知道为什么直接使用PHP失败的原因。
希望能够直接用PHP来完成功能,这样在效率上应该是更高的。

学习经过:

1、找寻现有的PHP邮件类
2、使用PHP的mail()函数,但因为无法登陆只能发送内网邮箱。
3、socket学习
4、SMTP协议命令学习
5、利用cmd,通过telnet连接服务器,发送SMTP命令发邮件
6、利用socket,发送SMTP命令发邮件
7、归纳总结data部分编码问题及邮件的附件格式
(1)标题编码
(2)正文分割
(3)带附件的具体正文
(4)具体实现
8、总结
9、附件

注:
下文使用了三个邮箱服务器:
1、公司旧服务器:mail.old.net
2、公司新服务器:mail.new.net
3、QQ邮箱:smtp.qq.com




1、找寻现有的PHP邮件类:

找到代码中原先的PHP类,还从网上找了一些别人写好的PHP类...
代码少则几百多则上千行...以我目前的水平要啃完其中的原理再找出问题所在应该是很难的...
还看到了安装PEAR后用邮件拓展的方式,但不知为什么我安装一直失败,也只能先放弃了。

2、使用PHP的mail()函数,但因为无法登陆只能发送内网邮箱:

想起PHP本身就有一个mail()函数...
所以...为什么没人用?先试试看...

具体实现:

1、修改php.ini配置:

SMTP = mail.old.net  //设置具体的邮件服务器地址
smtp_port = 25  //设置端口,一般为25
sendmail_from = "gs@old.net"  //设置发送者的邮箱(win环境)
;sendmail_path = ""  //unix环境?未测试过

2、php代码:
mail($to, $subject, $message, $headers, $parameters)
mail(收件人, 标题, 内容, 显示在邮件原文里的一些参数, 没用过不清楚)

$to = "gs@old.net";
$subject = "=?UTF-8?B?".base64_encode("邮件标题")."?=";
$message = "<a href='http://www.xx.com'>点击可以直接跳转</a>";

$headers = <<<HEADERS
MIME-Version: 1.0 \r\n
Content-type:text/html;charset=UTF-8 \r\n
from:<random@random> \r\n
to:<random@random>" \r\n
HEADERS;

$boolean = mail($to, $subject, $message, $headers);  //返回是否发送成功
说明:

1、邮件的发件人与收件人:
ini配置 --> 邮件原文Return-Path
$to参数 --> 实际收件人(必须是内网的)
$header参数 --> 显示发件人(可以随便添)
$header参数 --> 显示收件人(可以随便填)
2、发件范围:收件人必须是内网的人:
测试情况:只能够发送同一服务器范围中的邮箱(@后的一致)。
如果发送外网(如公司邮箱发送QQ邮箱等),错误码有554和550。
推测:
1、邮件服务器的设置问题。
2、因为mail发邮件不需要登陆具体账号和密码,所以无法发送外网?
注:
界面上显示的内容可以因为to的设置而显示为别人。
即:$to设置内网邮箱,该邮箱实际接受到此邮件。
但该邮件所显示的收件人可以不是该内网邮箱,而是依据$headers所设定的。
3、编码:
对于内容:先用base64编码,然后再UTF-8。
形如: =?UTF-8?B?".base64_encode("邮件标题")."?=
4、正文使用HTML代码:
在$headers中设置Content-type:text/html就可以了

总结:

如果只是内网进行邮件,mail()已经足够,但是如果需要和外网联系,则目前没找到mail()的解决办法。



3、socket学习

在网上找的一堆类中,发现了个代码量比较少的,使用的是socket方法。
尝试先学习socket,然后将这个类理解。

socket大致意义:

socket服务器:开个端口,持续监听,收到信息,回复信息。
socket客户端:发信息到具体端口,接受服务器回复的信息。

具体实现:

服务器代码:

set_time_limit(0);  //网页执行防止超时,cmd中执行无需该行

$socket = socket_create(AF_INET, SOCK_STREAM, 0);  //创建socket资源
socket_bind($socket, "127.0.0.1", 16161);  //将socket资源绑定到具体端口
socket_listen($socket, 3);  //该端口开始监听,第二个参数是具体连接数(并发数?)?

$receive_socket = socket_accept($socket);  //接收到客户端的信息,一个新的socket资源
$input = socket_read($receive_socket, 1024);  //读取该socket资源的内容
var_dump($input);  //客户端发出的内容,一般都是字符串?

$output = "服务器-->客户端 信息";
socket_write($receive_socket, $output, strlen ($output));  //给客户端返回信息。注意:用的是接受到的socket资源,而不是原本自身的资源。

socket_close($socket);  //将两个资源关闭
socket_close($receive_socket);

客户端代码:

$socket = socket_create(AF_INET, SOCK_STREAM, 0);  //创建socket资源
socket_connect($socket, "127.0.0.1", 16161);  //连接具体端口

$message = "客户端-->服务器 信息";
socket_write($socket, $message, strlen($message));  //发送信息给服务器

$result = socket_read ($socket, 1024);  //读取到具体的信息
var_dump($resule);

socket_close($socket);  //关闭socket资源
说明:

只测试了浏览器访问的方式,没有用cmd执行。(浏览器访问:先服务器,后客户端)
目前客户端第一次访问后服务器就停止了,当第二次访问的时候就会报错。
socket_close()注释掉 && socket_listen()第二个参数>1,依然无效。
不知道如何让服务器持续监听。

总结:

初步明白了socket的意义及简单的使用。对于更多参数及方法的使用等以后再深入学习。



4、SMTP协议命令学习

SMTP常用命令(大小写无关):

每个命令都会返回状态码+简短说明,用来告知成功失败。

//连接成功,220
helo xxx  //这一步是必须的!xxx貌似自定义,250
ehlo xxx  //xxx貌似自定义,查看服务器支持哪些命令,返回250-xxx 250-xxx
starttls  //QQ邮箱需要
auth login  //账号密码登陆,账号密码需要的是base64加密过的,
mail from:<x@y.z>  //发件人,250
rept to:<a@b.c>  //收件人,可以重复执行发送给多个人,250
data  //具体邮件内容,显示的from-to、编码及附件都在这里传输,以单行.结束(\r\n.\r\n),354+250
quit  //退出连接,221


5、利用cmd,通过telnet连接服务器,发送SMTP命令发邮件

telnet使用:

连接服务器后输入具体命令,此时无法使用退格键进行更错

telnet host port  //邮件通常端口为25 QQ邮箱需要使用587(SSL加密方式,官方465或587,但465我测试失败)

公司旧服务器:mail.old.net

无法auth login登陆

telnet mail.old.net 25  //220
helo abc  //250
ehlo abc  //返回250-xx 250-xx
mail from:<random@random>  //原文Return-Path,可外网邮箱,250
rcpt to:<gs@old.net>  //实际收件人(必须内网邮箱),250
data //354 开始可以输入具体内容
from: random@random  //显示的发件人(可以随便填)
to: random@random  //显示的收件人(可以随便填)

comment-comment  //具体内容需要和上面的隔一个空行
.  //用单行.结束,250
quit  //221

公司新服务器:mail.new.net

不论是否登陆,都可以发送邮件。
1、auth login不登陆

telnet mail.new.net 25  //220
helo abc  //250
ehlo abc  //返回250-xx 250-xx
mail from:<random@random>  //原文Return-Path,可外网邮箱,250
rcpt to:<gs@30new.net>  //实际收件人(必须内网邮箱),250
data //354 开始可以输入具体内容
from:random@random  //显示的发件人(可以随便填)
to: random@random  //显示的收件人(可以随便填)

comment-comment  //具体内容需要和上面的隔一个空行
.  //用单行.结束,250
quit  //221

2、auth login登陆
无论端口是25还是587,在starttls后都无法进行账号登陆。
另发现,如果 mail from 及 rcpt to 都是填写QQ邮箱,QQ邮箱会无效。

telnet mail.new.net 25  //220
helo abc  //250
ehlo abc  //返回250-xx 250-xx
auth login  //334
base64abcdefg  //@前的 base64编码,334
base64abcdefg  //密码 base64编码,235
mail from:<random@random>  //原文Return-Path,可外网邮箱,250
rcpt to:<random@random>  //实际收件人,可外网邮箱,250
data //354 开始可以输入具体内容
from:random@random  //显示的发件人(可以随便填)
to: random@random  //显示的收件人(可以随便填)

comment-comment  //具体内容需要和上面的隔一个空行
.  //用单行.结束,250
quit  //221

QQ邮箱服务器:smtp.qq.com 587

需要在QQ邮箱里设置,开启P0P3/SMTP。
QQ邮箱必须开启starttls后再登陆,可以发送邮件到外网。

telnet smtp.qq.com 587  //220
helo abc  //250
ehlo abc  //返回250-xx 250-xx
starttls  //220
auth login  //334
base64abcdefg  //@前的 base64编码,334
base64abcdefg  //授权码 base64编码,235
mail from:<gs@qq.com>  //原文Return-Path,必须为登陆账号,250
rcpt to:<random@random>  //实际收件人,可外网邮箱,250
data //354 开始可以输入具体内容
from:random@random  //显示的发件人,可以随便填
to: random@random  //显示的收件人,可以随便填

comment-comment  //具体内容需要和上面的隔一个空行
.  //用单行.结束,250
quit  //221
说明:

1、发送范围:
公司旧邮箱:只能内网。
公司新邮箱:不登陆只能内网,登陆了可以外网。
QQ邮箱:先starttls然后登陆,可以外网。
2、具体发件人收件人:
登陆的账号 --> 实际发送人
mail from --> 原文Return-Path的内容(如果与data-from设置不同,QQ邮箱会显示:由xxx代发)
rcpt to --> 实际收件人
data-from --> 显示的发送人(可以随便填)
data-to --> 显示的收件人(可以随便填,与实际不同)
3、其它:
编码及附件未测试

总结:

与mail()一样,不登陆的情况下,只能在内网发送。
不过telnet可以用auth login登陆账号,然后可以发送邮件到外网。



6、利用socket,发送SMTP命令发邮件

发送socket大致流程:

$socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));  //创建socket资源
socket_connect($socket, $mail_server_name, $mail_server_port);  //连接端口

$command = "helo abc \r\n";  //  \r\n表示回车
socket_write($socket, $command, strlen($command));  //发送命令给服务器
$num = socket_read($socket, 1024);  //接受状态码及短句
说明:

1、通过多次socket_write()来传送SMTP命令,完成整个发邮件的流程
2、\r\n等同于在命令行中按回车
3、QQ邮箱无法使用:在auth login命令后:
显示错误: unable to read from socket [0]: 远程主机强迫关闭了一个现有的连接。
可能是处于安全性的考虑??



7、归纳总结data部分编码问题及邮件的附件格式

查资料想找编码还有正文的格式,但大部分比较凌乱,不然就是RFC文件没勇气。
后来想到自己给自己发邮件,然后用查看邮件原文这个功能,去自己总结。
目前需求只是发送文本、html的a标签、附件就足够了,因此自己总结的不用太全面。

(1)标题编码:

$subject = "=?UTF-8?B?".base64_encode("标题")."?=";

(2)正文分割:

需要声明:Content-Type: multipart/mixed;
其中,mixed表明是混合类型。
规定boundary分割,然后按照具体情况分别写具体声明+内容。

boundary分割规则:

boundary是自定义的
第一段开始:--boundary
第n段开始: --boundary
结束: --boundary--
boundary是可以嵌套的:
如果就文本和附件,一般无需嵌套。

整体类似于:
标题
发件人
收件人
声明编码-类型-boundary声明
  --boundary
    声明编码-类型
    内容
  --boundary
    声明编码-类型
    内容
  --boundary
    声明编码-类型
  --boundary--

(3)带附件的具体正文

声明与内容,内容与后文 都需要有空行,即2次\r\n

html:
$command = <<<COMMAND
Content-Type: text/html; \r\n
charset="utf-8" \r\n
Content-Transfer-Encoding: base64 \r\n
COMMAND;
$command = "\r\n" . base64_encode("内容") . "\r\n\r\n";  //内容前后都有两次\r\n
附件:

先用fopen()及fread() 读取二进制,然后将二进制内容进行发送

$file_name = iconv('UTF-8', 'GB18030', "文件名.txt" );  //对文件名编码
$fp = fopen($file_name, 'rb');  //以二进制形式打开文件
$comment = fread($fp, filesize($file_name));  //读取文件内容
fclose($fp);  //关闭资源

然后声明编码-类型并传递数据

$file_name = "=?UTF-8?B?".base64_encode("文件名.txt")."?=";  //对文件名编码
$command = "Content-Type: application/octet-stream; \r\n"
$command .= "name=\"" . $file_name . "\"\r\n"
$command .= "Content-Disposition: attachment; filename=\"" . $file_name . "\"\r\n";
$command .= "Content-Transfer-Encoding: base64 \r\n";
$command .= "\r\n" . base64_encode($comment) . "\r\n\r\n";  //这里的$comment就是读出来的文件二进制数据
说明:

1、文件名编码:
对文件名的编码有两次,一次是为了打开文件,一次是为了邮件的显示。
编码方式不同,不知道能否统一?
2、开始的声明中,boundary前必须有一个Tab键的缩进:
原因未知

"Mime-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n    boundary=\"".$boundary."\"\r\nContent-Transfer-Encoding: 8Bit\r\n";

3、附件声明:
看别人的代码,好像通过文件后缀来判断的类型。
而自己只是笼统地用 application/octet-stream;

(4)具体实现:

send_email.php 文件 附件1

自己归纳的发邮件函数
缺陷,未添加:
1、没有抄送人、密送人。
2、没有对命令发送是否成功进行判断(可以通过正则判断返回码)。
3、没有选择是否text还是html,全部默认成为了html。
4、没有选择是否starttls,如果需要使用到QQ邮箱,需自行添加。
5、变量名及代码格式可能不太规范。

email.php 文件 附件2

网上已经封装好的类,通过socket发送邮件。



8、总结:

上述内容大约花了三天时间学习及测试,然后整理就花了一天多...
以前总以为socket深不可测,现在起码有了一定的了解,没有那么害怕了。
QQ邮箱必须登陆,而且mail from的发件人设置必须是登陆人,可防止有人故意隐藏自己真实地址。
所以说,公司新邮箱不用登陆的情况,估计是属于安全漏洞?
等PHP代码知识再熟悉之后,得去研究研究那上千行的PHP类...不知道具体实现原理究竟是怎么样的...



9、附件:使用smtp 发送邮件

附件一:send_email.php

<?php
$mail_server_name = "smtp.163.com";
$mail_server_port = 25;
$username = "1234@163.com";
$passward = "1234";
$mail_from = "1237@163.com";
$mail_to = '1234@qq.com';
$subject = "socket发送的邮件!";
$html_comment = "<a href='http://www.xxx.com'>超链接</a>";
// $file_name[0] = "啊.png";


send_email($mail_server_name, $username, $passward, $mail_from, $mail_to, $html_comment,$subject);
/**
 * 通过socket发送邮件
 * @param $mail_server_name   邮件服务器地址
 * @param $username           登陆账号
 * @param $passward           登陆密码
 * @param $mail_from          发件人地址
 * @param $mail_to            收件人地址,数组格式
 * @param $html_comment       文本内容
 * @param array $file_name    附件路径,数组格式
 * @param string $subject     标题,默认空
 * @param string $mail_server_port  邮件服务器端口 默认25
 * @param string $boundary    分割符
 */
function send_email($mail_server_name, $username, $passward, $mail_from, $mail_to, $html_comment, $subject="", $mail_server_port="25", $boundary="ABCDEFG"){
    //创建一个socket连接
    $socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
    //连接邮件服务器,需要返回状态码 220
    socket_connect($socket, $mail_server_name, $mail_server_port);

    //helo localhost,需要返回状态码 250
    $command = "helo localhost\r\n";
    socket_write($socket, $command, strlen($command));
    socket_read($socket, 1024);

    //登陆账号,分别返回状态码 334 334 235
    $command = "auth login\r\n";
    socket_write($socket, $command, strlen($command));
    socket_read($socket, 1024);

    //需要返回状态码 334
    $command = base64_encode($username)."\r\n";
    socket_write($socket, $command, strlen($command));
    socket_read($socket, 1024);

    //需要返回状态码 235
    $command = base64_encode($passward)."\r\n";
    socket_write($socket, $command, strlen($command));
    socket_read($socket, 1024);

    //设置邮件发送者,需要返回状态码 250
    $command = "MAIL FROM:<".$mail_from.">\r\n";
    socket_write($socket, $command, strlen($command));
    socket_read($socket, 1024);

    //设置邮件接受者,需要返回状态码 250
     // $mail_to_length = count($mail_to);
    // for($i=0; $i<$mail_to_length; $i++){
        $command = "RCPT TO:<".$mail_to.">\r\n";
        socket_write($socket, $command, strlen($command));
        socket_read($socket, 1024);
    // }

    //开始发送具体内容,需要返回状态码 354
    $command = "DATA\r\n";
    socket_write($socket, $command, strlen($command));
    socket_read($socket, 1024);

    //发送具体内容:需要返回状态码 250
    $command = "from: ".$mail_from."\r\n";
    // for($i=0; $i<$mail_to_length; $i++){
    //     $command .= "to: ".$mail_to[$i]."\r\n";
    // }
    $subject = "=?UTF-8?B?".base64_encode($subject)."?=";
    $command .= "Subject: ".$subject."\r\n";
    $command .= "Mime-Version: 1.0\r\nContent-Type: multipart/mixed;\r\n    boundary=\"".$boundary."\"\r\nContent-Transfer-Encoding: 8Bit\r\n";
    $command .= "--".$boundary."\r\n";
    $command .= "Content-Type: multipart/alternative;\r\n";
    $command .= "Content-Type: text/html;\r\n";
    $command .= "charset=\"utf-8\"\r\n";
    $command .= "Content-Transfer-Encoding: base64\r\n";
    $command .= "\r\n" .base64_encode($html_comment). "\r\n\r\n";

    // $file_length = count($file_name);
    // for($i=0; $i<$file_length; $i++){
    //     //读文件
    //     $file_name_tmp = iconv( 'UTF-8', 'GB18030', $file_name[$i] );
    //     $fp = fopen($file_name_tmp, 'rb');  // 以二进制形式打开文件
    //     $comment = fread($fp, filesize($file_name_tmp)); // 读取文件内容
    //     fclose($fp);

    //     $file_name_tmp = "=?UTF-8?B?".base64_encode($file_name[$i])."?=";
    //     $command .= "--".$boundary."\r\n";
    //     $command .= "Content-Type: application/octet-stream;\r\n";
    //     $command .= "name=\"".$file_name_tmp."\"\r\n";
    //     $command .= "Content-Disposition: attachment; filename=\"".$file_name_tmp."\"\r\n";
    //     $command .= "Content-Transfer-Encoding: base64\r\n";
    //     $command .= "\r\n".base64_encode($comment)."\r\n\r\n";
    // }

    $command .= "--".$boundary."--\r\n";
    $command .= ".\r\n";
    socket_write($socket, $command, strlen($command));
    socket_read($socket, 1024);

    //离开,需要返回状态码 221
    $command = "quit\r\n";
    socket_write($socket, $command, strlen($command));
    socket_read($socket, 1024);
}


附件二:email.php

<?php
//define("SOL", "\n");
define("EOL", "\r\n");
define("SMTP_HOST", "mail.new.net");  //SMTP服务器
define("SMTP_PORT", "25");  //SMTP服务器端口
define("SMTP_USER", "");  //SMTP服务器的用户帐号
define("SMTP_PASS", "");  //SMTP服务器的用户密码
$from = "random@random";  //SMTP服务器的用户邮箱
$to = "random@random";  //发送给谁 可用逗号隔开多个邮箱
$cc = "";
$bcc = "";
$subject="标题";  
$subject=iconv("utf-8","gb2312//IGNORE",$subject);  //邮件主题 很多客户端会有乱码,所以转一下码
$body = "测试";  //邮件内容
$smtp = new smtp(SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS, false);  //这里面的一个true是表示使用身份验证,否则不使用身份验证.
$smtp->addAttachment("123.doc","啊.doc");
$smtp->sendmail($to, $from, $subject, $body, $cc, $bcc);
 
 
class smtp {
 
    /* Public Variables */
    public $attachments = array();
    /* Private Variables */
    private $smtp_host;
    private $smtp_port;
    private $time_out;
    private $host_name;
    private $auth;
    private $user;
    private $pass;
    private $sock;
 
    /* Constractor */
    public function smtp($smtp_host = null, $smtp_port = null, $user = null, $pass = null, $auth = true) {
        $this->smtp_host = (!empty($smtp_host)) ? $smtp_host : SMTP_HOST;
        $this->smtp_port = (!empty($smtp_port)) ? $smtp_port : SMTP_PORT;
        $this->user = (!empty($user)) ? $user : SMTP_PORT;
        $this->pass = (!empty($pass)) ? $pass : SMTP_PORT;
        $this->auth = $auth;
        $this->time_out = 15;
        #
        $this->host_name = "localhost";
        $this->sock = FALSE;
    }
 
    /* Main Function */
    public function sendmail($to, $from, $subject = "", $body = "", $cc = "", $bcc = "") {
        $bndp = md5(uniqid("")) . rand(1000, 9999);
        $bnd  = md5(uniqid("")) . rand(1000, 9999);
        list ($msec, $sec) = explode(" ", microtime());
 
        $mail_from = $this->strip_line_breaks($from);
        $mail_to = explode(",", $to);
        $body = preg_replace("/(^|(\r\n))(\\.)/", "", $body);
        if ($cc != "") $mail_to = array_merge($mail_to, explode(",", $cc));
        if ($bcc != "") $mail_to = array_merge($mail_to, explode(",", $bcc));
 
        $headers  = "MIME-Version:1.0" . EOL;
        $headers .= "To: " . $to . EOL;
        if ($cc != "") {
        $headers .= "Cc: " . $cc . EOL;
        }
        $headers .= "From: $from<" . $from . ">" . EOL;
        $headers .= "Subject: " . $subject . EOL;
        $headers .= "Date: " . date("r") . EOL;
        $headers .= "X-Mailer: Webmail ver 1.0 (PHP Version/" . phpversion() . ")" . EOL;
        $headers .= "Message-ID: <" . date("YmdHis", $sec) . "." . ($msec * 1000000) . "." . $from . ">" . EOL;
        if (count($this->attachments) > 0) {
            $headers .= "Content-Type: multipart/mixed;" . EOL . chr(9) . " boundary=\"" . $bndp . "\"" . EOL . EOL;
            $headers .= '--'.$bndp . EOL;
            $headers .= 'Content-Type : multipart/alternative; boundary="' . $bnd . '"' . EOL . EOL;
            $headers .= '--' . $bnd . EOL;
            $headers .= 'Content-Type: text/plain; charset=utf-8' . EOL;
            $headers .= "Content-Transfer-Encoding: 8bit" . EOL . EOL;
            $headers .= $body . EOL;
            $headers .= '--' . $bnd . EOL;
            $headers .= 'Content-type: text/html; charset=utf-8' . EOL;
            $headers .= "Content-Transfer-Encoding: 8bit" . EOL . EOL;
            $headers .= $body . EOL;
            $headers .= '--' . $bnd . '--' . EOL;
 
            foreach ($this->attachments as $att) {
                $headers .= "--" . $bndp . EOL . $att;
                print_R($headers);
            }
            $headers .= '--' . $bndp . '--' . EOL;
            $this->clear_attachments();
        } else {
            $headers .= 'Content-Type : multipart/alternative;boundary="'.$bnd.'"' . EOL . EOL;
            $headers .= '--'.$bnd . EOL;
            $headers .= 'Content-Type: text/plain; charset=utf-8' . EOL;
            $headers .= "Content-Transfer-Encoding: 8bit" . EOL . EOL;
            $headers .= $body . EOL;
            $headers .= '--'.$bnd . EOL;
            $headers .= 'Content-type: text/html; charset=utf-8' . EOL;
            $headers .= "Content-Transfer-Encoding: 8bit" . EOL . EOL;
            $headers .= $body . EOL;
            $headers .= '--'.$bnd.'--' . EOL;
        }
 
        $sent = TRUE;
        foreach ($mail_to as $rcpt_to) {
            $rcpt_to = $this->strip_line_breaks($rcpt_to);
            if (!$this->smtp_sockopen($rcpt_to)) {
                $this->log_write("Error: Cannot send email to " . $rcpt_to);
                $sent = FALSE;
                continue;
            }
            if ($this->smtp_send($this->host_name, $mail_from, $rcpt_to, $headers, $body)) {
                $this->log_write("E-mail has been sent to <" . $rcpt_to . ">");
            } else {
                $this->log_write("Error: Cannot send email to <" . $rcpt_to . ">");
                $sent = FALSE;
            }
            fclose($this->sock);
        }
        $this->log_write("{$mail_to} send over;");
        return $sent;
    }
 
    public function addAttachment($file,$file_name="", $dispo = "attachment") {
        
        $file_data = (file_exists($file)) ? file_get_contents($file) : "";
        if ($file_data != "") {
            $filename = basename($file);
            if(!$file_name) $file_name=$filename;
            
            $file_name="=?UTF-8?B?".base64_encode($file_name)."?=";
            $ext = pathinfo($filename, PATHINFO_EXTENSION);
            $chunks = chunk_split(base64_encode($file_data));
            $parts  = "Content-Type: application/$ext; name=\"" . $file_name . "\"" . EOL;
            $parts .= "Content-Transfer-Encoding: base64" . EOL;
            $parts .= "Content-Disposition: " . $dispo . "; filename=\"" . $file_name . "\"" . EOL . EOL;
            $parts .= $chunks . EOL . EOL;
            $this->attachments[] = $parts;
        }
    }
 
    private function clear_attachments() {
        unset($this->attachments);
        $this->attachments = array();
    }
 
    /* Private Functions */
    private function smtp_send($helo, $from, $to, $header, $body = "") {
        if (!$this->smtp_putcmd("HELO", $helo)) {
            //$this->log_write("Error: Error occurred while sending HELO command.");
            return FALSE;
        }
        #auth
        if ($this->auth) {
            if (!$this->smtp_putcmd("AUTH LOGIN", base64_encode($this->user))) {
                //$this->log_write("Error: Error occurred while sending HELO command.");
                return FALSE;
            }
            if (!$this->smtp_putcmd("", base64_encode($this->pass))) {
                //$this->log_write("Error: Error occurred while sending HELO command.");
                return FALSE;
            }
        }
        if (!$this->smtp_putcmd("MAIL", "FROM:<" . $from . ">")) {
            //$this->log_write("Error: Error occurred while sending MAIL FROM command.");
            return FALSE;
        }
        if (!$this->smtp_putcmd("RCPT", "TO:<" . $to . ">")) {
            //$this->log_write("Error: Error occurred while sending RCPT TO command.");
            return FALSE;
        }
        if (!$this->smtp_putcmd("DATA")) {
            //$this->log_write("Error: Error occurred while sending DATA command.");
            return FALSE;
        }
        if (!$this->smtp_message($header, $body)) {
            //$this->log_write("Error: Error occurred while sending message.");
            return FALSE;
        }
        if (!$this->smtp_eom()) {
            //$this->log_write("Error: Error occurred while sending <CR><LF>.<CR><LF> [EOM].");
            return FALSE;
        }
        if (!$this->smtp_putcmd("QUIT")) {
            //$this->log_write("Error: Error occurred while sending QUIT command.");
            return FALSE;
        }
        return TRUE;
    }
    private function smtp_sockopen($address) {
        if ($this->smtp_host == "") {
            return $this->smtp_sockopen_mx($address);
        } else {
            return $this->smtp_sockopen_relay();
        }
    }
    private function smtp_sockopen_relay() {
        $this->log_write("Trying to Connect " . $this->smtp_host . ":" . $this->smtp_port . "...");
        $this->sock = @fsockopen($this->smtp_host, $this->smtp_port, $errno, $errstr, $this->time_out);
        if (!($this->sock && $this->smtp_ok())) {
            $this->log_write("Error: connenct error" . $errstr . " (" . $errno . ")");
            return FALSE;
        }
        $this->log_write("Connected Ok");
        return TRUE;
    }
    private function smtp_sockopen_mx($address) {
        $domain = preg_replace("/^.+@([^@]+)$/", "\1", $address);
        if (!@getmxrr($domain, $MXHOSTS)) {
            $this->log_write("Error: Cannot resolve MX \"" . $domain . "\"");
            return FALSE;
        }
        foreach ($MXHOSTS as $host) {
            $this->log_write("Trying to " . $host . ":" . $this->smtp_port);
            $this->sock = @fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out);
            if (!($this->sock && $this->smtp_ok())) {
                $this->log_write("Connect Error ," . $errstr . " (" . $errno . ")");
                continue;
            }
            $this->log_write("Connected to mx host " . $host);
            return TRUE;
        }
        $this->log_write("Error: Cannot connect to any mx hosts (" . implode(", ", $MXHOSTS) . ")");
        return FALSE;
    }
    private function smtp_message($header, $body) {
        fputs($this->sock, $header . "\r\n" . $body);
        return TRUE;
    }
    private function smtp_eom() {
        fputs($this->sock, "\r\n.\r\n");
        return $this->smtp_ok();
    }
    private function smtp_ok() {
        $response = str_replace("\r\n", "", fgets($this->sock, 512));
        if (!preg_match("/^[23]/", $response)) {
            fputs($this->sock, "QUIT\r\n");
            fgets($this->sock, 512);
            $this->log_write("Error: Remote host returned \"" . $response . "\"");
            return FALSE;
        }
        return TRUE;
    }
    private function smtp_putcmd($cmd, $arg = "") {
        if ($arg != "") $cmd = ($cmd == "") ? $arg : ($cmd . " " . $arg);
        fputs($this->sock, $cmd . "\r\n");
        return $this->smtp_ok();
    }
    private function strip_line_breaks($address) {
        $address = preg_replace("/([\t\r\n])+/", "", $address);
        $address = preg_replace("/^.*<(.+)>.*$/", "", $address);
        return $address;
    }
    public function log_write($message) {
        $message = date("M d H:i:s ") . get_current_user() . "[" . getmypid() . "]: " . $message;
        file_put_contents(dirname(__FILE__) . '/mail.log', $message . PHP_EOL, FILE_APPEND | LOCK_EX);
    }
}


作者:魔力小小鸟
链接:https://www.jianshu.com/p/14c014e55587
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值