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 }