SMTP

事先声明,整个过程以LOGIN认证方式为例,其他认证方式大同小异。按照时间顺序,主要分为22个步骤。

1、客户端TCP连接服务器25端口;

2、三次握手以后,连接建立成功,服务器主动推送服务就绪信息

网易邮箱一般都形如“220 163.com Anti-spam GT for Coremail System (163com[20111010])”;雅虎邮箱形如“220 smtp108.mail.gq1.yahoo.com ESMTP”;Google邮箱形如“220 mx.google.com ESMTP nw8sm917193igc.7”。其中220代表服务就绪,每一条服务就绪信息以“\r\n”为结尾标示符。    

3、客户端向服务器说明身份

交代自己认证SMTP服务器的域名,例如雅虎邮箱的SMTP服务器为smtp.mail.yahoo.com,则发送“EHLO smtp.mail.yahoo.com\r\n”。

4、如果身份有效,则服务器进入等待认证状态,主动推送自身支持的所有SMTP认证方式

网易邮箱发送的内容如下:

  1. 250-mail\r\n  
  2. 250-PIPELINING\r\n  
  3. 250-AUTH LOGIN PLAIN\r\n  
  4. 250-AUTH=LOGIN PLAIN\r\n  
  5. 250-coremail\r\n 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UFWNUp_UCa0xDrUUUUj  
  6. 250-STARTTLS\r\n  
  7. 250 8BITMIME\r\n  

表示其支持LOGIN、PLAIN两种认证方式;

雅虎邮箱发送的内容如下:

  1. 250-smtp206.mail.ne1.yahoo.com\r\n  
  2. 250-AUTH LOGIN PLAIN XYMCOOKIE\r\n  
  3. 250-PIPELINING\r\n  
  4. 250 8BITMIME\r\n  

表示其支持LOGIN、PLAIN、XYMCOOKIE三种认证方式。

5、客户端判断自身是否支持服务器提供的SMTP认证方式

如果认证方式指定“auto”则采用服务端提供的第一个认证方式,如果指定其他方式,则判断服务端是否支持该方式,否则返回错误。
这一歩相当关键,因为客户端程序可以根据具体的认证方式加载相应插件来完成认证过程。

6、客户端向服务器请求认证

发送“AUTH LOGIN\r\n”。

7、如果认证请求合理,服务器将进入等待用户输入状态

发送“334 VXNlcm5hbWU6\r\n”,334表示等待客户端输入,VXNlcm5hbWU6表示等待输入用户名。

8、客户端向服务器发送转码后的用户名

发送经过Base64转码后的用户名(taotown)”dGFvdG93bg==\r\n“。

9、服务器再次进入等待用户输入状态

发送“334 UGFzc3dvcmQ6\r\n”,334表示等待客户端输入,UGFzc3dvcmQ6表示等待输入密码

10、客户端向服务器发送转码后的密码

发送经过Base64转码后的密码(Haier)“SGFpZXI=\r\n”。

11、如果用户名或者密码出错,服务器将返回530错误,发送“530 Access denied\r\n”,表示认证失败。否则将返回235,发送“235 OK, go ahead\r\n”,表示用户认证成功。

12、客户端告诉服务器邮件来自何方

发送“MAIL FROM: <taotown@yahoo.com> \r\n”。

13、如果合理,服务端返回250表示成功

发送“250 OK , completed\r\n”。

14、客户端告诉服务器邮件去往何地

发送“RCPT TO: <taotown@126.com> \r\n”。

15、如果合理,服务器返回250表示成功

发送“250 OK , completed\r\n”。

16、客户端告诉服务器自己准备发送邮件正文

发送“DATA\r\n”。

17、服务器返回354,表示自己已经作好接受邮件的准备

发送“354 Start Mail. End with CRLF.CRLF\r\n”,提醒客户端开始发送邮件并以“.”结束。

18、客户端发送邮件正文(如果正文过长,可以分多次发送)

发送

  1. “To: taotown@126.com\r\n  
  2. Subject: Hello Trevor\r\n  
  3. My name is TaoZhen\r\n  
  4. ”。  

19、客户端发送完正文以后,紧接着发送结束符

发送“.”。

20、如果合理,服务端返回250表示成功

发送“250 OK , completed\r\n”。

