Added Virus Scanning to Downloads folder.

main
Robert 7 months ago
parent 65ebd1142b
commit 3d892725b8
  1. 10
      build.sh
  2. 2
      config.json.example
  3. 2
      core/configure/configure.go
  4. 2
      core/monitor_running_bins/monitor_running_bins.go
  5. 138
      core/new_file_monitor/new_file_monitor.go
  6. 4
      core/scanner/scanner.go
  7. 59
      execguard.go
  8. 2
      go.mod
  9. 2
      go.sum

@ -1,6 +1,8 @@
#!/bin/bash
go build -o execguard
sudo cp execguard /usr/local/bin/
sudo ./execguard --update /usr/local/bin/execguard
echo -e "Running execguard...Hit CTRL+C to end."
sudo execguard
if [ $? -eq 0 ]; then
sudo cp execguard /usr/local/bin/
sudo ./execguard --update /usr/local/bin/execguard
echo -e "Running execguard...Hit CTRL+C to end."
sudo execguard
fi

@ -2,6 +2,8 @@
"db_file": "/etc/execguard/system.db",
"log_file": "/var/log/execguard.log",
"mail_prog": "/usr/bin/mail",
"scanner_prog": "/usr/bin/clamscan",
"downloads": ["/home/bobs/Downloads"],
"scan_interval": 0,
"protected_dirs": ["/home"],
"skip_dirs": [".cache",".git"],

@ -9,7 +9,9 @@ type Config struct {
DbFile string `json:"db_file"` // optional DB File
LogFile string `json:"log_file"` // optional Log File
MailProg string `json:"mail_prog"` // optional Mail Program
ScannerProg string `json:"scanner_prog"` // optional Virus Scanner Program
ProtectedDirs []string `json:"protected_dirs"`
Downloads []string `josn:"downloads"`
AlertEmail string `json:"alert_email"` // optional root@localhost
SkipDirs []string `json:"skip_dirs"`
ScanInterval int `json:"scan_interval"` // in minutes, 0 disables scan

@ -1,4 +1,4 @@
package monitor
package monitor_running_bins
import (
"execguard/core/alert"

@ -0,0 +1,138 @@
package new_file_monitor
import (
"execguard/core/alert"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"time"
"sync"
"database/sql"
"io"
"strings"
"github.com/fsnotify/fsnotify"
)
var (
alertCache sync.Map
)
func Monitor_new_files(dirs []string, db *sql.DB, log log.Logger, scannerPath string) {
// Create new watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
// Start listening for events
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
// Check if the event is a file creation
if event.Op&fsnotify.Create == fsnotify.Create {
if strings.HasSuffix(event.Name, ".tmp") || strings.HasSuffix(event.Name, ".swp") {
continue
}
// Give the file a moment to finish writing (if downloading)
time.Sleep(2 * time.Second)
// Check if it's a file (not a directory)
fileInfo, err := os.Stat(event.Name)
if err != nil {
log.Printf("Error checking file: %v", err)
continue
}
if !fileInfo.IsDir() {
//exists, err := isFileNonEmpty(event.Name)
//if err != nil {
// log.Printf("Error checking file size: %v", err)
// continue
//}
if fileInfo.Size() > 0 {
log.Printf("New file detected: %s\n", event.Name)
go scanFile(event.Name, scannerPath, db, log)
}
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("Error:", err)
}
}
}()
for _, dir := range dirs {
// Add the folder to watch
err = watcher.Add(dir)
if err != nil {
log.Fatal(err)
}
}
select {}
}
func scanFile(filePath string, scannerPath string, db *sql.DB, log log.Logger) {
time.Sleep(time.Duration(300) * time.Millisecond)
// Get just the filename for cleaner output
fileName := filepath.Base(filePath)
log.Printf("Scanning file: %s\n", fileName)
cmd := exec.Command(scannerPath, "-v", filePath)
output, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Error scanning %s: %v\n", fileName, err)
}
log.Printf("Scan results for %s:\n%s\n", fileName, string(output))
// Check the exit status (0 = clean, 1 = virus found)
if cmd.ProcessState.ExitCode() == 1 {
log.Printf("WARNING: Virus detected in %s\n", fileName)
if _, seen := alertCache.LoadOrStore(filePath, struct{}{}); !seen {
go alert.SendAlert(fmt.Sprintf("Virus detected!: %s", filePath), db, log)
time.AfterFunc(10*time.Minute, func() {
alertCache.Delete(filePath)
})
}
} else if cmd.ProcessState.ExitCode() == 0 {
log.Printf("File %s is clean\n", fileName)
}
}
func isFileNonEmpty(filename string) (bool, error) {
file, err := os.Open(filename)
if err != nil {
return false, err
}
defer file.Close()
// Seek to the end
_, err = file.Seek(0, io.SeekEnd)
if err != nil {
return false, err
}
// Get current position (which is file size)
pos, err := file.Seek(0, io.SeekCurrent)
if err != nil {
return false, err
}
return pos > 0, nil
}

