use std::ffi::{CStr, CString, OsStr, OsString};
use std::fmt;
use std::mem;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::ptr;
use std::sync::Arc;
#[cfg(feature = "logging")]
extern crate log;
#[cfg(feature = "logging")]
use self::log::trace;
use libc::{c_char, uid_t, gid_t, c_int};
use libc::passwd as c_passwd;
use libc::group as c_group;
#[derive(Clone)]
pub struct User {
uid: uid_t,
primary_group: gid_t,
extras: os::UserExtras,
pub(crate) name_arc: Arc<OsStr>,
}
impl User {
pub fn new<S: AsRef<OsStr> + ?Sized>(uid: uid_t, name: &S, primary_group: gid_t) -> Self {
let name_arc = Arc::from(name.as_ref());
let extras = os::UserExtras::default();
Self { uid, name_arc, primary_group, extras }
}
pub fn uid(&self) -> uid_t {
self.uid
}
pub fn name(&self) -> &OsStr {
&*self.name_arc
}
pub fn primary_group_id(&self) -> gid_t {
self.primary_group
}
pub fn groups(&self) -> Option<Vec<Group>> {
get_user_groups(self.name(), self.primary_group_id())
}
}
impl fmt::Debug for User {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
f.debug_struct("User")
.field("uid", &self.uid)
.field("name_arc", &self.name_arc)
.field("primary_group", &self.primary_group)
.field("extras", &self.extras)
.finish()
}
else {
write!(f, "User({}, {})", self.uid(), self.name().to_string_lossy())
}
}
}
#[derive(Clone)]
pub struct Group {
gid: gid_t,
extras: os::GroupExtras,
pub(crate) name_arc: Arc<OsStr>,
}
impl Group {
pub fn new<S: AsRef<OsStr> + ?Sized>(gid: gid_t, name: &S) -> Self {
let name_arc = Arc::from(name.as_ref());
let extras = os::GroupExtras::default();
Self { gid, name_arc, extras }
}
pub fn gid(&self) -> gid_t {
self.gid
}
pub fn name(&self) -> &OsStr {
&*self.name_arc
}
}
impl fmt::Debug for Group {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
f.debug_struct("Group")
.field("gid", &self.gid)
.field("name_arc", &self.name_arc)
.field("extras", &self.extras)
.finish()
}
else {
write!(f, "Group({}, {})", self.gid(), self.name().to_string_lossy())
}
}
}
unsafe fn from_raw_buf<'a, T>(p: *const c_char) -> T
where T: From<&'a OsStr>
{
T::from(OsStr::from_bytes(CStr::from_ptr(p).to_bytes()))
}
unsafe fn passwd_to_user(passwd: c_passwd) -> User {
#[cfg(feature = "logging")]
trace!("Loading user with uid {}", passwd.pw_uid);
let name = from_raw_buf(passwd.pw_name);
User {
uid: passwd.pw_uid,
name_arc: name,
primary_group: passwd.pw_gid,
extras: os::UserExtras::from_passwd(passwd),
}
}
unsafe fn struct_to_group(group: c_group) -> Group {
#[cfg(feature = "logging")]
trace!("Loading group with gid {}", group.gr_gid);
let name = from_raw_buf(group.gr_name);
Group {
gid: group.gr_gid,
name_arc: name,
extras: os::GroupExtras::from_struct(group),
}
}
unsafe fn members(groups: *mut *mut c_char) -> Vec<OsString> {
let mut members = Vec::new();
for i in 0.. {
let username = groups.offset(i);
if username.is_null() || (*username).is_null() {
break;
}
else {
members.push(from_raw_buf(*username));
}
}
members
}
pub fn get_user_by_uid(uid: uid_t) -> Option<User> {
let mut passwd = unsafe { mem::zeroed::<c_passwd>() };
let mut buf = vec![0; 2048];
let mut result = ptr::null_mut::<c_passwd>();
#[cfg(feature = "logging")]
trace!("Running getpwuid_r for user #{}", uid);
loop {
let r = unsafe {
libc::getpwuid_r(uid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result)
};
if r != libc::ERANGE {
break;
}
let newsize = buf.len().checked_mul(2)?;
buf.resize(newsize, 0);
}
if result.is_null() {
return None;
}
if result != &mut passwd {
return None;
}
let user = unsafe { passwd_to_user(result.read()) };
Some(user)
}
pub fn get_user_by_name<S: AsRef<OsStr> + ?Sized>(username: &S) -> Option<User> {
let username = match CString::new(username.as_ref().as_bytes()) {
Ok(u) => u,
Err(_) => {
return None;
}
};
let mut passwd = unsafe { mem::zeroed::<c_passwd>() };
let mut buf = vec![0; 2048];
let mut result = ptr::null_mut::<c_passwd>();
#[cfg(feature = "logging")]
trace!("Running getpwnam_r for user {:?}", username.as_ref());
loop {
let r = unsafe {
libc::getpwnam_r(username.as_ptr(), &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result)
};
if r != libc::ERANGE {
break;
}
let newsize = buf.len().checked_mul(2)?;
buf.resize(newsize, 0);
}
if result.is_null() {
return None;
}
if result != &mut passwd {
return None;
}
let user = unsafe { passwd_to_user(result.read()) };
Some(user)
}
pub fn get_group_by_gid(gid: gid_t) -> Option<Group> {
let mut passwd = unsafe { mem::zeroed::<c_group>() };
let mut buf = vec![0; 2048];
let mut result = ptr::null_mut::<c_group>();
#[cfg(feature = "logging")]
trace!("Running getgruid_r for group #{}", gid);
loop {
let r = unsafe {
libc::getgrgid_r(gid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result)
};
if r != libc::ERANGE {
break;
}
let newsize = buf.len().checked_mul(2)?;
buf.resize(newsize, 0);
}
if result.is_null() {
return None;
}
if result != &mut passwd {
return None;
}
let group = unsafe { struct_to_group(result.read()) };
Some(group)
}
pub fn get_group_by_name<S: AsRef<OsStr> + ?Sized>(groupname: &S) -> Option<Group> {
let groupname = match CString::new(groupname.as_ref().as_bytes()) {
Ok(u) => u,
Err(_) => {
return None;
}
};
let mut group = unsafe { mem::zeroed::<c_group>() };
let mut buf = vec![0; 2048];
let mut result = ptr::null_mut::<c_group>();
#[cfg(feature = "logging")]
trace!("Running getgrnam_r for group {:?}", groupname.as_ref());
loop {
let r = unsafe {
libc::getgrnam_r(groupname.as_ptr(), &mut group, buf.as_mut_ptr(), buf.len(), &mut result)
};
if r != libc::ERANGE {
break;
}
let newsize = buf.len().checked_mul(2)?;
buf.resize(newsize, 0);
}
if result.is_null() {
return None;
}
if result != &mut group {
return None;
}
let group = unsafe { struct_to_group(result.read()) };
Some(group)
}
pub fn get_current_uid() -> uid_t {
#[cfg(feature = "logging")]
trace!("Running getuid");
unsafe { libc::getuid() }
}
pub fn get_current_username() -> Option<OsString> {
let uid = get_current_uid();
let user = get_user_by_uid(uid)?;
Some(OsString::from(&*user.name_arc))
}
pub fn get_effective_uid() -> uid_t {
#[cfg(feature = "logging")]
trace!("Running geteuid");
unsafe { libc::geteuid() }
}
pub fn get_effective_username() -> Option<OsString> {
let uid = get_effective_uid();
let user = get_user_by_uid(uid)?;
Some(OsString::from(&*user.name_arc))
}
pub fn get_current_gid() -> gid_t {
#[cfg(feature = "logging")]
trace!("Running getgid");
unsafe { libc::getgid() }
}
pub fn get_current_groupname() -> Option<OsString> {
let gid = get_current_gid();
let group = get_group_by_gid(gid)?;
Some(OsString::from(&*group.name_arc))
}
pub fn get_effective_gid() -> gid_t {
#[cfg(feature = "logging")]
trace!("Running getegid");
unsafe { libc::getegid() }
}
pub fn get_effective_groupname() -> Option<OsString> {
let gid = get_effective_gid();
let group = get_group_by_gid(gid)?;
Some(OsString::from(&*group.name_arc))
}
pub fn group_access_list() -> io::Result<Vec<Group>> {
let mut buff: Vec<gid_t> = vec![0; 1024];
#[cfg(feature = "logging")]
trace!("Running getgroups");
let res = unsafe {
libc::getgroups(1024, buff.as_mut_ptr())
};
if res < 0 {
Err(io::Error::last_os_error())
}
else {
let mut groups = buff.into_iter()
.filter_map(get_group_by_gid)
.collect::<Vec<_>>();
groups.dedup_by_key(|i| i.gid());
Ok(groups)
}
}
pub fn get_user_groups<S: AsRef<OsStr> + ?Sized>(username: &S, gid: gid_t) -> Option<Vec<Group>> {
#[cfg(all(unix, target_os="macos"))]
let mut buff: Vec<i32> = vec![0; 1024];
#[cfg(all(unix, not(target_os="macos")))]
let mut buff: Vec<gid_t> = vec![0; 1024];
let name = CString::new(username.as_ref().as_bytes()).unwrap();
let mut count = buff.len() as c_int;
#[cfg(feature = "logging")]
trace!("Running getgrouplist for user {:?} and group #{}", username.as_ref(), gid);
#[cfg(all(unix, target_os="macos"))]
let res = unsafe {
libc::getgrouplist(name.as_ptr(), gid as i32, buff.as_mut_ptr(), &mut count)
};
#[cfg(all(unix, not(target_os="macos")))]
let res = unsafe {
libc::getgrouplist(name.as_ptr(), gid, buff.as_mut_ptr(), &mut count)
};
if res < 0 {
None
}
else {
buff.dedup();
buff.into_iter()
.filter_map(|i| get_group_by_gid(i as gid_t))
.collect::<Vec<_>>()
.into()
}
}
struct AllUsers;
pub unsafe fn all_users() -> impl Iterator<Item=User> {
#[cfg(feature = "logging")]
trace!("Running setpwent");
#[cfg(not(target_os = "android"))]
libc::setpwent();
AllUsers
}
impl Drop for AllUsers {
#[cfg(target_os = "android")]
fn drop(&mut self) {
}
#[cfg(not(target_os = "android"))]
fn drop(&mut self) {
#[cfg(feature = "logging")]
trace!("Running endpwent");
unsafe { libc::endpwent() };
}
}
impl Iterator for AllUsers {
type Item = User;
#[cfg(target_os = "android")]
fn next(&mut self) -> Option<User> {
None
}
#[cfg(not(target_os = "android"))]
fn next(&mut self) -> Option<User> {
#[cfg(feature = "logging")]
trace!("Running getpwent");
let result = unsafe { libc::getpwent() };
if result.is_null() {
None
}
else {
let user = unsafe { passwd_to_user(result.read()) };
Some(user)
}
}
}
pub mod os {
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd", target_os = "solaris"))]
pub mod unix {
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use super::super::{c_passwd, c_group, members, from_raw_buf, Group};
pub trait UserExt {
fn home_dir(&self) -> &Path;
fn with_home_dir<S: AsRef<OsStr> + ?Sized>(self, home_dir: &S) -> Self;
fn shell(&self) -> &Path;
fn with_shell<S: AsRef<OsStr> + ?Sized>(self, shell: &S) -> Self;
fn password(&self) -> &OsStr;
fn with_password<S: AsRef<OsStr> + ?Sized>(self, password: &S) -> Self;
}
pub trait GroupExt {
fn members(&self) -> &[OsString];
fn add_member<S: AsRef<OsStr> + ?Sized>(self, name: &S) -> Self;
}
#[derive(Clone, Debug)]
pub struct UserExtras {
pub home_dir: PathBuf,
pub shell: PathBuf,
pub password: OsString,
}
impl Default for UserExtras {
fn default() -> Self {
Self {
home_dir: "/var/empty".into(),
shell: "/bin/false".into(),
password: "*".into(),
}
}
}
impl UserExtras {
pub(crate) unsafe fn from_passwd(passwd: c_passwd) -> Self {
#[cfg(target_os = "android")]
{
Default::default()
}
#[cfg(not(target_os = "android"))]
{
let home_dir = from_raw_buf::<OsString>(passwd.pw_dir).into();
let shell = from_raw_buf::<OsString>(passwd.pw_shell).into();
let password = from_raw_buf::<OsString>(passwd.pw_passwd);
Self { home_dir, shell, password }
}
}
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "solaris"))]
use super::super::User;
#[cfg(any(target_os = "linux", target_os = "android", target_os = "solaris"))]
impl UserExt for User {
fn home_dir(&self) -> &Path {
Path::new(&self.extras.home_dir)
}
fn with_home_dir<S: AsRef<OsStr> + ?Sized>(mut self, home_dir: &S) -> Self {
self.extras.home_dir = home_dir.into();
self
}
fn shell(&self) -> &Path {
Path::new(&self.extras.shell)
}
fn with_shell<S: AsRef<OsStr> + ?Sized>(mut self, shell: &S) -> Self {
self.extras.shell = shell.into();
self
}
fn password(&self) -> &OsStr {
&self.extras.password
}
fn with_password<S: AsRef<OsStr> + ?Sized>(mut self, password: &S) -> Self {
self.extras.password = password.into();
self
}
}
#[derive(Clone, Default, Debug)]
pub struct GroupExtras {
pub members: Vec<OsString>,
}
impl GroupExtras {
pub(crate) unsafe fn from_struct(group: c_group) -> Self {
Self { members: members(group.gr_mem) }
}
}
impl GroupExt for Group {
fn members(&self) -> &[OsString] {
&*self.extras.members
}
fn add_member<S: AsRef<OsStr> + ?Sized>(mut self, member: &S) -> Self {
self.extras.members.push(member.into());
self
}
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))]
pub mod bsd {
use std::ffi::OsStr;
use std::path::Path;
use libc::time_t;
use super::super::{c_passwd, User};
#[derive(Clone, Debug)]
pub struct UserExtras {
pub extras: super::unix::UserExtras,
pub change: time_t,
pub expire: time_t,
}
impl UserExtras {
pub(crate) unsafe fn from_passwd(passwd: c_passwd) -> Self {
Self {
change: passwd.pw_change,
expire: passwd.pw_expire,
extras: super::unix::UserExtras::from_passwd(passwd),
}
}
}
impl super::unix::UserExt for User {
fn home_dir(&self) -> &Path {
Path::new(&self.extras.extras.home_dir)
}
fn with_home_dir<S: AsRef<OsStr> + ?Sized>(mut self, home_dir: &S) -> Self {
self.extras.extras.home_dir = home_dir.into();
self
}
fn shell(&self) -> &Path {
Path::new(&self.extras.extras.shell)
}
fn with_shell<S: AsRef<OsStr> + ?Sized>(mut self, shell: &S) -> Self {
self.extras.extras.shell = shell.into();
self
}
fn password(&self) -> &OsStr {
&self.extras.extras.password
}
fn with_password<S: AsRef<OsStr> + ?Sized>(mut self, password: &S) -> Self {
self.extras.extras.password = password.into();
self
}
}
pub trait UserExt {
fn password_change_time(&self) -> time_t;
fn password_expire_time(&self) -> time_t;
}
impl UserExt for User {
fn password_change_time(&self) -> time_t {
self.extras.change
}
fn password_expire_time(&self) -> time_t {
self.extras.expire
}
}
impl Default for UserExtras {
fn default() -> Self {
Self {
extras: super::unix::UserExtras::default(),
change: 0,
expire: 0,
}
}
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))]
pub type UserExtras = bsd::UserExtras;
#[cfg(any(target_os = "linux", target_os = "android", target_os = "solaris"))]
pub type UserExtras = unix::UserExtras;
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd", target_os = "solaris"))]
pub type GroupExtras = unix::GroupExtras;
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn uid() {
get_current_uid();
}
#[test]
fn username() {
let uid = get_current_uid();
assert_eq!(&*get_current_username().unwrap(), &*get_user_by_uid(uid).unwrap().name());
}
#[test]
fn uid_for_username() {
let uid = get_current_uid();
let user = get_user_by_uid(uid).unwrap();
assert_eq!(user.uid, uid);
}
#[test]
fn username_for_uid_for_username() {
let uid = get_current_uid();
let user = get_user_by_uid(uid).unwrap();
let user2 = get_user_by_uid(user.uid).unwrap();
assert_eq!(user2.uid, uid);
}
#[test]
fn user_info() {
use base::os::unix::UserExt;
let uid = get_current_uid();
let user = get_user_by_uid(uid).unwrap();
println!("HOME={:?}, SHELL={:?}, PASSWD={:?}",
user.home_dir(), user.shell(), user.password());
}
#[test]
fn user_by_name() {
let name = get_current_username().unwrap();
let user_by_name = get_user_by_name(&name);
assert!(user_by_name.is_some());
assert_eq!(user_by_name.unwrap().name(), &*name);
let user = get_user_by_name("user\0");
assert!(user.is_none());
}
#[test]
fn user_get_groups() {
let uid = get_current_uid();
let user = get_user_by_uid(uid).unwrap();
let groups = user.groups().unwrap();
println!("Groups: {:?}", groups);
assert!(groups.len() > 0);
}
#[test]
fn group_by_name() {
let cur_uid = get_current_uid();
let cur_user = get_user_by_uid(cur_uid).unwrap();
let cur_group = get_group_by_gid(cur_user.primary_group).unwrap();
let group_by_name = get_group_by_name(&cur_group.name());
assert!(group_by_name.is_some());
assert_eq!(group_by_name.unwrap().name(), cur_group.name());
let group = get_group_by_name("users\0");
assert!(group.is_none());
}
}