//! Functions related to the in-kernel key management and retention facility
//!
//! For more details on this facility, see the `keyrings(7)` man page.
//!
//! Additional functions can be found in the [kernel::keys](crate::sys::kernel::keys) module.
use crate::{FileWrapper, ProcResult};
use bitflags::bitflags;
#[cfg(feature = "serde1")]
use serde::{Deserialize, Serialize};
use std::{
    collections::HashMap,
    io::{BufRead, BufReader},
    time::Duration,
};

bitflags! {
    /// Various key flags
    #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
    pub struct KeyFlags: u32 {
            /// The key has been instantiated
            const INSTANTIATED = 0x01;
            /// THe key has been revoked
            const REVOKED = 0x02;
            /// The key is dead
            ///
            /// I.e. the key type has been unregistered.  A key may be briefly in this state during garbage collection.
            const DEAD = 0x04;
            /// The key contributes to the user's quota
            const QUOTA = 0x08;
            /// The key is under construction via a callback to user space
            const UNDER_CONSTRUCTION = 0x10;
            /// The key is negatively instantiated
            const NEGATIVE = 0x20;
            /// The key has been invalidated
            const INVALID = 0x40;
    }
}

bitflags! {
    /// Bitflags that represent the permissions for a key
    #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
    pub struct PermissionFlags: u32 {
        /// The attributes of the key may be read
        ///
        /// This includes the type, description, and access rights (excluding the security label)
        const VIEW = 0x01;
        /// For a key: the payload of the key may be read.  For a keyring: the list of serial numbers (keys) to which the keyring has links may be read.
        const READ = 0x02;
        /// The payload of the key may be updated and the key may be revoked.
        ///
        /// For a keyring, links may be added to or removed from the keyring, and the keyring
        /// may be cleared completely (all links are removed).
        const WRITE = 0x04;
        /// The key may be found by a search.
        ///
        /// For keyrings: keys and keyrings that are linked to by the keyring may be searched.
        const SEARCH = 0x08;
        /// Links may be created from keyrings to the key.
        ///
        /// The initial link to a key that is established when the key is created doesn't require this permission.
        const LINK = 0x10;
        /// The ownership details and security label of the key may be changed, the key's expiration
        /// time may be set, and the key may be revoked.
        const SETATTR = 0x20;
        const ALL = Self::VIEW.bits | Self::READ.bits | Self::WRITE.bits | Self::SEARCH.bits | Self::LINK.bits | Self::SETATTR.bits;
    }
}

impl KeyFlags {
    fn from_str(s: &str) -> KeyFlags {
        let mut me = KeyFlags::empty();

        let mut chars = s.chars();
        match chars.next() {
            Some(c) if c == 'I' => me.insert(KeyFlags::INSTANTIATED),
            _ => {}
        }
        match chars.next() {
            Some(c) if c == 'R' => me.insert(KeyFlags::REVOKED),
            _ => {}
        }
        match chars.next() {
            Some(c) if c == 'D' => me.insert(KeyFlags::DEAD),
            _ => {}
        }
        match chars.next() {
            Some(c) if c == 'Q' => me.insert(KeyFlags::QUOTA),
            _ => {}
        }
        match chars.next() {
            Some(c) if c == 'U' => me.insert(KeyFlags::UNDER_CONSTRUCTION),
            _ => {}
        }
        match chars.next() {
            Some(c) if c == 'N' => me.insert(KeyFlags::NEGATIVE),
            _ => {}
        }
        match chars.next() {
            Some(c) if c == 'i' => me.insert(KeyFlags::INVALID),
            _ => {}
        }

        me
    }
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct Permissions {
    pub possessor: PermissionFlags,
    pub user: PermissionFlags,
    pub group: PermissionFlags,
    pub other: PermissionFlags,
}
impl Permissions {
    fn from_str(s: &str) -> ProcResult<Permissions> {
        let possessor = PermissionFlags::from_bits(from_str!(u32, &s[0..2], 16))
            .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?;

        let user = PermissionFlags::from_bits(from_str!(u32, &s[2..4], 16))
            .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?;

        let group = PermissionFlags::from_bits(from_str!(u32, &s[4..6], 16))
            .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?;

        let other = PermissionFlags::from_bits(from_str!(u32, &s[6..8], 16))
            .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?;

        Ok(Permissions {
            possessor,
            user,
            group,
            other,
        })
    }
}

#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum KeyTimeout {
    Permanent,
    Expired,
    Timeout(Duration),
}

