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 #!/bin/bash
go build -o execguard go build -o execguard
sudo cp execguard /usr/local/bin/ if [ $? -eq 0 ]; then
sudo ./execguard --update /usr/local/bin/execguard sudo cp execguard /usr/local/bin/
echo -e "Running execguard...Hit CTRL+C to end." sudo ./execguard --update /usr/local/bin/execguard
sudo execguard echo -e "Running execguard...Hit CTRL+C to end."
sudo execguard
fi

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

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

@ -1,4 +1,4 @@
package monitor package monitor_running_bins
import ( import (
"execguard/core/alert" "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 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{}) skipSet := make(map[string]struct{})
for _, skip := range config.SkipDirs { for _, skip := range config.SkipDirs {
if abs, err := filepath.Abs(skip); err == nil { if abs, err := filepath.Abs(skip); err == nil {
skipSet[abs] = struct{}{} skipSet[abs] = struct{}{}
} }
} }
interval := time.Duration(config.ScanInterval) * time.Minute interval := time.Duration(scanInterval) * time.Minute
// log.Printf("Starting periodic scan every %v...", interval) // log.Printf("Starting periodic scan every %v...", interval)
for { for {
for _, dir := range dirs { for _, dir := range dirs {

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

@ -7,3 +7,5 @@ require (
github.com/yang3yen/xxtea-go v1.0.3 github.com/yang3yen/xxtea-go v1.0.3
golang.org/x/sys v0.33.0 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 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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= github.com/yang3yen/xxtea-go v1.0.3 h1:C7yBcDRb909v39llhqx+QjAerOeWB+Oyqt/Z7yC7TBk=

Loading…
Cancel
Save