邮件认证发送过程

SMTP 认证功能介绍  
  此处不想向你详细介绍SMTP认证功能,因为我也说不清楚,详细的请参考[RFC 2554]规范。SMTP的认证功能主要是增加了AUTH命令。AUTH命令有多种用法,而且有多种认证机制。AUTH支持的认证机制主要有LOGIN,CRAM-MD5[注1]等。LOGIN应该是大多数免费邮件服务器都支持的,263与新浪都支持。而新浪还支持CRAM-MD5机制。认证机制一般只在真正发送邮件之前进行,而且只需要执行一次。当认证成功后,即可按原来正常的处理发送邮件。原理是口令-应答(Challenge-Response),即由服务器发送命令要求客户端回答,客户端根据服务器发送信息进行回答,如果应答通过了,则认证成功,即可继续处理。下面对这两种制作一个简单介绍。S:表示服务器返回,C:表示客户端发送。  


LOGIN  
它应该比较简单。口令-应答过程如下:  

1   C: AUTH LOGIN  
2   S: 334 dXNlcm5hbWU6  
3   C: dXNlcm5hbWU6  
4   S: 334 cGFzc3dvcmQ6  
5   C: cGFzc3dvcmQ6  
6   S: 235 Authentication successful.  
1 为客户端向服务器发送认证指令。  
2 服务端返回base64编码串,成功码为334。编码字符串解码后为“username:”,说明要求客户端发送用户名。  
3 客户端发送用base64编码的用户名,此处为“username:”。  
4 服务端返回base64编码串,成功码为334。编码字符串解码后为“password:”,说明要求客户端发送用户口令。  
5 客户端发送用base64编码的口令,此处为“password:”。  
6 成功后,服务端返回码为235,表示认证成功可以发送邮件了。  

对于LOGIN方式认证,其实就是将用户名与口令用base64进行编码,根据服务器的要求,分别发出即可。(就我看来,由于base64是一种公共的编码标准,也起不到太大的保护作用。)   
CRAM-MD5机制  
关于CRAM-MD5的机制可以参考[RFC 2195]规范,这里不详细说明了。主要就是通过口令-回答机制,由服务端发出一个信息串,这个由随机数,时间戳,服务器地址构成,并且用base64编码。客户端收到后,发送一个由用户名,加一个空格,再加一个摘要构成的串,并用base64编码。摘要是通过MD5算法求出。这种机制要求服务端与客户端有相同的加密串。当客户端发送摘要后,服务器对其合法性进行验证,成功后,返回235。   
如何得知邮件服务器支持什么认证?  
  在smtp的[RFC 821]中,在与邮件服务器连接成功后,第一个命令一般是“HELO”。但是在支持认证的邮件服务器中,第一个命令应改为“EHLO”[注2]。在命令成功后,263的返回可能为:   

EHLO hello  
250-smtp.263.net [注3]  
250-PIPELINING   
250-SIZE 10240000   
250-ETRN   
250-AUTH LOGIN   
250 8BITMIME   
  从而可以看到263支持LOGIN方式认证。当然,如果你已经知道邮件服务器是什么方式,也没有必要自动进行判断,但是如果不知道,就需要分析这个返回结果了。不过大部分的邮件服务器都支持最简单的LOGIN方式。  

  好了,下面开始对以前所写的sendmail.class.php3进行修改。你没有不要紧,本文在最后提供了sendmail.class.php3的打包文件,可以下载。至于例子则自已根据本文进行编写。  

修改sendmail.class.php3  
  此处只说出修改的重点,而不是全面的分析。  

  首先回顾一下sendmail.class.php3的思路,让大家先心中有数。  

  sendmail.class.php3一共有四个函数,分别为:   