impl KeyTimeout {
    fn from_str(s: &str) -> ProcResult<KeyTimeout> {
        if s == "perm" {
            Ok(KeyTimeout::Permanent)
        } else if s == "expd" {
            Ok(KeyTimeout::Expired)
        } else {
            let (val, unit) = s.split_at(s.len() - 1);
            let val = from_str!(u64, val);
            match unit {
                "s" => Ok(KeyTimeout::Timeout(Duration::from_secs(val))),
                "m" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60))),
                "h" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60))),
                "d" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60 * 24))),
                "w" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60 * 24 * 7))),
                _ => Err(build_internal_error!(format!("Unable to parse keytimeout of {:?}", s))),
            }
        }
    }
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum KeyType {
    /// This is a general-purpose key type.
    ///
    /// The key is kept entirely within kernel memory.  The payload may be read and updated by
    ///  user-space applications. The payload for keys of this type is a blob of arbitrary
    /// data of up to 32,767 bytes.

    /// The description may be any valid string, though it is preferred that it start
    /// with a colon-delimited prefix representing the service to which the key is of
    /// interest (for instance "afs:mykey").
    User,

    /// Keyrings are special keys which store a set of links to other keys (including
    /// other keyrings), analogous to a directory holding links to files.   The  main
    /// purpose  of  a  keyring is to prevent other keys from being garbage collected
    /// because nothing refers to them.
    ///
    /// Keyrings with descriptions (names) that begin with a  period  ('.')  are  re‐
    /// served to the implementation.
    Keyring,

    /// This  key  type  is  essentially  the same as "user", but it does not provide
    /// reading (i.e., the keyctl(2) KEYCTL_READ operation),  meaning  that  the  key
    /// payload is never visible from user space.  This is suitable for storing user‐
    /// name-password pairs that should not be readable from user space.
    ///
    /// The description of a "logon" key must start with a  non-empty colon-delimited
    /// prefix  whose  purpose  is  to identify the service to which the key belongs.
    /// (Note that this differs from keys of the "user" type, where the inclusion  of
    /// a prefix is recommended but is not enforced.)
    Logon,

    /// This key type is similar to the "user" key type, but it may hold a payload of
    /// up to 1 MiB in size.  This key type is useful for purposes  such  as  holding
    /// Kerberos ticket caches.
    ///
    /// The  payload  data may be stored in a tmpfs filesystem, rather than in kernel
    /// memory, if the data size exceeds the overhead of  storing  the  data  in  the
    /// filesystem.  (Storing the data in a filesystem requires filesystem structures
    /// to be allocated in the kernel.  The size of these structures  determines  the
    /// size  threshold  above  which the tmpfs storage method is used.)  Since Linux
    /// 4.8, the payload data is encrypted when stored in tmpfs,  thereby  preventing
    /// it from being written unencrypted into swap space.
    BigKey,

    /// Other specialized, but rare keys types
    Other(String),
}

impl KeyType {
    fn from_str(s: &str) -> KeyType {
        match s {
            "keyring" => KeyType::Keyring,
            "user" => KeyType::User,
            "logon" => KeyType::Logon,
            "big_key" => KeyType::BigKey,
            other => KeyType::Other(other.to_string()),
        }
    }
}

/// A key
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct Key {
    /// The ID (serial number) of the key
    pub id: u64,

    /// A set of flags describing the state of the key
    pub flags: KeyFlags,

    /// Count of the number of kernel credential structures that are
    /// pinning  the  key  (approximately: the number of threads and open file
    /// references that refer to this key).
    pub usage: u32,

    /// Key timeout
    pub timeout: KeyTimeout,

    /// Key permissions
    pub permissions: Permissions,

    /// The user ID of the key owner
    pub uid: u32,

    /// The group ID of the key.
    ///
    /// The value of `None` here means that the key has no group ID; this can occur in certain circumstances for
    /// keys created by the kernel.
    pub gid: Option<u32>,

    /// The type of key
    pub key_type: KeyType,

    /// The key description
    pub description: String,
}

impl Key {
    fn from_line(s: &str) -> ProcResult<Key> {
        let mut s = s.split_whitespace();

        let id = from_str!(u64, expect!(s.next()), 16);
        let s_flags = expect!(s.next());
        let usage = from_str!(u32, expect!(s.next()));
        let s_timeout = expect!(s.next());
        let s_perms = expect!(s.next());
        let uid = from_str!(u32, expect!(s.next()));
        let s_gid = expect!(s.next());
        let s_type = expect!(s.next());
        let desc: Vec<_> = s.collect();

        Ok(Key {
            id,
            flags: KeyFlags::from_str(s_flags),
            usage,
            timeout: KeyTimeout::from_str(s_timeout)?,
            permissions: Permissions::from_str(s_perms)?,
            uid,
            gid: if s_gid == "-1" {
                None
            } else {
                Some(from_str!(u32, s_gid))
            },
            key_type: KeyType::from_str(s_type),
            description: desc.join(" "),
        })
    }
}

