网络公有协议之邮件SMTP篇

1、写在开始之前

之前在工作中也是遇到过smtp协议,那个时候因为解决出现的bug比较急,所以并没有仔细去学习或者深入了解smtp相关知识,刚好最近工作又碰到相关问题,因为bug的奇怪,所以不得不放下手头的相关工作,好好研究了下smtp协议的相关流程和具体实施,所以记录下来和大家一起分享。

2、smtp理论基础知识

smpt(全称为 simple mail transfer protocol),中文的意思也就是简单的邮件传输协议,它是一组用于有源地址到目的地址传输邮件的规则,是由它来控制信件的中转方式。其实关于smtp协议在百度百科上讲了非常明白了,我也主要通过这里的相关介绍,然后自己实践代码抓包分析服务器回应回应来深入学习的。例如:当你的一个朋友向你发送邮件时,他的邮件服务器和你的邮件服务器假设是通过SMTP协议通信,将邮件传递给你邮件地址所指示的邮件服务器上,然后你的客户端通过POP3或SMPT协议与邮件服务器交互,将邮件信息传递到客户端。这就完成了一个发送的过程,可以参考百度百科上的例图的主要流程,具体的交互过程细节以及代码实现,将下面继续为大家逐步分析

3、smtp交互流程

SMTP的命令和响应都是基于文本,以命令行为单位,换行符为CR/LF。响应信息一般只有一行,由一个3位数的代码开始,代表你发送后的响应结果,后面则是附上很简短的文字说明。

SMTP要经过建立连接、传送邮件和释放连接3个阶段。具体为:

a TCP连接。

b 客户端向服务器发送EHLO命令以标识发件人自己的身份,并发送自身的地址和密码通过认证,然后客户端发送MAIL命令。

c 服务器端以OK作为响应,表示准备接收。

d 客户端发送MAIL FROM和RCPT TO命令,表明发送方和接收方,当然接收方可以多个。

e 服务器端表示是否愿意为收件人接收邮件。

f  协商结束,发送邮件,用命令DATA发送输入内容。

g 结束此次发送,发送‘.‘和QUIT命令退出。

C:telnet smtp.163.com 25   /* 以telnet方式连接163邮件服务器 */

S:220 163.com Anti-spam GT for Coremail System (163com[071018]) /* 220为响应数字,其后的为欢迎信息,会应服务器不同而不同*/

C:HELO smtp.163.com /* HELO 后用来填写返回域名(具体含义请参阅RFC821),但该命令并不检查后面的参数*/

S:250 OK

C: MAIL FROM: [email protected] /* 发送者邮箱 */

S:250 … ./* “…”代表省略了一些可读信息 */

C:RCPT TO: [email protected] /* 接收者邮箱 */

S:250 … ./* “…”代表省略了一些可读信息 */

C:DATA  /* 请求发送数据 */

S:354 Enter mail, end with "." on a line by itself

C:Enjoy Protocol Studing

C:.

S:250 Message sent

C:QUIT /* 退出连接 */

S:221 Bye

大致流程也就如上所示了,当然后面如果发送附件的话,也是在邮件体后面添加就好,有几点需要提醒下大家:

1、每个命令都需要以CR+LF结束,且不能有多余的信息,否则服务器会直接返回命令未实现或者格式不对。

2、每次操作成功后,服务器响应操作正确的返回值并不是都相同的

3、最后发送完以后,也需要发送一个boundary,并且结尾需要再加上‘--‘

4、具体代码实现

首先是和smtp协议交互的相互信息:

/*
 @remark:发送邮件之前到smtp协议交互和认证
 @param : param [in] 邮件用户相关信息
          len   [in] the length of param
 @return: 0 success and others failed
 */
