use std::{
convert::TryFrom,
error::Error as StdError,
ffi::CStr,
fmt::{self, Display},
io::Error as IOError,
mem::MaybeUninit,
os::raw::c_char,
ptr,
result::Result as StdResult,
};
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
use super::Fields;
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
use super::Time;
use super::{
group::{Error as GrError, Groups},
Gid, Uid,
};
use self::Error::*;
use libc::{geteuid, getpwnam_r, getpwuid_r, getuid, passwd};
use bstr::{BStr, BString, ByteSlice};
pub type Result<T> = StdResult<T, Error>;
#[derive(Debug)]
pub enum Error {
GetPasswdFailed(String, i32),
NameCheckFailed,
PasswdCheckFailed,
GecosCheckFailed,
DirCheckFailed,
ShellCheckFailed,
ClassCheckFailed,
AgeCheckFailed,
CommentCheckFailed,
PasswdNotFound,
Io(IOError),
Group(Box<GrError>),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GetPasswdFailed(fn_name, err_code) => write!(
f,
"Failed to get passwd with the following error code: {}. For more info search for \
the {} manual",
err_code, fn_name
),
NameCheckFailed => write!(f, "Passwd name check failed, `.pw_name` field is null"),
PasswdCheckFailed => write!(f, "Passwd passwd check failed, `.pw_passwd` is null"),
GecosCheckFailed => write!(f, "Passwd gecos check failed, `.pw_gecos` is null"),
DirCheckFailed => write!(f, "Passwd dir check failed, `.pw_dir` is null"),
ShellCheckFailed => write!(f, "Passwd shell check failed, `.pw_shell` is null"),
ClassCheckFailed => write!(f, "Passwd class check failed, `.pw_class` is null"),
AgeCheckFailed => write!(f, "Passwd class check failed, `.pw_age` is null"),
CommentCheckFailed => write!(f, "Passwd class check failed, `.pw_comment` is null"),
PasswdNotFound => write!(f, "Passwd was not found in the system"),
Io(err) => write!(f, "{}", err),
Group(err) => write!(f, "Group error: {}", err),
}
}
}
impl StdError for Error {
#[inline]
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Io(err) => Some(err),
Group(err) => Some(err),
_ => None,
}
}
}
impl From<GrError> for Error {
#[inline]
fn from(err: GrError) -> Error { Group(Box::new(err)) }
}
impl From<IOError> for Error {
#[inline]
fn from(err: IOError) -> Error { Io(err) }
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub struct Passwd {
name: BString,
passwd: BString,
user_id: Uid,
group_id: Gid,
gecos: BString,
dir: BString,
shell: BString,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
change: Time,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
class: BString,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
expire: Time,
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
fields: Fields,
#[cfg(target_os = "solaris")]
age: BString,
#[cfg(target_os = "solaris")]
comment: BString,
}
impl Passwd {
pub fn effective() -> Result<Self> {
let mut buff = [0; 16384]; let mut pw = MaybeUninit::uninit();
let mut pw_ptr = ptr::null_mut();
let res = unsafe {
getpwuid_r(geteuid(), pw.as_mut_ptr(), &mut buff[0], buff.len(), &mut pw_ptr)
};
if pw_ptr.is_null() {
if res == 0 {
return Err(PasswdNotFound);
} else {
return Err(GetPasswdFailed("getpwuid_r".to_string(), res));
}
}
let pw = unsafe { pw.assume_init() };
Ok(Passwd::try_from(pw)?)
}
pub fn real() -> Result<Self> {
let mut buff = [0; 16384]; let mut pw = MaybeUninit::uninit();
let mut pw_ptr = ptr::null_mut();
let res =
unsafe { getpwuid_r(getuid(), pw.as_mut_ptr(), &mut buff[0], buff.len(), &mut pw_ptr) };
if pw_ptr.is_null() {
if res == 0 {
return Err(PasswdNotFound);
} else {
return Err(GetPasswdFailed("getpwuid_r".to_string(), res));
}
}
let pw = unsafe { pw.assume_init() };
Ok(Passwd::try_from(pw)?)
}
pub fn from_uid(id: Uid) -> Result<Self> {
let mut buff = [0; 16384]; let mut pw = MaybeUninit::uninit();
let mut pw_ptr = ptr::null_mut();
let res = unsafe { getpwuid_r(id, pw.as_mut_ptr(), &mut buff[0], buff.len(), &mut pw_ptr) };
if pw_ptr.is_null() {
if res == 0 {
return Err(PasswdNotFound);
} else {
return Err(GetPasswdFailed("getpwuid_r".to_string(), res));
}
}
let pw = unsafe { pw.assume_init() };
Ok(Passwd::try_from(pw)?)
}
pub fn from_name(name: &str) -> Result<Self> {
let mut pw = MaybeUninit::uninit();
let mut pw_ptr = ptr::null_mut();
let mut buff = [0; 16384];
let name_null = {
let mut n = BString::from(name);
n.push(b'\0');
n
};
let res = unsafe {
getpwnam_r(
name_null.as_ptr() as *const c_char,
pw.as_mut_ptr(),
&mut buff[0],
buff.len(),
&mut pw_ptr,
)
};
if pw_ptr.is_null() {
if res == 0 {
return Err(PasswdNotFound);
} else {
return Err(GetPasswdFailed("getpwnam_r".to_string(), res));
}
}
let pw = unsafe { pw.assume_init() };
Ok(Passwd::try_from(pw)?)
}
#[inline]
pub fn name(&self) -> &BStr { self.name.as_bstr() }
#[inline]
pub fn passwd(&self) -> &BStr { self.passwd.as_bstr() }
#[inline]
pub fn uid(&self) -> Uid { self.user_id }
#[inline]
pub fn gid(&self) -> Gid { self.group_id }
#[inline]
pub fn gecos(&self) -> &BStr { self.gecos.as_bstr() }
#[inline]
pub fn dir(&self) -> &BStr { self.dir.as_bstr() }
#[inline]
pub fn shell(&self) -> &BStr { self.shell.as_bstr() }
#[inline]
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
pub fn class(&self) -> &BStr { &self.class.as_bstr() }
#[inline]
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
pub fn password_change(&self) -> Time { self.change }
#[inline]
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
pub fn expire(&self) -> Time { self.expire }
#[inline]
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub fn fields(&self) -> Fields { self.fields }
pub fn belongs_to(&self) -> Result<Groups> {
let name = {
let mut n = self.name.to_string();
n.push('\0');
n
};
let gr = Groups::from_username(&name)?;
Ok(gr)
}
}
impl TryFrom<passwd> for Passwd {
type Error = Error;
fn try_from(pw: passwd) -> StdResult<Self, Self::Error> {
let name = if pw.pw_name.is_null() {
return Err(NameCheckFailed);
} else {
let name_cstr = unsafe { CStr::from_ptr(pw.pw_name) };
BString::from(name_cstr.to_bytes())
};
let passwd = if pw.pw_passwd.is_null() {
return Err(PasswdCheckFailed);
} else {
let passwd_cstr = unsafe { CStr::from_ptr(pw.pw_passwd) };
BString::from(passwd_cstr.to_bytes())
};
let user_id = pw.pw_uid;
let group_id = pw.pw_gid;
let gecos = if pw.pw_gecos.is_null() {
return Err(GecosCheckFailed);
} else {
let gecos_cstr = unsafe { CStr::from_ptr(pw.pw_gecos) };
BString::from(gecos_cstr.to_bytes())
};
let dir = if pw.pw_dir.is_null() {
return Err(DirCheckFailed);
} else {
let dir_cstr = unsafe { CStr::from_ptr(pw.pw_dir) };
BString::from(dir_cstr.to_bytes())
};
let shell = if pw.pw_shell.is_null() {
return Err(ShellCheckFailed);
} else {
let shell_cstr = unsafe { CStr::from_ptr(pw.pw_shell) };
BString::from(shell_cstr.to_bytes())
};
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
let change = pw.pw_change;
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
let expire = pw.pw_expire;
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
let class = if pw.pw_class.is_null() {
return Err(ClassCheckFailed);
} else {
let class_cstr = unsafe { CStr::from_ptr(pw.pw_class) };
BString::from(class_cstr.to_bytes())
};
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
let fields = pw.pw_fields;
#[cfg(target_os = "solaris")]
let age = if pw.pw_age.is_null() {
return Err(AgeCheckFailed);
} else {
let class_cstr = unsafe { CStr::from_ptr(pw.pw_age) };
BString::from(class_cstr.to_bytes())
};
#[cfg(target_os = "solaris")]
let comment = if pw.pw_comment.is_null() {
return Err(CommentCheckFailed);
} else {
let class_cstr = unsafe { CStr::from_ptr(pw.pw_comment) };
BString::from(class_cstr.to_bytes())
};
Ok(Passwd {
name,
passwd,
user_id,
group_id,
gecos,
dir,
shell,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
change,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
class,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
expire,
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
fields,
#[cfg(target_os = "solaris")]
age,
#[cfg(target_os = "solaris")]
comment,
})
}
}
impl Display for Passwd {
#[cfg(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
))]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}:{}:{}:{}:{}:{}:{}",
self.name, self.passwd, self.user_id, self.group_id, self.gecos, self.dir, self.shell
)
}
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
self.name,
self.passwd,
self.user_id,
self.group_id,
self.class,
self.change,
self.expire,
self.gecos,
self.dir,
self.shell
)
}
}
impl From<Passwd> for passwd {
fn from(mut pw: Passwd) -> Self {
passwd {
pw_name: pw.name.as_mut_ptr() as *mut c_char,
pw_passwd: pw.passwd.as_mut_ptr() as *mut c_char,
pw_uid: pw.user_id,
pw_gid: pw.group_id,
pw_gecos: pw.gecos.as_mut_ptr() as *mut c_char,
pw_dir: pw.dir.as_mut_ptr() as *mut c_char,
pw_shell: pw.shell.as_mut_ptr() as *mut c_char,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
pw_change: pw.change,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
pw_class: pw.class.as_mut_ptr() as *mut c_char,
#[cfg(not(any(
target_os = "linux",
target_os = "haiku",
target_os = "fuchsia",
target_os = "solaris"
)))]
pw_expire: pw.expire,
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pw_fields: pw.fields,
#[cfg(target_os = "solaris")]
pw_age: pw.age.as_mut_ptr() as *mut c_char,
#[cfg(target_os = "solaris")]
pw_comment: pw.comment.as_mut_ptr() as *mut c_char,
}
}
}