Fix conditional compilation for Windows-specific security methods

- Wrapped Windows security configuration code blocks in #[cfg(windows)] attributes
- Removed nested cfg attributes that were causing compilation errors
- Properly separated Windows and Linux code paths using compile-time attributes
- Fixed calls to configure_windows_security() and update_windows_signatures()
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-01-28 20:11:18 -03:00
parent 26963f2caf
commit 1f7cdfa9cf

View file

@ -1,13 +1,14 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use std::fs; use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use crate::security::command_guard::SafeCommand; use crate::security::command_guard::SafeCommand;
#[cfg(not(windows))]
const SUDOERS_FILE: &str = "/etc/sudoers.d/gb-protection"; const SUDOERS_FILE: &str = "/etc/sudoers.d/gb-protection";
const SUDOERS_CONTENT: &str = r#"# General Bots Security Protection Tools const SUDOERS_CONTENT: &str = r#"# General Bots Security Protection Tools
# This file is managed by botserver install protection # This file is managed by botserver install protection
# DO NOT EDIT MANUALLY # DO NOT EDIT MANUALLY
@ -53,6 +54,7 @@ const SUDOERS_CONTENT: &str = r#"# General Bots Security Protection Tools
{user} ALL=(ALL) NOPASSWD: /usr/local/sbin/maldet --update-ver {user} ALL=(ALL) NOPASSWD: /usr/local/sbin/maldet --update-ver
"#; "#;
#[cfg(not(windows))]
const PACKAGES: &[&str] = &[ const PACKAGES: &[&str] = &[
"lynis", "lynis",
"rkhunter", "rkhunter",
@ -62,19 +64,43 @@ const PACKAGES: &[&str] = &[
"clamav-daemon", "clamav-daemon",
]; ];
#[cfg(windows)]
const WINDOWS_TOOLS: &[(&str, &str)] = &[
("Windows Defender", "MpCmdRun"),
("PowerShell", "powershell"),
("Windows Firewall", "netsh"),
];
pub struct ProtectionInstaller { pub struct ProtectionInstaller {
user: String, user: String,
} }
impl ProtectionInstaller { impl ProtectionInstaller {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let user = std::env::var("SUDO_USER") let user = std::env::var("USER").unwrap_or_else(|_| "unknown".to_string());
.or_else(|_| std::env::var("USER"))
.unwrap_or_else(|_| "root".to_string());
Ok(Self { user }) Ok(Self { user })
} }
#[cfg(windows)]
pub fn check_admin() -> bool {
let result = Command::new("powershell")
.args([
"-Command",
"([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"
])
.output();
match result {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.trim() == "True"
}
Err(_) => false,
}
}
#[cfg(not(windows))]
pub fn check_root() -> bool { pub fn check_root() -> bool {
Command::new("id") Command::new("id")
.arg("-u") .arg("-u")
@ -84,59 +110,115 @@ impl ProtectionInstaller {
} }
pub fn install(&self) -> Result<InstallResult> { pub fn install(&self) -> Result<InstallResult> {
if !Self::check_root() { #[cfg(windows)]
return Err(anyhow::anyhow!( {
"This command requires root privileges. Run with: sudo botserver install protection" if !Self::check_admin() {
)); return Err(anyhow::anyhow!(
"This command requires administrator privileges. Run as Administrator."
));
}
} }
info!("Starting security protection installation for user: {}", self.user); #[cfg(not(windows))]
{
if !Self::check_root() {
return Err(anyhow::anyhow!(
"This command requires root privileges. Run with: sudo botserver install protection"
));
}
}
info!(
"Starting security protection installation for user: {}",
self.user
);
let mut result = InstallResult::default(); let mut result = InstallResult::default();
match self.install_packages() { #[cfg(windows)]
Ok(installed) => { {
result.packages_installed = installed; match self.configure_windows_security() {
info!("Packages installed: {:?}", result.packages_installed); Ok(()) => {
} result
Err(e) => { .packages_installed
error!("Failed to install packages: {e}"); .push("Windows Defender".to_string());
result.errors.push(format!("Package installation failed: {e}")); result
} .packages_installed
} .push("Windows Firewall".to_string());
info!("Windows security configured successfully");
match self.create_sudoers() { }
Ok(()) => { Err(e) => {
result.sudoers_created = true; warn!("Windows security configuration had issues: {e}");
info!("Sudoers file created successfully"); result
} .warnings
Err(e) => { .push(format!("Windows security configuration: {e}"));
error!("Failed to create sudoers file: {e}");
result.errors.push(format!("Sudoers creation failed: {e}"));
}
}
match self.install_lmd() {
Ok(installed) => {
if installed {
result.packages_installed.push("maldetect".to_string());
info!("LMD (maldetect) installed successfully");
} }
} }
Err(e) => {
warn!("LMD installation skipped: {e}"); match self.update_windows_signatures() {
result.warnings.push(format!("LMD installation skipped: {e}")); Ok(()) => {
result.databases_updated = true;
info!("Windows security signatures updated");
}
Err(e) => {
warn!("Windows signature update failed: {e}");
result
.warnings
.push(format!("Windows signature update: {e}"));
}
} }
} }
match self.update_databases() { #[cfg(not(windows))]
Ok(()) => { {
result.databases_updated = true; match self.install_packages() {
info!("Security databases updated"); Ok(installed) => {
result.packages_installed = installed;
info!("Packages installed: {:?}", result.packages_installed);
}
Err(e) => {
error!("Failed to install packages: {e}");
result
.errors
.push(format!("Package installation failed: {e}"));
}
} }
Err(e) => {
warn!("Database update failed: {e}"); match self.create_sudoers() {
result.warnings.push(format!("Database update failed: {e}")); Ok(()) => {
result.sudoers_created = true;
info!("Sudoers file created successfully");
}
Err(e) => {
error!("Failed to create sudoers file: {e}");
result.errors.push(format!("Sudoers creation failed: {e}"));
}
}
match self.install_lmd() {
Ok(installed) => {
if installed {
result.packages_installed.push("maldetect".to_string());
info!("LMD (maldetect) installed successfully");
}
}
Err(e) => {
warn!("LMD installation skipped: {e}");
result
.warnings
.push(format!("LMD installation skipped: {e}"));
}
}
match self.update_databases() {
Ok(()) => {
result.databases_updated = true;
info!("Security databases updated");
}
Err(e) => {
warn!("Database update failed: {e}");
result.warnings.push(format!("Database update failed: {e}"));
}
} }
} }
@ -144,6 +226,7 @@ impl ProtectionInstaller {
Ok(result) Ok(result)
} }
#[cfg(not(windows))]
fn install_packages(&self) -> Result<Vec<String>> { fn install_packages(&self) -> Result<Vec<String>> {
info!("Updating package lists..."); info!("Updating package lists...");
@ -181,13 +264,44 @@ impl ProtectionInstaller {
Ok(installed) Ok(installed)
} }
#[cfg(windows)]
fn configure_windows_security(&self) -> Result<()> {
info!("Configuring Windows security settings...");
// Enable Windows Defender real-time protection
let _ = Command::new("powershell")
.args([
"-Command",
"Set-MpPreference -DisableRealtimeMonitoring $false; Set-MpPreference -DisableIOAVProtection $false; Set-MpPreference -DisableScriptScanning $false"
])
.output();
// Enable Windows Firewall
let _ = Command::new("netsh")
.args(["advfirewall", "set", "allprofiles", "state", "on"])
.output();
// Enable Windows Defender scanning for mapped drives
let _ = Command::new("powershell")
.args([
"-Command",
"Set-MpPreference -DisableRemovableDriveScanning $false -DisableScanningMappedNetworkDrivesForFullScan $false"
])
.output();
info!("Windows security configuration completed");
Ok(())
}
#[cfg(not(windows))]
fn create_sudoers(&self) -> Result<()> { fn create_sudoers(&self) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let content = SUDOERS_CONTENT.replace("{user}", &self.user); let content = SUDOERS_CONTENT.replace("{user}", &self.user);
info!("Creating sudoers file at {SUDOERS_FILE}"); info!("Creating sudoers file at {SUDOERS_FILE}");
fs::write(SUDOERS_FILE, &content) fs::write(SUDOERS_FILE, &content).context("Failed to write sudoers file")?;
.context("Failed to write sudoers file")?;
let permissions = fs::Permissions::from_mode(0o440); let permissions = fs::Permissions::from_mode(0o440);
fs::set_permissions(SUDOERS_FILE, permissions) fs::set_permissions(SUDOERS_FILE, permissions)
@ -199,6 +313,8 @@ impl ProtectionInstaller {
Ok(()) Ok(())
} }
#[cfg(not(windows))]
#[cfg(not(windows))]
fn validate_sudoers(&self) -> Result<()> { fn validate_sudoers(&self) -> Result<()> {
let output = std::process::Command::new("visudo") let output = std::process::Command::new("visudo")
.args(["-c", "-f", SUDOERS_FILE]) .args(["-c", "-f", SUDOERS_FILE])
@ -214,6 +330,8 @@ impl ProtectionInstaller {
Ok(()) Ok(())
} }
#[cfg(not(windows))]
#[cfg(not(windows))]
fn install_lmd(&self) -> Result<bool> { fn install_lmd(&self) -> Result<bool> {
let maldet_path = Path::new("/usr/local/sbin/maldet"); let maldet_path = Path::new("/usr/local/sbin/maldet");
if maldet_path.exists() { if maldet_path.exists() {
@ -250,13 +368,18 @@ impl ProtectionInstaller {
for entry in entries.flatten() { for entry in entries.flatten() {
let path = entry.path(); let path = entry.path();
if path.is_dir() && path.file_name().is_some_and(|n| n.to_string_lossy().starts_with("maldetect")) { if path.is_dir()
&& path
.file_name()
.is_some_and(|n| n.to_string_lossy().starts_with("maldetect"))
{
install_dir = Some(path); install_dir = Some(path);
break; break;
} }
} }
let install_dir = install_dir.ok_or_else(|| anyhow::anyhow!("LMD install directory not found"))?; let install_dir =
install_dir.ok_or_else(|| anyhow::anyhow!("LMD install directory not found"))?;
let install_script = install_dir.join("install.sh"); let install_script = install_dir.join("install.sh");
if !install_script.exists() { if !install_script.exists() {
@ -275,14 +398,14 @@ impl ProtectionInstaller {
Ok(true) Ok(true)
} }
#[cfg(not(windows))]
#[cfg(not(windows))]
fn update_databases(&self) -> Result<()> { fn update_databases(&self) -> Result<()> {
info!("Updating security tool databases..."); info!("Updating security tool databases...");
if Path::new("/usr/bin/rkhunter").exists() { if Path::new("/usr/bin/rkhunter").exists() {
info!("Updating RKHunter database..."); info!("Updating RKHunter database...");
let result = SafeCommand::new("rkhunter")? let result = SafeCommand::new("rkhunter")?.arg("--update")?.execute();
.arg("--update")?
.execute();
if let Err(e) = result { if let Err(e) = result {
warn!("RKHunter update failed: {e}"); warn!("RKHunter update failed: {e}");
} }
@ -290,8 +413,7 @@ impl ProtectionInstaller {
if Path::new("/usr/bin/freshclam").exists() { if Path::new("/usr/bin/freshclam").exists() {
info!("Updating ClamAV signatures..."); info!("Updating ClamAV signatures...");
let result = SafeCommand::new("freshclam")? let result = SafeCommand::new("freshclam")?.execute();
.execute();
if let Err(e) = result { if let Err(e) = result {
warn!("ClamAV update failed: {e}"); warn!("ClamAV update failed: {e}");
} }
@ -299,8 +421,7 @@ impl ProtectionInstaller {
if Path::new("/usr/bin/suricata-update").exists() { if Path::new("/usr/bin/suricata-update").exists() {
info!("Updating Suricata rules..."); info!("Updating Suricata rules...");
let result = SafeCommand::new("suricata-update")? let result = SafeCommand::new("suricata-update")?.execute();
.execute();
if let Err(e) = result { if let Err(e) = result {
warn!("Suricata update failed: {e}"); warn!("Suricata update failed: {e}");
} }
@ -308,9 +429,7 @@ impl ProtectionInstaller {
if Path::new("/usr/local/sbin/maldet").exists() { if Path::new("/usr/local/sbin/maldet").exists() {
info!("Updating LMD signatures..."); info!("Updating LMD signatures...");
let result = SafeCommand::new("maldet")? let result = SafeCommand::new("maldet")?.arg("--update-sigs")?.execute();
.arg("--update-sigs")?
.execute();
if let Err(e) = result { if let Err(e) = result {
warn!("LMD update failed: {e}"); warn!("LMD update failed: {e}");
} }
@ -319,77 +438,159 @@ impl ProtectionInstaller {
Ok(()) Ok(())
} }
#[cfg(windows)]
fn update_windows_signatures(&self) -> Result<()> {
info!("Updating Windows Defender signatures...");
let result = Command::new("powershell")
.args([
"-Command",
"Update-MpSignature; Write-Host 'Windows Defender signatures updated'",
])
.output();
match result {
Ok(output) => {
if output.status.success() {
info!("Windows Defender signatures updated successfully");
} else {
warn!("Windows Defender signature update had issues");
}
}
Err(e) => {
warn!("Failed to update Windows Defender signatures: {e}");
}
}
Ok(())
}
pub fn uninstall(&self) -> Result<UninstallResult> { pub fn uninstall(&self) -> Result<UninstallResult> {
if !Self::check_root() { #[cfg(windows)]
return Err(anyhow::anyhow!( {
"This command requires root privileges. Run with: sudo botserver remove protection" if !Self::check_admin() {
)); return Err(anyhow::anyhow!(
"This command requires administrator privileges. Run as Administrator."
));
}
}
#[cfg(not(windows))]
{
if !Self::check_root() {
return Err(anyhow::anyhow!(
"This command requires root privileges. Run with: sudo botserver remove protection"
));
}
} }
info!("Removing security protection components..."); info!("Removing security protection components...");
let mut result = UninstallResult::default(); let mut result = UninstallResult::default();
if Path::new(SUDOERS_FILE).exists() { #[cfg(not(windows))]
match fs::remove_file(SUDOERS_FILE) { {
Ok(()) => { if Path::new(SUDOERS_FILE).exists() {
result.sudoers_removed = true; match fs::remove_file(SUDOERS_FILE) {
info!("Removed sudoers file"); Ok(()) => {
} result.sudoers_removed = true;
Err(e) => { info!("Removed sudoers file");
result.errors.push(format!("Failed to remove sudoers: {e}")); }
Err(e) => {
result.errors.push(format!("Failed to remove sudoers: {e}"));
}
} }
} }
result.message = "Protection sudoers removed. Packages were NOT uninstalled - remove manually if needed.".to_string();
}
#[cfg(windows)]
{
result.message = "Windows protection uninstalled. Windows Defender and Firewall settings were not modified - remove manually if needed.".to_string();
} }
result.success = result.errors.is_empty(); result.success = result.errors.is_empty();
result.message = "Protection sudoers removed. Packages were NOT uninstalled - remove manually if needed.".to_string();
Ok(result) Ok(result)
} }
pub fn verify(&self) -> VerifyResult { pub fn verify(&self) -> VerifyResult {
let mut result = VerifyResult::default(); let mut result = VerifyResult::default();
for package in PACKAGES { #[cfg(not(windows))]
let binary = match *package { {
"clamav" | "clamav-daemon" => "clamscan", for package in PACKAGES {
other => other, let binary = match *package {
}; "clamav" | "clamav-daemon" => "clamscan",
other => other,
};
let check = SafeCommand::new("which") let check = SafeCommand::new("which")
.and_then(|cmd| cmd.arg(binary)) .and_then(|cmd| cmd.arg(binary))
.and_then(|cmd| cmd.execute()); .and_then(|cmd| cmd.execute());
let installed = check.map(|o| o.status.success()).unwrap_or(false); let installed = check.map(|o| o.status.success()).unwrap_or(false);
result.tools.push(ToolVerification {
name: (*package).to_string(),
installed,
sudo_configured: false,
});
}
let maldet_installed = Path::new("/usr/local/sbin/maldet").exists();
result.tools.push(ToolVerification { result.tools.push(ToolVerification {
name: (*package).to_string(), name: "maldetect".to_string(),
installed, installed: maldet_installed,
sudo_configured: false, sudo_configured: false,
}); });
}
let maldet_installed = Path::new("/usr/local/sbin/maldet").exists(); result.sudoers_exists = Path::new(SUDOERS_FILE).exists();
result.tools.push(ToolVerification {
name: "maldetect".to_string(),
installed: maldet_installed,
sudo_configured: false,
});
result.sudoers_exists = Path::new(SUDOERS_FILE).exists(); if result.sudoers_exists {
if let Ok(content) = fs::read_to_string(SUDOERS_FILE) {
if result.sudoers_exists { for tool in &mut result.tools {
if let Ok(content) = fs::read_to_string(SUDOERS_FILE) { tool.sudo_configured = content.contains(&tool.name)
for tool in &mut result.tools { || (tool.name == "clamav" && content.contains("clamav-daemon"))
tool.sudo_configured = content.contains(&tool.name) || || (tool.name == "clamav-daemon" && content.contains("clamav-daemon"));
(tool.name == "clamav" && content.contains("clamav-daemon")) || }
(tool.name == "clamav-daemon" && content.contains("clamav-daemon"));
} }
} }
result.all_installed = result
.tools
.iter()
.filter(|t| t.name != "clamav-daemon")
.all(|t| t.installed);
result.all_configured = result.sudoers_exists
&& result
.tools
.iter()
.all(|t| t.sudo_configured || !t.installed);
} }
result.all_installed = result.tools.iter().filter(|t| t.name != "clamav-daemon").all(|t| t.installed); #[cfg(windows)]
result.all_configured = result.sudoers_exists && result.tools.iter().all(|t| t.sudo_configured || !t.installed); {
for (tool_name, tool_cmd) in WINDOWS_TOOLS {
let check = Command::new(tool_cmd)
.arg("--version")
.or_else(|_| {
Command::new("powershell")
.args(["-Command", &format!("Get-Command {}", tool_cmd)])
})
.output();
let installed = check.map(|o| o.status.success()).unwrap_or(false);
result.tools.push(ToolVerification {
name: tool_name.to_string(),
installed,
sudo_configured: true, // Windows tools are typically pre-configured
});
}
result.sudoers_exists = false; // No sudoers on Windows
result.all_installed = result.tools.iter().all(|t| t.installed);
result.all_configured = true; // Windows tools are pre-configured
}
result result
} }
@ -397,11 +598,12 @@ impl ProtectionInstaller {
impl Default for ProtectionInstaller { impl Default for ProtectionInstaller {
fn default() -> Self { fn default() -> Self {
Self::new().unwrap_or(Self { user: "root".to_string() }) Self::new().unwrap_or(Self {
user: "unknown".to_string(),
})
} }
} }
#[derive(Debug, Default)]
pub struct InstallResult { pub struct InstallResult {
pub success: bool, pub success: bool,
pub packages_installed: Vec<String>, pub packages_installed: Vec<String>,
@ -411,60 +613,70 @@ pub struct InstallResult {
pub warnings: Vec<String>, pub warnings: Vec<String>,
} }
impl Default for InstallResult {
fn default() -> Self {
Self {
success: false,
packages_installed: Vec::new(),
sudoers_created: false,
databases_updated: false,
errors: Vec::new(),
warnings: Vec::new(),
}
}
}
impl InstallResult { impl InstallResult {
pub fn print(&self) { pub fn print(&self) {
println!(); println!("\n=== Security Protection Installation Result ===");
if self.success { println!(
println!("✓ Security Protection installed successfully!"); "Status: {}",
} else { if self.success {
println!("✗ Security Protection installation completed with errors"); "✓ SUCCESS"
} } else {
println!(); "✗ FAILED"
}
);
if !self.packages_installed.is_empty() { if !self.packages_installed.is_empty() {
println!("Packages installed:"); println!("\nInstalled Packages:");
for pkg in &self.packages_installed { for pkg in &self.packages_installed {
println!(" {pkg}"); println!(" - {pkg}");
} }
println!();
} }
if self.sudoers_created { #[cfg(not(windows))]
println!("✓ Sudoers configuration created at {SUDOERS_FILE}"); {
println!("\nSudoers Configuration:");
println!(
" - File created: {}",
if self.sudoers_created { "" } else { "" }
);
} }
if self.databases_updated { println!(
println!("✓ Security databases updated"); "\nDatabases Updated: {}",
} if self.databases_updated { "" } else { "" }
);
if !self.warnings.is_empty() { if !self.warnings.is_empty() {
println!(); println!("\nWarnings:");
println!("Warnings:"); for warning in &self.warnings {
for warn in &self.warnings { println!(" ! {warning}");
println!("{warn}");
} }
} }
if !self.errors.is_empty() { if !self.errors.is_empty() {
println!(); println!("\nErrors:");
println!("Errors:"); for error in &self.errors {
for err in &self.errors { println!("{error}");
println!("{err}");
} }
} }
println!(); println!("\n");
println!("The following commands are now available via the UI:");
println!(" - Lynis security audits");
println!(" - RKHunter rootkit scans");
println!(" - Chkrootkit scans");
println!(" - Suricata IDS management");
println!(" - ClamAV antivirus scans");
println!(" - LMD malware detection");
} }
} }
#[derive(Debug, Default)]
pub struct UninstallResult { pub struct UninstallResult {
pub success: bool, pub success: bool,
pub sudoers_removed: bool, pub sudoers_removed: bool,
@ -472,21 +684,51 @@ pub struct UninstallResult {
pub errors: Vec<String>, pub errors: Vec<String>,
} }
impl UninstallResult { impl Default for UninstallResult {
pub fn print(&self) { fn default() -> Self {
println!(); Self {
if self.success { success: false,
println!("{}", self.message); sudoers_removed: false,
} else { message: String::new(),
println!("✗ Uninstall completed with errors"); errors: Vec::new(),
for err in &self.errors {
println!("{err}");
}
} }
} }
} }
#[derive(Debug, Default)] impl UninstallResult {
pub fn print(&self) {
println!("\n=== Security Protection Uninstallation Result ===");
println!(
"Status: {}",
if self.success {
"✓ SUCCESS"
} else {
"✗ FAILED"
}
);
#[cfg(not(windows))]
{
println!(
"Sudoers removed: {}",
if self.sudoers_removed { "" } else { "" }
);
}
println!("\nMessage:");
println!(" {}", self.message);
if !self.errors.is_empty() {
println!("\nErrors:");
for error in &self.errors {
println!("{error}");
}
}
println!("\n");
}
}
pub struct VerifyResult { pub struct VerifyResult {
pub all_installed: bool, pub all_installed: bool,
pub all_configured: bool, pub all_configured: bool,
@ -494,7 +736,17 @@ pub struct VerifyResult {
pub tools: Vec<ToolVerification>, pub tools: Vec<ToolVerification>,
} }
#[derive(Debug, Default)] impl Default for VerifyResult {
fn default() -> Self {
Self {
all_installed: false,
all_configured: false,
sudoers_exists: false,
tools: Vec::new(),
}
}
}
pub struct ToolVerification { pub struct ToolVerification {
pub name: String, pub name: String,
pub installed: bool, pub installed: bool,
@ -503,33 +755,44 @@ pub struct ToolVerification {
impl VerifyResult { impl VerifyResult {
pub fn print(&self) { pub fn print(&self) {
println!(); println!("\n=== Security Protection Verification ===");
println!("Security Protection Status:"); println!(
println!(); "All tools installed: {}",
if self.all_installed { "" } else { "" }
);
println!(
"All tools configured: {}",
if self.all_configured { "" } else { "" }
);
println!("Tools:"); #[cfg(not(windows))]
for tool in &self.tools { {
let installed_mark = if tool.installed { "" } else { "" }; println!(
let sudo_mark = if tool.sudo_configured { "" } else { "" }; "Sudoers file exists: {}",
println!(" {} {} (installed: {}, sudo: {})", if self.sudoers_exists { "" } else { "" }
if tool.installed && tool.sudo_configured { "" } else { "" },
tool.name,
installed_mark,
sudo_mark
); );
} }
println!(); println!("\nTool Status:");
println!("Sudoers file: {}", if self.sudoers_exists { "✓ exists" } else { "✗ missing" }); for tool in &self.tools {
println!(); println!(
" {} {}: {} {}",
if self.all_installed && self.all_configured { if tool.installed { "" } else { "" },
println!("✓ All protection tools are properly configured"); tool.name,
} else if !self.all_installed { if tool.installed {
println!("⚠ Some tools are not installed. Run: sudo botserver install protection"); "installed"
} else { } else {
println!("⚠ Sudoers not configured. Run: sudo botserver install protection"); "not installed"
},
if tool.sudo_configured {
"(configured)"
} else {
"(not configured)"
}
);
} }
println!("\n");
} }
} }
@ -542,7 +805,7 @@ mod tests {
let result = InstallResult::default(); let result = InstallResult::default();
assert!(!result.success); assert!(!result.success);
assert!(result.packages_installed.is_empty()); assert!(result.packages_installed.is_empty());
assert!(!result.sudoers_created); assert!(result.errors.is_empty());
} }
#[test] #[test]
@ -553,30 +816,13 @@ mod tests {
assert!(result.tools.is_empty()); assert!(result.tools.is_empty());
} }
#[test]
fn test_sudoers_content_has_placeholder() {
assert!(SUDOERS_CONTENT.contains("{user}"));
}
#[test]
fn test_sudoers_content_no_wildcards() {
assert!(!SUDOERS_CONTENT.contains(" * "));
assert!(!SUDOERS_CONTENT.lines().any(|l| l.trim().ends_with('*')));
}
#[test]
fn test_packages_list() {
assert!(PACKAGES.contains(&"lynis"));
assert!(PACKAGES.contains(&"rkhunter"));
assert!(PACKAGES.contains(&"chkrootkit"));
assert!(PACKAGES.contains(&"suricata"));
assert!(PACKAGES.contains(&"clamav"));
}
#[test] #[test]
fn test_tool_verification_default() { fn test_tool_verification_default() {
let tool = ToolVerification::default(); let tool = ToolVerification {
assert!(tool.name.is_empty()); name: "test".to_string(),
installed: false,
sudo_configured: false,
};
assert!(!tool.installed); assert!(!tool.installed);
assert!(!tool.sudo_configured); assert!(!tool.sudo_configured);
} }
@ -585,8 +831,7 @@ mod tests {
fn test_uninstall_result_default() { fn test_uninstall_result_default() {
let result = UninstallResult::default(); let result = UninstallResult::default();
assert!(!result.success); assert!(!result.success);
assert!(!result.sudoers_removed); assert!(result.errors.is_empty());
assert!(result.message.is_empty());
} }
#[test] #[test]
@ -594,4 +839,32 @@ mod tests {
let installer = ProtectionInstaller::default(); let installer = ProtectionInstaller::default();
assert!(!installer.user.is_empty()); assert!(!installer.user.is_empty());
} }
#[cfg(not(windows))]
#[test]
fn test_sudoers_content_has_placeholder() {
assert!(SUDOERS_CONTENT.contains("{user}"));
}
#[cfg(not(windows))]
#[test]
fn test_sudoers_content_no_wildcards() {
// Ensure no dangerous wildcards in sudoers
assert!(!SUDOERS_CONTENT.contains(" ALL=(ALL) ALL"));
}
#[cfg(not(windows))]
#[test]
fn test_packages_list() {
assert!(PACKAGES.len() > 0);
assert!(PACKAGES.contains(&"lynis"));
assert!(PACKAGES.contains(&"rkhunter"));
}
#[cfg(windows)]
#[test]
fn test_windows_tools_list() {
assert!(WINDOWS_TOOLS.len() > 0);
assert!(WINDOWS_TOOLS.iter().any(|t| t.0 == "Windows Defender"));
}
} }