Skip to main content

windows_eventlog_native/
security.rs

1use chrono::{DateTime, Utc};
2
3use crate::error::Result;
4use crate::event::Event;
5use crate::query::{EventLog, QueryDirection};
6
7/// Fetch every Security-channel event whose `EventID` is in `ids` and whose
8/// `SystemTime` is >= `since`.
9///
10/// Builds an XPath structured query and returns matches in reverse chronological order
11/// (newest first) — that's the shape callers doing an OPSEC self-check want:
12/// "did my scan just now show up as 4624/4625/4662/4768/4769/4776?"
13///
14/// Security channel requires the caller to be in **Event Log Readers** or Administrators;
15/// otherwise this returns `Error::ChannelAccess`.
16pub fn security_audit_by_id(ids: &[u32], since: DateTime<Utc>) -> Result<Vec<Event>> {
17    let xpath = build_event_id_xpath(ids, since);
18    let iter = EventLog::query("Security", &xpath, QueryDirection::Reverse)?;
19
20    let mut out = Vec::new();
21    for evt in iter {
22        out.push(evt?);
23    }
24    Ok(out)
25}
26
27/// XPath builder — pulled out so it's unit-testable without touching the wevtapi.
28///
29/// Result shape:
30/// `*[System[(EventID=4624 or EventID=4625) and TimeCreated[@SystemTime>='2024-…Z']]]`
31pub(crate) fn build_event_id_xpath(ids: &[u32], since: DateTime<Utc>) -> String {
32    // ISO-8601 UTC with milliseconds and trailing Z, as Windows XPath expects.
33    let ts = since.format("%Y-%m-%dT%H:%M:%S%.3fZ");
34
35    if ids.is_empty() {
36        return format!("*[System[TimeCreated[@SystemTime>='{ts}']]]");
37    }
38
39    let mut clause = String::from("(");
40    for (i, id) in ids.iter().enumerate() {
41        if i > 0 {
42            clause.push_str(" or ");
43        }
44        clause.push_str(&format!("EventID={id}"));
45    }
46    clause.push(')');
47
48    format!("*[System[{clause} and TimeCreated[@SystemTime>='{ts}']]]")
49}