send_mail       类的构造函数,用于信息的初始化  
send            邮件发送函数,执行socket命令,发送邮件  
do_command      命令执行函数,执行一条smtp命令,并将处理返回结果  
show_debug      显示调示信息函数  
  首先用户应先调用类的构造函数,对必要的参数进行初始化。如smtp服务器地址($smtp),欢迎信息($welcome),及是否显示调示信息($debug)。同时还要初始化一些内部变量,如最后执行命令($lastact),最后响应信息($lastmessage),及端口号($port=25)。  

  然后,用户生成邮件信息,并调用send()函数发送邮件。在send()函数中,根据smtp规范,一条命令接一条命令执行(详情参见前面的文章)。在执行命令时,是通过调用do_command()来实现的。如果do_command()执行出错,则程序立即返回,否则继续向下执行。如果设置了显示调示信息标志,则do_command()在命令发送和信息响应时会返回调示信息。  

  好了,大家已经对它的运行有了一个了解,下面就是如何修改了。  

  修改构造函数(send_mail)  
  由于以前的send_mail类不支持认证功能,所以先要增加认证信息。增加了三个参数,$auth, $authuser,和$authpasswd。$auth是一个标志,表示是否要使用认证功能。$authuser和$authpasswd是smtp认证的用户名和口令,根据相应的邮件服务商的要求,例如263是同pop3相一致。大部分应该也是如此。这样,同时需要在类的内部变量表后面增加三个内部变量:$auth,$user,$passwd。  

  修改发送函数(send)  
  将发送命令HELO改为发送EHLO。同时要加入判断是否要进行认证处理:   

                //改为支持ESMTP EHLO命令  
                if($this->auth)  
                {  
                    $this->lastact="EHLO ";  
                }  
                else  
                    $this->lastact="HELO ";  
  即,如果需要认证处理,则发送EHLO命令,否则还发送HELO命令。  

  然后,增加认证处理:   

                //2000.02.28 增加认证处理  
                if($this->auth)  
                {  
                    $this->lastact="AUTH LOGIN" . "\n";  
                    if(!$this->do_command($this->lastact, "334"))  
                    {  
                        fclose($this->fp);  
                        return false;  
                    }  
                      
                    //回传用户名,用base64编码  
                    $this->lastact=base64_encode($this->user) . "\n";  
                    if(!$this->do_command($this->lastact, "334"))  
                    {  
                        fclose($this->fp);  
                        return false;  
                    }  

                    //回传口令,用base64编码  
                    $this->lastact=base64_encode($this->passwd) . "\n";  
                    if(!$this->do_command($this->lastact, "235"))  
                    {  
                        fclose($this->fp);  
                        return false;  
                    }  
                }  
  注意,这里只实现了AUTH LOGIN机制,CRAM-MD5没有实现。而且对服务器传回的信息没有判断,默认为第一次要求用户名,第二次要求口令。  

  修改命令执行函数(do_command)  
  原函数不能显示当响应串为多行的情况。修改为:   

        /*  2000.02.28 修改,将返回信息显示完全  
        $this->lastmessage = fgets ( $this->fp, 512 );  
        $this->show_debug($this->lastmessage, "in");  
        */  
        while(true)  
        {  
            $this->lastmessage = fgets ( $this->fp, 512 );  
            $this->show_debug($this->lastmessage, "in");  
            if(($this->lastmessage[3]==' ') or (empty($this->lastmessage)))  
                break;  
        }  
  这样类就改好了。  

测试send_mail类  
  下面是我编写的一个测试小程序,用于发送一封信,但是为了安全起见,我将用户名及口令没有用真实信息,如果大家想要测试请改成你自已的信息。程序如下(send.php):   

