quinoa/src/passwd.rs

221 lines
6.4 KiB
Rust

//! Forked from <https://github.com/gifnksm/etc-passwd>.
//!
//! Get user information stored in the password file `/etc/passwd`.
//!
//! This crate provides a safe wrapper for libc functions such as [`getpwnam_r(3)`] and [`getpwuid_r(3)`].
//!
//! # Usage
//!
//! Add this to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! etc-passwd = "0.2.0"
//! ```
//!
//! # Examples
//!
//! Get a current user information:
//!
//! ```
//! use passwd::Passwd;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! if let Some(passwd) = Passwd::current_user()? {
//! println!("current user name is: {}", passwd.name.to_str()?);
//! println!("your user id is: {}", passwd.uid);
//! println!("your group id is: {}", passwd.gid);
//! println!("your full name is: {}", passwd.gecos.to_str()?);
//! println!("your home directory is: {}", passwd.dir.to_str()?);
//! println!("your login shell is: {}", passwd.shell.to_str()?);
//! } else {
//! println!("oops! current user is not found... who are you?");
//! }
//! # Ok(())
//! # }
//! ```
//!
//! [`getpwnam_r(3)`]: https://man7.org/linux/man-pages/man3/getpwnam_r.3.html
//! [`getpwuid_r(3)`]: https://man7.org/linux/man-pages/man3/getpwuid_r.3.html
use std::ffi::CStr;
use std::io::{Error, Result};
use std::mem::MaybeUninit;
use nix::unistd::{Gid, Uid};
/// Representation of a user information stored in the password file `/etc/passwd`.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Passwd<'a> {
/// A username.
pub name: &'a CStr,
/// A user password.
pub passwd: &'a CStr,
/// A user ID.
pub uid: Uid,
/// A group ID.
pub gid: Gid,
/// A user full name or a comment.
pub gecos: &'a CStr,
/// A home directory.
pub dir: &'a CStr,
/// A shell program.
pub shell: &'a CStr,
}
impl<'a> Passwd<'a> {
/// Looks up the username in the password file and returns a `Passwd` with user information, if the user is found.
pub fn from_name(name: impl AsRef<CStr>, buf: &'a mut Vec<u8>) -> Result<Option<Self>> {
let name = name.as_ref();
getpw_r(name.as_ptr(), buf, libc::getpwnam_r)
}
/// Looks up the user ID and returns a `Passwd` with user information, if the user is found.
pub fn from_uid(uid: Uid, buf: &'a mut Vec<u8>) -> Result<Option<Self>> {
getpw_r(uid.as_raw(), buf, libc::getpwuid_r)
}
/// Looks up current user's information in the password file and return a `Passwd` with user information, if the user is found.
///
/// This is a shortcut for `Passwd::from_uid(libc::getuid())`.
pub fn current_user(buf: &'a mut Vec<u8>) -> Result<Option<Self>> {
Self::from_uid(nix::unistd::getuid(), buf)
}
unsafe fn from_c_struct(passwd: libc::passwd) -> Self {
let libc::passwd {
pw_name,
pw_passwd,
pw_uid,
pw_gid,
pw_gecos,
pw_dir,
pw_shell,
..
} = passwd;
Self {
name: CStr::from_ptr(pw_name),
passwd: CStr::from_ptr(pw_passwd),
uid: Uid::from_raw(pw_uid),
gid: Gid::from_raw(pw_gid),
gecos: CStr::from_ptr(pw_gecos),
dir: CStr::from_ptr(pw_dir),
shell: CStr::from_ptr(pw_shell),
}
}
}
fn getpw_r<'a, T>(
key: T,
buf: &'a mut Vec<u8>,
f: unsafe extern "C" fn(
key: T,
pwd: *mut libc::passwd,
buf: *mut libc::c_char,
buflen: libc::size_t,
result: *mut *mut libc::passwd,
) -> libc::c_int,
) -> Result<Option<Passwd<'a>>>
where
T: Copy,
{
let mut passwd = MaybeUninit::uninit();
let amt = unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) };
let mut amt = libc::c_long::max(amt, 512) as usize;
buf.clear();
buf.reserve(amt);
loop {
buf.reserve(amt);
let mut result = std::ptr::null_mut();
let code = unsafe {
f(
key,
passwd.as_mut_ptr(),
buf.as_mut_ptr() as _,
buf.capacity(),
&mut result,
)
};
return if !result.is_null() {
Ok(Some(unsafe { Passwd::from_c_struct(passwd.assume_init()) }))
} else if code == 0 {
Ok(None)
} else {
let e = Error::last_os_error();
let errno = e.raw_os_error().unwrap();
match errno {
// A signal was caught
libc::EINTR => continue,
// Insufficient buffer space
libc::ERANGE => {
amt *= 2;
continue;
}
// The given name or uid was not found.
// see https://man7.org/linux/man-pages/man3/getpwnam_r.3.html
0 | libc::ENOENT | libc::ESRCH | libc::EBADF | libc::EPERM => Ok(None),
// Other errors
_ => Err(e),
}
};
}
}
#[cfg(test)]
mod test {
use std::ffi::CString;
use super::*;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[test]
fn root() -> Result<()> {
let mut buf_a = Vec::new();
let mut buf_b = Vec::new();
let by_name =
Passwd::from_name(CStr::from_bytes_with_nul(b"root\0")?, &mut buf_a)?.unwrap();
let by_uid = Passwd::from_uid(0.into(), &mut buf_b)?.unwrap();
assert_eq!(by_name.uid, Uid::from_raw(0));
assert_eq!(by_name.gid, Gid::from_raw(0));
assert_eq!(by_name.name.to_str()?, "root");
assert_eq!(by_name.dir.to_str()?, "/root");
assert_eq!(by_uid, by_name);
Ok(())
}
#[test]
fn current_user() -> Result<()> {
let uid = unsafe { nix::unistd::getuid() };
let mut buf_a = Vec::new();
let mut buf_b = Vec::new();
let by_cu = Passwd::current_user(&mut buf_a)?.unwrap();
let by_name = Passwd::from_name(&by_cu.name, &mut buf_b)?.unwrap();
assert_eq!(by_cu.uid, uid);
// Assume $HOME is not modified
assert_eq!(by_cu.dir.to_str()?, std::env::var("HOME")?);
assert_eq!(by_cu, by_name);
Ok(())
}
#[test]
fn user_not_exist() -> Result<()> {
let mut buf_a = Vec::new();
let mut buf_b = Vec::new();
assert!(Passwd::from_uid(u32::MAX.into(), &mut buf_a)?.is_none());
assert!(Passwd::from_name(CString::new("")?, &mut buf_b)?.is_none());
Ok(())
}
}