21、邮件发送结束,客户端请求断开连接

发送“QUIT\r\n”。

22、服务器返回211,提示断开申请被采纳,并主动断开连接,整个邮件发送过程结束。

发送“221 Service Closing transmission\r\n”。

附:如果服务端传过来的错误码后面紧跟这”-“,则说明该次消息分了很多节,直到最后一节没有”-“为止。


资料二

在以前接触的项目中,一直都是在做网站时用到了发送mail 的功能,在asp 和.net 中都有相关的发送mail 的类, 实现起来非常简单。最近这段时间因工作需要在C++ 中使用发送mail 的功能,上网搜了一大堆资料,终于得以实现,总结自己开发过程中碰到的一些问题,希望对需的人有所帮助, 由于能力有限, 文中不免有些误解之处,望大家能指正!!

其实,使用C++ 发送mail 也是很简的事, 只需要了解一点SMTP 协议和socket 编程就OK 了, 网络上也有很多高人写好的mail 类源码,有兴趣的朋友可以下载看看.

 

1.     SMTP 常用命令简介

1). SMTP 常用命令

HELO/EHLO 向服务器标识用户身份

MAIL 初始化邮件传输

mail from:

RCPT 标识单个的邮件接收人;常在MAIL 命令后面

可有多个rcpt to:

DATA 在单个或多个RCPT 命令后,表示所有的邮件接收人已标识,并初始化数据传输,以. 结束。

VRFY 用于验证指定的用户/ 邮箱是否存在;由于安全方面的原因,服务器常禁止此命令

EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用

HELP 查询服务器支持什么命令

NOOP 无操作,服务器应响应OK

QUIT 结束会话

RSET 重置会话,当前传输被取消

 

如你对SMTP 命令不了解,可以用telnet 命令登陆到smtp 服务器用help 命令进行查看:

220 tdcsw.maintek.corpnet.asus ESMTP Sendmail 8.13.8/8.13.8; Sat, 9 Jan 2010 10:
45:09 +0800
help
214-2.0.0 This is sendmail
214-2.0.0 Topics:
214-2.0.0       HELO    EHLO    MAIL    RCPT    DATA
214-2.0.0       RSET    NOOP    QUIT    HELP    VRFY
214-2.0.0       EXPN    VERB    ETRN    DSN     AUTH
214-2.0.0       STARTTLS
214-2.0.0 For more info use "HELP <topic>".
214-2.0.0 To report bugs in the implementation see
214-2.0.0       http://www.sendmail.org/email-addresses.html
214-2.0.0 For local information send email to Postmaster at your site.
214 2.0.0 End of HELP info


2).SMTP 返回码含义

  *   邮件服务返回代码含义 

  *   500   格式错误,命令不可识别(此错误也包括命令行过长) 

  *   501   参数格式错误 

  *   502   命令不可实现 

  *   503   错误的命令序列 

  *   504   命令参数不可实现 

  *   211    系统状态或系统帮助响应 

  *   214   帮助信息 

  *   220     服务就绪 

  *   221     服务关闭传输信道 

  *   421     服务未就绪,关闭传输信道(当必须关闭时,此应答可以作为对任何命令的响应) 

  *   250   要求的邮件操作完成 

  *   251   用户非本地,将转发向 

  *   450   要求的邮件操作未完成,邮箱不可用(例如,邮箱忙) 

  *   550   要求的邮件操作未完成,邮箱不可用(例如,邮箱未找到,或不可访问) 

  *   451   放弃要求的操作;处理过程中出错 

  *   551   用户非本地,请尝试 

  *   452   系统存储不足,要求的操作未执行 

  *   552   过量的存储分配,要求的操作未执行 

  *   553   邮箱名不可用,要求的操作未执行(例如邮箱格式错误) 

  *   354   开始邮件输入,以. 结束 

  *   554   操作失败 

  *   535   用户验证失败 

  *   235   用户验证成功 

  *   334   等待用户输入验证信息 for next connection>;

 

3) SMTP 命令应用

我们下需使用telnet 命令实现smtp 邮件的发送,具体操作如下:

