最近几日学习了一下简单电子邮件协议smtp的使用,并移植了smtp协议栈中的code,几经折返,最终成功实现邮件的发送。
一、SMTP协议流程
首先通过计算机的telnet或者TCPIP调试工具来输入命令。我是使用TCP调试工具进行的验证。
1、SMTP服务器网络地址
首先去163邮箱注册一个用户,然后在邮箱里面设置去掉SSL并开启SMTP的3个选项。之后就可以使用了,
就163邮箱来说他的服务器是smtp.163.com。为了和程序一致所以需要转换为IP地址。通过DNS可以知道他的IP地址映射。
这样就获得了一个IP地址
2、SMTP服务器端口
SMTP默认的端口是 port =25
3、会话
使用TCP连接连接这个IP地址和25端口,连接成功之后服务器会发回来一串数如下
220 163.com Anti-spam GT for Coremail System (163com[20141201])
然后需要发送helo xxx命令发送完毕 ,否则他会返回503 Error: send HELO/EHLO first xxx可以是个任意的名字他会返回
250 OK
表示完成握手接下来就是用户名和密码的认证了。需要你主动发送认证请求发送命令auth login空格+回车
334 dXNlcm5hbWU6
这是表示提示输入用户名,dXNlcm5hbWU6这个是用base64加密的username,然后输入你的xxx@163.com,同样的也需要转换为BASE64码,之后
334 UGFzc3dvcmQ6
这是表示提示输入密码了。同样的 UGFzc3dvcmQ6表示password了输入密码******转换为BASE64代码,之后
235 Authentication successful
返回这个之后表示认证成功了,接下来就是发件人和收件人的email地址,然后依次输入
mail from: <wjw890912@163.com> +回车
rcpt to: <wjw890912@163.com> +回车
服务器都会回复250 Mail OK 来确认。接下来就是邮件的标题和正文了,输入之
data
From:PC
To:XX163
Subject:Test for Smtp
The PC sent e-mail to here pass the SMTP.
.
最后的一个点时必要的。必须要。最后输入quit命令,就断开连接退出了此次会话。一封电子邮件也就被发到了邮箱中了。可以在邮箱中看到发送的内容。
二、代码移植和修改
已经通过TCP工具进行了模拟,只要按照这个流程就可以用程序把人发的变成自动发。首先gitHub拿到代码后找到smtp.c和smtp.h俩文件。加入到project 中设置C/C++ 中include paths 。 找到API接口重写那个接口函数
smtp_set_server_addr("220.181.12.18");//smtp.163.com
smtp_set_auth("wjw890912@163.com","**************");//密码和用户名使用明文即可,系统自动转码
smtp_send_mail("wjw890912@163.com", "wjw890912@163.com", "Reporter", "The MCU sent e-mail to here pass the SMTP", my_smtp_result_fn,0 );
并添加一个回执函数my_smtp_result_fn
void my_smtp_result_fn(void *arg, u8_t smtp_result, u16_t srv_err, err_t err)
{
printf("mail (%p) sent with results: 0x%02x, 0x%04x, 0x%08x\n", arg, smtp_result, srv_err, err);
}
这是一个发送完成回执事件,发送完成后无论成功与否都会返回一个smtp_result,根据这个值应用程序作出相应的反应了。
最后编译下载到目标板中执行
在debug的过程中发现这个程序并不能发出正确的邮件,于是对这个文件中的代码稍微看了一下,
首先是程序的框架,首先他会建立一个描述 所谓的smtp session,用来记录所有的有关系的项结构。然后调用TCP,创建一个用于连接的TCP PCB之后开始执行SMTP的状态机。最后结束发送完成事件,非正常就是直接发送完成事件。所以关键是SMTP的装态机。代码如下
switch(s->state)
{
case(SMTP_NULL):
/* wait for 220 */
if (response_code == 220) {
/* then send HELO */
next_state = smtp_prepare_helo(s, &tx_buf_len, pcb);
}
break;
case(SMTP_HELO):
/* wait for 250 */
if (response_code == 250) {
#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
/* then send AUTH or MAIL */
next_state = smtp_prepare_auth_or_mail(s, &tx_buf_len);
}
break;
case(SMTP_AUTH_LOGIN):
case(SMTP_AUTH_PLAIN):
/* wait for 235 */
if (response_code == 235) {
#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
/* send MAIL */
next_state = smtp_prepare_mail(s, &tx_buf_len);
}
break;
#if SMTP_SUPPORT_AUTH_LOGIN
case(SMTP_AUTH_LOGIN_UNAME):
/* wait for 334 Username */
if (response_code == 334) {
if (pbuf_strstr(s->p, SMTP_RESP_LOGIN_UNAME) != 0xFFFF) {
/* send username */
next_state = smtp_prepare_auth_login_uname(s, &tx_buf_len);
}
}
break;
case(SMTP_AUTH_LOGIN_PASS):
/* wait for 334 Password */
if (response_code == 334) {
if (pbuf_strstr(s->p, SMTP_RESP_LOGIN_PASS) != 0xFFFF) {
/* send username */
next_state = smtp_prepare_auth_login_pass(s, &tx_buf_len);
}
}
break;
#endif /* SMTP_SUPPORT_AUTH_LOGIN */
case(SMTP_MAIL):
/* wait for 250 */
if (response_code == 250) {
/* send RCPT */
next_state = smtp_prepare_rcpt(s, &tx_buf_len);
}
break;
case(SMTP_RCPT):
/* wait for 250 */
if (response_code == 250) {
/* send DATA */
SMEMCPY(s->tx_buf, SMTP_CMD_DATA, SMTP_CMD_DATA_LEN);
tx_buf_len = SMTP_CMD_DATA_LEN;
next_state = SMTP_DATA;
}
break;
case(SMTP_DATA):
/* wait for 354 */
if (response_code == 354) {
/* send email header */
next_state = smtp_prepare_header(s, &tx_buf_len);
}
break;
case(SMTP_BODY):
/* nothing to be done here, handled somewhere else */
break;
case(SMTP_QUIT):
/* wait for 250 */
if (response_code == 250) {
/* send QUIT */
next_state = smtp_prepare_quit(s, &tx_buf_len);
}
break;
case(SMTP_CLOSED):
/* nothing to do, wait for connection closed from server */
return;
然后改成本地服务器进行模拟,发现流程是正常的但是系统打印的代码是大写,服务器不能识别,于是改成小写
#define SMTP_CMD_AUTHLOGIN "auth login \r\n"
#define SMTP_CMD_AUTHLOGIN_LEN 13
#define SMTP_CMD_MAIL_1 "mail from: <"
#define SMTP_CMD_MAIL_1_LEN 12
#define SMTP_CMD_MAIL_2 ">\r\n"
#define SMTP_CMD_MAIL_2_LEN 3
#define SMTP_CMD_RCPT_1 "rcpt to: <"
#define SMTP_CMD_RCPT_1_LEN 10
#define SMTP_CMD_RCPT_2 ">\r\n"
#define SMTP_CMD_RCPT_2_LEN 3
#define SMTP_CMD_DATA "data\r\n"
#define SMTP_CMD_DATA_LEN 6
#define SMTP_CMD_HEADER_1 "From: <"
#define SMTP_CMD_HEADER_1_LEN 7
#define SMTP_CMD_HEADER_2 ">\r\nTo: <"
#define SMTP_CMD_HEADER_2_LEN 8
#define SMTP_CMD_HEADER_3 ">\r\nSubject: "
#define SMTP_CMD_HEADER_3_LEN 12
#define SMTP_CMD_HEADER_4 "\r\n\r\n"
#define SMTP_CMD_HEADER_4_LEN 4
#define SMTP_CMD_BODY_FINISHED "\r\n.\r\n"
#define SMTP_CMD_BODY_FINISHED_LEN 5
#define SMTP_CMD_QUIT "quit\r\n"
#define SMTP_CMD_QUIT_LEN 6
依次修改这些宏。
还有一个地方就是握手的回复上是250 OK而原作者写的是auth 或者auth=,但是实际并不会返回这个而是OK,所以需要改成
#define SMTP_KEYWORD_AUTH_SP "OK"
和
#define SMTP_AUTH_PARAM_LOGIN "OK"
最后修改一下改为小写即可
#define SMTP_CMD_EHLO_1 "helo ["
#define SMTP_CMD_EHLO_1_LEN 6
#define SMTP_CMD_EHLO_2 "]\r\n"
#define SMTP_CMD_EHLO_2_LEN 3
#define SMTP_CMD_AUTHPLAIN_1 "AUTH PLAIN "
#define SMTP_CMD_AUTHPLAIN_1_LEN 11
#define SMTP_CMD_AUTHPLAIN_2 "\r\n"
#define SMTP_CMD_AUTHPLAIN_2_LEN 2
并且关闭 A UTH PLAIN 开关#define SMTP_SUPPORT_AUTH_PLAIN 0
最后一件事就是把板子的IP地址调整为可以访问外网的IP号段,否则防火墙会拦截这个试图发送邮件的非法IP地址,导致发送失败。我这里是0和254、253修改为0号段后就可以正常的发出邮件了!
最后的最后@163邮箱一天中有邮件限制。不能一直发,2秒发一封这样发不到200封邮件就会被限制,并返回错误。此时程序依旧是正常运行的只是服务器会返回中止的代码而已。
如果要做应用把函数的输入参数改为const char *ptr,然后指向一个数组并在末尾加‘\0’就可以把内存引用出来作为信件内容发送出去。比如送出个采集到的温度值什么的,即可向邮箱完成一个小报告了。
|