package base import ( "context" "fmt" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "strings" "sync" "time" ) // ============================================================================= // 通用扫描器基础实现 // ============================================================================= // BaseScanner 基础扫描器,提供通用的扫描逻辑 type BaseScanner struct { Name string metadata *PluginMetadata capabilities []Capability } // NewBaseScanner 创建基础扫描器 func NewBaseScanner(name string, metadata *PluginMetadata) *BaseScanner { return &BaseScanner{ Name: name, metadata: metadata, } } // GetName 获取扫描器名称 func (s *BaseScanner) GetName() string { return s.Name } // GetCapabilities 获取扫描器支持的能力 func (s *BaseScanner) GetCapabilities() []Capability { return s.capabilities } // SetCapabilities 设置扫描器能力 func (s *BaseScanner) SetCapabilities(caps []Capability) { s.capabilities = caps } // GetMetadata 获取插件元数据 func (s *BaseScanner) GetMetadata() *PluginMetadata { return s.metadata } // ============================================================================= // 通用并发扫描框架 // ============================================================================= // ConcurrentScanConfig 并发扫描配置 type ConcurrentScanConfig struct { MaxConcurrent int // 最大并发数 Timeout time.Duration // 单次扫描超时 MaxRetries int // 最大重试次数 RetryDelay time.Duration // 重试延迟 } // CredentialScanner 凭据扫描器接口 type CredentialScanner interface { // ScanCredential 扫描单个凭据 ScanCredential(ctx context.Context, info *common.HostInfo, cred *Credential) (*ScanResult, error) } // ConcurrentCredentialScan 并发凭据扫描通用实现 func ConcurrentCredentialScan( ctx context.Context, scanner CredentialScanner, info *common.HostInfo, credentials []*Credential, config *ConcurrentScanConfig, ) (*ScanResult, error) { if len(credentials) == 0 { return nil, fmt.Errorf("没有提供凭据") } // 设置默认配置 if config == nil { config = &ConcurrentScanConfig{ MaxConcurrent: 10, Timeout: time.Duration(common.Timeout) * time.Second, MaxRetries: common.MaxRetries, RetryDelay: 500 * time.Millisecond, } } // 限制并发数 maxConcurrent := config.MaxConcurrent if maxConcurrent <= 0 { maxConcurrent = 10 } if maxConcurrent > len(credentials) { maxConcurrent = len(credentials) } // 创建工作池 var wg sync.WaitGroup resultChan := make(chan *ScanResult, 1) workChan := make(chan *Credential, maxConcurrent) scanCtx, scanCancel := context.WithCancel(ctx) defer scanCancel() // 启动工作协程 for i := 0; i < maxConcurrent; i++ { wg.Add(1) go func() { defer wg.Done() for credential := range workChan { select { case <-scanCtx.Done(): return default: // 开始监控连接 target := fmt.Sprintf("%s:%s", info.Host, info.Ports) monitor := common.GetConcurrencyMonitor() monitor.StartConnection("credential", target) result := scanCredentialWithRetry(scanCtx, scanner, info, credential, config) // 完成连接监控 monitor.FinishConnection("credential", target) if result != nil && result.Success { select { case resultChan <- result: scanCancel() // 找到有效凭据,取消其他工作 default: } return } } } }() } // 发送工作 go func() { for _, cred := range credentials { select { case <-scanCtx.Done(): break default: workChan <- cred } } close(workChan) }() // 等待结果或完成 go func() { wg.Wait() close(resultChan) }() // 获取结果 select { case result, ok := <-resultChan: if ok && result != nil && result.Success { return result, nil } return nil, fmt.Errorf(i18n.GetText("plugin_all_creds_failed")) case <-ctx.Done(): scanCancel() return nil, ctx.Err() } } // scanCredentialWithRetry 带重试的单凭据扫描 func scanCredentialWithRetry( ctx context.Context, scanner CredentialScanner, info *common.HostInfo, cred *Credential, config *ConcurrentScanConfig, ) *ScanResult { for retry := 0; retry < config.MaxRetries; retry++ { select { case <-ctx.Done(): return &ScanResult{ Success: false, Error: ctx.Err(), } default: if retry > 0 { time.Sleep(config.RetryDelay) } // 创建独立的超时上下文 connCtx, cancel := context.WithTimeout(ctx, config.Timeout) result, err := scanner.ScanCredential(connCtx, info, cred) cancel() if result != nil && result.Success { return result } // 检查是否需要重试 if err != nil && !shouldRetry(err) { break } } } return &ScanResult{ Success: false, Error: fmt.Errorf("重试次数耗尽"), } } // shouldRetry 判断是否应该重试 func shouldRetry(err error) bool { if err == nil { return false } errStr := strings.ToLower(err.Error()) // 不需要重试的错误 noRetryErrors := []string{ "access denied", "authentication failed", "invalid credentials", "permission denied", "unauthorized", } for _, noRetry := range noRetryErrors { if strings.Contains(errStr, noRetry) { return false } } return true } // ============================================================================= // 凭据生成工具 // ============================================================================= // GenerateCredentials 生成用户名密码组合的凭据列表 func GenerateCredentials(usernames []string, passwords []string) []*Credential { var credentials []*Credential for _, username := range usernames { for _, password := range passwords { // 支持 {user} 占位符替换 actualPassword := strings.ReplaceAll(password, "{user}", username) credentials = append(credentials, &Credential{ Username: username, Password: actualPassword, Extra: make(map[string]string), }) } } return credentials } // GeneratePasswordOnlyCredentials 生成仅密码的凭据列表(如Redis) func GeneratePasswordOnlyCredentials(passwords []string) []*Credential { var credentials []*Credential for _, password := range passwords { credentials = append(credentials, &Credential{ Password: password, Extra: make(map[string]string), }) } return credentials } // ============================================================================= // 结果处理工具 // ============================================================================= // SaveScanResult 保存扫描结果到通用输出系统 func SaveScanResult(info *common.HostInfo, result *ScanResult, pluginName string) { if result == nil || !result.Success { return } target := fmt.Sprintf("%s:%d", info.Host, info.Ports) // 保存成功的凭据 for _, cred := range result.Credentials { var message string if cred.Username != "" && cred.Password != "" { message = fmt.Sprintf("%s %s %s %s", pluginName, target, cred.Username, cred.Password) } else if cred.Password != "" { message = fmt.Sprintf("%s %s [密码] %s", pluginName, target, cred.Password) } else { message = fmt.Sprintf("%s %s 未授权访问", pluginName, target) } common.LogSuccess(message) // 保存到输出系统的详细实现... // 这里可以调用common.SaveResult等函数 } }