220 tdcsw.com ESMTP Sendmail 8.13.8/8.13.8; Wed, 23 Dec 2009 18
:18:18 +0800
HELO tdcsw
250 tdcsw.com Hello x-128-101-1-240.ahc.umn.edu [128.101.1.240], pleased to meet you
MAIL FROM:lily@tdcsw.com
250 2.1.0 lily@tdcsw.com... Sender ok
RCPR TO:sam@163.com
250 2.1.5 carven@tdcsw.pegatroncorp.com... Recipient ok
DATA
354 Enter mail, end with "." on a line by itself
SUBJECT:HELLO
HI:
HAR are you?
.
250 2.0.0 nBNAIIG4000507 Message accepted for delivery
quit
221 2.0.0 tdcsw.maintek.corpnet.asus closing connection
Connection to host lost.

 

2.     用C++ 实现Mail 发送

为了便于理解, 在此就不封装Mail 类了, 而是以过程式函数方式给出.

1). 首先需要建立TCP 套接字, 连接端口依服务器而定,SMTP 服务默认端口为25, 我们以 默认端口为例

WSADATA wsaData;

int  SockFD;

WSAStartup(MAKEWORD(2,2), &wsaData);

SockFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

ServAddr.sin_family = AF_INET;

ServAddr.sin_addr.s_addr = inet_addr (“192.168.1.1”);             //192.168.1.1 为服务器地址

ServAddr.sin_port = htons(25);

connect(SockFD, (struct sockaddr *)&ServAddr, sizeof(ServAddr));

2). 发送SMTP 命令及数据

const char HEADER[] = "HELO smtpSrv/r/n"

  "MAIL FROM: sender@126.com/r/n"

  "RCPT TO: recv@gmail.com/r/n"

  "DATA/r/n"

  "FROM: sender@126.com/r/n"

  "TO: recv@gmail.com/r/n"

  "SUBJECT: this is a test/r/n"

  "Date: Fri, 8 Jan 2010 16:12:30/r/n"

"X-Mailer: shadowstar's mailer/r/n"

  "MIME-Version: 1.0/r/n"

  "Content-type: text/plain/r/n/r/n";

//send HEADER

send(SockFD, HEADER, strlen(HEADER), 0);

 

const char CONTENT[]="this is content./r/n";

//send CONTENT

send(SockFD, CONTENT, strlen(CONTENT), 0);

 

send(SockFD, "./r/n", strlen("./r/n"), 0);   //end

send(SockFD, "QUIT/r/n", strlen("QUIT/r/n"), 0); //quit

 

mail 发送的功能基本上就完成了, 当然, 如果是应用的话还是需要很多改动的地方的, 比如说添加附件等.

3). 附件功能

要使用SMTP 发送附件, 需要对SMTP 头信息进行说明, 改变Content-type 及为每一段正文添加BOUNDARY名, 示例如下:

"DATA/r/n"

  "FROM: sender@126.com/r/n"

  "TO: recv@gmail.com/r/n"

  "SUBJECT: this is a test/r/n"

  "Date: Fri, 8 Jan 2010 16:12:30/r/n"

"X-Mailer: shadowstar's mailer/r/n"

  "MIME-Version: 1.0/r/n"

  "Content-type: multipart/mixed; boundary=/"#BOUNDARY#/"/r/n/r/n";

 

// 正文

"--#BOUNDARY#/r/n"

  "Content-Type: text/plain; charset=gb2312/r/n"

  "Content-Transfer-Encoding: quoted-printable/r/n"

邮件正文……….

 

// 附件

"/r/n--#BOUNDARY#/r/n"

  "Content-Type: application/octet-stream; name=att.txt/r/n"

  "Content-Disposition: attachment; filename=att.txt/r/n"

  "Content-Transfer-Encoding: base64/r/n"

  "/r/n"

附件正信息(base64 编码)…..

 

Base64 编码函数在网络上很容易找到, 这里就不给出源码了, 如需要支持HTML 格式而又不知道如何写这些头信息, 可以用outlook 或foxmail 写一封支持HTML 格式的mail, 查看其原文信息, 依照相同的格式发送就行了.

 

4). 实现抄送及密送

在SMTP 命令集中并没有RCPT CC 或RCPT BCC 相关命令, 那要如何来实现抄送和密送功能呢?

