博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于公网smtp协议实现邮件服务器
阅读量:4328 次
发布时间:2019-06-06

本文共 15025 字,大约阅读时间需要 50 分钟。

刚开始做邮件服务器开发,一切都是茫然的。在书上网上都很难找到一套完整的邮件服务器开发教程。在个人的摸索中碰到了很多蛋疼得问题。现终于完成了,将我的开发经验分享给大家。

 

 

开发环境:vs2012 mfc

 

注意事项:

1、 网络环境:

作为邮件服务器,要接收来自互联网的邮件,就得有能映射到外网的服务器。至少映射25(SMTP)端口(pop3都暂时不重要)。对于没有外网条件的的小伙伴,推荐以下几种方法调试:

A、如果你使用model上网,查看你的电脑的外网IP。看看是否和model的一致。一致则说明你有一个公网IP。如果没有,就致电网络运营商,叫他给你换回公网IP,语气一定要强硬。有了公网IP,你就能把smtp的端口映射出去。具体方法请搜索。

B、对于一些校园网或大型局域网的用户,那就有点悲剧了,基本无法直接将端口映射出去。但可以参考C方法。

C、先在的网上的网络穿透都做的比较好,甚至能媲美公网ip的直接映射。去网上下载一些穿透软件:花生壳之类的。都有提供内网穿透。给大家推荐个免费的网络穿透软件:nat123 。这软件很好有,up主不经意的发现有大量的小学生用它在开MC的服务器。

D、如果你暂不需要使用外部的邮件服务器来测试你的服务器,那就在内网用其他的邮件服务器发送至你的邮箱进行测试,要注意端口冲突。

2、 编写smtp邮件服务器:

对于拥有外网映射的,可以先编写smtp 的接收服务器。这样的好处是,能接收识别它域的不同类型格式的邮件,到自己发送邮件的时候,对邮件格式能有一个很好的组织。 而对于只能使用内网的用户,建议先编写发送端。成功进行对各个邮件服务器的发送后(这里比较蛋疼,后面会介绍到),就可以在编写邮件接收端的时候用来测试。

3、 邮件服务器域名(MX)的获取:

最开始用telnet测试163的smtp。网上搜索各大邮件服务器的stmp服务器,当初搜索出来的结果把我着实误导了好一阵子。举个例子把,就常用的企鹅邮箱,搜索出来的结果是:smtp.qq.com  25 。

于是傻傻地telnet上去helo他,他告诉我他是ehlo服务器,要登陆验证遇到这种问题真是想哭,我一个发邮件的我给你登陆什么啊,其他smtp.jbjb.com邮箱也是这样?与是想了各种奇葩的情况与方法浪费了很多时间。最后自己去抓了一个邮件服务器的向企鹅邮箱发送的包,首先是几个dns的包。看到有几个dns包,自己心理顿时想到了smtp前缀的域名不是接收外域邮件的域名,看了下dns到的地址:mx1.qq.com mx2.qq.com... 原来这才是接收邮件的服务器域名。这时候才明白自己以前挂的域名时也配置有MX地址。 总之,我们发邮件一定要先获取邮件接收服务器的域名。

4、 防止反垃圾邮件:

把邮件发往其他邮件服务器,很容易被识别为垃圾文件。新浪邮箱最讨厌,直接不信任我的域名和IP地址。最初邮件格式不完善,也被企鹅断断续续封了几天。就163最包容,我的邮件都慷慨地接收了。

现在发邮件,除了基本的格式,不用MINE根本不行,尤其是当需要图片或者附件的邮件。

5、 加密:

一般可用base64对邮件标题和内容进行加密。

6、 邮件存储:

建议储存为eml格式的邮件文件,不仅利于转发或其他应用查看,也便于之后的pop3服务器使用。

7、 web端cms

超麻烦的东西,找个模板改改吧。

 

 

 

先粗略讲讲SMTP协议发送邮件的过程:

直接举个例子:

R: 220 mx.jb.com MX JB Mail Sever

S: helo wchrt.vicp.cc

R: 250 OK

S: mail from: <jb@wchrt.vicp.cc>

R: 250 OK

S: rcpt to: <414322153@qq.com>

R: 250 OK

S: data

R: 354 End data with<CR><LF>.<CR><LF>

