mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

- 完成Rsync文件同步服务插件迁移 * 实现RSYNCD协议支持和模块列表获取 * 支持匿名访问和认证扫描 * 添加Docker测试环境配置 - 完成SMTP邮件服务插件迁移 * 实现SMTP协议和PLAIN认证支持 * 支持匿名访问检测和弱密码扫描 * 添加Docker测试环境配置 - 更新国际化消息和插件注册机制 - 两个插件均通过完整功能测试验证
219 lines
4.6 KiB
Go
219 lines
4.6 KiB
Go
package smtp
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net"
|
||
"net/smtp"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/shadow1ng/fscan/common"
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
"github.com/shadow1ng/fscan/plugins/base"
|
||
)
|
||
|
||
// SMTPConnector SMTP连接器实现
|
||
type SMTPConnector struct {
|
||
host string
|
||
port int
|
||
}
|
||
|
||
// SMTPConnection SMTP连接结构
|
||
type SMTPConnection struct {
|
||
client *smtp.Client
|
||
username string
|
||
password string
|
||
info string
|
||
host string
|
||
}
|
||
|
||
// NewSMTPConnector 创建SMTP连接器
|
||
func NewSMTPConnector() *SMTPConnector {
|
||
return &SMTPConnector{}
|
||
}
|
||
|
||
// Connect 建立SMTP连接
|
||
func (c *SMTPConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
|
||
// 解析端口
|
||
port, err := strconv.Atoi(info.Ports)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("无效的端口号: %s", info.Ports)
|
||
}
|
||
|
||
c.host = info.Host
|
||
c.port = port
|
||
|
||
timeout := time.Duration(common.Timeout) * time.Second
|
||
addr := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
// 结果通道
|
||
type connResult struct {
|
||
conn *SMTPConnection
|
||
err error
|
||
banner string
|
||
}
|
||
resultChan := make(chan connResult, 1)
|
||
|
||
// 在协程中尝试连接
|
||
go func() {
|
||
// 建立TCP连接
|
||
tcpConn, err := net.DialTimeout("tcp", addr, timeout)
|
||
if err != nil {
|
||
select {
|
||
case <-ctx.Done():
|
||
case resultChan <- connResult{nil, err, ""}:
|
||
}
|
||
return
|
||
}
|
||
|
||
// 设置连接超时
|
||
tcpConn.SetDeadline(time.Now().Add(timeout))
|
||
|
||
// 创建SMTP客户端
|
||
client, err := smtp.NewClient(tcpConn, info.Host)
|
||
if err != nil {
|
||
tcpConn.Close()
|
||
select {
|
||
case <-ctx.Done():
|
||
case resultChan <- connResult{nil, err, ""}:
|
||
}
|
||
return
|
||
}
|
||
|
||
// 获取服务器信息
|
||
banner := "SMTP Service (Detected)"
|
||
|
||
// 创建连接对象
|
||
smtpConn := &SMTPConnection{
|
||
client: client,
|
||
info: banner,
|
||
host: info.Host,
|
||
}
|
||
|
||
select {
|
||
case <-ctx.Done():
|
||
client.Close()
|
||
case resultChan <- connResult{smtpConn, nil, banner}:
|
||
}
|
||
}()
|
||
|
||
// 等待连接结果
|
||
select {
|
||
case result := <-resultChan:
|
||
if result.err != nil {
|
||
return nil, result.err
|
||
}
|
||
return result.conn, nil
|
||
case <-ctx.Done():
|
||
return nil, ctx.Err()
|
||
}
|
||
}
|
||
|
||
// Authenticate 进行SMTP认证
|
||
func (c *SMTPConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
|
||
smtpConn, ok := conn.(*SMTPConnection)
|
||
if !ok {
|
||
return fmt.Errorf("无效的SMTP连接类型")
|
||
}
|
||
|
||
// 如果是空用户名和密码,测试匿名访问
|
||
if cred.Username == "" && cred.Password == "" {
|
||
// 尝试匿名发送测试邮件
|
||
return c.testAnonymousAccess(smtpConn)
|
||
}
|
||
|
||
// 结果通道
|
||
type authResult struct {
|
||
err error
|
||
}
|
||
resultChan := make(chan authResult, 1)
|
||
|
||
// 在协程中尝试认证
|
||
go func() {
|
||
// 尝试PLAIN认证
|
||
auth := smtp.PlainAuth("", cred.Username, cred.Password, smtpConn.host)
|
||
err := smtpConn.client.Auth(auth)
|
||
|
||
if err == nil {
|
||
// 认证成功,测试MAIL FROM权限
|
||
mailErr := smtpConn.client.Mail("test@test.com")
|
||
if mailErr != nil {
|
||
err = fmt.Errorf("认证成功但缺少发送权限: %v", mailErr)
|
||
}
|
||
}
|
||
|
||
select {
|
||
case <-ctx.Done():
|
||
case resultChan <- authResult{err}:
|
||
}
|
||
}()
|
||
|
||
// 等待认证结果
|
||
select {
|
||
case result := <-resultChan:
|
||
if result.err != nil {
|
||
return fmt.Errorf(i18n.GetText("smtp_auth_failed"), result.err)
|
||
}
|
||
|
||
// 更新连接信息
|
||
smtpConn.username = cred.Username
|
||
smtpConn.password = cred.Password
|
||
return nil
|
||
case <-ctx.Done():
|
||
return ctx.Err()
|
||
}
|
||
}
|
||
|
||
// testAnonymousAccess 测试匿名访问
|
||
func (c *SMTPConnector) testAnonymousAccess(smtpConn *SMTPConnection) error {
|
||
// 尝试匿名发送测试邮件
|
||
err := smtpConn.client.Mail("test@test.com")
|
||
if err != nil {
|
||
return fmt.Errorf("SMTP服务不支持匿名访问: %v", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Close 关闭SMTP连接
|
||
func (c *SMTPConnector) Close(conn interface{}) error {
|
||
if smtpConn, ok := conn.(*SMTPConnection); ok {
|
||
if smtpConn.client != nil {
|
||
smtpConn.client.Close()
|
||
}
|
||
return nil
|
||
}
|
||
return fmt.Errorf("无效的SMTP连接类型")
|
||
}
|
||
|
||
// GetConnectionInfo 获取连接信息
|
||
func (conn *SMTPConnection) GetConnectionInfo() map[string]interface{} {
|
||
info := map[string]interface{}{
|
||
"protocol": "SMTP",
|
||
"service": "SMTP",
|
||
"info": conn.info,
|
||
}
|
||
|
||
if conn.username != "" {
|
||
info["username"] = conn.username
|
||
info["authenticated"] = true
|
||
}
|
||
|
||
return info
|
||
}
|
||
|
||
// IsAlive 检查连接是否仍然有效
|
||
func (conn *SMTPConnection) IsAlive() bool {
|
||
if conn.client == nil {
|
||
return false
|
||
}
|
||
|
||
// 简单的NOOP命令测试连接
|
||
err := conn.client.Noop()
|
||
return err == nil
|
||
}
|
||
|
||
// GetServerInfo 获取服务器信息
|
||
func (conn *SMTPConnection) GetServerInfo() string {
|
||
return conn.info
|
||
} |