在网络上找到这样一句话: “ 所有的接收者协商都通过RCPT TO 命令来实现,如果是BCC ,则协商发送后在对方接收时被删掉信封接收者”, 开始一直不明白这句话是什么意思? 后来通看查看foxmail 的邮件原文发现:

Date: Wed, 6 Jan 2010 12:11:48 +0800

From: "carven_li" < carven_li @smtp.com>

To: "carven" <carven@smtp.com>

Cc: "sam" <sam@smtp.com>,

  "yoyo" <yoyo@smtp.com>

BCC: "clara" <clara@tsmtp.com>

Subject: t

X-mailer: Foxmail 5.0 [cn]

Mime-Version: 1.0

Content-Type: multipart/mixed;

    boundary="=====001_Dragon237244850520_====="

才恍然大悟, 所谓的” 协商” 应该就是指发送方在Data 中指定哪些为CC, 哪些为BCC, 默认情况下什么都不写, 只发送第一个RCPT TO 的mail, 其他的都被过滤掉

3. SMTP身份认证  
SMTP身份认证方式有很多种, 每种认证方式验证发送的信息都有点细微的差别, 这里我主要介绍下LOGIN,PLAIN及NTLM三种简单的认证方式, 附带CRAM-MD5和DIGEST-MD5方式(验证没通过, 不知道问题出在哪了? 有待高人帮忙解决!).

要进行身份认证, 先要知道当前SMTP服务器支持哪些认证方式, 在ESMTP中有个与HELO命令相同功能的命令EHLO可以得到当前服务器支持的认证方式(有些服务器无返回信息, 可能服务器端作了限制).
 

1) LOGIN认证方式
LOGIN认证方式是基于明文传输的, 因此没什么安全性可言, 如信息被截获, 那么用户名和密码也就泄露了. 认证过程如下:
AUTH LOGIN
334 VXNlcm5hbWU6                            //服务器返回信息, Base64编码的Username:
bXlOYW1l                                //输入用户名, 也需Base64编码
334 UGFzc3dvcmQ6                            //服务器返回信息, Base64编码的Password::
bXlQYXNzd29yZA==                            //输入密码, 也需Base64编码
235 2.0.0 OK Authenticated                        // 535 5.7.0 authentication failed

2). NTLM认证方式
NTLM认证方式过程与LOGIN认证方式是一模一样的, 只需将AUTH LOGN改成AUTH NTLM.就行了.

3). PLAIN认证方式
PLAIN认证方式消息过过程与LOGIN和NTLM有所不同, 其格式为: “NULL+UserName+NULL+Password”, 其中NULL为C语言中的’/0’, 不方便使用命令行测试, 因此下面给出C++代码来实现:
char szSend[] = "$user$pwd";
size_t n = stlen(szSend);
for(int i=0; i<n; i++)
    if(szSend[i] == '$') szSend[i] = '/0';

char szMsg[512]
base64_encode(szSend, n, szMsg);
send (skt, szMsg, strlen(szMsg), 0);

4). CRAM-MD5认证方式
前面所介绍的三种方式, 都是将用户名和密码经过BASE64编码后直接发送到服务器端的, BASE64编码并不是一种安全的加密算法, 其所有信息都可能通过反编码, 没有什么安全性可言. 而CRAM-MD5方式与前三种不同, 它是基于Challenge/Response的方式, 其中Challenge是由服务器产生的, 每次连接产生的Challenge都不同, 而Response是由用户名,密码,Challenge组合而成的, 具体格式如下:
response=base64_encode(username : H_MAC(challenge, password))
H_MAC是Keyed MD5算法(见http://www.faqs.org/rfcs/rfc2195.html), 先由challenge和password生成16位的散列码, 将其转换成16进制32个字节的字符串数组digest(即以%02x输出), 再对(username+空格+digest[32])进行base64编码,就是要发送的response了.
另外, 在http://www.net-track.ch/opensource/cmd5/提供了SMTP CRAM-MD5认证源码, 可用于测试CRAM-MD5认证, 但不知道是不是我这边测试的SendMail服务器配置有问题, 测试时一直不能通过.

5). DIGEST-MD5认证方式
DIGEST-MD5认证也是Challenge/Response的方式, 与CRAM-MD5相比, 它的Challenge信息更多, 其Response计算方式也非常复杂, 我在测试时也是以认证失败而告终, 只是将在网上找到的资料整理于此, 能为后来研究的人多提供点资料, 或者有兴趣的朋友们可以和我一起讨论下.

