use serde::{ de::{self, Visitor}, Deserialize, Deserializer, Serialize, }; use std::fmt; #[derive(Serialize, Deserialize, Debug)] pub struct EtcUser { pub name: String, pub password: String, pub uid: u32, pub gid: u32, pub gecos: String, pub homedir: String, pub shell: String, } pub fn parse_etc_passwd(bytes: &[u8]) -> Result, UnixIntegrationError> { use csv::ReaderBuilder; let mut rdr = ReaderBuilder::new() .has_headers(false) .delimiter(b':') .from_reader(bytes); rdr.deserialize() .map(|result| result.map_err(|_e| UnixIntegrationError)) .collect::, UnixIntegrationError>>() } fn members<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { struct InnerCsv; impl<'de> Visitor<'de> for InnerCsv { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("string") } fn visit_str(self, value: &str) -> Result, E> where E: de::Error, { Ok(value.split(',').map(|s| s.to_string()).collect()) } } deserializer.deserialize_str(InnerCsv) } #[derive(Serialize, Deserialize, Debug)] pub struct EtcGroup { pub name: String, pub password: String, pub gid: u32, #[serde(deserialize_with = "members")] pub members: Vec, } #[derive(Debug)] pub struct UnixIntegrationError; pub fn parse_etc_group(bytes: &[u8]) -> Result, UnixIntegrationError> { use csv::ReaderBuilder; let mut rdr = ReaderBuilder::new() .has_headers(false) .delimiter(b':') .from_reader(bytes); rdr.deserialize() .map(|result| result.map_err(|_e| UnixIntegrationError)) .collect::, UnixIntegrationError>>() } #[cfg(test)] mod tests { use super::*; const EXAMPLE_PASSWD: &str = r#"root:x:0:0:root:/root:/bin/bash systemd-timesync:x:498:498:systemd Time Synchronization:/:/usr/sbin/nologin messagebus:x:484:484:User for D-Bus:/run/dbus:/usr/sbin/nologin tftp:x:483:483:TFTP Account:/srv/tftpboot:/usr/sbin/nologin nobody:x:65534:65534:nobody:/var/lib/nobody:/bin/bash "#; const EXAMPLE_GROUP: &str = r#"root:x:0: shadow:x:15: trusted:x:42: users:x:100: systemd-journal:x:499: systemd-timesync:x:498: kmem:x:497: lock:x:496: tty:x:5: wheel:x:481:admin,testuser "#; #[test] fn test_parse_passwd() { for record in parse_etc_passwd(EXAMPLE_PASSWD.as_bytes()).expect("Failed to parse passwd data") { println!("{:?}", record); } } #[test] fn test_parse_group() { for record in parse_etc_group(EXAMPLE_GROUP.as_bytes()).expect("Failed to parse group") { println!("{:?}", record); } } }