package main // Copyright (c) 2025 Robert Strutts // License: MIT // GIT: https://git.mysnippetsofcode.com/bobs/execguard import ( "execguard/core/alert" "execguard/core/configure" "execguard/core/rotating_logger" "execguard/core/hasher" "execguard/core/make_key" "execguard/core/monitor_running_bins" "execguard/core/new_file_monitor" "execguard/core/scanner" "execguard/core/sys_database" "database/sql" "flag" "log" "strings" "os" "fmt" "time" "path/filepath" _ "github.com/mattn/go-sqlite3" ) const ( configFileDefault = "/etc/execguard/config.yaml" 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 enforceMode bool initFile string updateFile string migrateMode bool newKey bool dirs []string scanInterval int config *configure.Config ) func main() { var err error 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.BoolVar(&enforceMode, "enforce", false, "enforce policies from Database Lock-down...") 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.BoolVar(&migrateMode, "migrate", false, "recompute hashes of all allowed paths using current settings") flag.BoolVar(&newKey, "newKey", false, "generate a new XXTEA-compatible encryption key") flag.Parse() if newKey { if make_key.Make_a_key() { return } else { os.Exit(1) // Exit with status code 1 } } if os.Geteuid() != 0 { fmt.Printf("This program must be run as root") os.Exit(1) // Exit with status code 1 } scanner.SetModes(initMode, initFile, updateFile, migrateMode) monitor_running_bins.SetModes(initMode, initFile, updateFile, migrateMode) sys_database.SetModes(initMode, initFile, updateFile, migrateMode) if configFlag != "" { configFile = configFlag } else { configFile = configFileDefault } if logFlag != "" { logFile = logFlag } else { logFile = logFileDefault } config, err := configure.LoadConfig(configFile, logFile) if err != nil { fmt.Printf("Error loading config: %v", err) os.Exit(3) // Exit with status code 3 } hasher.SetGlobalConfig(*config) alert.SetGlobalConfig(*config) monitor_running_bins.SetGlobalConfig(*config) scanner.SetGlobalConfig(*config) // Set Vars...arguemtns first, then config, then defaults if dbFlag != "" { dbFile = dbFlag } else if config.DbFile != "" { dbFile = config.DbFile } else { dbFile = dbFileDefault } if mailFlag != "" { mailPath = mailFlag } else if config.MailProg != "" { mailPath = config.MailProg } else { mailPath = mailPathDefault } 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 } rl, err := rotating_logger.NewRotatingLogger(config.Logging) if err != nil { fmt.Printf("Failed to initialize logger: %v", err) } defer rl.Close() logger := log.New(rl, "", 0) logger.SetPrefix(fmt.Sprintf("[%s] ", time.Now().Format(config.Logging.TimestampFormat))) db, err := sql.Open("sqlite3", dbFile) if err != nil { logger.Fatalf("Error opening database: %v", err) os.Exit(2) // Exit with status code 2 } defer db.Close() sys_database.CreateTable(db, logger) if initFile != "" { absPath, err := filepath.Abs(initFile) if err != nil { logger.Fatalf("Invalid init file path: %v", err) os.Exit(1) // Exit with status code 1 } sys_database.RunInit(db, logger, absPath) return } if updateFile != "" { absPath, err := filepath.Abs(updateFile) if err != nil { logger.Fatalf("Invalid update file path: %v", err) os.Exit(1) // Exit with status code 1 } sys_database.AddToAllowed(db, logger, absPath) logger.Printf("Added to allowed list: %s", absPath) return } if migrateMode { sys_database.RunMigration(db, logger) return } if scanInterval > 0 { go func() { defer func() { if r := recover(); r != nil { logger.Printf("Recovered from scan panic: %v", r) } }() scanner.PeriodicScan(config.ProtectedDirs, db, logger, mailPath, scanInterval) }() } if len(dirs) > 0 { go new_file_monitor.Monitor_new_files(dirs, db, logger, clamscanPath) } if err := monitor_running_bins.MonitorExecutions(db, logger, mailPath); err != nil { logger.Fatalf("Execution monitoring failed: %v", err) os.Exit(4) // Exit with status code 4 } }