diff --git a/cbindgen.toml b/cbindgen.toml index 5f16c5fb..1c22d270 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -11,9 +11,6 @@ prefix = "ROR" # Here we exclude entries belonging to the C side which we use on the Rust side and so # doesn't make sense to re-export. exclude = ["RpmOstreeOrigin", - "PasswdDB", - "rpmostree_add_group_to_hash", - "rpmostree_add_passwd_to_hash", "rpmostree_get_repodata_chksum_repr", "rpmostree_origin_get_live_state" ] diff --git a/rust/src/ffiutil.rs b/rust/src/ffiutil.rs index 158cc2d4..0776a53b 100644 --- a/rust/src/ffiutil.rs +++ b/rust/src/ffiutil.rs @@ -122,6 +122,7 @@ pub(crate) fn ref_from_raw_ptr(p: *mut T) -> &'static mut T { // TODO: Try upstreaming this into the glib crate? /// Convert C results (int + GError convention) to anyhow. +#[allow(dead_code)] pub(crate) fn int_gerror_to_result(res: i32, gerror: *mut glib_sys::GError) -> anyhow::Result<()> { if res != 0 { Ok(()) diff --git a/rust/src/includes.rs b/rust/src/includes.rs index 10fa5d88..a52f5ead 100644 --- a/rust/src/includes.rs +++ b/rust/src/includes.rs @@ -8,7 +8,6 @@ NOTICE: The C header definitions are canonical, please update those first then synchronize the entries here. !*/ -use crate::passwd::PasswdDB; use libdnf_sys::DnfPackage; // From `libpriv/rpmostree-rpm-util.h`. @@ -19,22 +18,3 @@ extern "C" { gerror: *mut *mut glib_sys::GError, ) -> libc::c_int; } - -// From `libpriv/rpmostree-passwd-util.h`. -extern "C" { - #[allow(improper_ctypes)] - pub(crate) fn rpmostree_add_passwd_to_hash( - rootfs_dfd: libc::c_int, - path: *const libc::c_char, - db: *mut PasswdDB, - gerror: *mut *mut glib_sys::GError, - ) -> libc::c_int; - - #[allow(improper_ctypes)] - pub(crate) fn rpmostree_add_group_to_hash( - rootfs_dfd: libc::c_int, - path: *const libc::c_char, - db: *mut PasswdDB, - gerror: *mut *mut glib_sys::GError, - ) -> libc::c_int; -} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e167ed48..e87d35f3 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -175,13 +175,8 @@ mod ffi { fn passwd_cleanup(rootfd: i32) -> Result<()>; type PasswdDB; - fn add_user(self: &mut PasswdDB, uid: u32, username: &str); fn lookup_user(self: &PasswdDB, uid: u32) -> Result; - fn add_group(self: &mut PasswdDB, gid: u32, groupname: &str); fn lookup_group(self: &PasswdDB, gid: u32) -> Result; - // TODO(lucab): get rid of the two methods below. - fn add_group_content(self: &mut PasswdDB, rootfs: i32, group_path: &str) -> Result<()>; - fn add_passwd_content(self: &mut PasswdDB, rootfs: i32, passwd_path: &str) -> Result<()>; } // countme.rs @@ -228,6 +223,7 @@ mod lockfile; pub use self::lockfile::*; mod live; pub(crate) use self::live::*; +mod nameservice; // An origin parser in Rust but only built when testing until // we're ready to try porting the C++ code. #[cfg(test)] diff --git a/rust/src/nameservice/group.rs b/rust/src/nameservice/group.rs new file mode 100644 index 00000000..1f4338c8 --- /dev/null +++ b/rust/src/nameservice/group.rs @@ -0,0 +1,97 @@ +//! Helpers for [user passwd file](https://man7.org/linux/man-pages/man5/passwd.5.html). + +use anyhow::{anyhow, Context, Result}; +use std::io::BufRead; + +// Entry from group file. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct GroupEntry { + pub(crate) name: String, + pub(crate) passwd: String, + pub(crate) gid: u32, + pub(crate) users: Vec, +} + +impl GroupEntry { + pub fn parse_line(s: impl AsRef) -> Option { + let mut parts = s.as_ref().splitn(4, ':'); + let entry = Self { + name: parts.next()?.to_string(), + passwd: parts.next()?.to_string(), + gid: parts.next().and_then(|s| s.parse().ok())?, + users: { + let users = parts.next()?; + users.split(',').map(String::from).collect() + }, + }; + Some(entry) + } +} + +pub(crate) fn parse_group_content(content: impl BufRead) -> Result> { + let mut groups = vec![]; + for (line_num, line) in content.lines().enumerate() { + let input = + line.with_context(|| format!("failed to read group entry at line {}", line_num))?; + + // Skip empty and comment lines + if input.is_empty() || input.starts_with('#') { + continue; + } + // Skip NSS compat lines, see "Compatibility mode" in + // https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html + if input.starts_with('+') || input.starts_with('-') { + continue; + } + + let entry = GroupEntry::parse_line(&input).ok_or_else(|| { + anyhow!( + "failed to parse group entry at line {}, content: {}", + line_num, + &input + ) + })?; + groups.push(entry); + } + Ok(groups) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{BufReader, Cursor}; + + #[test] + fn test_parse_lines() { + let content = r#" ++groupA +-groupB + +root:x:0: +daemon:x:1: +bin:x:2: +sys:x:3: +adm:x:4: +www-data:x:33: +backup:x:34: +operator:x:37: + +# Dummy comment +staff:x:50:operator + ++ +"#; + + let input = BufReader::new(Cursor::new(content)); + let groups = parse_group_content(input).unwrap(); + assert_eq!(groups.len(), 9); + + let staff = GroupEntry { + name: "staff".to_string(), + passwd: "x".to_string(), + gid: 50, + users: vec!["operator".to_string()], + }; + assert_eq!(groups[8], staff); + } +} diff --git a/rust/src/nameservice/mod.rs b/rust/src/nameservice/mod.rs new file mode 100644 index 00000000..ac91bcc2 --- /dev/null +++ b/rust/src/nameservice/mod.rs @@ -0,0 +1,6 @@ +//! Linux name-service information helpers. + +// TODO(lucab): consider moving this to its own crate. + +pub(crate) mod group; +pub(crate) mod passwd; diff --git a/rust/src/nameservice/passwd.rs b/rust/src/nameservice/passwd.rs new file mode 100644 index 00000000..e244c775 --- /dev/null +++ b/rust/src/nameservice/passwd.rs @@ -0,0 +1,102 @@ +//! Helpers for [user group file](https://man7.org/linux/man-pages/man5/group.5.html). + +use anyhow::{anyhow, Context, Result}; +use std::io::BufRead; + +// Entry from passwd file. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct PasswdEntry { + pub(crate) name: String, + pub(crate) passwd: String, + pub(crate) uid: u32, + pub(crate) gid: u32, + pub(crate) gecos: String, + pub(crate) home_dir: String, + pub(crate) shell: String, +} + +impl PasswdEntry { + pub fn parse_line(s: impl AsRef) -> Option { + let mut parts = s.as_ref().splitn(7, ':'); + let entry = Self { + name: parts.next()?.to_string(), + passwd: parts.next()?.to_string(), + uid: parts.next().and_then(|s| s.parse().ok())?, + gid: parts.next().and_then(|s| s.parse().ok())?, + gecos: parts.next()?.to_string(), + home_dir: parts.next()?.to_string(), + shell: parts.next()?.to_string(), + }; + Some(entry) + } +} + +pub(crate) fn parse_passwd_content(content: impl BufRead) -> Result> { + let mut passwds = vec![]; + for (line_num, line) in content.lines().enumerate() { + let input = + line.with_context(|| format!("failed to read passwd entry at line {}", line_num))?; + + // Skip empty and comment lines + if input.is_empty() || input.starts_with('#') { + continue; + } + // Skip NSS compat lines, see "Compatibility mode" in + // https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html + if input.starts_with('+') || input.starts_with('-') { + continue; + } + + let entry = PasswdEntry::parse_line(&input).ok_or_else(|| { + anyhow!( + "failed to parse group entry at line {}, content: {}", + line_num, + &input + ) + })?; + passwds.push(entry); + } + Ok(passwds) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{BufReader, Cursor}; + + #[test] + fn test_parse_lines() { + let content = r#" +root:x:0:0:root:/root:/bin/bash + ++userA +-userB + +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +systemd-coredump:x:1:1:systemd Core Dumper:/:/usr/sbin/nologin + ++@groupA +-@groupB + +# Dummy comment +someuser:x:1000:1000:Foo BAR,,,:/home/foobar:/bin/bash + ++ +"#; + + let input = BufReader::new(Cursor::new(content)); + let groups = parse_passwd_content(input).unwrap(); + assert_eq!(groups.len(), 4); + + let someuser = PasswdEntry { + name: "someuser".to_string(), + passwd: "x".to_string(), + uid: 1000, + gid: 1000, + gecos: "Foo BAR,,,".to_string(), + home_dir: "/home/foobar".to_string(), + shell: "/bin/bash".to_string(), + }; + assert_eq!(groups[3], someuser); + } +} diff --git a/rust/src/passwd.rs b/rust/src/passwd.rs index d5ee7559..3e2e7df4 100644 --- a/rust/src/passwd.rs +++ b/rust/src/passwd.rs @@ -1,11 +1,11 @@ use crate::cxxrsutil; use crate::ffiutil; -use crate::includes::*; +use crate::nameservice; use anyhow::{anyhow, Result}; -use c_utf8::CUtf8Buf; use nix::unistd::{Gid, Uid}; use openat_ext::OpenatDirExt; use std::collections::HashMap; +use std::io::BufReader; use std::os::unix::io::AsRawFd; use std::path::PathBuf; @@ -167,37 +167,29 @@ impl PasswdDB { .ok_or_else(|| anyhow!("failed to find group ID '{}'", gid)) } - /// Add a user ID with the associated name. - pub fn add_user(&mut self, uid: u32, username: &str) { - let id = Uid::from_raw(uid); - self.users.insert(id, username.to_string()); - } - - /// Add a group ID with the associated name. - pub fn add_group(&mut self, gid: u32, groupname: &str) { - let id = Gid::from_raw(gid); - self.groups.insert(id, groupname.to_string()); - } - /// Add content from a `group` file. - pub fn add_group_content(&mut self, rootfs: i32, group_path: &str) -> anyhow::Result<()> { - let c_path: CUtf8Buf = group_path.to_string().into(); - let db_ptr = self as *mut Self; - let mut gerror: *mut glib_sys::GError = std::ptr::null_mut(); - // TODO(lucab): find a replacement for `fgetgrent` and drop this. - let res = - unsafe { rpmostree_add_group_to_hash(rootfs, c_path.as_ptr(), db_ptr, &mut gerror) }; - ffiutil::int_gerror_to_result(res, gerror) + fn add_group_content(&mut self, rootfs_dfd: i32, group_path: &str) -> anyhow::Result<()> { + let rootfs = ffiutil::ffi_view_openat_dir(rootfs_dfd); + let db = rootfs.open_file(group_path)?; + let entries = nameservice::group::parse_group_content(BufReader::new(db))?; + + for group in entries { + let id = Gid::from_raw(group.gid); + self.groups.insert(id, group.name); + } + Ok(()) } /// Add content from a `passwd` file. - pub fn add_passwd_content(&mut self, rootfs: i32, passwd_path: &str) -> anyhow::Result<()> { - let c_path: CUtf8Buf = passwd_path.to_string().into(); - let db_ptr = self as *mut Self; - let mut gerror: *mut glib_sys::GError = std::ptr::null_mut(); - // TODO(lucab): find a replacement for `fgetpwent` and drop this. - let res = - unsafe { rpmostree_add_passwd_to_hash(rootfs, c_path.as_ptr(), db_ptr, &mut gerror) }; - ffiutil::int_gerror_to_result(res, gerror) + fn add_passwd_content(&mut self, rootfs_dfd: i32, passwd_path: &str) -> anyhow::Result<()> { + let rootfs = ffiutil::ffi_view_openat_dir(rootfs_dfd); + let db = rootfs.open_file(passwd_path)?; + let entries = nameservice::passwd::parse_passwd_content(BufReader::new(db))?; + + for user in entries { + let id = Uid::from_raw(user.uid); + self.users.insert(id, user.name); + } + Ok(()) } } diff --git a/src/libpriv/rpmostree-passwd-util.cxx b/src/libpriv/rpmostree-passwd-util.cxx index 155c6366..976f863c 100644 --- a/src/libpriv/rpmostree-passwd-util.cxx +++ b/src/libpriv/rpmostree-passwd-util.cxx @@ -1133,61 +1133,3 @@ rpmostree_passwd_compose_prep (int rootfs_dfd, return TRUE; } - -gboolean -rpmostree_add_passwd_to_hash (int rootfs_dfd, const char *path, - rpmostreecxx::PasswdDB *db, - GError **error) -{ - g_autoptr(FILE) src_stream = open_file_stream_read_at (rootfs_dfd, path, error); - if (!src_stream) - return FALSE; - - while (TRUE) - { - errno = 0; - struct passwd *pw = fgetpwent (src_stream); - if (!pw) - { - if (errno != 0 && errno != ENOENT) - return glnx_throw_errno_prefix (error, "fgetpwent"); - break; - } - if (pw->pw_name != NULL) - { - std::string username(pw->pw_name); - db->add_user (pw->pw_uid, username); - } - } - - return TRUE; -} - -gboolean -rpmostree_add_group_to_hash (int rootfs_dfd, const char *path, - rpmostreecxx::PasswdDB *db, - GError **error) -{ - g_autoptr(FILE) src_stream = open_file_stream_read_at (rootfs_dfd, path, error); - if (!src_stream) - return FALSE; - - while (TRUE) - { - errno = 0; - struct group *gr = fgetgrent (src_stream); - if (!gr) - { - if (errno != 0 && errno != ENOENT) - return glnx_throw_errno_prefix (error, "fgetgrent"); - break; - } - if (gr->gr_name != NULL) - { - std::string groupname(gr->gr_name); - db->add_group (gr->gr_gid, groupname); - } - } - - return TRUE; -} diff --git a/src/libpriv/rpmostree-passwd-util.h b/src/libpriv/rpmostree-passwd-util.h index 1426496f..083225a0 100644 --- a/src/libpriv/rpmostree-passwd-util.h +++ b/src/libpriv/rpmostree-passwd-util.h @@ -30,15 +30,6 @@ G_BEGIN_DECLS -gboolean -rpmostree_add_passwd_to_hash (int rootfs_dfd, const char *path, - rpmostreecxx::PasswdDB *db, - GError **error); -gboolean -rpmostree_add_group_to_hash (int rootfs_dfd, const char *path, - rpmostreecxx::PasswdDB *db, - GError **error); - gboolean rpmostree_check_passwd (OstreeRepo *repo, int rootfs_dfd,