找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 10679|回复: 1
收起左侧

简单电子邮件SMTP协议解析

[复制链接]
ID:113276 发表于 2016-4-10 20:22 | 显示全部楼层 |阅读模式
最近几日学习了一下简单电子邮件协议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’就可以把内存引用出来作为信件内容发送出去。比如送出个采集到的温度值什么的,即可向邮箱完成一个小报告了。






回复

使用道具 举报

ID:86450 发表于 2018-9-20 18:03 | 显示全部楼层
神奇的 东西!!!!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表