package scanner // Copyright (c) 2025 Robert Strutts // License: MIT // GIT: https://git.mysnippetsofcode.com/bobs/execguard import ( "execguard/core/alert" "execguard/core/configure" "execguard/core/sys_database" "database/sql" "path/filepath" "time" "strings" "fmt" "log" "os" "io/fs" "sync" ) var ( config configure.Config initMode bool initFile string updateFile string migrateMode bool dbMutex sync.Mutex alertCache sync.Map ) func SetModes(mode bool, file string, update string, migrate bool) { initMode = mode initFile = file updateFile = update migrateMode = migrate } func SetGlobalConfig(c configure.Config) { config = c } func PeriodicScan(dirs []string, db *sql.DB, log log.Logger, mailPath string, scanInterval int) { skipSet := make(map[string]struct{}) for _, skip := range config.SkipDirs { if abs, err := filepath.Abs(skip); err == nil { skipSet[abs] = struct{}{} } } interval := time.Duration(scanInterval) * time.Minute // log.Printf("Starting periodic scan every %v...", interval) for { for _, dir := range dirs { filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { return nil } absPath, err := filepath.Abs(path) if err != nil { return nil } // Skip if in any of the SkipDirs for skipDir := range skipSet { if strings.HasPrefix(absPath, skipDir) { return filepath.SkipDir } } if d.Type().IsRegular() { info, err := d.Info() if err != nil || (info.Mode().Perm()&0111 == 0) { return nil } absPath, _ = filepath.EvalSymlinks(absPath) if initMode { sys_database.AddToAllowed(db, log, absPath) } else if !sys_database.IsAllowed(db, log, absPath) { log.Printf("Found unauthorized executable: %s", absPath) os.Chmod(absPath, info.Mode()&^0111) if _, seen := alertCache.LoadOrStore(absPath, struct{}{}); !seen { go alert.SendAlert(fmt.Sprintf("Unauthorized execution attempt blocked: %s", absPath), db, log) time.AfterFunc(10*time.Minute, func() { alertCache.Delete(absPath) }) } } } return nil }) } time.Sleep(interval) } }