搭建简单SMTP服务器
By
money
at 2022-04-18 • 0人收藏 • 1018人看过
参考git库,asynFtpServer,分享一个asynSMTPServer
GitHub - alajia626/aardio-FTPServer: 异步FTP服务端,支持主动/被动模式,用户帐号合法性验证
//asynSMTPServer 异步SMTP服务端 import wsock.tcp.server; import wsock.tcp.asynServer; import fsys; import fsys.file; import fsys.codepage; import com.smtp class asynSMTPServer{ ctor(fdPort){ var errMsg; this,errMsg = ..wsock.tcp.asynServer(); if(!this) return null,errMsg; this.maxConnection = 500; this.keepAliveTimeout = 120; this.localPort = fdPort:25; this.conn_list = {}; this.onOpen = function(hSocket,err){ var client = this.client(hSocket); client.getRemoteIp() if(this.acceptCount < this.maxConnection){ this.conn_list[hSocket] = { ip = client.getRemoteIp(); client = client; }; return client.send("220 Win Mail Server, Version 5.8 (5.8.8.9)" + '\r\n'); } else { client.send("500 Too many connections!" + '\r\n'); return client.close(); } } this.onRead = function(hSocket,err){ var client = this.client(hSocket); var data = client.readAll(); if(this.conn_list[hSocket] and this.conn_list[hSocket].dataing){ this.conn_list[hSocket].data = this.conn_list[hSocket].data + data; if(..string.endWith(data, '.\r\n')){ this.conn_list[hSocket].dataing = false client.send("250 Message accepted for delivery."+'\r\n'); if(type(this.onMail)==type.function){ this.onMail(this.conn_list[hSocket]); } } return ; } if(!..string.endWith(data,'\r\n',true)){ return client.send("550 failed."); } var cmdLine = ""; var arg = ""; var space = ..string.indexOf(data," "); if(space){ cmdLine = ..string.upper(..string.left(data,space-1)); arg = ..string.slice(data,space+1,-3); }else { cmdLine = ..string.slice(data,1,-3); } /* HELO <SP> <domain> <CRLF> rfc5321弃用 EHLO <SP> <domain /address-literal > <CRLF> 新标准用于替换 HELO 命令 MAIL <SP> FROM:<reverse-path> <CRLF> RCPT <SP> TO:<forward-path> <CRLF> DATA <CRLF> RSET <CRLF> SEND <SP> FROM:<reverse-path> <CRLF> SOML <SP> FROM:<reverse-path> <CRLF> SAML <SP> FROM:<reverse-path> <CRLF> VRFY <SP> <string> <CRLF> EXPN <SP> <string> <CRLF> HELP [<SP> <string>] <CRLF> NOOP <CRLF> QUIT <CRLF> TURN <CRLF> rfc5321弃用 原文链接:https://blog.csdn.net/sinat_36219858/article/details/71069515 */ if(type(this.onCommand)==type.function){ this.onCommand(cmdLine, arg); } this.conn_list[hSocket][cmdLine] = arg; select(cmdLine) { case "HELO","EHLO" { client.send("250-Welcome "+arg+"["+this.conn_list[hSocket].ip+"], pleased to meet you" + '\r\n') client.send("250-AUTH=LOGIN" + '\r\n') client.send("250-AUTH LOGIN" + '\r\n') client.send("250-SIZE 5242880" + '\r\n') return client.send("250 HELP" + '\r\n') } case "MAIL" { var from = ..string.match(arg,"FROM\: *\<(.*)\>"); if(from){ this.conn_list[hSocket].mailFrom = from return client.send('250 Sender "'+from+'" OK...' + '\r\n') }else { return client.send('501 Need sender param' + '\r\n') } } case "RCPT" { var to = ..string.match(arg,"TO\: *\<(.*)\>"); if(to){ this.conn_list[hSocket].mailTo := {} ..table.push(this.conn_list[hSocket].mailTo, to) return client.send('250 Recipient "'+to+'" OK...' + '\r\n') }else { return client.send('501 Need recipient param' + '\r\n') } } case "DATA" { this.conn_list[hSocket].data="" this.conn_list[hSocket].dataing=true return client.send('354 Enter mail, end with "." on a line by itself.' + '\r\n') } case "REST" { this.conn_list[hSocket].file_pos = int(arg); return client.send("250 OK" + '\r\n'); } case "NOOP" { return client.send("200 Command okay." + '\r\n') } case "QUIT" { this.conn_list[hSocket] = null; return client.send("221 See you. " + '\r\n'); } case "VRFY","SEND","SOML","SAML","EXPN","HELP" { return client.send('502 Unimplemented command.' + '\r\n') } else { return client.send('502 Unknown command.' + '\r\n') } } }; }; run = function(){ return this.start("0.0.0.0",this.localPort) }; } namespace asynSMTPServer{ parseEml = function(emlFile){ var mail = ..com.smtp(); var path = ..io.fullpath(emlFile) var stm = ..com.CreateObject( "ADODB.Stream" ); stm.Open(, 0, -1, "", ""); stm.Type = 1; stm.LoadFromFile(path); mail.DataSource.OpenObject(stm, "_stream") return mail; } } /**intellisense() asynSMTPServer() = 创建单线程异步SMTP服务端\n!stdasynSMTPServer. asynSMTPServer.parseEml() = 解析邮件文件\n!cdo_smtp. end intellisense**/ /**intellisense(!stdasynSMTPServer) run() = 启动单线程异步TCP服务端,成功返回true,失败返回null,\n\nIP默认设为"0.0.0.0",端口省略为25 getLocalIp() = 返回当前绑定的IP,端口号 maxConnection = 最大连接数 keepAliveTimeout = 最大保持连接时间,以秒为单位,\n负数表示不限时间 documentRoot = SMTP根目录,默认为"/" onMail = @.onMail = function(mail){ /*邮件接收完成触发*/ console.dump(mail.data) } onCommand = @.onCommand = function(cmd, arg){ /*单行命令触发*/ console.dump(cmd, arg) } end intellisense**/
示例:
//使用演示 import win.ui; /*DSG{{*/ var winform = win.form(text="SMTP服务器";right=746;bottom=451) winform.add( txtMessage={cls="edit";left=10;top=11;right=738;bottom=444;db=1;dl=1;dr=1;dt=1;edge=1;font=LOGFONT(h=-16);hscroll=1;multiline=1;vscroll=1;z=1} ) /*}}*/ import win.ui.atom; var atom,hwnd = winform.atom("736116B6-9CB7-415F-A4EB-052D28D8ABB1"); if(!atom){ /*为窗口设置原子值可以避免一个程序重复运行多个实例*/ win.quitMessage(); return; } webhook = function(s){ thread.invoke( function(s){ import inet.whttpEx; import console; var http = inet.whttpEx() var json = http.post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=94f1dbcc-f3df-4293-bcfd-8f7106daf6da",{ http_post_type="json"; msgtype= "text"; text={ content=s } }) console.dump(json) },s ) } import asynSMTPServer; smtpServer = asynSMTPServer(); smtpServer.logInfo = true; //用户帐号合法性验证 smtpServer.onMail = function(mail){ var tick = tonumber(time()) var emlFile = string.format("/eml/%s/%s/%d.eml", mail.mailTo[1], mail.mailFrom, tick) string.save(emlFile, mail.data) var eml = asynSMTPServer.parseEml(emlFile); // webhook(eml.html) winform.txtMessage.print(eml.html) } smtpServer.onCommand = function(cmd, arg){ winform.txtMessage.print(cmd, arg) } winform.onClose = function(hwnd,message,wParam,lParam){ smtpServer.stop(); } if(smtpServer.run()){ winform.txtMessage.print("SMTP服务器已启动:"); } else { winform.txtMessage.print("SMTP服务器启动失败."); } winform.show(); win.loopMessage();
域名解析:如图
测试步骤:
1、需要解析三条记录:
假如有域名:abc.dom,有公网服务器IP:192.168.1.1
1)、mail A记录指向服务器IP:192.168.1.1
2)、smtp CNAME指向上面的mail.abc.com
3)、@ MX记录指向上面的mail.abc.com
2、在服务器192.168.1.1上防火墙放行端口25
3、在服务器192.168.1.1上运行编译出来的软件
4、可选步骤,修改demo中的webhook函数,将机器人推送地址修改为自己的推送链接,或删除此函数调用
5、QQ邮箱发一封测试邮件到test@abc.com或 任意字符@abc.com(因为代码中并没有验证邮箱名)
以下是预期效果:
QQ邮箱发信
软件收信:
微信群机器人推送:(删除webhook函数无以下效果,whttpEx相关请自行修改为whttp)
2 个回复 | 最后更新于 2022-04-18
登录后方可回帖
很有想法(⌒▽⌒),赞一个