我们先看下DIGEST-MD5认证发送响应信息:

DIGEST-MD5服务器格式说明(见RFC 2831 Digest SASL Mechanism Mai 2000):
   digest-challenge =
         1 # (Reich | Nonce | qop-Optionen | schal | MAXBUF | charset
               Algorithmus | Chiffre-opts | auth-param)

        realm = "Reich" "=" < "> Reich-Wert <">
        Reich-Wert = qdstr-val
        Nonce = "Nonce" "=" < "> Nonce-Wert <">
        Nonce-Wert = qdstr-val
        qop-options = "qop" "=" < "> qop-Liste <">
        qop-list = 1 # qop-Wert
        qop-Wert = "auth" | "auth-int" | "auth-conf" |
                             Token
        stale = "veraltete" "=" "true"
        MAXBUF = "MAXBUF" "=" MAXBUF-Wert
        MAXBUF-Wert = 1 * DIGIT
        charset = "charset" = "" UTF-8 "
        algorithm = "Algorithmus" "=" "md5-sess"
        Chiffre-opts = "Chiffre" "=" < "> 1 # Null-Wert <">
        Chiffre-value = "3des" | "des" | "RC4-40" | "RC4" |
                            "RC4-56" | Token
        auth-param = Token "=" (token | quoted-string)
DIGEST-MD5客户端响应格式说明(见RFC 2831 Digest SASL Mechanism Mai 2000):
   digest-response = 1 # (Benutzername | Reich | Nonce | cnonce |
                          Nonce-count | qop | digest-uri | Antwort |
                          MAXBUF | charset | Chiffre | authzid |
                          auth-param)

       username = "username" = "<"> username-Wert < ">
       Benutzernamen-Wert = qdstr-val
       cnonce = "cnonce" "=" < "> cnonce-Wert <">
       cnonce-Wert = qdstr-val
       Nonce-count = "nc" "=" nc-Wert
       nc-Wert = 8LHEX
       qop = "qop" "=" qop-Wert
       digest-uri = "digest-uri" = "<"> digest-uri-value < ">
       digest-uri-value = serv-type "/" host [ "/" serv-name]   //eg: smtp/mail3.example.com/example.com
       serv-type = 1 * ALPHA            //www for web-service, ftp for ftp-dienst, SMTP for mail-versand-service …
       host = 1 * (ALPHA | DIGIT | "-" | ".")
       serv-name = host
       response = "Antwort" "=" Response-Wert
       response-value = 32LHEX
       LHEX = "0" | "1" | "2" | "3" |
                          "4" | "5" | "6" | "7" |
                          "8" | "9" | "a" | "b" |
                          "c" | "d" | "e" | "f"
       cipher = "Chiffre" "=" Null-Wert
       authzid = "authzid" "=" < "> authzid-Wert <">
       authzid-Wert = qdstr-val

其各字段具体含义见相关文档, 这里只介始几个需要用到的字段是如何产生的, C/S响应示例如下:
    S: realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
       algorithm=md5-sess,charset=utf-8
    C: charset=utf-8,username="chris",realm="elwood.innosoft.com",
       nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
       digest-uri="imap/elwood.innosoft.com",
       response=d388dad90d4bbd760a152321f2143af7,qop=auth
    S: rspauth=ea40f60335c427b5527b84dbabcdfffd

    The password in this example was "secret".
从这个示例可以看出, 客户端返回的信息比服务器端发送过来的多了以下几个:
username, nc, cnonce, digest-uri和respone
username就不用说了, nc是8位长的16进制数字符串,统计客户端使用nonce发出请求的次数(包含当前请求),例示我们可以设为”00000001”, cnonce是是用了4个随机数组成一个8位长16进制的字符串,digest-uri是由在realm前加上请求类型(如http, smtp等), response是一个32位长的16进制数组, 计算公式如下:
If the "qop" value is "auth" or "auth-int":
      request-digest  = <"> < KD ( H(A1),     unq(nonce-value)
                                          ":" nc-value
                                          ":" unq(cnonce-value)
                                          ":" unq(qop-value)
                                          ":" H(A2)
                                  ) <">
   If the "qop" directive is not present (this construction is for
   compatibility with RFC 2069):
      request-digest  =
                 <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) >
   <">
   See below for the definitions for A1 and A2.