int smtp_start_server(void *param, int len)
{
	char smtpSnd[96];
	if( param == NULL || len != sizeof(SmtpInfo_S))
	{
		DBG_SMTP_INFO(" param error!\n");
		return -1;
	}

	SmtpInfo_S *pSmtpInfo = (SmtpInfo_S *)param;
        /************ 1 step: connect to the smtp server ************************************/
	char srvPort[8];
	memset(srvPort, 0, 8);
	sprintf(srvPort, "%d", pSmtpInfo->smtpPort);
	int smtpSock = hi_tcp_noblock_connect(NULL, NULL, pSmtpInfo->smtpSrv, srvPort, SMTP_TIMEOUT);
	if( smtpSock <= 0 ||
			smtp_rcvfrom_server(smtpSock) != 220 ) // 220  is this option success
	{
		DBG_SMTP_INFO("connect %s failed:%s", pSmtpInfo->smtpSrv, strerror(errno));
		goto SMTP_ERROR;
	}

        /************ 2 step: send  'EHLO'***************************************/
	memset(smtpSnd, 0, 96);
	sprintf(smtpSnd, "EHLO %s\r\n", pSmtpInfo->smtpSrv);
	if( smtp_sendto_server(smtpSock, smtpSnd, strlen(smtpSnd)) != 0 ||
			(smtp_rcvfrom_server(smtpSock) != 250 )) // 250 is this option success
	{
		DBG_SMTP_INFO(" EHLO failed\n");
		goto SMTP_ERROR;
	}

        /************ 3 step: auth login *************************************/
	memset(smtpSnd, 0, 96);
	strcpy(smtpSnd, "AUTH LOGIN\r\n");
	if( smtp_sendto_server(smtpSock, smtpSnd, strlen(smtpSnd)) != 0 ||
			(smtp_rcvfrom_server(smtpSock) !=  334)) // 334 is this option success
	{
		DBG_SMTP_INFO("Auth login failed\n");
		goto SMTP_ERROR;
	}

	/* send username */
	memset(smtpSnd, 0, 96);
	base64_bits_to_64((unsigned char *)smtpSnd, (unsigned char *)pSmtpInfo->smtpFromUsername, strlen(pSmtpInfo->smtpFromUsername));
	strcat(smtpSnd, "\r\n");
	if( smtp_sendto_server(smtpSock, smtpSnd, strlen(smtpSnd)) != 0 ||
			(smtp_rcvfrom_server(smtpSock) != 334 )) // 334 is this option success
	{
		DBG_SMTP_INFO(" Auth username failed\n");
		goto SMTP_ERROR;
	}
	/* send password */
	memset(smtpSnd, 0, 96);
	base64_bits_to_64((unsigned char *)smtpSnd, (unsigned char *)pSmtpInfo->smtpFromPassword, strlen(pSmtpInfo->smtpFromPassword));
	strcat(smtpSnd, "\r\n");
	if( smtp_sendto_server(smtpSock, smtpSnd, strlen(smtpSnd)) != 0 ||
			(smtp_rcvfrom_server(smtpSock) != 235 )) // 235 is auth option success
	{
		DBG_SMTP_INFO(" Auth password failed\n");
		goto SMTP_ERROR;
	}

	/****************** 4 step: start to send mail ***********************************/
	if( smtp_send_email_start(smtpSock, pSmtpInfo) != 0 )
		goto SMTP_ERROR;

	/* 5 step: end to send mail */
	if(smtp_send_email_end(smtpSock) != 0)
		goto SMTP_ERROR;
	return 0;
SMTP_ERROR:
	return -1;
}

当完成基本的协议需要操作后就需要发送邮件实际消息,借口实现如下:

/*
 @remark:send email
 @param :param all [in]
 @return: 0 success, and -1 is failed
 */
