最近幾日學習了一下簡單電子郵件協(xié)議smtp的使用,并移植了smtp協(xié)議棧中的code,幾經(jīng)折返,最終成功實現(xiàn)郵件的發(fā)送。
一、SMTP協(xié)議流程
首先通過計算機的telnet或者TCPIP調(diào)試工具來輸入命令。我是使用TCP調(diào)試工具進行的驗證。
1、SMTP服務器網(wǎng)絡地址
首先去163郵箱注冊一個用戶,然后在郵箱里面設(shè)置去掉SSL并開啟SMTP的3個選項。之后就可以使用了,
就163郵箱來說他的服務器是smtp.163.com。為了和程序一致所以需要轉(zhuǎn)換為IP地址。通過DNS可以知道他的IP地址映射。
這樣就獲得了一個IP地址
2、SMTP服務器端口
SMTP默認的端口是 port =25
3、會話
使用TCP連接連接這個IP地址和25端口,連接成功之后服務器會發(fā)回來一串數(shù)如下
220 163.com Anti-spam GT for Coremail System (163com[20141201])
然后需要發(fā)送helo xxx命令發(fā)送完畢 ,否則他會返回503 Error: send HELO/EHLO first xxx可以是個任意的名字他會返回
250 OK
表示完成握手接下來就是用戶名和密碼的認證了。需要你主動發(fā)送認證請求發(fā)送命令auth login空格+回車
334 dXNlcm5hbWU6
這是表示提示輸入用戶名,dXNlcm5hbWU6這個是用base64加密的username,然后輸入你的xxx@163.com,同樣的也需要轉(zhuǎn)換為BASE64碼,之后
334 UGFzc3dvcmQ6
這是表示提示輸入密碼了。同樣的 UGFzc3dvcmQ6表示password了輸入密碼******轉(zhuǎn)換為BASE64代碼,之后
235 Authentication successful
返回這個之后表示認證成功了,接下來就是發(fā)件人和收件人的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命令,就斷開連接退出了此次會話。一封電子郵件也就被發(fā)到了郵箱中了。可以在郵箱中看到發(fā)送的內(nèi)容。
二、代碼移植和修改
已經(jīng)通過TCP工具進行了模擬,只要按照這個流程就可以用程序把人發(fā)的變成自動發(fā)。首先gitHub拿到代碼后找到smtp.c和smtp.h倆文件。加入到project 中設(shè)置C/C++ 中include paths 。 找到API接口重寫那個接口函數(shù)
smtp_set_server_addr("220.181.12.18");//smtp.163.com
smtp_set_auth("wjw890912@163.com","**************");//密碼和用戶名使用明文即可,系統(tǒng)自動轉(zhuǎn)碼
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 );
并添加一個回執(zhí)函數(shù)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);
}
這是一個發(fā)送完成回執(zhí)事件,發(fā)送完成后無論成功與否都會返回一個smtp_result,根據(jù)這個值應用程序作出相應的反應了。
最后編譯下載到目標板中執(zhí)行
在debug的過程中發(fā)現(xiàn)這個程序并不能發(fā)出正確的郵件,于是對這個文件中的代碼稍微看了一下,
首先是程序的框架,首先他會建立一個描述 所謂的smtp session,用來記錄所有的有關(guān)系的項結(jié)構(gòu)。然后調(diào)用TCP,創(chuàng)建一個用于連接的TCP PCB之后開始執(zhí)行SMTP的狀態(tài)機。最后結(jié)束發(fā)送完成事件,非正常就是直接發(fā)送完成事件。所以關(guān)鍵是SMTP的裝態(tài)機。代碼如下
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;
然后改成本地服務器進行模擬,發(fā)現(xiàn)流程是正常的但是系統(tǒng)打印的代碼是大寫,服務器不能識別,于是改成小寫
#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
并且關(guān)閉 A UTH PLAIN 開關(guān)#define SMTP_SUPPORT_AUTH_PLAIN 0
最后一件事就是把板子的IP地址調(diào)整為可以訪問外網(wǎng)的IP號段,否則防火墻會攔截這個試圖發(fā)送郵件的非法IP地址,導致發(fā)送失敗。我這里是0和254、253修改為0號段后就可以正常的發(fā)出郵件了!
最后的最后@163郵箱一天中有郵件限制。不能一直發(fā),2秒發(fā)一封這樣發(fā)不到200封郵件就會被限制,并返回錯誤。此時程序依舊是正常運行的只是服務器會返回中止的代碼而已。
如果要做應用把函數(shù)的輸入?yún)?shù)改為const char *ptr,然后指向一個數(shù)組并在末尾加‘\0’就可以把內(nèi)存引用出來作為信件內(nèi)容發(fā)送出去。比如送出個采集到的溫度值什么的,即可向郵箱完成一個小報告了。
|