Read more: http://www.faqs.org/rfcs/rfc2617.html#ixzz0c4s8ck3F

KD(secret,data)表示分类算法,其中data指数据,secret表示采用的方法.如果表示校验和算法时,data要写成H(data);而unq(X)表示将带引号字符串的引号去掉。       
           对于"MD5" 和"MD5-sess" 算法: 
H(data) = MD5(data)
和 
KD(secret, data) = H(concat(secret, ":", data))

如果"algorithm"指定为"MD5"或没有指定,A1计算方式如下:
A1  =  unq(username-value) ":" unq(realm-value) ":" passwd
//Password为用户密码
如果"algorithm"指定为"MD5-sess", 则需要nonce和cnonce的参与:
A1       = H(unq(username-value) ":" unq(realm-value) ":" passwd )
                     ":" unq(nonce-value) ":" unq(cnonce-value)

如果"qop"没有指定或指定为"auth", A2计算方式如下:
A2  = Method ":" digest-uri-value
如果"qop"没有指定或指定为"auth int", A2计算方式如下:
A2 = Method ":" digest-uri-value ":" H(entity-body)

Method是http请求时的方法(post,get), 由于英文水平比较差, 很多都看不明白, 有兴趣的朋友可以自己去看看原文(http://www.faqs.org/rfcs/rfc2617.html), 这里还提供了DIGEST验证的代码:
   File "digcalc.h":

#define HASHLEN 16
typedef char HASH[HASHLEN];
#define HASHHEXLEN 32
typedef char HASHHEX[HASHHEXLEN+1];
#define IN
#define OUT

/* calculate H(A1) as per HTTP Digest spec */
void DigestCalcHA1(
    IN char * pszAlg,
    IN char * pszUserName,
    IN char * pszRealm,
    IN char * pszPassword,
    IN char * pszNonce,
    IN char * pszCNonce,
    OUT HASHHEX SessionKey
    );

/* calculate request-digest/response-digest as per HTTP Digest spec */
void DigestCalcResponse(
    IN HASHHEX HA1,           /* H(A1) */
    IN char * pszNonce,       /* nonce from server */
    IN char * pszNonceCount,  /* 8 hex digits */
    IN char * pszCNonce,      /* client nonce */
    IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
    IN char * pszMethod,      /* method from the request */
    IN char * pszDigestUri,   /* requested URL */
    IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
    OUT HASHHEX Response      /* request-digest or response-digest */
    );

File "digcalc.c":

#include <global.h>
#include <md5.h>

#include <string.h>
#include "digcalc.h"

void CvtHex(
    IN HASH Bin,
    OUT HASHHEX Hex
    )
{
    unsigned short i;
    unsigned char j;

    for (i = 0; i < HASHLEN; i++) {
        j = (Bin[i] >> 4) & 0xf;
        if (j <= 9)
            Hex[i*2] = (j + '0');
         else
            Hex[i*2] = (j + 'a' - 10);
        j = Bin[i] & 0xf;
        if (j <= 9)
            Hex[i*2+1] = (j + '0');
         else
            Hex[i*2+1] = (j + 'a' - 10);
    };
    Hex[HASHHEXLEN] = '/0';
};

/* calculate H(A1) as per spec */
void DigestCalcHA1(
    IN char * pszAlg,
    IN char * pszUserName,
    IN char * pszRealm,
    IN char * pszPassword,
    IN char * pszNonce,
    IN char * pszCNonce,
    OUT HASHHEX SessionKey
    )
{
      MD5_CTX Md5Ctx;
      HASH HA1;

      MD5Init(&Md5Ctx);
      MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName));
      MD5Update(&Md5Ctx, ":", 1);
      MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm));
      MD5Update(&Md5Ctx, ":", 1);
      MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword));
      MD5Final(HA1, &Md5Ctx);
      if (stricmp(pszAlg, "md5-sess") == 0) {

            MD5Init(&Md5Ctx);
            MD5Update(&Md5Ctx, HA1, HASHLEN);
            MD5Update(&Md5Ctx, ":", 1);
            MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
            MD5Update(&Md5Ctx, ":", 1);
            MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
            MD5Final(HA1, &Md5Ctx);
      };
      CvtHex(HA1, SessionKey);
};

