搭建简单SMTP服务器
By
money
at 2022-04-18 • 0人收藏 • 1169人看过
参考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
登录后方可回帖
很有想法(⌒▽⌒),赞一个