package mysql import ( "context" "database/sql" "fmt" "net" "strconv" "time" "github.com/go-sql-driver/mysql" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins/base" ) // MySQLConnector 实现MySQL数据库服务连接器 // 遵循 base.ServiceConnector 接口规范,提供标准化的MySQL连接和认证功能 // MySQLConnector MySQL数据库连接器 type MySQLConnector struct { host string // 目标主机地址 port int // 目标端口号 } // NewMySQLConnector 创建新的MySQL连接器实例 // 自动注册SOCKS代理支持,统一使用Context超时控制 func NewMySQLConnector() *MySQLConnector { connector := &MySQLConnector{} // 注册SOCKS代理支持的dialer(如果配置了代理) connector.registerProxyDialer() return connector } // Connect 建立到MySQL服务的基础连接 // 实现 base.ServiceConnector 接口的 Connect 方法 func (c *MySQLConnector) 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 // 构建基础连接字符串(无认证信息) connStr := c.buildConnectionString(info.Host, port, "", "") // 创建数据库连接实例 db, err := sql.Open("mysql", connStr) if err != nil { return nil, fmt.Errorf("创建连接失败: %v", err) } // 配置连接池参数 timeout := time.Duration(common.Timeout) * time.Second db.SetConnMaxLifetime(timeout) db.SetConnMaxIdleTime(timeout) db.SetMaxIdleConns(0) return db, nil } // Authenticate 使用凭据对MySQL服务进行身份认证 // 实现 base.ServiceConnector 接口的 Authenticate 方法 // 关键优化:使用独立的Context避免上游超时问题,并优化内存使用 func (c *MySQLConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error { // 直接使用传入的Context,它已经包含了正确的超时设置 // 内存优化:预构建连接字符串,避免重复分配 connStr := c.buildConnectionString(c.host, c.port, cred.Username, cred.Password) common.LogDebug(fmt.Sprintf("MySQL尝试认证: %s@%s:%d", cred.Username, c.host, c.port)) // 内存优化:直接建立连接而不创建连接池 // 避免为单次认证创建不必要的连接池开销 rawConn, err := c.connectDirect(ctx, connStr) if err != nil { common.LogDebug(fmt.Sprintf("MySQL直连失败: %s@%s:%d - %v", cred.Username, c.host, c.port, err)) return fmt.Errorf("连接失败: %v", err) } defer rawConn.Close() // 执行简单的认证验证 err = c.validateConnection(ctx, rawConn) if err != nil { common.LogDebug(fmt.Sprintf("MySQL认证失败: %s@%s:%d - %v", cred.Username, c.host, c.port, err)) return fmt.Errorf("认证失败: %v", err) } common.LogDebug(fmt.Sprintf("MySQL认证成功: %s@%s:%d", cred.Username, c.host, c.port)) return nil } // Close 关闭MySQL连接 // 实现 base.ServiceConnector 接口的 Close 方法 func (c *MySQLConnector) Close(conn interface{}) error { if db, ok := conn.(*sql.DB); ok { return db.Close() } return nil } // 已移除未使用的 connectWithCredentials 方法 // buildConnectionString 构建MySQL连接字符串 // 根据是否配置SOCKS代理选择合适的连接方式 // 移除timeout参数,统一使用Context控制超时 func (c *MySQLConnector) buildConnectionString(host string, port int, username, password string) string { // 根据代理配置选择网络类型 if common.Socks5Proxy != "" { // SOCKS代理连接模式,移除timeout参数,由Context控制 return fmt.Sprintf("%v:%v@tcp-proxy(%v:%v)/mysql?charset=utf8", username, password, host, port) } else { // 标准TCP直连模式,移除timeout参数,由Context控制 return fmt.Sprintf("%v:%v@tcp(%v:%v)/mysql?charset=utf8", username, password, host, port) } } // 已移除未使用的 buildConnectionStringWithCredentials 方法 // connectDirect 内存优化:直接建立MySQL连接,避免连接池开销 // 用于单次认证场景,减少内存分配和资源浪费 func (c *MySQLConnector) connectDirect(ctx context.Context, connStr string) (*sql.Conn, error) { // 创建最小化配置的临时数据库实例 db, err := sql.Open("mysql", connStr) if err != nil { return nil, fmt.Errorf("创建连接实例失败: %v", err) } defer db.Close() // 确保临时db实例被清理 // 禁用连接池以减少内存开销 db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) db.SetConnMaxLifetime(0) // 获取原始连接 conn, err := db.Conn(ctx) if err != nil { return nil, fmt.Errorf("获取连接失败: %v", err) } return conn, nil } // validateConnection 内存优化:轻量级连接验证 // 使用最小开销的方式验证MySQL连接有效性 func (c *MySQLConnector) validateConnection(ctx context.Context, conn *sql.Conn) error { // 使用传入的Context进行验证,统一超时控制 return conn.PingContext(ctx) } // registerProxyDialer 注册SOCKS代理支持的网络拨号器 // 仅在配置了SOCKS代理时才注册,避免不必要的开销 func (c *MySQLConnector) registerProxyDialer() { if common.Socks5Proxy == "" { return // 未配置代理,跳过注册 } // 向MySQL驱动注册自定义的代理拨号器 mysql.RegisterDialContext("tcp-proxy", func(ctx context.Context, addr string) (net.Conn, error) { return common.WrapperTcpWithContext(ctx, "tcp", addr) }) }