fscan/plugins/legacy/smb/common/scanner.go
ZacharyZcR daa7fb2dcb refactor: 重构SMB和SMB2插件架构,大幅减少代码重复
- 创建通用SMB框架,包含抽象接口、凭据管理和并发扫描引擎
- SMB2插件代码量从492行减少到159行,减少68%代码量
- 统一错误分类和处理机制,提高代码可维护性
- 支持密码和哈希两种认证方式,保持向后兼容性
- 模块化设计便于单元测试和功能扩展
2025-08-12 19:11:40 +08:00

198 lines
4.4 KiB
Go

package common
import (
"context"
"fmt"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
)
// DefaultScanner 默认并发扫描器实现
type DefaultScanner struct{}
// NewScanner 创建新的扫描器
func NewScanner() *DefaultScanner {
return &DefaultScanner{}
}
// Scan 执行并发扫描
func (s *DefaultScanner) Scan(ctx context.Context, target *TargetInfo,
connector SmbConnector, credMgr CredentialManager, config *ScanConfig) (*ScanResult, error) {
credentials := credMgr.GenerateCredentials()
if len(credentials) == 0 {
return nil, fmt.Errorf("没有可用的认证凭据")
}
common.LogDebug(fmt.Sprintf("开始%s扫描 %s:%d (总用户数: %d, 总组合数: %d)",
connector.GetProtocolName(), target.Host, target.Port,
len(common.Userdict["smb"]), len(credentials)))
// 确定并发数
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 cred := range workChan {
select {
case <-scanCtx.Done():
return
default:
// 检查用户是否已锁定
if credMgr.IsUserLocked(cred.Username) {
common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", cred.Username))
continue
}
// 尝试连接
result := s.tryCredential(scanCtx, target, connector, cred, config.Timeout)
// 处理认证失败
if !result.Success && result.Error != nil {
credMgr.HandleAuthFailure(cred.Username, result.Error)
}
// 如果成功,发送结果并取消其他工作
if result.Success {
select {
case resultChan <- result:
scanCancel()
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
defer close(workChan)
for i, cred := range credentials {
select {
case <-scanCtx.Done():
return
default:
// 跳过已锁定用户
if credMgr.IsUserLocked(cred.Username) {
continue
}
// 记录尝试日志
s.logAttempt(connector.GetProtocolName(), i+1, len(credentials), cred)
workChan <- cred
}
}
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result, nil
}
return nil, nil
case <-ctx.Done():
common.LogDebug(fmt.Sprintf("%s扫描全局超时", connector.GetProtocolName()))
scanCancel()
return nil, fmt.Errorf("全局超时")
}
}
// tryCredential 尝试单个凭据
func (s *DefaultScanner) tryCredential(ctx context.Context, target *TargetInfo,
connector SmbConnector, cred Credential, timeout time.Duration) *ScanResult {
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// 在协程中尝试连接
resultChan := make(chan *ConnectionResult, 1)
go func() {
result, err := connector.Connect(connCtx, target, &cred)
if err != nil {
result = &ConnectionResult{
Success: false,
Error: err,
}
}
select {
case <-connCtx.Done():
case resultChan <- result:
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
if result.Success {
return &ScanResult{
Success: true,
Credential: cred,
Shares: result.Shares,
}
}
// 分类错误
classifiedError := ClassifySmbError(result.Error)
return &ScanResult{
Success: false,
Error: classifiedError,
Credential: cred,
Shares: result.Shares,
}
case <-connCtx.Done():
var err error
if ctx.Err() != nil {
err = ctx.Err() // 全局超时
} else {
err = ErrTimeout // 连接超时
}
return &ScanResult{
Success: false,
Error: err,
Credential: cred,
}
}
}
// logAttempt 记录尝试日志
func (s *DefaultScanner) logAttempt(protocol string, current, total int, cred Credential) {
if cred.IsHash {
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试%s: %s Hash:%s",
current, total, protocol, cred.Username, common.HashValue))
} else {
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试%s: %s:%s",
current, total, protocol, cred.Username, cred.Password))
}
}