rust/passwd: finish moving group and passwd parsers

This drops the remaining C compatibility hops, moving group and
passwd parsing logic fully into Rust, under a `nameservice`
module.
This commit is contained in:
Luca BRUNO 2021-01-15 08:45:52 +00:00 committed by OpenShift Merge Robot
parent a0e6427bb6
commit 15a32c12d6
10 changed files with 229 additions and 125 deletions

View File

@ -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" ]

View File

@ -122,6 +122,7 @@ pub(crate) fn ref_from_raw_ptr<T>(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(())

View File

@ -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;
}

View File

@ -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<String>;
fn add_group(self: &mut PasswdDB, gid: u32, groupname: &str);
fn lookup_group(self: &PasswdDB, gid: u32) -> Result<String>;
// 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)]

View File

@ -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<String>,
}
impl GroupEntry {
pub fn parse_line(s: impl AsRef<str>) -> Option<Self> {
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<Vec<GroupEntry>> {
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);
}
}

View File

@ -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;

View File

@ -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<str>) -> Option<Self> {
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<Vec<PasswdEntry>> {
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);
}
}

View File

@ -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(())
}
}

View File

@ -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;
}

View File

@ -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,