Merge branch 'sprout-2'

This commit is contained in:
Ben S
2016-01-26 19:19:52 +00:00
8 changed files with 509 additions and 476 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
extern crate users;
use users::{Users, Groups, OSUsers};
use users::{Users, Groups, UsersCache};
fn main() {
let cache = OSUsers::empty_cache();
let cache = UsersCache::new();
let current_uid = cache.get_current_uid();
println!("Your UID is {}", current_uid);
+4 -4
View File
@@ -1,4 +1,4 @@
//! This example demonstrates how to use an `OSUsers` cache in a
//! This example demonstrates how to use a `UsersCache` cache in a
//! multi-threaded situation. The cache uses `RefCell`s internally, so it
//! is distinctly not thread-safe. Instead, youll need to place it within
//! some kind of lock in order to have threads access it one-at-a-time.
@@ -16,7 +16,7 @@
// spew compile errors at you.
extern crate users;
use users::{Users, OSUsers, uid_t};
use users::{Users, UsersCache, uid_t};
use std::sync::{Arc, Mutex};
use std::time::Duration;
@@ -30,8 +30,8 @@ fn main() {
// For thread-safely, our users cache needs to be within a Mutex, so
// only one thread can access it once. This Mutex needs to be within an
// Arc, so multiple threads can access the Mutex.
let cache = Arc::new(Mutex::new(OSUsers::empty_cache()));
// let cache = OSUsers::empty_cache();
let cache = Arc::new(Mutex::new(UsersCache::new()));
// let cache = UsersCache::empty_cache();
// Loop over the range and query all the users in the range. Although we
// could use the `&User` values returned, we just ignore them.
+305
View File
@@ -0,0 +1,305 @@
use std::ffi::{CStr, CString};
use std::ptr::read;
use std::str::from_utf8_unchecked;
use std::sync::Arc;
use libc::{uid_t, gid_t};
#[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;
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly"))]
#[repr(C)]
struct c_passwd {
pw_name: *const c_char, // user name
pw_passwd: *const c_char, // password field
pw_uid: uid_t, // user ID
pw_gid: gid_t, // group ID
pw_change: time_t, // password change time
pw_class: *const c_char,
pw_gecos: *const c_char,
pw_dir: *const c_char, // user's home directory
pw_shell: *const c_char, // user's shell
pw_expire: time_t, // password expiry time
}
#[cfg(target_os = "linux")]
#[repr(C)]
struct c_passwd {
pw_name: *const c_char, // user name
pw_passwd: *const c_char, // password field
pw_uid: uid_t, // user ID
pw_gid: gid_t, // group ID
pw_gecos: *const c_char,
pw_dir: *const c_char, // user's home directory
pw_shell: *const c_char, // user's shell
}
#[repr(C)]
struct c_group {
gr_name: *const c_char, // group name
gr_passwd: *const c_char, // password
gr_gid: gid_t, // group id
gr_mem: *const *const c_char, // names of users in the group
}
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 getgid() -> gid_t;
fn getegid() -> gid_t;
}
/// Information about a particular user.
#[derive(Clone)]
pub struct User {
/// This user's ID
pub uid: uid_t,
/// This user's name
pub name: Arc<String>,
/// The ID of this user's primary group
pub primary_group: gid_t,
/// This user's home directory
pub home_dir: String,
/// This user's shell
pub shell: String,
}
/// Information about a particular group.
#[derive(Clone)]
pub struct Group {
/// This group's ID
pub gid: uid_t,
/// This group's name
pub name: Arc<String>,
/// Vector of the names of the users who belong to this group as a non-primary member
pub members: Vec<String>,
}
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: Arc::new(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: Arc::new(name), members: members })
}
else {
None
}
}
unsafe fn members(groups: *const *const c_char) -> Vec<String> {
let mut i = 0;
let mut members = vec![];
// The list of members is a pointer to a pointer of characters, terminated
// by a null pointer.
loop {
let username = groups.offset(i);
// The first null check here should be unnecessary, but if libc sends
// us bad data, it's probably better to continue on than crashing...
if username.is_null() || (*username).is_null() {
return members;
}
members.push(from_raw_buf(*username));
i += 1;
}
}
/// Searches for a `User` with the given ID in the systems user database.
/// Returns it if one is found, otherwise returns `None`.
pub fn get_user_by_uid(uid: uid_t) -> Option<User> {
unsafe { passwd_to_user(getpwuid(uid)) }
}
/// Searches for a `User` with the given username in the systems user database.
/// Returns it if one is found, otherwise returns `None`.
pub fn get_user_by_name(username: &str) -> Option<User> {
let username_c = CString::new(username);
if !username_c.is_ok() {
// This usually means the given username contained a '\0' already
// It is debatable what to do here
return None;
}
unsafe { passwd_to_user(getpwnam(username_c.unwrap().as_ptr())) }
}
/// Searches for a `Group` with the given ID in the systems group database.
/// Returns it if one is found, otherwise returns `None`.
pub fn get_group_by_gid(gid: gid_t) -> Option<Group> {
unsafe { struct_to_group(getgrgid(gid)) }
}
/// Searches for a `Group` with the given group name in the systems group database.
/// Returns it if one is found, otherwise returns `None`.
pub fn get_group_by_name(group_name: &str) -> Option<Group> {
let group_name_c = CString::new(group_name);
if !group_name_c.is_ok() {
// This usually means the given username contained a '\0' already
// It is debatable what to do here
return None;
}
unsafe { struct_to_group(getgrnam(group_name_c.unwrap().as_ptr())) }
}
/// Returns the user ID for the user running the process.
pub fn get_current_uid() -> uid_t {
unsafe { getuid() }
}
/// Returns the username of the user running the process.
pub fn get_current_username() -> Option<String> {
let uid = get_current_uid();
get_user_by_uid(uid).map(|u| Arc::try_unwrap(u.name).unwrap())
}
/// Returns the user ID for the effective user running the process.
pub fn get_effective_uid() -> uid_t {
unsafe { geteuid() }
}
/// Returns the username of the effective user running the process.
pub fn get_effective_username() -> Option<String> {
let uid = get_effective_uid();
get_user_by_uid(uid).map(|u| Arc::try_unwrap(u.name).unwrap())
}
/// Returns the group ID for the user running the process.
pub fn get_current_gid() -> gid_t {
unsafe { getgid() }
}
/// Returns the groupname of the user running the process.
pub fn get_current_groupname() -> Option<String> {
let gid = get_current_gid();
get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name).unwrap())
}
/// Returns the group ID for the effective user running the process.
pub fn get_effective_gid() -> gid_t {
unsafe { getegid() }
}
/// Returns the groupname of the effective user running the process.
pub fn get_effective_groupname() -> Option<String> {
let gid = get_effective_gid();
get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name).unwrap())
}
#[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() {
let uid = get_current_uid();
let user = get_user_by_uid(uid).unwrap();
// Not a real test but can be used to verify correct results
// Use with --nocapture on test executable to show output
println!("HOME={}, SHELL={}", user.home_dir, user.shell);
}
#[test]
fn user_by_name() {
// We cannot really test for arbitrary user as they might not exist on the machine
// Instead the name of the current user is used
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);
// User names containing '\0' cannot be used (for now)
let user = get_user_by_name("user\0");
assert!(user.is_none());
}
#[test]
fn group_by_name() {
// We cannot really test for arbitrary groups as they might not exist on the machine
// Instead the primary group of the current user is used
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);
// Group names containing '\0' cannot be used (for now)
let group = get_group_by_name("users\0");
assert!(group.is_none());
}
}
+13 -12
View File
@@ -2,7 +2,7 @@
//!
//! ## Caching, multiple threads, and mutability
//!
//! The `OSUsers` type is caught between a rock and a hard place when it comes
//! The `UsersCache` type is caught between a rock and a hard place when it comes
//! to providing references to users and groups.
//!
//! Instead of returning a fresh `User` struct each time, for example, it will
@@ -25,7 +25,7 @@
//! to be read at a time!
//!
//! ``norun
//! let mut cache = OSUsers::empty_cache();
//! let mut cache = UsersCache::empty_cache();
//! let uid = cache.get_current_uid(); // OK...
//! let user = cache.get_user_by_uid(uid).unwrap() // OK...
//! let group = cache.get_group_by_gid(user.primary_group); // No!
@@ -68,11 +68,12 @@ use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::HashMap;
use std::sync::Arc;
use super::{User, Groups, Group, Users};
use base::{User, Group};
use traits::{Users, Groups};
/// A producer of user and group instances that caches every result.
pub struct OSUsers {
pub struct UsersCache {
users: BiMap<uid_t, User>,
groups: BiMap<gid_t, Group>,
@@ -96,9 +97,9 @@ struct BiMap<K, V> {
// Default impl on User or Group, even though those types aren't
// needed to produce a default instance of any HashMaps...
impl Default for OSUsers {
fn default() -> OSUsers {
OSUsers {
impl Default for UsersCache {
fn default() -> UsersCache {
UsersCache {
users: BiMap {
forward: RefCell::new(HashMap::new()),
backward: RefCell::new(HashMap::new()),
@@ -117,15 +118,15 @@ impl Default for OSUsers {
}
}
impl OSUsers {
impl UsersCache {
/// Create a new empty cache.
pub fn empty_cache() -> OSUsers {
OSUsers::default()
pub fn new() -> UsersCache {
UsersCache::default()
}
}
impl Users for OSUsers {
impl Users for UsersCache {
fn get_user_by_uid(&self, uid: uid_t) -> Option<Arc<User>> {
let mut users_forward = self.users.forward.borrow_mut();
@@ -218,7 +219,7 @@ impl Users for OSUsers {
}
}
impl Groups for OSUsers {
impl Groups for UsersCache {
fn get_group_by_gid(&self, gid: gid_t) -> Option<Arc<Group>> {
let mut groups_forward = self.groups.forward.borrow_mut();
+15 -452
View File
File diff suppressed because it is too large Load Diff
+11 -6
View File
@@ -32,12 +32,12 @@
//!
//! To set your program up to use either type of Users object, make your
//! functions and structs accept a generic parameter that implements the `Users`
//! trait. Then, you can pass in an object of either OS or Mock type.
//! trait. Then, you can pass in a value of either Cache or Mock type.
//!
//! Here's a complete example:
//!
//! ```rust
//! use users::{Users, OSUsers, User};
//! use users::{Users, UsersCache, User};
//! use users::mock::MockUsers;
//! use std::sync::Arc;
//!
@@ -49,14 +49,17 @@
//! users.add_user(User { uid: 1001, name: Arc::new("fred".to_string()), primary_group: 101 , home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string()});
//! print_current_username(&mut users);
//!
//! let mut actual_users = OSUsers::empty_cache();
//! let mut actual_users = UsersCache::new();
//! print_current_username(&mut actual_users);
//! ```
pub use super::{Users, Groups, User, Group};
use std::collections::HashMap;
use std::sync::Arc;
use libc::{uid_t, gid_t};
pub use libc::{uid_t, gid_t};
pub use base::{User, Group};
pub use traits::{Users, Groups};
/// A mocking users object that you can add your own users and groups to.
pub struct MockUsers {
@@ -141,7 +144,9 @@ impl Groups for MockUsers {
#[cfg(test)]
mod test {
use super::{Users, Groups, User, Group, MockUsers};
use super::{MockUsers};
use base::{User, Group};
use traits::{Users, Groups};
use std::sync::Arc;
#[test]
+110
View File
@@ -0,0 +1,110 @@
use std::io::{Error as IOError, Result as IOResult};
use libc::{uid_t, gid_t, c_int};
use base::{get_effective_uid, get_effective_gid};
extern {
fn setuid(uid: uid_t) -> c_int;
fn seteuid(uid: uid_t) -> c_int;
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;
}
/// Sets current user for the running process, requires root priviledges.
pub fn set_current_uid(uid: uid_t) -> IOResult<()> {
match unsafe { setuid(uid) } {
0 => Ok(()),
-1 => Err(IOError::last_os_error()),
_ => unreachable!()
}
}
/// Set current group for the running process, requires root priviledges.
pub fn set_current_gid(gid: gid_t) -> IOResult<()> {
match unsafe { setgid(gid) } {
0 => Ok(()),
-1 => Err(IOError::last_os_error()),
_ => unreachable!()
}
}
/// Set effective user for the running process, requires root priviledges.
pub fn set_effective_uid(uid: uid_t) -> IOResult<()> {
match unsafe { seteuid(uid) } {
0 => Ok(()),
-1 => Err(IOError::last_os_error()),
_ => unreachable!()
}
}
/// Set effective user for the running process, requires root priviledges.
pub fn set_effective_gid(gid: gid_t) -> IOResult<()> {
match unsafe { setegid(gid) } {
0 => Ok(()),
-1 => Err(IOError::last_os_error()),
_ => unreachable!()
}
}
/// Atomically set current and effective user for the running process, requires root priviledges.
pub fn set_both_uid(ruid: uid_t, euid: uid_t) -> IOResult<()> {
match unsafe { setreuid(ruid, euid) } {
0 => Ok(()),
-1 => Err(IOError::last_os_error()),
_ => unreachable!()
}
}
/// Atomically set current and effective group for the running process, requires root priviledges.
pub fn set_both_gid(rgid: gid_t, egid: gid_t) -> IOResult<()> {
match unsafe { setregid(rgid, egid) } {
0 => Ok(()),
-1 => Err(IOError::last_os_error()),
_ => unreachable!()
}
}
pub struct SwitchUserGuard {
uid: uid_t,
gid: gid_t,
}
impl Drop for SwitchUserGuard {
fn drop(&mut self) {
// Panic on error here, as failing to set values back
// is a possible security breach.
set_effective_uid(self.uid).unwrap();
set_effective_gid(self.gid).unwrap();
}
}
/// Safely switch user and group for the current scope.
/// Requires root access.
///
/// ```ignore
/// {
/// let _guard = switch_user_group(1001, 1001);
/// // current and effective user and group ids are 1001
/// }
/// // back to the old values
/// ```
///
/// Use with care! Possible security issues can happen, as Rust doesn't
/// guarantee running the destructor! If in doubt run `drop()` method
/// on the guard value manually!
pub fn switch_user_group(uid: uid_t, gid: gid_t) -> Result<SwitchUserGuard, IOError> {
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)
}
+49
View File
@@ -0,0 +1,49 @@
use std::sync::Arc;
pub use libc::{uid_t, gid_t, c_int};
use super::{User, Group};
/// Trait for producers of users.
pub trait Users {
/// Returns a User if one exists for the given user ID; otherwise, returns None.
fn get_user_by_uid(&self, uid: uid_t) -> Option<Arc<User>>;
/// Returns a User if one exists for the given username; otherwise, returns None.
fn get_user_by_name(&self, username: &str) -> Option<Arc<User>>;
/// Returns the user ID for the user running the process.
fn get_current_uid(&self) -> uid_t;
/// Returns the username of the user running the process.
fn get_current_username(&self) -> Option<Arc<String>>;
/// Returns the effective user id.
fn get_effective_uid(&self) -> uid_t;
/// Returns the effective username.
fn get_effective_username(&self) -> Option<Arc<String>>;
}
/// Trait for producers of groups.
pub trait Groups {
/// Returns a Group object if one exists for the given group ID; otherwise, returns None.
fn get_group_by_gid(&self, gid: gid_t) -> Option<Arc<Group>>;
/// Returns a Group object if one exists for the given groupname; otherwise, returns None.
fn get_group_by_name(&self, group_name: &str) -> Option<Arc<Group>>;
/// Returns the group ID for the user running the process.
fn get_current_gid(&self) -> gid_t;
/// Returns the group name of the user running the process.
fn get_current_groupname(&self) -> Option<Arc<String>>;
/// Returns the effective group id.
fn get_effective_gid(&self) -> gid_t;
/// Returns the effective group name.
fn get_effective_groupname(&self) -> Option<Arc<String>>;
}