fscan/Plugins/services/pop3/connector.go
ZacharyZcR 532daf16ed feat: POP3邮件服务插件迁移到新架构完成
- 实现POP3协议连接器,支持普通和TLS连接(端口110/995)
- 支持弱密码检测和邮件服务识别功能
- 简化实现,不提供利用功能,专注于安全扫描
- 集成国际化消息系统
- 测试通过:服务识别和弱密码检测功能
2025-08-09 13:16:47 +08:00

269 lines
6.4 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 pop3
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// POP3Connector POP3连接器实现
type POP3Connector struct {
host string
port int
}
// POP3Connection POP3连接结构
type POP3Connection struct {
conn net.Conn
reader *bufio.Reader
isTLS bool
banner string
username string
password string
}
// NewPOP3Connector 创建POP3连接器
func NewPOP3Connector() *POP3Connector {
return &POP3Connector{}
}
// Connect 建立POP3连接
func (c *POP3Connector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
timeout := time.Duration(common.Timeout) * time.Second
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 结果通道
type connResult struct {
conn *POP3Connection
isTLS bool
banner string
err error
}
resultChan := make(chan connResult, 1)
// 在协程中尝试连接
go func() {
// 首先尝试普通连接
conn, err := common.WrapperTcpWithTimeout("tcp", address, timeout)
if err == nil {
banner, authErr := c.readBanner(conn, timeout)
if authErr == nil {
pop3Conn := &POP3Connection{
conn: conn,
reader: bufio.NewReader(conn),
isTLS: false,
banner: banner,
}
select {
case <-ctx.Done():
conn.Close()
case resultChan <- connResult{pop3Conn, false, banner, nil}:
}
return
}
conn.Close()
}
// 如果普通连接失败尝试TLS连接(端口995)
if info.Ports == "995" {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
dialer := &net.Dialer{Timeout: timeout}
tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", address, tlsConfig)
if tlsErr == nil {
banner, authErr := c.readBanner(tlsConn, timeout)
if authErr == nil {
pop3Conn := &POP3Connection{
conn: tlsConn,
reader: bufio.NewReader(tlsConn),
isTLS: true,
banner: banner,
}
select {
case <-ctx.Done():
tlsConn.Close()
case resultChan <- connResult{pop3Conn, true, banner, nil}:
}
return
}
tlsConn.Close()
}
}
select {
case <-ctx.Done():
case resultChan <- connResult{nil, false, "", fmt.Errorf(i18n.GetText("pop3_connection_failed"), err)}:
}
}()
// 等待连接结果
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
}
return result.conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// Authenticate 进行POP3认证
func (c *POP3Connector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
pop3Conn, ok := conn.(*POP3Connection)
if !ok {
return fmt.Errorf("无效的POP3连接类型")
}
timeout := time.Duration(common.Timeout) * time.Second
// 发送用户名
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
_, err := pop3Conn.conn.Write([]byte(fmt.Sprintf("USER %s\r\n", cred.Username)))
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
// 读取用户名响应
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
response, err := pop3Conn.reader.ReadString('\n')
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
if !strings.Contains(response, "+OK") {
return fmt.Errorf("用户名无效: %s", strings.TrimSpace(response))
}
// 短暂延迟
time.Sleep(300 * time.Millisecond)
// 发送密码
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
_, err = pop3Conn.conn.Write([]byte(fmt.Sprintf("PASS %s\r\n", cred.Password)))
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
// 读取密码响应
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
response, err = pop3Conn.reader.ReadString('\n')
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
if strings.Contains(response, "+OK") {
// 认证成功,保存凭据信息
pop3Conn.username = cred.Username
pop3Conn.password = cred.Password
return nil
}
return fmt.Errorf("认证失败: %s", strings.TrimSpace(response))
}
// Close 关闭POP3连接
func (c *POP3Connector) Close(conn interface{}) error {
if pop3Conn, ok := conn.(*POP3Connection); ok {
// 尝试优雅退出
if pop3Conn.conn != nil {
pop3Conn.conn.Write([]byte("QUIT\r\n"))
pop3Conn.conn.Close()
}
return nil
}
return fmt.Errorf("无效的POP3连接类型")
}
// readBanner 读取POP3服务器横幅信息
func (c *POP3Connector) readBanner(conn net.Conn, timeout time.Duration) (string, error) {
reader := bufio.NewReader(conn)
conn.SetDeadline(time.Now().Add(timeout))
response, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("读取横幅失败: %v", err)
}
// 检查是否为正常的POP3响应
if strings.Contains(response, "+OK") {
return strings.TrimSpace(response), nil
}
// 检查错误响应
if strings.Contains(strings.ToLower(response), "error") ||
strings.Contains(strings.ToLower(response), "too many") {
return "", fmt.Errorf("服务器拒绝连接: %s", strings.TrimSpace(response))
}
return strings.TrimSpace(response), nil
}
// GetConnectionInfo 获取连接信息
func (conn *POP3Connection) GetConnectionInfo() map[string]interface{} {
info := map[string]interface{}{
"protocol": "POP3",
"tls": conn.isTLS,
"banner": conn.banner,
}
if conn.username != "" {
info["username"] = conn.username
info["authenticated"] = true
}
return info
}
// IsAlive 检查连接是否仍然有效
func (conn *POP3Connection) IsAlive() bool {
if conn.conn == nil {
return false
}
// 尝试发送NOOP命令检查连接
conn.conn.SetDeadline(time.Now().Add(5 * time.Second))
_, err := conn.conn.Write([]byte("NOOP\r\n"))
if err != nil {
return false
}
response, err := conn.reader.ReadString('\n')
if err != nil {
return false
}
return strings.Contains(response, "+OK")
}
// ExecuteCommand 执行POP3命令
func (conn *POP3Connection) ExecuteCommand(command string) (string, error) {
if conn.conn == nil {
return "", fmt.Errorf("连接未建立")
}
timeout := time.Duration(common.Timeout) * time.Second
conn.conn.SetDeadline(time.Now().Add(timeout))
// 发送命令
_, err := conn.conn.Write([]byte(command + "\r\n"))
if err != nil {
return "", fmt.Errorf("发送命令失败: %v", err)
}
// 读取响应
response, err := conn.reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
return strings.TrimSpace(response), nil
}