// SPDX-License-Identifier: MIT

// This example shows how to use `netlink-proto` with the tokio runtime to print
// audit events.
//
// This example shows how the netlink socket can be accessed
// `netlink_proto::Connection`, and configured (in this case to
// register to a multicast group).
//
// Compilation:
// ------------
//
// cargo build --example audit_netlink_events
//
// Usage:
// ------
//
// Find the example binary in the target directory, and run it *as
// root*. If you compiled in debug mode with the command above, the
// binary should be under:
// `<repo-root>/target/debug/examples/audit_netlink_events`. This example runs
// forever, you must hit ^C to kill it.

use futures::stream::StreamExt;
use netlink_packet_audit::{AuditMessage, StatusMessage};
use netlink_packet_core::{
    NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_REQUEST,
};
use std::process;

use netlink_proto::{
    new_connection,
    sys::{protocols::NETLINK_AUDIT, SocketAddr},
};

const AUDIT_STATUS_ENABLED: u32 = 1;
const AUDIT_STATUS_PID: u32 = 4;

#[tokio::main]
async fn main() -> Result<(), String> {
    // Create a netlink socket. Here:
    //
    // - `conn` is a `Connection` that has the netlink socket. It's a `Future`
    //   that keeps polling the socket and must be spawned an the event loop.
    //
    // - `handle` is a `Handle` to the `Connection`. We use it to send netlink
    //   messages and receive responses to these messages.
    //
    // - `messages` is a channel receiver through which we receive messages that
    //   we have not sollicated, ie that are not response to a request we made.
    //   In this example, we'll receive the audit event through that channel.
    let (conn, handle, mut messages) =
        new_connection(NETLINK_AUDIT).map_err(|e| {
            format!("Failed to create a new netlink connection: {e}")
        })?;

    // Spawn the `Connection` so that it starts polling the netlink
    // socket in the background.
    tokio::spawn(conn);

    // Use the `ConnectionHandle` to send a request to the kernel
    // asking it to start multicasting audit event messages.
    tokio::spawn(async move {
        // Craft the packet to enable audit events
        let mut status = StatusMessage::new();
        status.enabled = 1;
        status.pid = process::id();
        status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID;
        let payload = AuditMessage::SetStatus(status);
        let mut nl_msg = NetlinkMessage::from(payload);
        nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK;

        // We'll send unicast messages to the kernel.
        let kernel_unicast: SocketAddr = SocketAddr::new(0, 0);
        let mut response = match handle.request(nl_msg, kernel_unicast) {
            Ok(response) => response,
            Err(e) => {
                eprintln!("{e}");
                return;
            }
        };

        while let Some(message) = response.next().await {
            if let NetlinkPayload::Error(err_message) = message.payload {
                eprintln!("Received an error message: {err_message:?}");
                return;
            }
        }
    });

    // Finally, start receiving event through the `messages` channel.
    println!("Starting to print audit events... press ^C to interrupt");
    while let Some((message, _addr)) = messages.next().await {
        if let NetlinkPayload::Error(err_message) = message.payload {
            eprintln!("received an error message: {err_message:?}");
        } else {
            println!("{message:?}");
        }
    }

    Ok(())
}
