#![crate_name = "users"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
extern crate libc;
pub use libc::{uid_t, gid_t, c_int};
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly"))]
use libc::{c_char, time_t};
#[cfg(target_os = "linux")]
use libc::c_char;
use std::borrow::ToOwned;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::io;
use std::ptr::read;
use std::str::from_utf8_unchecked;
pub mod mock;
pub trait Users {
fn get_user_by_uid(&mut self, uid: uid_t) -> Option<User>;
fn get_user_by_name(&mut self, username: &str) -> Option<User>;
fn get_group_by_gid(&mut self, gid: gid_t) -> Option<Group>;
fn get_group_by_name(&mut self, group_name: &str) -> Option<Group>;
fn get_current_uid(&mut self) -> uid_t;
fn get_current_username(&mut self) -> Option<String>;
fn get_current_gid(&mut self) -> gid_t;
fn get_current_groupname(&mut self) -> Option<String>;
fn get_effective_uid(&mut self) -> uid_t;
fn get_effective_gid(&mut self) -> gid_t;
fn get_effective_username(&mut self) -> Option<String>;
fn get_effective_groupname(&mut self) -> Option<String>;
}
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly"))]
#[repr(C)]
struct c_passwd {
pub pw_name: *const c_char, pub pw_passwd: *const c_char, pub pw_uid: uid_t, pub pw_gid: gid_t, pub pw_change: time_t, pub pw_class: *const c_char,
pub pw_gecos: *const c_char,
pub pw_dir: *const c_char, pub pw_shell: *const c_char, pub pw_expire: time_t, }
#[cfg(target_os = "linux")]
#[repr(C)]
struct c_passwd {
pub pw_name: *const c_char, pub pw_passwd: *const c_char, pub pw_uid: uid_t, pub pw_gid: gid_t, pub pw_gecos: *const c_char,
pub pw_dir: *const c_char, pub pw_shell: *const c_char, }
#[repr(C)]
struct c_group {
pub gr_name: *const c_char, pub gr_passwd: *const c_char, pub gr_gid: gid_t, pub gr_mem: *const *const c_char, }
extern {
fn getpwuid(uid: uid_t) -> *const c_passwd;
fn getpwnam(user_name: *const c_char) -> *const c_passwd;
fn getgrgid(gid: gid_t) -> *const c_group;
fn getgrnam(group_name: *const c_char) -> *const c_group;
fn getuid() -> uid_t;
fn geteuid() -> uid_t;
fn setuid(uid: uid_t) -> c_int;
fn seteuid(uid: uid_t) -> c_int;
fn getgid() -> gid_t;
fn getegid() -> gid_t;
fn setgid(gid: gid_t) -> c_int;
fn setegid(gid: gid_t) -> c_int;
fn setreuid(ruid: uid_t, euid: uid_t) -> c_int;
fn setregid(rgid: gid_t, egid: gid_t) -> c_int;
}
#[derive(Clone)]
pub struct User {
pub uid: uid_t,
pub name: String,
pub primary_group: gid_t,
pub home_dir: String,
pub shell: String,
}
#[derive(Clone)]
pub struct Group {
pub gid: uid_t,
pub name: String,
pub members: Vec<String>,
}
#[derive(Clone)]
pub struct OSUsers {
users: HashMap<uid_t, Option<User>>,
users_back: HashMap<String, Option<uid_t>>,
groups: HashMap<gid_t, Option<Group>>,
groups_back: HashMap<String, Option<gid_t>>,
uid: Option<uid_t>,
gid: Option<gid_t>,
euid: Option<uid_t>,
egid: Option<gid_t>,
}
unsafe fn from_raw_buf(p: *const i8) -> String {
from_utf8_unchecked(CStr::from_ptr(p).to_bytes()).to_string()
}
unsafe fn passwd_to_user(pointer: *const c_passwd) -> Option<User> {
if !pointer.is_null() {
let pw = read(pointer);
Some(User {
uid: pw.pw_uid as uid_t,
name: from_raw_buf(pw.pw_name as *const i8),
primary_group: pw.pw_gid as gid_t,
home_dir: from_raw_buf(pw.pw_dir as *const i8),
shell: from_raw_buf(pw.pw_shell as *const i8)
})
}
else {
None
}
}
unsafe fn struct_to_group(pointer: *const c_group) -> Option<Group> {
if !pointer.is_null() {
let gr = read(pointer);
let name = from_raw_buf(gr.gr_name as *const i8);
let members = members(gr.gr_mem);
Some(Group { gid: gr.gr_gid, name: name, members: members })
}
else {
None
}
}
unsafe fn members(groups: *const *const c_char) -> Vec<String> {
let mut i = 0;
let mut members = vec![];
loop {
let username = groups.offset(i);
if username.is_null() || (*username).is_null() {
return members;
}
members.push(from_raw_buf(*username));
i += 1;
}
}
impl Users for OSUsers {
fn get_user_by_uid(&mut self, uid: uid_t) -> Option<User> {
match self.users.entry(uid) {
Vacant(entry) => {
let user = unsafe { passwd_to_user(getpwuid(uid)) };
match user {
Some(user) => {
entry.insert(Some(user.clone()));
self.users_back.insert(user.name.clone(), Some(user.uid));
Some(user)
},
None => {
entry.insert(None);
None
}
}
},
Occupied(entry) => entry.get().clone(),
}
}
fn get_user_by_name(&mut self, username: &str) -> Option<User> {
match self.users_back.entry(username.to_owned()) {
Vacant(entry) => {
let username_c = CString::new(username);
if !username_c.is_ok() {
return None;
}
let user = unsafe { passwd_to_user(getpwnam(username_c.unwrap().as_ptr())) };
match user {
Some(user) => {
entry.insert(Some(user.uid));
self.users.insert(user.uid, Some(user.clone()));
Some(user)
},
None => {
entry.insert(None);
None
}
}
},
Occupied(entry) => match entry.get() {
&Some(uid) => self.users[&uid].clone(),
&None => None,
}
}
}
fn get_group_by_gid(&mut self, gid: gid_t) -> Option<Group> {
match self.groups.entry(gid) {
Vacant(entry) => {
let group = unsafe { struct_to_group(getgrgid(gid)) };
match group {
Some(group) => {
entry.insert(Some(group.clone()));
self.groups_back.insert(group.name.clone(), Some(group.gid));
Some(group)
},
None => {
entry.insert(None);
None
}
}
},
Occupied(entry) => entry.get().clone(),
}
}
fn get_group_by_name(&mut self, group_name: &str) -> Option<Group> {
match self.groups_back.entry(group_name.to_owned()) {
Vacant(entry) => {
let group_name_c = CString::new(group_name);
if !group_name_c.is_ok() {
return None;
}
let user = unsafe { struct_to_group(getgrnam(group_name_c.unwrap().as_ptr())) };
match user {
Some(group) => {
entry.insert(Some(group.gid));
self.groups.insert(group.gid, Some(group.clone()));
Some(group)
},
None => {
entry.insert(None);
None
}
}
},
Occupied(entry) => match entry.get() {
&Some(gid) => self.groups[&gid].clone(),
&None => None,
}
}
}
fn get_current_uid(&mut self) -> uid_t {
match self.uid {
Some(uid) => uid,
None => {
let uid = unsafe { getuid() };
self.uid = Some(uid);
uid
}
}
}
fn get_current_username(&mut self) -> Option<String> {
let uid = self.get_current_uid();
self.get_user_by_uid(uid).map(|u| u.name)
}
fn get_current_gid(&mut self) -> gid_t {
match self.gid {
Some(gid) => gid,
None => {
let gid = unsafe { getgid() };
self.gid = Some(gid);
gid
}
}
}
fn get_current_groupname(&mut self) -> Option<String> {
let gid = self.get_current_gid();
self.get_group_by_gid(gid).map(|g| g.name)
}
fn get_effective_gid(&mut self) -> gid_t {
match self.egid {
Some(gid) => gid,
None => {
let gid = unsafe { getegid() };
self.egid = Some(gid);
gid
}
}
}
fn get_effective_groupname(&mut self) -> Option<String> {
let gid = self.get_effective_gid();
self.get_group_by_gid(gid).map(|g| g.name)
}
fn get_effective_uid(&mut self) -> uid_t {
match self.euid {
Some(uid) => uid,
None => {
let uid = unsafe { geteuid() };
self.euid = Some(uid);
uid
}
}
}
fn get_effective_username(&mut self) -> Option<String> {
let uid = self.get_effective_uid();
self.get_user_by_uid(uid).map(|u| u.name)
}
}
impl OSUsers {
pub fn empty_cache() -> OSUsers {
OSUsers {
users: HashMap::new(),
users_back: HashMap::new(),
groups: HashMap::new(),
groups_back: HashMap::new(),
uid: None,
gid: None,
euid: None,
egid: None,
}
}
}
pub fn get_user_by_uid(uid: uid_t) -> Option<User> {
OSUsers::empty_cache().get_user_by_uid(uid)
}
pub fn get_user_by_name(username: &str) -> Option<User> {
OSUsers::empty_cache().get_user_by_name(username)
}
pub fn get_group_by_gid(gid: gid_t) -> Option<Group> {
OSUsers::empty_cache().get_group_by_gid(gid)
}
pub fn get_group_by_name(group_name: &str) -> Option<Group> {
OSUsers::empty_cache().get_group_by_name(group_name)
}
pub fn get_current_uid() -> uid_t {
OSUsers::empty_cache().get_current_uid()
}
pub fn get_current_username() -> Option<String> {
OSUsers::empty_cache().get_current_username()
}
pub fn get_effective_uid() -> uid_t {
OSUsers::empty_cache().get_effective_uid()
}
pub fn get_effective_username() -> Option<String> {
OSUsers::empty_cache().get_effective_username()
}
pub fn get_current_gid() -> gid_t {
OSUsers::empty_cache().get_current_gid()
}
pub fn get_current_groupname() -> Option<String> {
OSUsers::empty_cache().get_current_groupname()
}
pub fn get_effective_gid() -> gid_t {
OSUsers::empty_cache().get_effective_gid()
}
pub fn get_effective_groupname() -> Option<String> {
OSUsers::empty_cache().get_effective_groupname()
}
pub fn set_current_uid(uid: uid_t) -> Result<(), io::Error> {
match unsafe { setuid(uid) } {
0 => Ok(()),
-1 => Err(io::Error::last_os_error()),
_ => unreachable!()
}
}
pub fn set_current_gid(gid: gid_t) -> Result<(), io::Error> {
match unsafe { setgid(gid) } {
0 => Ok(()),
-1 => Err(io::Error::last_os_error()),
_ => unreachable!()
}
}
pub fn set_effective_uid(uid: uid_t) -> Result<(), io::Error> {
match unsafe { seteuid(uid) } {
0 => Ok(()),
-1 => Err(io::Error::last_os_error()),
_ => unreachable!()
}
}
pub fn set_effective_gid(gid: gid_t) -> Result<(), io::Error> {
match unsafe { setegid(gid) } {
0 => Ok(()),
-1 => Err(io::Error::last_os_error()),
_ => unreachable!()
}
}
pub fn set_both_uid(ruid: uid_t, euid: uid_t) -> Result<(), io::Error> {
match unsafe { setreuid(ruid, euid) } {
0 => Ok(()),
-1 => Err(io::Error::last_os_error()),
_ => unreachable!()
}
}
pub fn set_both_gid(rgid: gid_t, egid: gid_t) -> Result<(), io::Error> {
match unsafe { setregid(rgid, egid) } {
0 => Ok(()),
-1 => Err(io::Error::last_os_error()),
_ => unreachable!()
}
}
pub struct SwitchUserGuard {
uid: uid_t,
gid: gid_t,
}
impl Drop for SwitchUserGuard {
fn drop(&mut self) {
set_effective_uid(self.uid).unwrap();
set_effective_gid(self.gid).unwrap();
}
}
pub fn switch_user_group(uid: uid_t, gid: gid_t) -> Result<SwitchUserGuard, io::Error> {
let current_state = SwitchUserGuard {
uid: get_effective_uid(),
gid: get_effective_gid(),
};
try!(set_effective_uid(uid));
try!(set_effective_gid(gid));
Ok(current_state)
}
#[cfg(test)]
mod test {
use super::{Users, OSUsers, get_current_username};
#[test]
fn uid() {
OSUsers::empty_cache().get_current_uid();
}
#[test]
fn username() {
let mut users = OSUsers::empty_cache();
let uid = users.get_current_uid();
assert_eq!(get_current_username().unwrap(), users.get_user_by_uid(uid).unwrap().name);
}
#[test]
fn uid_for_username() {
let mut users = OSUsers::empty_cache();
let uid = users.get_current_uid();
let user = users.get_user_by_uid(uid).unwrap();
assert_eq!(user.uid, uid);
}
#[test]
fn username_for_uid_for_username() {
let mut users = OSUsers::empty_cache();
let uid = users.get_current_uid();
let user = users.get_user_by_uid(uid).unwrap();
let user2 = users.get_user_by_uid(user.uid).unwrap();
assert_eq!(user2.uid, uid);
}
#[test]
fn user_info() {
let mut users = OSUsers::empty_cache();
let uid = users.get_current_uid();
let user = users.get_user_by_uid(uid).unwrap();
println!("HOME={}, SHELL={}", user.home_dir, user.shell);
}
#[test]
fn get_user_by_name() {
let mut users = OSUsers::empty_cache();
let name = users.get_current_username().unwrap();
let user_by_name = users.get_user_by_name(&name);
assert!(user_by_name.is_some());
assert_eq!(user_by_name.unwrap().name, name);
let user = users.get_user_by_name("user\0");
assert!(user.is_none());
}
#[test]
fn get_group_by_name() {
let mut users = OSUsers::empty_cache();
let cur_uid = users.get_current_uid();
let cur_user = users.get_user_by_uid(cur_uid).unwrap();
let cur_group = users.get_group_by_gid(cur_user.primary_group).unwrap();
let group_by_name = users.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 = users.get_group_by_name("users\0");
assert!(group.is_none());
}
}