int smtp_send_email_start(int sockfd, SmtpInfo_S *pSmtp)
{
	int dst_num = 0;
	char smtpField[96];
	char smtpHeader[256];
	char smtpbody[SMTP_BODY_SIZE];
	if( sockfd <=0 || pSmtp == NULL )
	{
		DBG_SMTP_INFO(" param error!\n");
		return -1;
	}
	/********************** 1 step: send the src address *********************************/
	memset(smtpField, 0, 96);
	sprintf(smtpField, "MAIL FROM: <%s>\r\n", pSmtp->smtpFromUsername);
	if( smtp_sendto_server(sockfd, smtpField, strlen(smtpField)) != 0 ||
			(smtp_rcvfrom_server(sockfd) != 250 )) // 250 is this option success
	{
		DBG_SMTP_INFO(" send src mail address failed\n");
		goto SMTP_SEND_ERR;
	}
        /************************* 2 step: send the dst address *********************/
	for(dst_num =0; dst_num < 1; dst_num ++)//这里可以循环发送多个接收方
	{
		memset(smtpField, 0, 96);
		sprintf(smtpField, "RCPT TO: <%s>\r\n", pSmtp->smtpFromToUsername[dst_num]);
		if( smtp_sendto_server(sockfd, smtpField, strlen(smtpField)) != 0 ||
				(smtp_rcvfrom_server(sockfd) != 250 )) // 250 is this option success
		{
			DBG_SMTP_INFO(" send %d dst mail address failed\n", dst_num +1);
			goto SMTP_SEND_ERR;
		}
	}
	/************************ 3 step: send  'DATA' ****************************/
	memset(smtpField, 0, 96);
	strcpy(smtpField, "DATA\r\n");
	if( smtp_sendto_server(sockfd, smtpField, strlen(smtpField)) != 0 ||
			(smtp_rcvfrom_server(sockfd) != 354 )) // 354 is this option success
	{
		DBG_SMTP_INFO(" send 'DATA' field failed\n");
		goto SMTP_SEND_ERR;
	}
	//这里才是真正开始发送数据,前面都是确认为smtp协议的铺垫工作
	/********************** 4 step: send mail header *****************************/
	memset(smtpHeader, 0, 256);
	sprintf(smtpHeader, SMTP_HEARDER_FORMAT,
			pSmtp->smtpFromUsername,
			pSmtp->smtpFromToUsername[0],
			(char *)"SMTP-Test");
	DBG_SMTP_INFO("Header:%s\n", smtpHeader);
	if( smtp_sendto_server(sockfd, smtpHeader, strlen(smtpHeader)) != 0)
	{
		DBG_SMTP_INFO(" send smtp header field failed\n");
		goto SMTP_SEND_ERR;
	}
	/********************* 5 step: send mail body ******************************/
	memset(smtpbody, 0, SMTP_BODY_SIZE);
	sprintf(smtpbody, SMTP_CONTENT_FORMAT,
			(char *)"just for test the smtp protocol!!!!!");
	DBG_SMTP_INFO("body:\n%s\n", smtpbody);
	if( smtp_sendto_server(sockfd, smtpbody, strlen(smtpbody)) != 0)
	{
		DBG_SMTP_INFO(" send smtp body field failed\n");
		goto SMTP_SEND_ERR;
	}
	return 0;
SMTP_SEND_ERR:
	return -1;
}

最后发送完结束后,需要发送‘.‘和QUIT信令,如下:

/*
 @remark: send the quit field msg
 @param : sockfd [in]
 @return: 0 success, and -1 is failed
 */
int smtp_send_email_end(int sockfd)
{
	char smtpField[48];
	/*************** 1 step: send the last boundary ************************/
	memset(smtpField, 0, 48);
	strcpy(smtpField, "\r\n--smtp-test-boundary--\r\n");
	if( smtp_sendto_server(sockfd, smtpField, strlen(smtpField)) != 0)
	{
		DBG_SMTP_INFO(" send last boundary field failed\n");
		return -1;
	}
	/**************** 2 step: send '.' ************************************/
	memset(smtpField, 0, 48);
	strcpy(smtpField, "\r\n.\r\n");
	if( smtp_sendto_server(sockfd, smtpField, strlen(smtpField)) != 0 ||
			(smtp_rcvfrom_server(sockfd) != 250 )) // 250 is this option success
	{
		DBG_SMTP_INFO(" send '.' field failed\n");
		return -1;
	}

	/**************** 3 step: send 'QUIT' *********************************/
	memset(smtpField, 0, 48);
	strcpy(smtpField, "QUIT\r\n");
	if( smtp_sendto_server(sockfd, smtpField, strlen(smtpField)) != 0 ||
			(smtp_rcvfrom_server(sockfd) != 221 )) // 250 is this option success
	{
		DBG_SMTP_INFO(" send 'QUIT' field failed\n");
		return -1;
	}

	return 0;
}

在发送邮件的过程中定义的Header和content结构如下:

