Skip to content

Commit de4a186

Browse files
committed
Detect when unix_chkpwd failure message is added
1 parent d448daa commit de4a186

File tree

4 files changed

+49
-20
lines changed

4 files changed

+49
-20
lines changed

src/auth_message_parser.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,41 @@
1-
const PAM_PREFIX: &str = "pam_unix";
2-
const PAM_PREFIX_LENGTH: usize = PAM_PREFIX.len();
3-
const AUTH_FAILURE_MESSAGE: &str = "authentication failure";
1+
pub struct AuthMessageParser {
2+
patterns: Vec<AuthFailedMessagePattern>,
3+
}
4+
5+
struct AuthFailedMessagePattern {
6+
prefix: String,
7+
message: String,
8+
}
9+
10+
impl AuthMessageParser {
11+
pub fn new() -> AuthMessageParser {
12+
let pam_message = AuthFailedMessagePattern {
13+
prefix: String::from("pam_unix"),
14+
message: String::from("authentication failure"),
15+
};
16+
let unix_chkpwd_message = AuthFailedMessagePattern {
17+
prefix: String::from("unix_chkpwd"),
18+
message: String::from("password check failed"),
19+
};
20+
return AuthMessageParser {
21+
patterns: vec![pam_message, unix_chkpwd_message],
22+
};
23+
}
424

5-
pub fn is_auth_failed_message(message: &str) -> bool {
6-
return match message.find(PAM_PREFIX) {
7-
None => false,
8-
Some(pam_prefix_position) => {
9-
message[pam_prefix_position + PAM_PREFIX_LENGTH..].contains(AUTH_FAILURE_MESSAGE)
25+
pub fn is_auth_failed_message(&self, message: &str) -> bool {
26+
for pattern in &self.patterns {
27+
match message.find(&pattern.prefix) {
28+
None => {}
29+
Some(prefix_position) => {
30+
let message_after_prefix = &message[prefix_position + pattern.prefix.len()..];
31+
if message_after_prefix.contains(&pattern.message) {
32+
return true;
33+
}
34+
}
35+
};
1036
}
11-
};
37+
return false;
38+
}
1239
}
1340

1441
#[cfg(test)]

src/auth_message_parser.test.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
use crate::auth_message_parser::is_auth_failed_message;
1+
use crate::auth_message_parser::AuthMessageParser;
2+
use crate::test_utils::test_file::AUTH_FAILED_TEST_MESSAGES;
23

34
#[test]
45
fn when_message_is_auth_failed_message_then_returns_true() {
5-
let messages = [
6-
"2023-04-22T12:20:32.512681+02:00 workstation sudo: pam_unix(sudo:auth): authentication failure; logname=john uid=1000 euid=0 tty=/dev/pts/7 ruser=john rhost= user=john",
7-
"2023-04-22T12:22:53.157054+02:00 workstation kscreenlocker_greet: pam_unix(kde:auth): authentication failure; logname= uid=1000 euid=1000 tty= ruser= rhost= user=john",
8-
];
9-
for message in messages {
10-
assert!(is_auth_failed_message(message));
6+
let parser = AuthMessageParser::new();
7+
for message in AUTH_FAILED_TEST_MESSAGES {
8+
assert!(parser.is_auth_failed_message(message));
119
}
1210
}
1311

@@ -52,7 +50,8 @@ fn when_message_is_not_auth_failed_message_then_returns_false() {
5250
2024-02-10T14:34:24.371421+01:00 workstation sudo: john : TTY=pts/3 ; PWD=/home/john ; USER=root ; COMMAND=/usr/bin/ls
5351
2024-02-10T14:34:24.372326+01:00 workstation sudo: pam_unix(sudo:session): session opened for user root(uid=0) by john(uid=1000)
5452
2024-02-10T14:34:24.374716+01:00 workstation sudo: pam_unix(sudo:session): session closed for user root";
53+
let parser = AuthMessageParser::new();
5554
for message in messages.split('\n') {
56-
assert!(!is_auth_failed_message(message));
55+
assert!(!parser.is_auth_failed_message(message));
5756
}
5857
}

src/auth_monitor.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ use std::error::Error;
22
use std::time::{Duration, SystemTime};
33

44
use crate::auth_file_watcher::AuthFileWatcher;
5-
use crate::auth_message_parser::is_auth_failed_message;
5+
use crate::auth_message_parser::AuthMessageParser;
66
use crate::auth_monitor_options::AuthMonitorOptions;
77
use crate::auth_monitor_params::AuthMonitorParams;
88

99
pub struct AuthMonitor {
1010
failed_attempts: i32,
1111
options: AuthMonitorOptions,
1212
file_watcher: AuthFileWatcher,
13+
auth_message_parser: AuthMessageParser,
1314
last_failed_auth: SystemTime,
1415
}
1516

@@ -20,6 +21,7 @@ impl AuthMonitor {
2021
failed_attempts: 0,
2122
options: params.options,
2223
file_watcher: AuthFileWatcher::new(&params.filepath)?,
24+
auth_message_parser: AuthMessageParser::new(),
2325
last_failed_auth: SystemTime::UNIX_EPOCH,
2426
});
2527
}
@@ -30,7 +32,7 @@ impl AuthMonitor {
3032
}
3133
let mut failed_attempts = 0;
3234
self.file_watcher.update(|line| {
33-
if is_auth_failed_message(line) {
35+
if self.auth_message_parser.is_auth_failed_message(line) {
3436
failed_attempts += 1;
3537
}
3638
});

src/test_utils/test_file.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use std::sync::atomic::{AtomicUsize, Ordering};
55

66
use chrono::Local;
77

8-
const AUTH_FAILED_TEST_MESSAGES: [&str; 2] = [
8+
pub const AUTH_FAILED_TEST_MESSAGES: [&str; 3] = [
99
"workstation sudo: pam_unix(sudo:auth): authentication failure; logname=john uid=1000 euid=0 tty=/dev/pts/7 ruser=john rhost= user=john",
1010
"workstation kscreenlocker_greet: pam_unix(kde:auth): authentication failure; logname= uid=1000 euid=1000 tty= ruser= rhost= user=john",
11+
"workstation unix_chkpwd[222793]: password check failed for user (john)"
1112
];
1213

1314
const OTHER_TEST_MESSAGES: [&str; 4] = [

0 commit comments

Comments
 (0)