/* calculate request-digest/response-digest as per HTTP Digest spec */
void DigestCalcResponse(
    IN HASHHEX HA1,           /* H(A1) */
    IN char * pszNonce,       /* nonce from server */
    IN char * pszNonceCount,  /* 8 hex digits */
    IN char * pszCNonce,      /* client nonce */
    IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
    IN char * pszMethod,      /* method from the request */
    IN char * pszDigestUri,   /* requested URL */
    IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
    OUT HASHHEX Response      /* request-digest or response-digest */
    )
{
      MD5_CTX Md5Ctx;
      HASH HA2;
      HASH RespHash;
       HASHHEX HA2Hex;

      // calculate H(A2)
      MD5Init(&Md5Ctx);
      MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod));
      MD5Update(&Md5Ctx, ":", 1);
      MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));
      if (stricmp(pszQop, "auth-int") == 0) {
            MD5Update(&Md5Ctx, ":", 1);
            MD5Update(&Md5Ctx, HEntity, HASHHEXLEN);
      };
      MD5Final(HA2, &Md5Ctx);
       CvtHex(HA2, HA2Hex);

      // calculate response
      MD5Init(&Md5Ctx);
      MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
      MD5Update(&Md5Ctx, ":", 1);
      MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
      MD5Update(&Md5Ctx, ":", 1);
      if (*pszQop) {

          MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));
          MD5Update(&Md5Ctx, ":", 1);
          MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
          MD5Update(&Md5Ctx, ":", 1);
          MD5Update(&Md5Ctx, pszQop, strlen(pszQop));
          MD5Update(&Md5Ctx, ":", 1);
      };
      MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
      MD5Final(RespHash, &Md5Ctx);
      CvtHex(RespHash, Response);
};

File "digtest.c":

#include <stdio.h>
#include "digcalc.h"

void main(int argc, char ** argv) {

      char * pszNonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093";
      char * pszCNonce = "0a4f113b";
      char * pszUser = "Mufasa";
      char * pszRealm = "testrealm@host.com";
      char * pszPass = "Circle Of Life";
      char * pszAlg = "md5";
      char szNonceCount[9] = "00000001";
      char * pszMethod = "GET";
      char * pszQop = "auth";
      char * pszURI = "/dir/index.html";
      HASHHEX HA1;
      HASHHEX HA2 = "";
      HASHHEX Response;

      DigestCalcHA1(pszAlg, pszUser, pszRealm, pszPass, pszNonce,
pszCNonce, HA1);
      DigestCalcResponse(HA1, pszNonce, szNonceCount, pszCNonce, pszQop,
       pszMethod, pszURI, HA2, Response);
      printf("Response = %s/n", Response);
}




SMTP是Simple Mail Transfer Protocol的简写。

邮件是日常工作、生活中不能缺少的一个工具,下面是邮件收发的流程。

Image

邮件的发送,主要是通过SMTP协议来实现的。SMTP协议最早在RFC 821(1982年)中定义,最后更新是在RFC 5321(2008年)中,更新中包含了扩展SMTP(ESMTP)。

在平时的程序开发中经常会有发送邮件的这种需求,所以免不了要对邮件发送服务器的可用性进行测试。下面是整理的命令,用来在命令行下测试SMTP服务器,进行邮件发送等操作,相信对于加深SMTP的理解可以起到促进作用。

SMTP默认使用25端口,我们可以使用telnet工具进行测试。

1、连接SMTP服务器,查看是否存活。

     >telnet smtp.sina.com 25

     >Connected to mail.sina.com.

     >Escape character is '^]'.

     >220 smtp ready

     在telnet下转义符是Ctrol+],如果想退出,按转义符后输入quit,就可以退回到命令行了。

2、用户登陆

     连接到服务器后,使用AUTH LOGIN命令进行用户登陆(SMTP命令不区分大小写)

     >auth login

     >334 VXNlcm5hbWU6

     >c2VydmljZUBoZWVwLmNx

     >334 UGFzc3dvcmQ6

     >xxxxxxxx

     >235 go ahead

     235返回码表明登陆验证成功,用户可以进行后续的操作了。用户邮箱和密码是经过Base64编码的,这个与服务器的安全特性相关,属于可配置项。

