mirror of
https://github.com/uutils/rust-users.git
synced 2026-06-10 15:48:37 -07:00
Move large parts of lib into base
This commit is contained in:
+409
@@ -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<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 system’s 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 system’s 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 system’s 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 system‘s 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())
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
||||
+2
-405
@@ -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<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 system’s 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 system’s 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 system’s 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 system‘s 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())
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user