@ -37,14 +37,14 @@ func SetGlobalConfig(c configure.Config) {
config = c
}
func PeriodicScan(dirs []string, db *sql.DB, log log.Logger, mailPath string) {
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(config.ScanInterval) * time.Minute
interval := time.Duration(scanInterval) * time.Minute
// log.Printf("Starting periodic scan every %v...", interval)
for {
for _, dir := range dirs {

@ -5,12 +5,14 @@ import (
"execguard/core/configure"
"execguard/core/hasher"
"execguard/core/make_key"
"execguard/core/monitor"
"execguard/core/monitor_running_bins"
"execguard/core/new_file_monitor"
"execguard/core/scanner"
"execguard/core/sys_database"
"database/sql"
"flag"
"log"
"strings"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
@ -21,22 +23,32 @@ const (
dbFileDefault = "/etc/execguard/system.db"
logFileDefault = "/var/log/execguard.log"
mailPathDefault = "/usr/bin/mail"
clamscanDefault = "/usr/bin/clamscan"
scanIntervalDefault = 0 // Disabled
)
var (
downloadsDefault []string
downloads []string
configFile string
dbFile string
logFile string
mailPath string
clamscanPath string
scanIntervalFlag int
downloadsFlag string
configFlag string
dbFlag string
logFlag string
mailFlag string
clamscanFlag string
initMode bool
initFile string
updateFile string
migrateMode bool
newKey bool
dirs []string
scanInterval int
config *configure.Config
)
@ -44,10 +56,13 @@ func main() {
var err error
var log log.Logger
flag.IntVar(&scanIntervalFlag, "scanDelayMinutes", 99, "0 disables scanner")
flag.StringVar(&downloadsFlag, "downloads", "none", "use specified Downloads folders comma-seperated list")
flag.StringVar(&configFlag, "config", "", "use specified file for config")
flag.StringVar(&dbFlag, "db", "", "use specified file for database")
flag.StringVar(&logFlag, "log", "", "use specified file for Logging")
flag.StringVar(&mailFlag, "mail", "", "use specified file for Mail sending")
flag.StringVar(&clamscanFlag, "scanner", "", "use specified binary for Virus Scanning")
flag.BoolVar(&initMode, "init", false, "initialize and populate allowed executable database")
flag.StringVar(&initFile, "initFile", "", "file containing files to add to allowed database with hash")
flag.StringVar(&updateFile, "update", "", "add specified file to allowed database with hash")
@ -61,7 +76,7 @@ func main() {
}
scanner.SetModes(initMode, initFile, updateFile, migrateMode)
monitor.SetModes(initMode, initFile, updateFile, migrateMode)
monitor_running_bins.SetModes(initMode, initFile, updateFile, migrateMode)
sys_database.SetModes(initMode, initFile, updateFile, migrateMode)
if configFlag != "" {
@ -78,7 +93,7 @@ func main() {
hasher.SetGlobalConfig(*config)
alert.SetGlobalConfig(*config)
monitor.SetGlobalConfig(*config)
monitor_running_bins.SetGlobalConfig(*config)
scanner.SetGlobalConfig(*config)
// Set Vars...arguemtns first, then config, then defaults
@ -107,6 +122,34 @@ func main() {
}
alert.SetGlobalMail(mailPath)
if clamscanFlag != "" {
clamscanPath = clamscanFlag
} else if config.ScannerProg != "" {
clamscanPath = config.ScannerProg
} else {
clamscanPath = clamscanDefault
}
if scanIntervalFlag != 99 {
scanInterval = scanIntervalFlag
} else if config.ScanInterval != 99 {
scanInterval = config.ScanInterval
} else {
scanInterval = scanIntervalDefault
}
if downloadsFlag != "none" {
downloads = strings.Split(downloadsFlag, ",")
}
if len(downloads) > 0 {
dirs = downloads
} else if len(config.Downloads) > 0 {
dirs = config.Downloads
} else {
dirs = downloadsDefault
}
logf, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
@ -156,18 +199,22 @@ func main() {
return
}
if config.ScanInterval > 0 {
if scanInterval > 0 {
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from scan panic: %v", r)
}
}()
scanner.PeriodicScan(config.ProtectedDirs, db, log, mailPath)
scanner.PeriodicScan(config.ProtectedDirs, db, log, mailPath, scanInterval)
}()
}
if len(dirs) > 0 {
go new_file_monitor.Monitor_new_files(dirs, db, log, clamscanPath)
}
if err := monitor.MonitorExecutions(db, log, mailPath); err != nil {
if err := monitor_running_bins.MonitorExecutions(db, log, mailPath); err != nil {
log.Fatalf("Execution monitoring failed: %v", err)
os.Exit(4) // Exit with status code 4
}

@ -7,3 +7,5 @@ require (
github.com/yang3yen/xxtea-go v1.0.3
golang.org/x/sys v0.33.0
)
require github.com/fsnotify/fsnotify v1.9.0 // indirect

@ -1,3 +1,5 @@
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/yang3yen/xxtea-go v1.0.3 h1:C7yBcDRb909v39llhqx+QjAerOeWB+Oyqt/Z7yC7TBk=

Loading…
Cancel
Save