3、发送邮件。

     用户身份验证通过后,执行下面的命令进行邮件的发送。

     >235 #2.0.0 OK Authenticated

     >MAIL FROM: yunpan001@sina.com

     >250 sender <yunpan001@sina.com> ok

     >RCPT to: cocowool@gmail.com

     >250 recipient <cocowool@gmail.com> ok

     >DATA

     >354 go ahead

     >Subject: Hi smtp mail

     >hello mail

     >.

     >250 ok:  Message 1763097690 accepted

SMTP命令列表

HELO

客户端为标识自己的身份而发送的命令(通常带域名)

EHLO


使服务器可以表明自己支持扩展简单邮件传输协议 (ESMTP) 命令。

MAIL FROM

标识邮件的发件人;以 MAIL FROM: 的形式使用。

RCPT TO

标识邮件的收件人;以 RCPT TO: 的形式使用。

TURN

允许客户端和服务器交换角色,并在相反的方向发送邮件,而不必建立新的连接。

ATRN

ATRN (Authenticated TURN) 命令可以选择将一个或多个域作为参数。如果该会话已通过身份验证,则ATRN 命令一定会被拒绝。

SIZE

提供一种使 SMTP 服务器可以指出所支持的最大邮件大小的机制。兼容的服务器必须提供大小范围,以指出可以接受的最大邮件大小。客户端发送的邮件不应大于服务器所指出的这一大小。

ETRN

SMTP 的扩展。SMTP 服务器可以发送 ETRN 以请求另一台服务器发送它所拥有的任何电子邮件。

PIPELINING

提供发送命令流(而无需在每个命令之后都等待响应)的能力。

CHUNKING

替换 DATA 命令的 ESMTP 命令。该命令使 SMTP 主机不必持续地扫描数据的末尾,它发送带参数的 BDAT 命令,该参数包含邮件的总字节数。接收方服务器计算邮件的字节数,如果邮件大小等于 BDAT 命令发送的值时,则该服务器假定它收到了全部的邮件数据。

DATA

客户端发送的、用于启动邮件内容传输的命令。

DSN

启用传递状态通知的 ESMTP 命令。

RSET

使整个邮件的处理无效,并重置缓冲区。

VRFY

确认在邮件传递过程中可以使用邮箱;例如,vrfy ted 确认在本地服务器上驻留 Ted 的邮箱。该命令在 Exchange 实现中默认关闭。

HELP

返回 SMTP 服务所支持的命令列表。

QUIT

终止会话。

SMTP命令响应码

211  System status, or system help reply 
214  Help message (Information on how to use the receiver or the meaning of a particular non-standard command; this reply is useful only to the human user) 
220  <domain> Service ready 
221  <domain> Service closing transmission channel 
250  Requested mail action okay, completed 
251  User not local; will forward to <forward-path> (See Section 3.4) 
252  Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3) 
354  Start mail input; end with <CRLF>.<CRLF> 
421  <domain> Service not available, closing transmission channel (This may be a reply to any command if the service knows it must shut down) 
450  Requested mail action not taken: mailbox unavailable (e.g., mailbox busy or temporarily blocked for policy reasons) 
451  Requested action aborted: local error in processing 
452  Requested action not taken: insufficient system storage 
455  Server unable to accommodate parameters 
500  Syntax error, command unrecognized (This may include errors such as command line too long) 
501  Syntax error in parameters or arguments502  Command not implemented (see Section 4.2.4)

503  Bad sequence of commands 
504  Command parameter not implemented 
550  Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or command rejected for policy reasons)

551  User not local; please try <forward-path> (See Section 3.4) 
552  Requested mail action aborted: exceeded storage allocation 
553  Requested action not taken: mailbox name not allowed (e.g.,mailbox syntax incorrect) 
554  Transaction failed (Or, in the case of a connection-opening response, "No SMTP service here") 
555  MAIL FROM/RCPT TO parameters not recognized or not implemente

参考资料:

1、SMTP指令说明

2、SMTP百度百科

3、Wiki Smtp

4、RFC 5321

5、电子邮件收发原理和实现

6、Telnet下Smtp命令发送邮件

7、Send Mail Using Telnet


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值