<?  
        include("sendmail.class.php3");  
          
        $sendmail=new send_mail("smtp.263.net", true, "username", "password", "hello", true);  
        $sendmail->send("toemail, "fromemail", "test", "This is a test!");  
?>  
结论  
  对于263的测试很顺利,也比较快。但是新浪网则不容易成功,主要是超时,而且发成功也收不着,不知为何?  

  注意:由于发送smtp需要用户名及口令,且大部分的smtp认证使用与pop3相同的用户名和口令。所以如果大家使用这个方法,可能会把用户名和口令写入程序,上传到服务器。但是这样做是不安全的。加密也不一定好用,因为信息放在服务器上,相应的解密信息也会放到服务器上。我的建议是,再申请一个专门用来发信用的信箱,这样别人知道了也不怕。  

  希望这个程序对你有用。sendmail.class.php3下载。   

附:相关的RFC   

RFC 1869                SMTP Service Extensions  
RFC 2195                IMAP/POP AUTHorize Extension(里面有关于CRAM-MD5的说明)  
RFC 2222                Simple Authentication and Security Layer  
RFC 2554                SMTP Service Extension for Authentication  

--------------------------------------------------------------------------------  
[注1]  
CRAM=Challenge-Response Authentication Mechanism 口令-应答认证机制  
MD5是一种摘要算法,主要用于RSA,PGP中。  
[注2]  
关于EHLO的说明参见[RFC 1869]。  
[注3]  
在邮件服务器应答串中,如果应响码后面跟空格(' ')表示,应答串只有一行;如果为减号('-')表示有多行,且最后一行响应码后面为空格(' ')。   

本文所有权属于limodou。如要转载请保留此信息。  



本文打算详细分析一封邮件从发件人发出邮件到收件人收到邮件的过程,讲述该过程涉及到的各种知识,为初步接触邮件系统的系统管理员深入学习邮件服务器配置和反垃圾邮件软件或者硬件的配置打下扎实的基础。
1) SMTP  会话
a. 发件人在自己的邮件客户端(比如outlookfoxmail等等,称之为MUA【邮件用户    代理】)写邮件,完成后,按“发送”按钮;
b. 
发件人邮件客户端根据发件人先前的配置(SMTP 服务器【发件人公司邮局服务器】   域名或者IP地址,如果发送邮件需要身份验证的话,还有发件人用来向SMTP服务器    表明身份的用户名和密码),建立到发件人公司邮局服务器25号端口的TCP连接;
c. 
发件人邮件客户端向发件人公司邮局服务器发送命令 HELO < 发件人主机名 或者     EHLO < 发件人主机名>,向发件人公司邮局服务器表明自己的身份;
d. 
发件人公司邮局服务器响应发件人邮件客户端,如果该邮件服务器配置了SMTP身份   验证的话,还会把自己支持的身份验证加密算法返回给发件人邮件客户端;
  
    注意:邮件服务器可以通过两种方式来限定能够通过它发送邮件的邮件客户端,   防止自己被当成开放中转(open relay),被用来发送垃圾邮件:
        
一种是通过 IP 地址来限定,比如把该邮件服务器负责发送邮件的客户端 电脑的IP地址段写到邮件服务器的相应配置文件里;
    