/// Returns a list of the keys for which the reading thread has **view** permission, providing various information about each key.
pub fn keys() -> ProcResult<Vec<Key>> {
    let file = FileWrapper::open("/proc/keys")?;
    let reader = BufReader::new(file);
    let mut v = Vec::new();

    for line in reader.lines() {
        let line = line?;
        v.push(Key::from_line(&line)?);
    }
    Ok(v)
}

/// Information about a user with at least one key
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct KeyUser {
    /// The user that owns the key
    pub uid: u32,
    /// The kernel-internal usage count for the kernel structure used to record key users
    pub usage: u32,
    /// The total number of keys owned by the user
    pub nkeys: u32,
    /// THe number of keys that have been instantiated
    pub nikeys: u32,
    /// The number of keys owned by the user
    pub qnkeys: u32,
    /// The maximum number of keys that the user may own
    pub maxkeys: u32,
    /// The number of bytes consumed in playloads of the keys owned by this user
    pub qnbytes: u32,
    /// The upper limit on the number of bytes in key payloads for this user
    pub maxbytes: u32,
}

impl KeyUser {
    fn from_str(s: &str) -> ProcResult<KeyUser> {
        let mut s = s.split_whitespace();
        let uid = expect!(s.next());
        let usage = from_str!(u32, expect!(s.next()));
        let keys = expect!(s.next());
        let qkeys = expect!(s.next());
        let qbytes = expect!(s.next());

        let (nkeys, nikeys) = {
            let mut s = keys.split('/');
            (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next())))
        };
        let (qnkeys, maxkeys) = {
            let mut s = qkeys.split('/');
            (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next())))
        };
        let (qnbytes, maxbytes) = {
            let mut s = qbytes.split('/');
            (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next())))
        };

        Ok(KeyUser {
            uid: from_str!(u32, &uid[0..uid.len() - 1]),
            usage,
            nkeys,
            nikeys,
            qnkeys,
            maxkeys,
            qnbytes,
            maxbytes,
        })
    }
}

/// Get various information for each user ID that has at least one key on the system.
pub fn key_users() -> ProcResult<HashMap<u32, KeyUser>> {
    let file = FileWrapper::open("/proc/key-users")?;
    let reader = BufReader::new(file);
    let mut map = HashMap::new();

    for line in reader.lines() {
        let line = line?;
        let user = KeyUser::from_str(&line)?;
        map.insert(user.uid, user);
    }
    Ok(map)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn key_flags() {
        assert_eq!(KeyFlags::from_str("I------"), KeyFlags::INSTANTIATED);
        assert_eq!(KeyFlags::from_str("IR"), KeyFlags::INSTANTIATED | KeyFlags::REVOKED);
        assert_eq!(KeyFlags::from_str("IRDQUNi"), KeyFlags::all());
    }

    #[test]
    fn timeout() {
        assert_eq!(KeyTimeout::from_str("perm").unwrap(), KeyTimeout::Permanent);
        assert_eq!(KeyTimeout::from_str("expd").unwrap(), KeyTimeout::Expired);
        assert_eq!(
            KeyTimeout::from_str("2w").unwrap(),
            KeyTimeout::Timeout(Duration::from_secs(1209600))
        );
        assert_eq!(
            KeyTimeout::from_str("14d").unwrap(),
            KeyTimeout::Timeout(Duration::from_secs(1209600))
        );
        assert_eq!(
            KeyTimeout::from_str("336h").unwrap(),
            KeyTimeout::Timeout(Duration::from_secs(1209600))
        );
        assert_eq!(
            KeyTimeout::from_str("20160m").unwrap(),
            KeyTimeout::Timeout(Duration::from_secs(1209600))
        );
        assert_eq!(
            KeyTimeout::from_str("1209600s").unwrap(),
            KeyTimeout::Timeout(Duration::from_secs(1209600))
        );
    }

    #[test]
    fn live_keys() {
        for key in keys().unwrap() {
            println!("{:#?}", key);
        }
    }

    #[test]
    fn live_key_users() {
        for (_user, data) in key_users().unwrap() {
            println!("{:#?}", data);
        }
    }
}