// DEBUG
#define DBG_SMTP_INFO(pFmt, ...)  	do{		fprintf(stderr, "[SMTP_DBG]-[%s]-[%d]:"pFmt, __func__, __LINE__, ##__VA_ARGS__);		fflush(stderr);	}while(0)

// SMTP Header def
#define SMTP_HEARDER_FORMAT 	"From:%s\r\n"	"To:%s\r\n"	"Subject:%s\r\n"	"MIME-Version:1.0\r\n"	"Content-type:multipart/mixed;boundary=\"smtp-test-boundary\"\r\n"	"\r\n"
// SMTP Content def
#define SMTP_CONTENT_FORMAT 	"\r\n--smtp-test-boundary\r\n"	"Content-type:text/plain; charset=utf-8\r\n"	"Content-Transfer-Encoding: 7bit\r\n"	"\r\n"	"%s\r\n"
/* mail user info  */
typedef struct _SmtpInfo_S_
{
	char smtpSrv[16];
	int smtpPort;

	char smtpFrom[32];
	char smtpFromUsername[32];
	char smtpFromPassword[32];
	char smtpFromToUsername[3][32];  //最多三个接收者

	char smtpSSLFlag;
	char smtpReserverd[7];
}SmtpInfo_S;

注意点:在头中定义的boudary = smtp-test-boundary,那么在后面的内容或者附件的每次开始的时候都需要加上“--smtp-test-boundary”,并且在邮件体发送结束后,则需要加上“--smtp-test-boundary--”("smtp-test-boundary"的值可以根据自己定义,只要保持和头中的一致即可)。

5 、抓包对比分析

整个smtp协议的交互流程就走完,下面是通过程序的分析如下:

如上图所示了,对于红色标出部分即为boudary,每次email信息体都需要包含独自一个开头,但是最后之需要一个结尾,注意结         尾和开头的不同

6、相关错误码对比,各个动作返回的错误码对比如下:

‘*************************

‘*   邮件服务返回代码含义

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

‘*   501   参数格式错误

‘*   502   命令不可实现

‘*   503   错误的命令序列

‘*   504   命令参数不可实现

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

‘*   214   帮助信息

‘*   220     服务就绪

‘*   221     服务关闭传输信道

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

‘*   250   要求的邮件操作完成

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

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

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

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

‘*   551   用户非本地,请尝试

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

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

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

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

‘*   554   操作失败

‘*   535   用户验证失败

‘*   235   用户验证成功

‘*   334   等待用户输入验证信息

8 尾声

匆匆写下,可能还有诸多细节没有点出,如有疑惑,就留言相互请教学习,交流也是一种学习方式。

本文借助的相关参考:

http://blog.csdn.net/bripengandre/article/details/2191048

http://linux.chinaunix.net/techdoc/system/2008/09/06/1030551.shtml

http://baike.baidu.com/view/5450.htm?fr=aladdin

时间: 2024-12-26 08:23:48

网络公有协议之邮件SMTP篇的相关文章

linux服务之邮件-smtp协议

yum install nc nc用来取代telnet 这里我们希望让大家知道网络协议中的一个经验:参数越多,死得越快:参数越少,越能持久.参数太多了,根本不利于使用,无法推广,早晚会被别的协议取代.这也符合科学的基础原理,简单. smtp协议SMTP也是个请求/响应协议,命令和响应都是基于ASCⅡ文本,并以CR和LF符结束.响应包括一个表示返回状态的三位数字代码.SMTP在TCP协议25号端口监听连续请求.由于这个协议开始是基于纯ASCⅡ文本的,在二进制文件上处理得并不好.后来开发了用来编码二

若腾网络大站协议邮件群发软件不换ip不用小号

