搭建简单SMTP服务器

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

参考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

超级喜欢 

登录后方可回帖

登 录
信息栏
 私人小站

本站域名

ChengXu.XYZ

投诉联系:  popdes@126.com



快速上位机开发学习,本站主要记录了学习过程中遇到的问题和解决办法及上位机代码分享

这里主要专注于学习交流和经验分享.
纯私人站,当笔记本用的,学到哪写到哪.
如果侵权,联系 Popdes@126.com

友情链接
Aardio官方
Aardio资源网


才仁机械


网站地图SiteMap

Loading...