From 543925889c9a78b224376195244edb43aef8a82a Mon Sep 17 00:00:00 2001 From: Ben S Date: Tue, 26 Jan 2016 18:39:00 +0000 Subject: [PATCH 1/5] Stop using 'object' terminology! --- src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 92a6875..5472f7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -316,12 +316,14 @@ unsafe fn members(groups: *const *const c_char) -> Vec { } -/// Returns a User object if one exists for the given user ID; otherwise, return None. +/// Searches for a `User` with the given ID in the system’s user database. +/// Returns it if one is found, otherwise returns `None`. pub fn get_user_by_uid(uid: uid_t) -> Option { unsafe { passwd_to_user(getpwuid(uid)) } } -/// Returns a User object if one exists for the given username; otherwise, return None. +/// Searches for a `User` with the given username in the system’s user database. +/// Returns it if one is found, otherwise returns `None`. pub fn get_user_by_name(username: &str) -> Option { let username_c = CString::new(username); @@ -334,12 +336,14 @@ pub fn get_user_by_name(username: &str) -> Option { unsafe { passwd_to_user(getpwnam(username_c.unwrap().as_ptr())) } } -/// Returns a Group object if one exists for the given group ID; otherwise, return None. +/// Searches for a `Group` with the given ID in the system’s group database. +/// Returns it if one is found, otherwise returns `None`. pub fn get_group_by_gid(gid: gid_t) -> Option { unsafe { struct_to_group(getgrgid(gid)) } } -/// Returns a Group object if one exists for the given groupname; otherwise, return None. +/// Searches for a `Group` with the given group name in the system‘s group database. +/// Returns it if one is found, otherwise returns `None`. pub fn get_group_by_name(group_name: &str) -> Option { let group_name_c = CString::new(group_name); From 5e4967a3778703de40ef7996ab5173e67b629be6 Mon Sep 17 00:00:00 2001 From: Ben S Date: Tue, 26 Jan 2016 18:57:39 +0000 Subject: [PATCH 2/5] Move the traits into their own module --- src/lib.rs | 45 ++------------------------------------------- src/traits.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 43 deletions(-) create mode 100644 src/traits.rs diff --git a/src/lib.rs b/src/lib.rs index 5472f7d..d749d41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,49 +127,8 @@ pub mod mock; pub mod os; pub use os::OSUsers; -/// 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>; - - /// Returns a User if one exists for the given username; otherwise, returns None. - fn get_user_by_name(&self, username: &str) -> Option>; - - /// 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>; - - /// Returns the effective user id. - fn get_effective_uid(&self) -> uid_t; - - /// Returns the effective username. - fn get_effective_username(&self) -> Option>; -} - -/// 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>; - - /// Returns a Group object if one exists for the given groupname; otherwise, returns None. - fn get_group_by_name(&self, group_name: &str) -> Option>; - - /// 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>; - - /// Returns the effective group id. - fn get_effective_gid(&self) -> gid_t; - - /// Returns the effective group name. - fn get_effective_groupname(&self) -> Option>; -} +mod traits; +pub use traits::{Users, Groups}; #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly"))] #[repr(C)] diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..b4080ba --- /dev/null +++ b/src/traits.rs @@ -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>; + + /// Returns a User if one exists for the given username; otherwise, returns None. + fn get_user_by_name(&self, username: &str) -> Option>; + + /// 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>; + + /// Returns the effective user id. + fn get_effective_uid(&self) -> uid_t; + + /// Returns the effective username. + fn get_effective_username(&self) -> Option>; +} + +/// 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>; + + /// Returns a Group object if one exists for the given groupname; otherwise, returns None. + fn get_group_by_name(&self, group_name: &str) -> Option>; + + /// 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>; + + /// Returns the effective group id. + fn get_effective_gid(&self) -> gid_t; + + /// Returns the effective group name. + fn get_effective_groupname(&self) -> Option>; +} \ No newline at end of file From 80e0c7d6f22a549e1dd9af268422cace7b41a602 Mon Sep 17 00:00:00 2001 From: Ben S Date: Tue, 26 Jan 2016 19:05:10 +0000 Subject: [PATCH 3/5] Move large parts of lib into base --- src/base.rs | 409 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 407 +-------------------------------------------------- src/mock.rs | 11 +- src/os.rs | 3 +- 4 files changed, 421 insertions(+), 409 deletions(-) create mode 100644 src/base.rs diff --git a/src/base.rs b/src/base.rs new file mode 100644 index 0000000..a2d756d --- /dev/null +++ b/src/base.rs @@ -0,0 +1,409 @@ +use std::io::{Error as IOError, Result as IOResult}; +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, 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; + + +#[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 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; +} + +/// 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, + + /// 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, + + /// Vector of the names of the users who belong to this group as a non-primary member + pub members: Vec, +} + +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 { + 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 { + 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 { + 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 system’s user database. +/// Returns it if one is found, otherwise returns `None`. +pub fn get_user_by_uid(uid: uid_t) -> Option { + unsafe { passwd_to_user(getpwuid(uid)) } +} + +/// Searches for a `User` with the given username in the system’s user database. +/// Returns it if one is found, otherwise returns `None`. +pub fn get_user_by_name(username: &str) -> Option { + 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 system’s group database. +/// Returns it if one is found, otherwise returns `None`. +pub fn get_group_by_gid(gid: gid_t) -> Option { + unsafe { struct_to_group(getgrgid(gid)) } +} + +/// Searches for a `Group` with the given group name in the system‘s group database. +/// Returns it if one is found, otherwise returns `None`. +pub fn get_group_by_name(group_name: &str) -> Option { + 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 { + 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 { + 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 { + 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 { + let gid = get_effective_gid(); + get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name).unwrap()) +} + +/// 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 { + 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::*; + + #[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()); + } +} diff --git a/src/lib.rs b/src/lib.rs index d749d41..5de5a6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,20 +108,11 @@ //! Use the mocking module to create custom tables to test your code for these //! edge cases. -use std::ffi::{CStr, CString}; -use std::io::{Error as IOError, Result as IOResult}; -use std::ptr::read; -use std::str::from_utf8_unchecked; -use std::sync::Arc; - 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; +mod base; +pub use base::*; pub mod mock; pub mod os; @@ -129,397 +120,3 @@ pub use os::OSUsers; mod traits; pub use traits::{Users, Groups}; - -#[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 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; -} - -/// 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, - - /// 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, - - /// Vector of the names of the users who belong to this group as a non-primary member - pub members: Vec, -} - -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 { - 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 { - 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 { - 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 system’s user database. -/// Returns it if one is found, otherwise returns `None`. -pub fn get_user_by_uid(uid: uid_t) -> Option { - unsafe { passwd_to_user(getpwuid(uid)) } -} - -/// Searches for a `User` with the given username in the system’s user database. -/// Returns it if one is found, otherwise returns `None`. -pub fn get_user_by_name(username: &str) -> Option { - 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 system’s group database. -/// Returns it if one is found, otherwise returns `None`. -pub fn get_group_by_gid(gid: gid_t) -> Option { - unsafe { struct_to_group(getgrgid(gid)) } -} - -/// Searches for a `Group` with the given group name in the system‘s group database. -/// Returns it if one is found, otherwise returns `None`. -pub fn get_group_by_name(group_name: &str) -> Option { - 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 { - 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 { - 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 { - 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 { - let gid = get_effective_gid(); - get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name).unwrap()) -} - -/// 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 { - 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::*; - - #[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()); - } -} diff --git a/src/mock.rs b/src/mock.rs index 5480b02..23f9fd2 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -53,10 +53,13 @@ //! 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] diff --git a/src/os.rs b/src/os.rs index dd92427..7870fca 100644 --- a/src/os.rs +++ b/src/os.rs @@ -68,7 +68,8 @@ 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. From 9dadca8993cdc279ec8eda5a8f00b59ade73383f Mon Sep 17 00:00:00 2001 From: Ben S Date: Tue, 26 Jan 2016 19:10:48 +0000 Subject: [PATCH 4/5] Move user-switching functions to their own module Remove `c_int` from the libc public exports list while I'm at it. This might break someone's code, but it shouldn't have been used from this library anyway (only for user switching functions!) --- src/base.rs | 106 +----------------------------------------------- src/lib.rs | 5 ++- src/switch.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 106 deletions(-) create mode 100644 src/switch.rs diff --git a/src/base.rs b/src/base.rs index a2d756d..dd643a4 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,11 +1,9 @@ -use std::io::{Error as IOError, Result as IOResult}; 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, c_int}; +use libc::{uid_t, gid_t}; #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly"))] use libc::{c_char, time_t}; @@ -59,17 +57,8 @@ extern { 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; } /// Information about a particular user. @@ -243,99 +232,6 @@ pub fn get_effective_groupname() -> Option { get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name).unwrap()) } -/// 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 { - 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 { diff --git a/src/lib.rs b/src/lib.rs index 5de5a6f..9950bce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,7 @@ //! edge cases. extern crate libc; -pub use libc::{uid_t, gid_t, c_int}; +pub use libc::{uid_t, gid_t}; mod base; pub use base::*; @@ -118,5 +118,8 @@ pub mod mock; pub mod os; pub use os::OSUsers; +pub mod switch; +use switch::*; + mod traits; pub use traits::{Users, Groups}; diff --git a/src/switch.rs b/src/switch.rs new file mode 100644 index 0000000..4cceac0 --- /dev/null +++ b/src/switch.rs @@ -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 { + 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) +} From 77eefe01760d79ded2051a3b2cd390e41751e2dd Mon Sep 17 00:00:00 2001 From: Ben S Date: Tue, 26 Jan 2016 19:17:24 +0000 Subject: [PATCH 5/5] Rename OSUsers to UsersCache It's more descriptive to call it a cache when there are functions that do all the *actual* OS users stuff separately now. Similarly, we may as well call its constructor 'new' instead of 'empty_cache', because it's obvious that it's a cache now. --- examples/example.rs | 4 ++-- examples/threading.rs | 8 ++++---- src/{os.rs => cache.rs} | 22 +++++++++++----------- src/lib.rs | 20 ++++++++++---------- src/mock.rs | 6 +++--- 5 files changed, 30 insertions(+), 30 deletions(-) rename src/{os.rs => cache.rs} (96%) diff --git a/examples/example.rs b/examples/example.rs index 069d0ef..c2f34a5 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -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); diff --git a/examples/threading.rs b/examples/threading.rs index 24ac1a3..27ffa54 100644 --- a/examples/threading.rs +++ b/examples/threading.rs @@ -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, you’ll 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. diff --git a/src/os.rs b/src/cache.rs similarity index 96% rename from src/os.rs rename to src/cache.rs index 7870fca..bd96834 100644 --- a/src/os.rs +++ b/src/cache.rs @@ -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! @@ -73,7 +73,7 @@ use traits::{Users, Groups}; /// A producer of user and group instances that caches every result. -pub struct OSUsers { +pub struct UsersCache { users: BiMap, groups: BiMap, @@ -97,9 +97,9 @@ struct BiMap { // 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()), @@ -118,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> { let mut users_forward = self.users.forward.borrow_mut(); @@ -219,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> { let mut groups_forward = self.groups.forward.borrow_mut(); diff --git a/src/lib.rs b/src/lib.rs index 9950bce..a3f185e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,12 +60,12 @@ //! which offers the same functionality while holding on to every result, //! caching the information so it can be re-used. //! -//! To introduce a cache, create a new `OSUsers` object and call the same +//! To introduce a cache, create a new `UsersCache` and call the same //! methods on it. For example: //! //! ```rust -//! use users::{Users, Groups, OSUsers}; -//! let mut cache = OSUsers::empty_cache(); +//! use users::{Users, Groups, UsersCache}; +//! let mut cache = UsersCache::new(); //! let uid = cache.get_current_uid(); //! let user = cache.get_user_by_uid(uid).unwrap(); //! println!("Hello again, {}!", user.name); @@ -74,13 +74,13 @@ //! This cache is **only additive**: it’s not possible to drop it, or erase //! selected entries, as when the database may have been modified, it’s best to //! start entirely afresh. So to accomplish this, just start using a new -//! `OSUsers` object. +//! `UsersCache`. //! //! //! ## Groups //! //! Finally, it’s possible to get groups in a similar manner. -//! A `Group` object has the following public fields: +//! A `Group` has the following public fields: //! //! - **gid:** The group’s ID //! - **name:** The group’s name @@ -89,8 +89,8 @@ //! And again, a complete example: //! //! ```rust -//! use users::{Users, Groups, OSUsers}; -//! let mut cache = OSUsers::empty_cache(); +//! use users::{Users, Groups, UsersCache}; +//! let mut cache = UsersCache::new(); //! let group = cache.get_group_by_name("admin").expect("No such group 'admin'!"); //! println!("The '{}' group has the ID {}", group.name, group.gid); //! for member in &group.members { @@ -114,12 +114,12 @@ pub use libc::{uid_t, gid_t}; mod base; pub use base::*; +pub mod cache; +pub use cache::UsersCache; + pub mod mock; -pub mod os; -pub use os::OSUsers; pub mod switch; -use switch::*; mod traits; pub use traits::{Users, Groups}; diff --git a/src/mock.rs b/src/mock.rs index 23f9fd2..65c7c0d 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -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,7 +49,7 @@ //! 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); //! ```