-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
509 additions
and
476 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 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()) | ||
} | ||
|
||
|
||
#[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()); | ||
} | ||
} |
Oops, something went wrong.