若腾网络大站协议邮件群发软件 千呼万唤始出来,大家期待的大站协议邮件群发软件终于正式售卖啦! 功能强大,而且容易上手.  若腾网络大站协议邮件群发软件Q&A 1.购买软件后你们能提供什么? 我们提供傻瓜式标准化的方法视频教程+技术软件+全部配套工具给你,并保证你学会使用独立操作,教你如何提取邮件集群服务器的发信接口,用于群发.故我们提供的不仅仅是软件,更重要的是群发技术和群发思维! 2.你们的软件和方法好用么? 分两个层次说明: ①基础比较好的客户,可以自己制作私人的发送协议(我们客服会教你 !

linux网络编程之TCP/IP基础篇(一)

从今天起,将会接触到网络编程,平台是linux,实现语言C语言,最后将会实现一个简易的miniftp服务器. 主要的内容安排为:linux网络编程之TCP/IP基础篇,SOCKET编程篇,进程间通信篇,线程篇,实战ftp篇. 1.ISO/OSI参考模型:open system interconnection开放系统互联模型是由OSI(international organization for standardization )国际标准化组织定义的网络分层模型,共七层. 各层的具体含义: 物理层

网络传输协议

网络传输协议 1.常见协议 1.HTTP.HTTPS 超文本传输协议 2.FTP 文件传输协议 3.SMTP 简单邮件传输协议 2.http协议 超文本传输协议(HTTP,HyperText Transfer Protocol) 网站是基于HTTP协议的, 例如网站的图片.CSS.JS等都是基于HTTP协议进行传输的. HTML Hypertext Markup Language HTTP协议是由从客户机到服务器的请求(Request)和从服务器到客户机的响应(Response)进行了约束和规范

PHP-02.文件上传、php保存/转移上传的文件、常见的网络传输协议、请求报文及属性、响应报文及属性

关系数组 array("key"=>"value",...) ; get没有数据大小的限制 post上传大小没有限制 不指定上传方式,默认是get 文件上传 需要在html中 form属性中添加 enctype = "multipart/form-data" <!-- 上传文件必须设置 enctype ='multipart/form-data' --> <form action="text01.php"

tcp/ip (网络通讯协议)

介绍 TCP: 传输控制协议, IP: 网际协议, TCP/IP: 供已连接互联网的计算机之间进行通信的通信协议 在tcp/ip内部 , 包含一系列处理数据通信的协议: tcp.udp.icmp.dhcp ip负责将每个包路由至它的目的地.(网络层) tcp用于应用程序之间的通信, 负责处理ip包.(传输层) RFC是tcp/ip协议的标准文档. tcp/ip, 又名: 网络通讯协议. 寻址 ip地址: 每个计算机必须有ip地址才能够接入互联网, 每个ip包必须有一个地址才能够发送到目的计算机.

学习笔记之邮件发送篇

用脚本语言发送邮件是系统管理员必备技能 对系统定期检查或者当服务器受到攻击时生成文档和报表. 发布这些文档最快速有效的方法就是发送邮件. python中email模块使得处理邮件变得比较简单 发送邮件主要用到了smtplib和email两个模块,这里首先就两个模块进行一下简单的介绍: 本段摘录于    http://www.cnblogs.com/xiaowuyi/archive/2012/03/17/2404015.html 1.smtplib模块 smtplib.SMTP([host[, p

【Python3】POP3协议收邮件

初学Python3,做一个email的例子,虽然知道做的很渣渣,还是分享一下吧 POP3协议 POP3全称Post Official Protocol3,即邮局协议的第三个版本,它规定了怎样将个人计算机连接到Internet的邮件服务器和下载电子邮件的电子协议,它是因特网电子协议的第一个离线标准,POP3允许用户从服务器上将邮件存储到本地主机(个人计算机),同时删除保存在邮件服务器上的邮件,而POP3服务器则是遵循POP3协议的邮件服务器,用来接收电子邮件. Python3支持POP3的模块是p

Raknet是一个基于UDP网络传输协议的C++网络库(还有一些其它库,比如nanomsg,fastsocket等等)

Raknet是一个基于UDP网络传输协议的C++网络库,允许程序员在他们自己的程序中实现高效的网络传输服务.通常情况下用于游戏,但也可以用于其它项目. Raknet有以下好处: 高性能 在同一台计算机上,Radnet可以实现在两个程序之间每秒传输25,000条信息: 容易使用 Raknet有在线用户手册,视频教程.每一个函数和类都有详细的讲解,每一个功能都有自己的例程 跨平台,当前Raknet支持Windows, Linux, Macs,可以建立在Visual Studio, GCC, Code