搭建简单SMTP服务器

By money at 2022-04-18 • 0人收藏 • 455人看过

参考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();

域名解析:如图

image.png

测试步骤:

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邮箱发信

image.png

软件收信:

image.png

微信群机器人推送:(删除webhook函数无以下效果,whttpEx相关请自行修改为whttp)

image.png

2 个回复 | 最后更新于 2022-04-18
2022-04-18   #1

很有想法(⌒▽⌒),赞一个

2022-04-18   #2

超级喜欢 

登录后方可回帖

登 录
信息栏
公 告:

专注分享

谢绝纯提问

谢谢合作!
本站域名:HtmLayout.Cn
aardio可以快速开发上位机,本站主要记录了学习过程中遇到的问题和解决办法及aardio代码分享

这里主要专注于aardio学习交流和经验分享.
纯私人站,当笔记本用的,学到哪写到哪.

Aardio 官方站:Aardio官方
Aardio最新功能:Aardio官方更新日志
本 站 主 站:Stm32cube中文网
Sciter中文在线文档Sciter在线学习文档
空间赞助:才仁机械
打赏本站
Loading...