diff --git a/.gitignore b/.gitignore index a7690f8..37316a8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ *.sw? # Build files -target \ No newline at end of file +target +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index ecd4412..a5e3371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,11 +34,12 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cai-watchdog" -version = "0.2.0" +version = "0.3.0" dependencies = [ "chrono", "exitcode", "file-utils", + "hashmap", "ini", "reqwest", "tokio", @@ -216,6 +217,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +[[package]] +name = "hashmap" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f4c5bfe5d332cdefc57bd31471448f8b6cb0398542f4aecc2620e577e6fd54" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -395,9 +402,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.3.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mime" diff --git a/Cargo.toml b/Cargo.toml index 4f6d70f..20bf62a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cai-watchdog" -version = "0.2.0" +version = "0.3.0" authors = ["Alexander I. Chebykin "] edition = "2018" @@ -15,4 +15,5 @@ chrono = "0.4.19" reqwest = "0.11" tokio = { version = "1", features = ["full"] } ini = "1.3.0" -exitcode = "1.1.2" \ No newline at end of file +exitcode = "1.1.2" +hashmap = "0.0.1" \ No newline at end of file diff --git a/README.md b/README.md index 71c98e9..f508d36 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ You can specify config file as parameter: ```cai-watchdog /path/to/config/config ``` [main] -check_interval - Interval between checks in seconds -rules_count - Rules count to be loaded from config. Rules sections must be enumerated continuously [rule1], [rule2] ... etc +check_interval = Interval between checks in seconds +rules_count = Rules count to be loaded from config. Rules sections must be enumerated continuously [rule1], [rule2] ... etc [notifications] -email - E-mail address for system notifications. Can be empty -command - Command to send notification -service_start - Send program start notification [true | false] +email = E-mail address for system notifications. Can be empty +command = Command to send notification +service_start = Send program start notification [true | false] [rule1] service = Service name @@ -102,3 +102,39 @@ Next you need to find your Telegram Chat ID. 1. From the Telegram home screen, search for ```chatid_echo_bot```. Click Chat ID Echo to open a chat 1. Enter ```/start``` to get the bot to send you your Telegram Chat ID 1. Take note of the Telegram Chat ID returned + +## User logins monitoring (*nix) + +Watchdog can send notifications on user login. Just add to ```/etc/profile.d/sshinfo.sh``` next lines: + +- For Telegram: + 1. ```User=$(whoami)``` + 1. ```send-telegram "SSH: User ${Users} is logged in"``` +- For e-mail: + 1. ```User=$(whoami)``` + 1. ```send-mail your@mail.addr 'SSH: User ${Users} is logged in' 'SSH: User ${Users} is logged in'``` + +## User logouts monitoring (*nix) + +1. Create file ```/etc/pam.d/pam_session.sh``` with next content: + + For Telegram: + + ``` + #!/bin/sh + if [ "$PAM_TYPE" = "close_session" ]; then + send-telegram "SSH: User is logged out" + fi + ``` + + For e-mail: + + ``` + #!/bin/sh + if [ "$PAM_TYPE" = "close_session" ]; then + send-mail your@mail.addr 'SSH: User is logged out' 'SSH: User is logged out' + fi + ``` + and set executable flag on it + +1. Modify ```/etc/pam.d/sshd```, add line ```session optional pam_exec.so quiet /etc/pam.d/pam_session.sh``` diff --git a/src/main.rs b/src/main.rs index d67882e..7961456 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,6 +154,8 @@ fn main() { print_help(args.clone()); + let mut just_started = true; + let cfg_file = if args.len() > 1 { &args[1] } else if cfg!(windows) { @@ -171,43 +173,85 @@ fn main() { let cfg = ini!(cfg_file); - let check_interval = cfg["main"]["check_interval"].clone().unwrap().parse::().unwrap() * 1000; - let rules_count = cfg["main"]["rules_count"].clone().unwrap().parse::().unwrap(); + let check_interval = if cfg["main"].contains_key("check_interval") { + cfg["main"]["check_interval"].clone().unwrap().parse::().unwrap() * 1000 + } else { + 10000 + }; + + let rules_count = if cfg["main"].contains_key("check_interval") { + cfg["main"]["rules_count"].clone().unwrap().parse::().unwrap() + } else { + 0 + }; let mut tasks = vec![]; for i in 1..rules_count + 1 { - let service = cfg[&format!("{}{}", "rule", i)]["service"].clone().unwrap(); - let uri = cfg[&format!("{}{}", "rule", i)]["uri"].clone().unwrap(); - let email = cfg[&format!("{}{}", "rule", i)]["email"].clone().unwrap(); - let command = cfg[&format!("{}{}", "rule", i)]["command"].clone().unwrap(); + let service = if cfg[&format!("{}{}", "rule", i)].contains_key("service") { + cfg[&format!("{}{}", "rule", i)]["service"].clone().unwrap() + } else { + format!("Service {}", i) + }; - debug_log(format!("rule {}", i)); + let uri = if cfg[&format!("{}{}", "rule", i)].contains_key("uri") { + cfg[&format!("{}{}", "rule", i)]["uri"].clone().unwrap() + } else { + "".to_string() + }; + + let email = if cfg[&format!("{}{}", "rule", i)].contains_key("email") { + cfg[&format!("{}{}", "rule", i)]["email"].clone().unwrap() + } else { + "".to_string() + }; + + let command = if cfg[&format!("{}{}", "rule", i)].contains_key("command") { + cfg[&format!("{}{}", "rule", i)]["command"].clone().unwrap() + } else { + "".to_string() + }; + + debug_log(format!("rule {}", i)); debug_log(format!("service {}", service)); - debug_log(format!("uri {}", uri)); - debug_log(format!("email {}", email)); + debug_log(format!("uri {}", uri)); + debug_log(format!("email {}", email)); debug_log(format!("command {}", command)); tasks.push( Rule{ - service: cfg[&format!("{}{}", "rule", i)]["service"].clone().unwrap().to_string(), - uri: cfg[&format!("{}{}", "rule", i)]["uri"].clone().unwrap().to_string(), - email: cfg[&format!("{}{}", "rule", i)]["email"].clone().unwrap().to_string(), - command: cfg[&format!("{}{}", "rule", i)]["command"].clone().unwrap().to_string(), + service: service, + uri: uri, + email: email, + command: command, last_state: false, } ); } - let start_notification = cfg["notifications"]["service_start"].clone().unwrap().to_string() == "true".to_string(); - let notification_email = cfg["notifications"]["email"].clone().unwrap(); - let notification_command = cfg["notifications"]["command"].clone().unwrap(); + let start_notification = if cfg["notifications"].contains_key("service_start") { + cfg["notifications"]["service_start"].clone().unwrap().to_string() == "true".to_string() + } else { + true + }; - if start_notification { + let notification_email = if cfg["notifications"].contains_key("email") { + cfg["notifications"]["email"].clone().unwrap() + } else { + "".to_string() + }; + + let notification_command = if cfg["notifications"].contains_key("command") { + cfg["notifications"]["command"].clone().unwrap() + } else { + "".to_string() + }; + + if start_notification && notification_command.to_string() != "" { debug_log(format!("Service started")); let shell_cmd = notification_command.to_string() - .replace("", ¬ification_email.to_string()) + .replace("", ¬ification_email.to_string()) .replace("", &format!("\"Watchdog service started\"")) .replace("", &format!("\"Watchdog service started\"")); @@ -219,41 +263,52 @@ fn main() { loop { for i in 0..tasks.len() { let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { if check(tasks[i].uri.clone()).await { - if tasks[i].last_state != true { - debug_log(format!("{} state changed to true", tasks[i].uri)); + if tasks[i].last_state != true || just_started { + if tasks[i].command.to_string() == "".to_string() { + println!("{} state changed to true", tasks[i].uri); + } else { + debug_log(format!("{} state changed to true", tasks[i].uri)); - let shell_cmd = tasks[i].command.to_string() - .replace("", &tasks[i].email) - .replace("", &format!("\"Service {} ({}) is online\"", tasks[i].service, tasks[i].uri)) - .replace("", &format!("\"Service {} ({}) is now online\"", tasks[i].service, tasks[i].uri)); + let shell_cmd = tasks[i].command.to_string() + .replace("", &tasks[i].email) + .replace("", &format!("\"Service {} ({}) is online\"", tasks[i].service, tasks[i].uri)) + .replace("", &format!("\"Service {} ({}) is now online\"", tasks[i].service, tasks[i].uri)); - debug_log(format!("execute {}", shell_cmd)); + debug_log(format!("execute {}", shell_cmd)); - execute(shell_cmd); + execute(shell_cmd); + } } debug_log(format!("{} check is ok", tasks[i].uri)); - tasks[i].last_state = true + tasks[i].last_state = true; } else { - if tasks[i].last_state != false { - debug_log(format!("{} state changed to false", tasks[i].uri)); + if tasks[i].last_state != false || just_started { + if tasks[i].command.to_string() == "".to_string() { + println!("{} state changed to false", tasks[i].uri); + } else { + debug_log(format!("{} state changed to false", tasks[i].uri)); - let shell_cmd = tasks[i].command.to_string() - .replace("", &tasks[i].email) - .replace("", &format!("\"Service {} ({}) is offline\"", tasks[i].service, tasks[i].uri)) - .replace("", &format!("\"Service {} ({}) is now offline\"", tasks[i].service, tasks[i].uri)); + let shell_cmd = tasks[i].command.to_string() + .replace("", &tasks[i].email) + .replace("", &format!("\"Service {} ({}) is offline\"", tasks[i].service, tasks[i].uri)) + .replace("", &format!("\"Service {} ({}) is now offline\"", tasks[i].service, tasks[i].uri)); - debug_log(format!("execute {}", shell_cmd)); - execute(shell_cmd); + debug_log(format!("execute {}", shell_cmd)); + execute(shell_cmd); + } } debug_log(format!("{} check failed", tasks[i].uri)); - tasks[i].last_state = false + tasks[i].last_state = false; } + + just_started = false; }); }