另外一种就是通过配置SMTP身份验证来限制,只有通过身份验证的客户端 才能通过它来发送邮件;
e. 如果邮件服务器使用SMTP身份验证来限制邮件客户端,那么发件人邮件客户端向 发件人公司邮局服务器发送命令 AUTH < 发件人邮件客户端选择的加密算法> ,把自己选择的算法发送给发件人公司邮局服务器,否则,转到步骤 k
f. 发件人公司邮局服务器响应发件人邮件客户端,并用双方协商的加密算法加密响应数据;
g. 发件人邮件客户端向发件人公司邮局服务器发送命令 USER < 发件人用户名> ,并用双方协商的加密算法加密命令;
h. 发件人公司邮局服务器响应发件人邮件客户端,并用双方协商的加密算法加密响应数据;
i. 发件人邮件客户端向发件人公司邮局服务器发送命令 PASS <发件人密码>,并用双方 协商的加密算法加密命令;
j. 发件人公司邮局服务器响应发件人邮件客户端,告诉发件人邮件客户端身份验证的结果;
k. 发件人邮件客户端向发件人公司邮局服务器发送命令 MAIL FROM: < 发件人邮箱> ,告诉邮局服务器发件人的邮箱地址;
l. 发件人公司邮局服务器响应发件人邮件客户端;
m. 发件人邮件客户端向发件人公司邮局服务器发送命令 RCPT TO: < 收件人邮箱> ,告诉邮局服务器收件人的邮箱地址;
n. 发件人公司邮局服务器判断邮件客户端是否位于自己负责为其转发邮件的IP地址段内,或者客户端是否通过了SMTP身份验证,如果该客户端在自己负责的IP地址段内,或者通过SMTP身份验证,那么允许该客户端发送外部邮件,反之如果该客户端既不在自己负责的IP地址段内,又没有通过SMTP身份验证,那么发件人公司邮局服务器会认为该发件人邮件客户端是一台外部邮件服务器,试图通过它发送邮件,那么它会判断RCPT TO 命令的参数收件人邮箱是否是本地邮箱,如果是,则允许发送,如果是外部邮箱,则拒绝发送,并用判断的结果响应发件人邮件客户端;
o. 发件人邮件客户端向发件人公司邮局服务器发送命令 DATA ,要求发送邮件;
p. 发件人公司邮局服务器响应发件人邮件客户端;
q. 发件人邮件客户端向发件人公司邮局服务器发送邮件;
      r.  发件人公司邮局服务器响应发件人邮件客户端,告诉其邮件已经接收了, 然后关闭连接。
      
 
2) SMTP  会话
a. 
发件人公司邮件服务器分析刚收到的邮件,取出其“收件人邮箱”部分(比如,   zoukj@sinogrid.com ),并分离出收件人邮箱的域名 sinogrid.com)
b. 
发件人公司邮件服务器执行 DNS 查询,查询类别为 MX ,查找 sinogrid.com  这个域的   邮件服务器的 IP 地址(可以用 nslookup 命令模拟: nslookup –type=mx sinogrid.com );
c. 
收件人公司的 DNS 服务器将本公司的邮件服务器列表都返回给发件人公司邮件 服务器;
d. 
发件人公司邮件服务器分析获得的收件人公司邮件服务器列表,找出优先级最高的   邮件服务器,试图与其建立 TCP 连接,如果优先级最高的邮件服务器有多个,那么 就轮流使用,如果不能与优先级高的邮件服务器建立连接,则跟优先级次高的服务器   建立连接,以此类推。
   
注意: MX  查询的返回值,形如  sinogrid.com   MX preference = 20 mail exchanger  = mail.sinogrid.com   其中的那个数值越低,则该记录对应的邮件服务器的 优先级就越高。
e. 
如果收件人公司邮件服务器配置了根据发件邮件服务器的 IP 地址来过滤垃圾邮件, 那么它会将发件人公司邮件服务器的 IP 地址跟自己的黑白名单进行匹配,如果发件人 公司邮件服务器的 IP 在黑名单里面,那么它可能会拒绝发件人公司邮件服务器的连接;
f. 
发件人公司邮件服务器向收件人公司邮件服务器发送命令  HELO < 发件人公司邮件 服务器域名 或者 EHLO < 发件人公司邮件服务器域名 > ,表明自己的身份;
g. 
收件人公司邮件服务器响应;
h. 
发件人公司邮件服务器取出试图发送的邮件的“发件人邮箱”,向收件人公司邮件 服务器发送命令  MAIL FROM: < 发件人邮箱 >
i. 
收件人公司邮件服务器如果配置了 DNS 反向查询验证,那么它会这样做:
  