S: mail from: jb@wchrt.vicp.cc

S: rcpt to: 414322153@qq.com

S: subject: 你好

S: 约不约?

S: <CR><LF>.<CR><LF>

R: 250 OK in queue

S: QUIT

R: 221 close

 

 

该次对话中首先我们链接服务器后服务器给我们返回了220 和服务器的域名信息,表示该邮件服务器正常工作,可以提供服务。

然后我们发送helo过去标示自己服务器的域名。

服务收到后250说明和helo成功

之后是mailfrom 说明发件人的email地址

250 ok后再rcptto 说明发送目标的Email地址

成功后,就可以发送data命令发送邮件内容了。

Data返回的值说明邮件以<CR><LF>.<CR><LF>为结束标记。<CR><LF>这个标示符用换行符\r\n即可。

最后返回250 标示邮件接收成功。QUIT退出。

 

以下是服务器返回的一些编号:

501 参数格式错误

502 命令不可实现

503错误的命令序列

504 命令参数不可实现

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

214帮助信息

220 <domain>服务就绪

221 <domain>服务关闭传输信道

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

250要求的邮件操作完成

251用户非本地,将转发向<forward-path> 

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

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

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

551用户非本地,请尝试<forward-path> 

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

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

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

354 开始邮件输入,以<CRLF>.<CRLF>结束

554 操作失败

 

具体的内容请自行搜索smtp协议

 

 

大致清楚协议后就可以开始邮件接收端的编程,协议的细节的在调试过成功会慢慢清楚的。

服务器接收端代码:

1 UINT mailsever::listenthread(LPVOID Param)  2 {  3     int ret;  4     SOCKET listensock;//接收邮件请求的套接字  5     listensock=socket(AF_INET,SOCK_STREAM,0);  6     TRACE("listensock: %d\n",listensock);  7    8     sockaddr_in saddr;  9     saddr.sin_family=AF_INET; 10     saddr.sin_port=htons(25);//25 smtp端口 11     saddr.sin_addr.S_un.S_addr=INADDR_ANY; 12   13     ret=bind(listensock,(sockaddr *)&saddr,sizeof(sockaddr_in)); 14   15     ret=listen(listensock,20);//一个小服务器,暂时就只设置20的链接上限 16   17     that->isseverstart=true; 18     CString str; 19     that->GetDlgItemTextA(IDC_LOG,str); 20     str+="收信服务开启\r\n";; 21     that->SetDlgItemTextA(IDC_LOG,str); 22   23     sockaddr_in newaddr; 24     int newlen; 25     while(1) 26     { 27         SOCKET newsock=accept(listensock,(sockaddr *)&newaddr,&newlen);//获取到新邮件请求的套接字 28         if(newsock!=SOCKET_ERROR) 29         { 30             AfxBeginThread(dealthread,&newsock);//开启新线程接收邮件 31         } 32     } 33     that->isseverstart=false; 34     return 0; 35 } 36   37 static bool strcom(const char *s1,const char *s2) 38 { 39     int len=strlen(s2); 40     if(strlen(s1)
='A'&&s1[i]<='Z') 48 { 49 py=32; 50 } 51 else 52 { 53 py=0; 54 } 55 if(s1[i]+py!=s2[i]) 56 { 57 return false; 58 } 59 } 60 61 return true; 62 } 63 64 static bool isdataend(const char *ss) 65 { 66 int len=strlen(ss); 67 for(int i=0;i
2048||rlen<0)107 {108 continue;109 }110 rdata[rlen]='\0';111 CString str;112 CString ss;113 114 115 if(strcom(rdata,"helo")||strcom(rdata,"ehlo"))//处理helo请求116 {117 sdata.Format("250-wchrt.vicp.cc\r\n250 OK\r\n");118 ishelo=true;119 }120 else if(strcom(rdata,"mail from"))//处理邮件来源信息121 {122 int i=0;123 while(i
.
\r\n");157 isdata=true;158 }159 }160 else if(strcom(rdata,"quit"))//处理退出服务请求161 {162 isend=true;163 break;164 }165 else166 {167 if(isdata)//接收邮件内容168 {169 rmail.alldata+=rdata;170 if(isdataend(rdata))171 {172 rmail.alldata.Replace("\r\n.\r\n","\r\n");173 isdata=false;174 sdata.Format("250 OK\r\n");175 isenddata=true;176 }177 else178 {179 continue;180 }181 }182 else183 {184 sdata.Format("250 OK\r\n");185 }186 }187 send(sock,(LPCTSTR)sdata,sdata.GetLength(),0);//返回应答188 }189 190 // 开始处理并储存接收到的邮件,处理过程详见maildata.cpp191 //maildata::getmailinfo(rmail);192 CString mid;193 mid.Format("%d",that->mailid+1);194 if(maildata::saveeml("all",mid,rmail))195 {196 that->mailid++;197 that->mailnum++;198 CFile file;199 if(!file.Open("mail\\info",CFile::modeReadWrite))200 {201 if(!file.Open("mail\\info",CFile::modeCreate|CFile::modeReadWrite))202 {203 AfxMessageBox("makeinfo error");204 return false;205 }206 }207 CString str;208 str.Format("%d\r\n%d",that->mailid,that->mailnum);209 file.Write(str.GetString(),str.GetLength());210 file.Close();211 212 that->GetDlgItemTextA(IDC_LOG,str);213 str+="new mail\r\n";214 that->SetDlgItemTextA(IDC_LOG,str);215 }216 return 0;217 }

 

 

邮件发送端:

 

举个例子,我们要往邮箱:414322153@qq.com 发送一个邮件。应遵循以下步骤:

1、提取出域名后缀:qq.com。

2、DNS查询该域名的MX记录。

3、根据查询到的MX域名或IP地址链接该邮件服务器

4、使用smtp协议发送邮件

 

因为要进行DNS查询,mfc没有提供关于dns查询的类。只好自己手动链接dns服务器根据dns协议查询mx记录。或者是调用windows自带的nslookup来查询dns。

为了防止大家一些接触太多东西,这里就给大家讲一下简单实用nslookup查询mx记录的方法。等有精力再去学习dns协议。

 

cmd输入:nslookup

将查询设置为mx:set q=mx

开始查询:qq.com

 

见截图:

 

为方便起见,我们就不做服务器的连通测试,直接使用第一个MX地址发送邮件。

以下是发送邮件的代码:

 

1 static bool getres(SOCKET sock,const char *s,const char *s2=NULL)  2 {  3     char *rdata=new char [2048];  4     int rlen;  5     CString rr,str;  6    7     rlen=recv(sock,rdata,2047,0);  8     rdata[rlen]='\0';  9       10     rr.Format("%s",rdata); 11     that->GetDlgItemTextA(IDC_LOG,str); 12     str+=rr+"\r\n";; 13     that->SetDlgItemTextA(IDC_LOG,str); 14       15     /*TRACE("%s\n",rdata); 16     CString ss=rdata; 17     AfxMessageBox(ss);*/ 18   19     if(!strcom(rdata,s)) 20     { 21         if(s2!=NULL) 22         { 23             if(!strcom(rdata,s2)) 24             { 25                 return false; 26             } 27         } 28         else 29         { 30             return false; 31         } 32     } 33     return true; 34 } 35 UINT mailsever::sendthread(LPVOID Param) 36 { 37     CString mpath; 38     mpath.Format("%s",Param); 39     //AfxMessageBox(mpath); 40     CFile file; 41     if(!file.Open(mpath,CFile::modeRead)) 42     { 43         return -1; 44     } 45   46     maildata rmail; 47     char s[20480]; 48     memset(s,'\0',sizeof(s)); 49     file.Read(s,min(file.GetLength(),20470)); 50     rmail.alldata.Format(s); 51   52     maildata::getmailinfo(rmail); 53   54     rmail.gettoaddress(); 55   56     //AfxMessageBox(rmail.toaddress); 57       58       59     //dns获取域名mx记录 60     char *szDomainName= (char *)rmail.toaddress.GetString(); 61     std::vector
veculIPList; 62 std::vector
vecstrIPList; 63 std::vector
vecMXList; 64 ULONG ulTimeSpent = 0; 65 CDNSLookup dnslookup; 66 //使用114.114.114.144 dns服务 67 BOOL bRet = dnslookup.DNSLookup(inet_addr("114.114.114.114"), szDomainName, &vecstrIPList, &vecMXList, 1000, &ulTimeSpent); 68 if(!bRet) 69 { 70 return -1; 71 } 72 vecMXList[0].c_str(); 73 CString ss; 74 ss.Format("%s",vecMXList[0].c_str());//获取第一条记录 75 76 //AfxMessageBox(ss); 77 78 SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 79 sockaddr_in saddr; 80 saddr.sin_family =AF_INET; 81 saddr.sin_port=htons(25); 82 saddr.sin_addr.S_un.S_addr=*(u_long *)gethostbyname(vecMXList[0].c_str())->h_addr_list[0]; 83 84 /*CString sd=inet_ntoa(saddr.sin_addr); 85 86 AfxMessageBox(sd);*/ 87 88 if(SOCKET_ERROR==connect(sock,(sockaddr*)&saddr,sizeof(saddr))) 89 { 90 return -1; 91 } 92 93 94 95 CString sdata; 96 97 98 if(!getres(sock,"220")) 99 {100 return -1;101 }102 103 104 sdata.Format("HELO wchrt.vicp.cc\r\n");105 send(sock,sdata.GetString(),sdata.GetLength(),0);106 if(!getres(sock,"250"))107 {108 return -1;109 }110 111 112 sdata.Format("MAIL FROM: <%s>\r\n",rmail.from.GetString());113 send(sock,sdata.GetString(),sdata.GetLength(),0);114 if(!getres(sock,"250"))115 {116 return -1;117 }118 119 120 sdata.Format("RCPT TO: <%s>\r\n",rmail.to.GetString());121 send(sock,sdata.GetString(),sdata.GetLength(),0);122 if(!getres(sock,"250"))123 {124 return -1;125 }126 127 128 129 sdata.Format("DATA\r\n");130 send(sock,sdata.GetString(),sdata.GetLength(),0);131 if(!getres(sock,"2","3"))132 {133 return -1;134 }135 136 137 sdata=rmail.alldata;138 139 140 141 142 for(int i=0;i

 

maildata类:

 

1 maildata.h 2   3 #pragma once 4 #define MAIL_TYPE_RECV           1 5 #define MAIL_TYPE_SEND           2 6 #define MAIL_TYPE_USEND          3 7   8 class maildata:public CObject 9 {10 public:11     maildata(void);12      13     ~maildata(void);14  15     USHORT type;16     CString alldata;17  18     CString date;19     CString from;20     CString to;21     CString subject;22     CString content;23     CString contenttype;24  25     CString toaddress;26  27     //void operator = (const maildata &);28     void Serialize(CArchive &);29     DECLARE_SERIAL(maildata);30  31  32     static void getmailbaseinfo(maildata &);33     static void getmailinfo(maildata &);34     static bool setmailforsend(maildata &);35  36     static bool openeml(const CString,const CString,maildata &);37     static bool saveeml(const CString,const CString,maildata &);38     bool gettoaddress();39 };

 

1 maildata.cpp  2    3 #include "stdafx.h"  4 #include "maildata.h"  5    6 #include "include/atlrx.h"  7 #include "base64.h"  8    9 IMPLEMENT_SERIAL(maildata,CObject,VERSIONABLE_SCHEMA|2); 10 #ifdef _DEBUG 11 #define new DEBUG_NEW 12 #endif 13   14 maildata::maildata(void) 15 { 16 } 17   18   19 maildata::~maildata(void) 20 { 21 } 22   23   24 /*void maildata::operator = (const maildata &m) 25 { 26 }*/ 27   28   29 void maildata::Serialize(CArchive &ar) 30 { 31     if(ar.IsStoring()) 32     { 33         ar<
>alldata; 38 } 39 } 40 41 42 static bool strcom(const char *s1,const char *s2) 43 { 44 int len=strlen(s2); 45 if(strlen(s1)
='A'&&s1[i]<='Z') 53 { 54 py=32; 55 } 56 else 57 { 58 py=0; 59 } 60 if(s1[i]+py!=s2[i]) 61 { 62 return false; 63 } 64 } 65 66 return true; 67 } 68 static CString* strmake(const char *st,const char *ed) 69 { 70 if(ed<=st) 71 { 72 CString *str=new CString; 73 *str=""; 74 return str; 75 } 76 char *data=new char[ed-st+1]; 77 int i=0; 78 while(st+i
reurl; 91 REParseError statu=reurl.Parse(getstr); 92 if(REPARSE_ERROR_OK!=statu) 93 { 94 return; 95 } 96 CAtlREMatchContext<> mcurl; 97 if(!reurl.Match(content,&mcurl)) 98 { 99 return;100 }101 const CAtlREMatchContext<>::RECHAR *szstart=0;102 const CAtlREMatchContext<>::RECHAR *szend=0;103 if(mcurl.m_uNumGroups<=0)104 {105 return;106 }107 mcurl.GetMatch(0,&szstart,&szend);108 data=*strmake(szstart,szend);109 }110 111 static void dealsbstr(CString &str)//解码单行的=??= base64编码字符串112 {113 char *s=str.GetBuffer();114 int len=str.GetLength();115 s[len]='\0';116 int i=0;117 while(i
0)235 {236 str.Format("Content-Type: text/plain; charset=GBK\r\nContent-Transfer-Encoding: base64\r\n\r\n%s\r\n----=_Part=--\r\n",237 base64_encode((unsigned char *)rmail.content.GetString(),rmail.content.GetLength()).c_str()238 );239 rmail.alldata+=str;240 }241 242 //需要发送附件的话将文件读入后添加MIME部分即可243 244 AfxMessageBox(rmail.alldata);245 }246 247 248 bool maildata::openeml(const CString uname,const CString mid,maildata &rmail)//打开邮件249 {250 CString mpath="mail";251 mpath+="\\";252 mpath+=uname;253 mpath+="\\";254 mpath+=mid+".eml";255 CFile file;256 if(!file.Open(mpath,CFile::modeRead))257 {258 return false;259 }260 char temp[40960];261 UINT len=file.Read(temp,40900);262 temp[len]='\0';263 rmail.alldata.Format(temp);264 //AfxMessageBox(rmail.alldata);265 return true;266 }267 268 bool maildata::saveeml(const CString uname,const CString mid,maildata &rmail)//邮件储存269 {270 CString mpath="mail";271 if(!PathIsDirectory(mpath))272 {273 if(!CreateDirectory(mpath,NULL))274 {275 return false;276 }277 }278 mpath+="\\";279 mpath+=uname;280 if(!PathIsDirectory(mpath))281 {282 if(!CreateDirectory(mpath,NULL))283 {284 return false;285 }286 }287 mpath+="\\";288 mpath+=mid+".eml";289 290 CFile file;291 if(!file.Open(mpath,CFile::modeWrite))292 {293 if(!file.Open(mpath,CFile::modeCreate|CFile::modeWrite))294 {295 return false;296 }297 }298 file.Write(rmail.alldata.GetString(),rmail.alldata.GetLength());299 file.Close();300 return true;301 }302 303 bool maildata::gettoaddress()//获取后缀地址304 {305 getcontent("@{.*}",to,toaddress);306 if(toaddress.GetLength()<1)307 {308 return false;309 }310 toaddress.Replace(" ","");311 return true;312 }

 

