fscan/Plugins/services/smtp/connector.go
ZacharyZcR c2bb4bfd35 feat: Rsync和SMTP服务插件迁移到新架构完成
- 完成Rsync文件同步服务插件迁移
  * 实现RSYNCD协议支持和模块列表获取
  * 支持匿名访问和认证扫描
  * 添加Docker测试环境配置

- 完成SMTP邮件服务插件迁移
  * 实现SMTP协议和PLAIN认证支持
  * 支持匿名访问检测和弱密码扫描
  * 添加Docker测试环境配置

- 更新国际化消息和插件注册机制
- 两个插件均通过完整功能测试验证
2025-08-09 13:46:46 +08:00

219 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}