首先他执行一个  PTR  类型的 DNS 查询(根据 IP 查找域名),查找出发件人公司邮件 服务器的域名,然后执行一个  MX  类型的 DNS 查询(根据域名查找负责该域的 邮件服务器列表),查找出负责“发件人邮箱”所在域的邮件的邮件服务器列表,判断 发件人公司邮件服务器的域名是否在这个列表里面。如果 PTR  类型的 DNS 查询失败 了(例如我们没有配置 DNS 反向查询,这个需要向 ISP 申请,由他们来做),或者域名 不在列表里面,那么收件人公司邮件服务器会拒绝接收邮件,终止会话。 反之,如果两个条件都满足了,那么以“ OK ”响应发件人公司邮件服务器。
j. 
发件人邮件服务器向收件人公司邮局服务器发送命令  RCPT TO: < 收件人邮箱> ,告诉 收件人邮件服务器收件人的邮箱地址;
k. 
收件人公司邮局服务器判断该“收件人邮箱”地址(比如 zoukj@sinogrid.com )是否 属于自己负责投递的域的邮箱地址(这些域在配置邮件服务器的时候写到相应配置文件 里面),如果收件人公司邮件服务器配置了收件人地址验证的话,它还会去验证该地址 是否存在,如果不是自己负责的域或者地址不存在,那么它会提示发件人公司邮件 服务器。反之,以“ OK ”响应发件人公司邮件服务器。
l. 
发件人公司邮件服务器向收件人公司邮件服务器发送命令  DATA  命令,请求发送 邮件;
m. 
收件人公司邮局服务器响应发件人公司邮件服务器;
n. 
发件人公司邮件服务器向收件人公司邮件服务器发送邮件;
o. 
收件人公司邮局服务器响应发件人公司邮件服务器发送命令  QUIT ,终止会话。
 
 
3) POP3/IMAP  会话
通常情况下,邮件服务器软件,比如 sendmail qmail postfix ,称之为 MTA (邮件发送代理),只负责为本地邮件用户向外发送邮件和接收外部发给本地邮件用户的邮件并将外来邮件投递到本地邮件用户的邮箱里面,并不包含让用户通过邮件客户端软件读取自己在邮件服务器上的邮箱里面的邮件的功能,即 POP3 或者 IMAP 服务。用户只能通过命令行,在邮件服务器上查看自己邮箱里面的邮件,很不方便。所以,在搭建邮件服务器的时候,我们需要另外安装 POP3 或者 IMAP 服务器,以方便用户通过邮件客户端方便地收发邮件;
邮件接收流程如下:
a. 
收件人点击邮件客户端的“接收”按钮;
b. 
邮件客户端根据发件人先前的配置( POP3/IMAP  服务器【收件人公司邮局服务器】   域名或者 IP 地址,和收件人用来向 POP3/IMAP 服务器表明身份的用户名和密码),建立到收件人公司邮局服务器 110 号端口( POP3 )或者 445 端口( IMAP )的 TCP 连接;
c.  收件人公司邮局服务器响应收件人邮件客户端,表明自己已经准备就绪,可以接收 命令。
d.  收件人邮件客户端向收件人公司邮局服务器发送命令  USER < 用户名>
e.  收件人公司邮局服务器响应收件人邮件客户端,请求发送密码;
f.  收件人邮件客户端向收件人公司邮局服务器发送命令  PASS  <密码>
g.  收件人公司邮局服务器验证收件人邮件客户端发送的用户名和密码,并把验证结果 通知收件人邮件客户端,如果验证失败,则断开连接;
           通常情况下,我们可以将邮件服务器配置为根据邮件服务器本机上的帐户来验证 用户身份,或者根据外部用户数据库来验证,比如 LDAP 等等。
h.  收件人邮件客户端向收件人公司邮局服务器发送邮件操作命令,比如 STAT UIDL LIST
i.  收件人邮件客户端向收件人公司邮局服务器发送命令  QUIT ,中止会话;
 
4)总结
        一台配置得当的邮件服务器,本身已经包含了多种防垃圾邮件技术。一旦我们理解并掌 握了邮件收发的整个流程,不管是配置邮件服务器,还是配置防垃圾邮件网关,我们都能够做到不但知道要做什么,而且知道为什么要那样做。

本文出自 “邹可见” 博客,请务必保留此出处http://zoukejian.blog.51cto.com/131276/87514


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值