基本的smtp邮件服务器就告一段落了。剩下的就是用户管理以及在smtp服务器基础上制作pop3服务器以及web等。

 

转载于:https://www.cnblogs.com/wchrt/p/4195831.html

你可能感兴趣的文章
51Nod1601 完全图的最小生成树计数 Trie Prufer编码
查看>>
Codeforces 1110D. Jongmah 动态规划
查看>>
android驱动在win10系统上安装的心酸历程
查看>>
优雅的程序员
查看>>
oracle之三 自动任务调度
查看>>
Android dex分包方案
查看>>
ThreadLocal为什么要用WeakReference
查看>>
删除本地文件
查看>>
FOC实现概述
查看>>
base64编码的图片字节流存入html页面中的显示
查看>>
这个大学时代的博客不在维护了,请移步到我的新博客
查看>>
GUI学习之二十一——QSlider、QScroll、QDial学习总结
查看>>
nginx反向代理docker registry报”blob upload unknown"解决办法
查看>>
gethostbyname与sockaddr_in的完美组合
查看>>
kibana的query string syntax 笔记
查看>>
旋转变换(一)旋转矩阵
查看>>
thinkphp3.2.3 bug集锦
查看>>
[BZOJ 4010] 菜肴制作
查看>>
C# 创建 读取 更新 XML文件
查看>>
KD树
查看>>