221 lines
6.4 KiB
Rust
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(())
|
|
}
|
|
}
|