diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go new file mode 100644 index 0000000..3850b5f --- /dev/null +++ b/internal/scanner/scanner.go @@ -0,0 +1,127 @@ +package scanner + +import ( + "log" + "os" + "path/filepath" + "time" + + "github.com/shirou/gopsutil/v4/cpu" + "github.com/wuko233/sysmonitord/internal/network" + "github.com/wuko233/sysmonitord/internal/whitelist" +) + +type Scanner struct { + wlManager *whitelist.Manager + client *network.WSClient + cpuLimit float64 + scanPaths []string + stopChan chan struct{} +} + +func NewScanner(wl *whitelist.Manager, client *network.WSClient) *Scanner { + return &Scanner{ + wlManager: wl, + client: client, + cpuLimit: 50.0, + scanPaths: []string{"/bin", "/sbin", "/usr/bin", "/usr/sbin", "/etc", "/tmp", "/home"}, + stopChan: make(chan struct{}), + } +} + +func (s *Scanner) Start() { + log.Println("[扫描器] 启动文件完整性扫描...") + go s.scanLoop() +} + +func (s *Scanner) scanLoop() { + ticker := time.NewTicker(10 * time.Minute) + defer ticker.Stop() + for { + select { + case <-s.stopChan: + return + case <-ticker.C: + s.performScan() + } + } +} + +func (s *Scanner) performScan() { + log.Println("[扫描器] 开始新一轮全盘扫描") + + for _, root := range s.scanPaths { + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + + select { + case <-s.stopChan: + return filepath.SkipDir + default: + } + + if err != nil { + return nil + } + + s.checkCPUAndSleep() + + if info.IsDir() { + if s.wlManager.IsPathIgnored(path) { + return filepath.SkipDir + } + + return nil + } + + isWhitelisted, isHashMatch, err := s.wlManager.CheckFileStatus(path) + if err != nil { + log.Printf("[扫描器] 检查文件状态失败: %v", err) + return nil + } + + if !isWhitelisted { + log.Printf("[扫描器] 发现未在白名单文件: %s", path) + s.reportFile(path, "NON_WHITELISTED_FILE") + } else if !isHashMatch { + log.Printf("[扫描器] 警告!文件Hash不匹配(可能被篡改): %s", path) + s.reportFile(path, "FILE_HASH_MISMATCH") + } + + return nil + }) + if err != nil { + log.Printf("[扫描器] 扫描目录 %s 出错: %v", root, err) + } + } +} + +func (s *Scanner) checkCPUAndSleep() { + percent, err := cpu.Percent(time.Second, false) + if err != nil || len(percent) == 0 { + log.Printf("[扫描器] 获取CPU使用率失败: %v", err) + return + } + + if percent[0] > s.cpuLimit { + log.Printf("[扫描器] CPU使用率过高 (%.2f%%),暂停扫描5秒", percent[0]) + time.Sleep(5 * time.Second) + } + + time.Sleep(10 * time.Millisecond) +} + +func (s *Scanner) reportFile(path string, alertType string) { + payload := map[string]interface{}{ + "filepath": path, + "status": "detected", + } + + packet := network.NewPactet(alertType, payload) + + s.client.SendQueue(packet) +} + +func (s *Scanner) Stop() { + log.Println("[扫描器] 停止文件完整性扫描...") + close(s.stopChan) +} diff --git a/internal/scanner/watcher.go b/internal/scanner/watcher.go new file mode 100644 index 0000000..fc2e76f --- /dev/null +++ b/internal/scanner/watcher.go @@ -0,0 +1,119 @@ +package scanner + +import ( + "log" + "os" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/wuko233/sysmonitord/internal/network" + "github.com/wuko233/sysmonitord/internal/whitelist" +) + +type Watcher struct { + wlManager *whitelist.Manager + client *network.WSClient + watcher *fsnotify.Watcher + stopChan chan struct{} + watchPaths []string +} + +func NewWatcher(wl *whitelist.Manager, client *network.WSClient) (*Watcher, error) { + fsWatch, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + return &Watcher{ + wlManager: wl, + client: client, + watcher: fsWatch, + stopChan: make(chan struct{}), + + // TODO: 当前仅实现对主目录的监控,后续实现递归监控子目录 + watchPaths: []string{ + "/bin", "/sbin", "/usr/bin", "/etc/init.d", "/tmp", + }, + }, nil +} + +func (w *Watcher) Start() { + log.Println("[监听器] 启动实时文件监控...") + + for _, path := range w.watchPaths { + if _, err := os.Stat(path); err == nil { + if err := w.watcher.Add(path); err != nil { + log.Printf("[监听器] 无法监控路径 %s: %v", path, err) + } else { + log.Printf("[监听器] 开始监控路径: %s", path) + } + } + } + + go w.eventLoop() +} + +func (w *Watcher) eventLoop() { + for { + select { + case <-w.stopChan: + return + case event, ok := <-w.watcher.Events: + if !ok { + return + } + + if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) { + if w.wlManager.IsPathIgnored(event.Name) { + continue + } + + go w.handleFileChange(event.Name, event.Op.String()) + } + case err, ok := <-w.watcher.Errors: + if !ok { + return + } + log.Printf("[监听器] 错误: %v", err) + } + } +} + +func (w *Watcher) handleFileChange(path string, op string) { + + time.Sleep(200 * time.Millisecond) // 等待文件写入完成 + + if _, err := os.Stat(path); os.IsNotExist(err) { + return + } + + isWhitelisted, isHashMatch, err := w.wlManager.CheckFileStatus(path) + if err != nil { + return + } + + if !isWhitelisted { + log.Printf("[监听器] 实时拦截:检测到非白名单文件变动 (%s): %s", op, path) + w.reportEvent(path, "REALTIME_FILE_ALERT", op) + } else if !isHashMatch { + log.Printf("[监听器] 实时拦截:检测到白名单文件被篡改 (%s): %s", op, path) + w.reportEvent(path, "REALTIME_HASH_MISMATCH", op) + } +} + +func (w *Watcher) reportEvent(path, alertType, op string) { + payload := map[string]interface{}{ + "filepath": path, + "operation": op, + "time": time.Now(), + } + + packet := network.NewPactet(alertType, payload) + w.client.SendQueue(packet) +} + +func (w *Watcher) Stop() { + log.Println("[监听器] 停止实时文件监控...") + close(w.stopChan) + w.watcher.Close() +}