這幾天敝人並沒有及時更新部落格,一是由於白天工作中的鎖事比較繁雜,二來連續看了兩個晚上的SMTP協議。
我比較喜歡用實踐來證明一切,這樣才會加深自己的理解。
一、準備工作:
1、本機環境:Windows XP SP3、ADSL 10M光纖
2、開發工具:WildPackets OmniPeek V5.1.4
Visual C++ 6.0
Outlook Express6.0
FlexEdit V2.3.1871
二、SMTP命令:
1、HELO 向伺服器標識使用者身份
2、MAIL 初始化郵件傳輸mail from: <xxx>
3、RCPT 標識單個的郵件接收人;常在MAIL命令後面可有多個rcpt to: <xxx>
4、DATA 在單個或多個RCPT命令後,表示所有的郵件接收人已標識,初始化資料轉送,以.結束
5、NOOP 無操作,伺服器應響應OK
6、RSET 重設會話,當前傳輸被取消
7、QUIT 結束會話
三、分析資料包:
1、開啟Outlook Express6.0,建立新郵件,內容如下:
2、開啟OmniPeek,選擇SMTP:
3、先開始抓包,再發送上面的測試郵件,得到的資料包如下圖:
(要注意Source與Destination,即源地址與目標地址)
4、只需要看第20條資料包,其他可以比較容易理解:
5、其他小技巧
<CR> <LF>即對應C/C++中的"/r /n"
比如 Line 8: charset="gb2312"<CR><LF>,因為需要用到逸出字元
所以對應的字串應該為char * sData = "charset=/"gb2312/"/r/n";
四、相關代碼:
1、以下代碼通過socket與SMTP伺服器建立串連並驗證身份。開啟Visual C++,建立一個控制台工程、並添加一個CPP檔案:
/************************************************************************/ /* main.cpp SMTP協議學習筆記 - 登入驗證 /* by Koma 2009.9.10 11:35 /* http://blog.csdn.net/wangningyu /************************************************************************/ #include "stdio.h" #include "ZBase64.h" #include "winsock2.h" #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]) { SOCKADDR_IN saServer; LPHOSTENT lphostent; WSADATA wsadata; SOCKET hsocket; int nRet; char* host_name="smtp.tom.com"; char* req= // 每發送一行資料服務器都會作出響應 "EHLO KOMAWANG/r/n" "AUTH LOGIN/r/n" // 兩行是登入使用者與密碼(採用Base64加密) "bm**********20=/r/n" "d2******W4=/r/n" "QUIT/r/n"; // 初始化通訊端 if(WSAStartup(MAKEWORD(2,2),&wsadata)) printf("初始化SOCKET出錯。"); // SMTP連接埠預設是25 lphostent=gethostbyname(host_name); if(lphostent==NULL) printf("lphostent為空白。"); hsocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); saServer.sin_family = AF_INET; saServer.sin_port = htons(25); saServer.sin_addr = *((LPIN_ADDR)*lphostent->h_addr_list); // 利用SOCKET串連 nRet = connect(hsocket,(LPSOCKADDR)&saServer,sizeof(SOCKADDR_IN)); if(nRet == SOCKET_ERROR) { printf("建立串連時出錯。/n"); closesocket(hsocket); return 0; } // 利用SOCKET發送 nRet = send(hsocket,req,strlen(req),0); if(nRet==SOCKET_ERROR) { printf("發送資料包時出錯。"); closesocket(hsocket); } char Dest[3000]; memset(Dest,0,3000); nRet=1; while(nRet>0) { // 接收返回資料包 nRet=recv(hsocket,(LPSTR)Dest,sizeof(Dest),0); if(nRet>0) Dest[nRet]=0; else Dest[0]=0; // 顯示返回資料包的大小、內容 printf("/n返回資料包大小:%d/n",nRet); printf("返回資料包內容:/n%s",Dest); } return 0; }
2、下面是Base64加解密頭與CPP檔案:
/************************************************************************/ /* ZBase64.h /************************************************************************/ #ifndef _ZBASE64 #define _ZBASE64 #pragma warning(disable:4786) #include <string> using namespace std; class ZBase64 { public: /*編碼 DataByte [in]輸入的資料長度,以位元組為單位 */ string Encode(const unsigned char* Data,int DataByte); /*解碼 DataByte [in]輸入的資料長度,以位元組為單位 OutByte [out]輸出的資料長度,以位元組為單位,請不要通過傳回值計算 輸出資料的長度 */ string Decode(const char* Data,int DataByte,int& OutByte); }; #endif
/************************************************************************/ /* ZBase64.cpp /************************************************************************/ #include "ZBase64.h" string ZBase64::Encode(const unsigned char* Data,int DataByte) { //編碼錶 const char EncodeTable[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; //傳回值 string strEncode; unsigned char Tmp[3]={0}; int LineLength=0; for(int i=0;i<(int)(DataByte / 3);i++) { Tmp[1] = *Data++; Tmp[2] = *Data++; Tmp[3] = *Data++; strEncode+= EncodeTable[Tmp[1] >> 2]; strEncode+= EncodeTable[((Tmp[1] << 4) | (Tmp[2] >> 4)) & 0x3F]; strEncode+= EncodeTable[((Tmp[2] << 2) | (Tmp[3] >> 6)) & 0x3F]; strEncode+= EncodeTable[Tmp[3] & 0x3F]; if(LineLength+=4,LineLength==76) {strEncode+="/r/n";LineLength=0;} } //對剩餘資料進行編碼 int Mod=DataByte % 3; if(Mod==1) { Tmp[1] = *Data++; strEncode+= EncodeTable[(Tmp[1] & 0xFC) >> 2]; strEncode+= EncodeTable[((Tmp[1] & 0x03) << 4)]; strEncode+= "=="; } else if(Mod==2) { Tmp[1] = *Data++; Tmp[2] = *Data++; strEncode+= EncodeTable[(Tmp[1] & 0xFC) >> 2]; strEncode+= EncodeTable[((Tmp[1] & 0x03) << 4) | ((Tmp[2] & 0xF0) >> 4)]; strEncode+= EncodeTable[((Tmp[2] & 0x0F) << 2)]; strEncode+= "="; } return strEncode; } string ZBase64::Decode(const char* Data,int DataByte,int& OutByte) { //解碼錶 const char DecodeTable[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, // '+' 0, 0, 0, 63, // '/' 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9' 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 'A'-'Z' 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 'a'-'z' }; //傳回值 string strDecode; int nValue; int i= 0; while (i < DataByte) { if (*Data != '/r' && *Data!='/n') { nValue = DecodeTable[*Data++] << 18; nValue += DecodeTable[*Data++] << 12; strDecode+=(nValue & 0x00FF0000) >> 16; OutByte++; if (*Data != '=') { nValue += DecodeTable[*Data++] << 6; strDecode+=(nValue & 0x0000FF00) >> 8; OutByte++; if (*Data != '=') { nValue += DecodeTable[*Data++]; strDecode+=nValue & 0x000000FF; OutByte++; } } i += 4; } else// 斷行符號換行,跳過 { Data++; i++; } } return strDecode; }
3、程式運行效果: