├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── examples ├── example.rs ├── list.rs ├── os.rs └── threading.rs └── src ├── base.rs ├── cache.rs ├── lib.rs ├── mock.rs ├── switch.rs └── traits.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | matrix: 3 | include: 4 | - rust: nightly 5 | os: linux 6 | - rust: nightly 7 | os: osx 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-users" 3 | description = "Library for getting information on Unix users and groups" 4 | 5 | authors = [ "ogham@bsago.me" ] 6 | documentation = "https://docs.rs/users/users/" 7 | homepage = "https://github.com/ogham/rust-users" 8 | license = "MIT" 9 | readme = "README.md" 10 | version = "0.6.0" 11 | 12 | [dependencies] 13 | libc = "0.2" 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-users [![users on crates.io](http://meritbadge.herokuapp.com/users)](https://crates.io/crates/users) [![Build status](https://travis-ci.org/ogham/rust-users.svg?branch=master)](https://travis-ci.org/ogham/rust-users) 2 | 3 | This is a library for getting information on Unix users and groups. It supports getting the system users, and creating your own mock tables. 4 | 5 | ### [View the Rustdoc](https://docs.rs/users) 6 | 7 | 8 | # Installation 9 | 10 | This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section: 11 | 12 | ```toml 13 | [dependencies] 14 | users = "0.5.0" 15 | ``` 16 | 17 | 18 | # Usage 19 | 20 | In Unix, each user has an individual *user ID*, and each process has an *effective user ID* that says which user’s permissions it is using. 21 | Furthermore, users can be the members of *groups*, which also have names and IDs. 22 | This functionality is exposed in libc, the C standard library, but as an unsafe Rust interface. 23 | This wrapper library provides a safe interface, using User and Group objects instead of low-level pointers and strings. 24 | It also offers basic caching functionality. 25 | 26 | It does not (yet) offer *editing* functionality; the objects returned are read-only. 27 | 28 | 29 | ## Users 30 | 31 | The function `get_current_uid` returns a `uid_t` value representing the user currently running the program, and the `get_user_by_uid` function scans the users database and returns a User object with the user’s information. 32 | This function returns `None` when there is no user for that ID. 33 | 34 | A `User` object has the following accessors: 35 | 36 | - **uid:** The user’s ID 37 | - **name:** The user’s name 38 | - **primary_group:** The ID of this user’s primary group 39 | 40 | Here is a complete example that prints out the current user’s name: 41 | 42 | ```rust 43 | use users::{get_user_by_uid, get_current_uid}; 44 | let user = get_user_by_uid(get_current_uid()).unwrap(); 45 | println!("Hello, {}!", user.name()); 46 | ``` 47 | 48 | This code assumes (with `unwrap()`) that the user hasn’t been deleted after the program has started running. 49 | For arbitrary user IDs, this is **not** a safe assumption: it’s possible to delete a user while it’s running a program, or is the owner of files, or for that user to have never existed. 50 | So always check the return values from `user_to_uid`! 51 | 52 | There is also a `get_current_username` function, as it’s such a common operation that it deserves special treatment. 53 | 54 | 55 | ## Caching 56 | 57 | Despite the above warning, the users and groups database rarely changes. 58 | While a short program may only need to get user information once, a long-running one may need to re-query the database many times, and a medium-length one may get away with caching the values to save on redundant system calls. 59 | 60 | For this reason, this crate offers a caching interface to the database, which offers the same functionality while holding on to every result, caching the information so it can be re-used. 61 | 62 | To introduce a cache, create a new `OSUsers` object and call the same methods on it. 63 | For example: 64 | 65 | ```rust 66 | use users::{Users, Groups, UsersCache}; 67 | let mut cache = UsersCache::new(); 68 | let uid = cache.get_current_uid(); 69 | let user = cache.get_user_by_uid(uid).unwrap(); 70 | println!("Hello again, {}!", user.name()); 71 | ``` 72 | 73 | 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. 74 | So to accomplish this, just start using a new `OSUsers` object. 75 | 76 | 77 | ## Groups 78 | 79 | Finally, it’s possible to get groups in a similar manner. 80 | A `Group` object has the following accessors: 81 | 82 | - **gid:** The group’s ID 83 | - **name:** The group’s name 84 | 85 | And again, a complete example: 86 | 87 | ```rust 88 | use users::{Users, Groups, UsersCache}; 89 | let mut cache = UsersCache::new(); 90 | let group = cache.get_group_by_name("admin").expect("No such group 'admin'!"); 91 | println!("The '{}' group has the ID {}", group.name(), group.gid()); 92 | ``` 93 | 94 | 95 | ## Caveats 96 | 97 | You should be prepared for the users and groups tables to be completely broken: IDs shouldn’t be assumed to map to actual users and groups, and usernames and group names aren’t guaranteed to map either! 98 | 99 | Use the mocking module to create custom tables to test your code for these edge cases. 100 | 101 | 102 | # Mockable users and groups 103 | 104 | When you’re testing your code, you don’t want to actually rely on the system actually having various users and groups present - it’s much better to have a custom set of users that are *guaranteed* to be there, so you can test against them. 105 | 106 | This sub-library allows you to create these custom users and groups definitions, then access them using the same `Users` trait as in the main library, with few changes to your code. 107 | 108 | 109 | ## Creating mock users 110 | 111 | The only thing a mock users object needs to know in advance is the UID of the current user. 112 | Aside from that, you can add users and groups with `add_user` and `add_group` to the object: 113 | 114 | ```rust 115 | use users::mock::{MockUsers, User, Group}; 116 | use users::os::unix::{UserExt, GroupExt}; 117 | use std::sync::Arc; 118 | 119 | let mut users = MockUsers::with_current_uid(1000); 120 | let bobbins = User::new(1000, "Bobbins", 1000).with_home_dir("/home/bobbins"); 121 | users.add_user(bobbins); 122 | users.add_group(Group::new(100, "funkyppl")); 123 | ``` 124 | 125 | The exports get re-exported into the mock module, for simpler `use` lines. 126 | 127 | 128 | ## Using mock users 129 | 130 | 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. 131 | Then, you can pass in an object of either OS or Mock type. 132 | 133 | Here's a complete example: 134 | 135 | ```rust 136 | use users::{Users, UsersCache, User}; 137 | use users::os::unix::UserExt; 138 | use users::mock::MockUsers; 139 | use std::sync::Arc; 140 | 141 | fn print_current_username(users: &mut U) { 142 | println!("Current user: {:?}", users.get_current_username()); 143 | } 144 | 145 | let mut users = MockUsers::with_current_uid(1001); 146 | users.add_user(User::new(1001, "fred", 101)); 147 | print_current_username(&mut users); 148 | 149 | let mut actual_users = UsersCache::new(); 150 | print_current_username(&mut actual_users); 151 | ``` 152 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | extern crate rust_users; 2 | use rust_users::{Users, Groups, UsersCache}; 3 | 4 | fn main() { 5 | let cache = UsersCache::new(); 6 | 7 | let current_uid = cache.get_current_uid(); 8 | println!("Your UID is {}", current_uid); 9 | 10 | let you = cache.get_user_by_uid(current_uid).expect("No entry for current user!"); 11 | println!("Your username is {}", you.name()); 12 | 13 | let primary_group = cache.get_group_by_gid(you.primary_group_id()).expect("No entry for your primary group!"); 14 | println!("Your primary group has ID {} and name {}", primary_group.gid(), primary_group.name()); 15 | } 16 | -------------------------------------------------------------------------------- /examples/list.rs: -------------------------------------------------------------------------------- 1 | extern crate rust_users; 2 | use rust_users::{User, AllUsers}; 3 | 4 | fn main() { 5 | let mut users: Vec = unsafe { AllUsers::new() }.collect(); 6 | users.sort_by(|a, b| a.uid().cmp(&b.uid())); 7 | 8 | for user in users { 9 | println!("User {} has name {}", user.uid(), user.name()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/os.rs: -------------------------------------------------------------------------------- 1 | extern crate rust_users; 2 | use rust_users::{Users, Groups, UsersCache}; 3 | use rust_users::os::unix::{UserExt, GroupExt}; 4 | //use users::os::bsd::UserExt as BSDUserExt; 5 | 6 | fn main() { 7 | let cache = UsersCache::new(); 8 | 9 | let current_uid = cache.get_current_uid(); 10 | println!("Your UID is {}", current_uid); 11 | 12 | let you = cache.get_user_by_uid(current_uid).expect("No entry for current user!"); 13 | println!("Your username is {}", you.name()); 14 | println!("Your shell is {}", you.shell().display()); 15 | println!("Your home directory is {}", you.home_dir().display()); 16 | 17 | // The two fields below are only available on BSD systems. 18 | // Linux systems don’t have the fields in their `passwd` structs! 19 | //println!("Your password change timestamp is {}", you.password_change_time()); 20 | //println!("Your password expiry timestamp is {}", you.password_expire_time()); 21 | 22 | let primary_group = cache.get_group_by_gid(you.primary_group_id()).expect("No entry for your primary group!"); 23 | println!("Your primary group has ID {} and name {}", primary_group.gid(), primary_group.name()); 24 | 25 | if primary_group.members().is_empty() { 26 | println!("There are no other members of that group."); 27 | } 28 | else { 29 | for username in primary_group.members() { 30 | println!("User {} is also a member of that group.", username); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/threading.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how to use a `UsersCache` cache in a 2 | //! multi-threaded situation. The cache uses `RefCell`s internally, so it 3 | //! is distinctly not thread-safe. Instead, you’ll need to place it within 4 | //! some kind of lock in order to have threads access it one-at-a-time. 5 | //! 6 | //! It queries all the users it can find in the range 500..510. This is the 7 | //! default uid range on my Apple laptop -- Linux starts counting from 1000, 8 | //! but I can’t include both in the range! It spawns one thread per user to 9 | //! query, with each thread accessing the same cache. 10 | //! 11 | //! Then, afterwards, it retrieves references to the users that had been 12 | //! cached earlier. 13 | 14 | // For extra fun, try uncommenting some of the lines of code below, making 15 | // the code try to access the users cache *without* a Mutex, and see it 16 | // spew compile errors at you. 17 | 18 | extern crate rust_users; 19 | use rust_users::{Users, UsersCache, uid_t}; 20 | 21 | use std::sync::{Arc, Mutex}; 22 | use std::time::Duration; 23 | use std::thread; 24 | 25 | const LO: uid_t = 500; 26 | const HI: uid_t = 510; 27 | 28 | fn main() { 29 | 30 | // For thread-safely, our users cache needs to be within a Mutex, so 31 | // only one thread can access it once. This Mutex needs to be within an 32 | // Arc, so multiple threads can access the Mutex. 33 | let cache = Arc::new(Mutex::new(UsersCache::new())); 34 | // let cache = UsersCache::empty_cache(); 35 | 36 | // Loop over the range and query all the users in the range. Although we 37 | // could use the `&User` values returned, we just ignore them. 38 | for uid in LO .. HI { 39 | let cache = cache.clone(); 40 | 41 | thread::spawn(move || { 42 | let cache = cache.lock().unwrap(); // Unlock the mutex 43 | let _ = cache.get_user_by_uid(uid); // Query our users cache! 44 | }); 45 | } 46 | 47 | // Wait for all the threads to finish. 48 | thread::sleep(Duration::from_millis(100)); 49 | 50 | // Loop over the same range and print out all the users we find. 51 | // These users will be retrieved from the cache. 52 | for uid in LO .. HI { 53 | let cache = cache.lock().unwrap(); // Re-unlock the mutex 54 | if let Some(u) = cache.get_user_by_uid(uid) { // Re-query our cache! 55 | println!("User #{} is {}", u.uid(), u.name()) 56 | } 57 | else { 58 | println!("User #{} does not exist", uid); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/base.rs: -------------------------------------------------------------------------------- 1 | //! Integration with the C library’s users and groups. 2 | //! 3 | //! This module uses `extern` functions and types from `libc` that integrate 4 | //! with the system’s C library, which integrates with the OS itself to get user 5 | //! and group information. It’s where the “core” user handling is done. 6 | //! 7 | //! 8 | //! ## Name encoding rules 9 | //! 10 | //! Under Unix, usernames and group names are considered to be 11 | //! null-terminated, UTF-8 strings. These are `CString`s in Rust, although in 12 | //! this library, they are just `String` values. Why? 13 | //! 14 | //! The reason is that any user or group values with invalid `CString` data 15 | //! can instead just be assumed to not exist: 16 | //! 17 | //! - If you try to search for a user with a null character in their name, 18 | //! such a user could not exist anyway—so it’s OK to return `None`. 19 | //! - If the OS returns user information with a null character in a field, 20 | //! then that field will just be truncated instead, which is valid behaviour 21 | //! for a `CString`. 22 | //! 23 | //! The downside is that we use `from_utf8_lossy` instead, which has a small 24 | //! runtime penalty when it calculates and scans the length of the string for 25 | //! invalid characters. However, this should not be a problem when dealing with 26 | //! usernames of a few bytes each. 27 | //! 28 | //! In short, if you want to check for null characters in user fields, your 29 | //! best bet is to check for them yourself before passing strings into any 30 | //! functions. 31 | 32 | 33 | #![allow(missing_copy_implementations)] // for the C structs 34 | 35 | use std::ffi::{CStr, CString}; 36 | use std::fmt; 37 | use std::ptr::read; 38 | use std::sync::Arc; 39 | 40 | use libc::{uid_t, gid_t}; 41 | use libc::passwd as c_passwd; 42 | use libc::group as c_group; 43 | 44 | #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))] 45 | use libc::{c_char, time_t}; 46 | 47 | #[cfg(target_os = "linux")] 48 | use libc::c_char; 49 | 50 | 51 | extern { 52 | fn getpwuid(uid: uid_t) -> *const c_passwd; 53 | fn getpwnam(user_name: *const c_char) -> *const c_passwd; 54 | 55 | fn getgrgid(gid: gid_t) -> *const c_group; 56 | fn getgrnam(group_name: *const c_char) -> *const c_group; 57 | 58 | fn getuid() -> uid_t; 59 | fn geteuid() -> uid_t; 60 | 61 | fn getgid() -> gid_t; 62 | fn getegid() -> gid_t; 63 | 64 | fn setpwent(); 65 | fn getpwent() -> *const c_passwd; 66 | fn endpwent(); 67 | } 68 | 69 | 70 | /// Information about a particular user. 71 | #[derive(Clone)] 72 | pub struct User { 73 | uid: uid_t, 74 | primary_group: gid_t, 75 | extras: os::UserExtras, 76 | 77 | /// This user’s name, as an owned `String` possibly shared with a cache. 78 | /// Prefer using the `name()` accessor to using this field, if possible. 79 | pub name_arc: Arc, 80 | } 81 | 82 | impl User { 83 | 84 | /// Create a new `User` with the given user ID, name, and primary 85 | /// group ID, with the rest of the fields filled with dummy values. 86 | /// 87 | /// This method does not actually create a new user on the system—it 88 | /// should only be used for comparing users in tests. 89 | pub fn new(uid: uid_t, name: &str, primary_group: gid_t) -> User { 90 | User { 91 | uid: uid, 92 | name_arc: Arc::new(name.to_owned()), 93 | primary_group: primary_group, 94 | extras: os::UserExtras::default(), 95 | } 96 | } 97 | 98 | /// Returns this user’s ID. 99 | pub fn uid(&self) -> uid_t { 100 | self.uid.clone() 101 | } 102 | 103 | /// Returns this user’s name. 104 | pub fn name(&self) -> &str { 105 | &**self.name_arc 106 | } 107 | 108 | /// Returns the ID of this user’s primary group. 109 | pub fn primary_group_id(&self) -> gid_t { 110 | self.primary_group.clone() 111 | } 112 | } 113 | 114 | impl fmt::Debug for User { 115 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 116 | if f.alternate() { 117 | f.debug_struct("User") 118 | .field("uid", &self.uid) 119 | .field("name_arc", &self.name_arc) 120 | .field("primary_group", &self.primary_group) 121 | .field("extras", &self.extras) 122 | .finish() 123 | } 124 | else { 125 | write!(f, "User({}, {})", self.uid(), self.name()) 126 | } 127 | } 128 | } 129 | 130 | 131 | /// Information about a particular group. 132 | #[derive(Clone)] 133 | pub struct Group { 134 | gid: gid_t, 135 | extras: os::GroupExtras, 136 | 137 | /// This group’s name, as an owned `String` possibly shared with a cache. 138 | /// Prefer using the `name()` accessor to using this field, if possible. 139 | pub name_arc: Arc, 140 | } 141 | 142 | impl Group { 143 | 144 | /// Create a new `Group` with the given group ID and name, with the 145 | /// rest of the fields filled in with dummy values. 146 | /// 147 | /// This method does not actually create a new group on the system—it 148 | /// should only be used for comparing groups in tests. 149 | pub fn new(gid: gid_t, name: &str) -> Self { 150 | Group { 151 | gid: gid, 152 | name_arc: Arc::new(String::from(name)), 153 | extras: os::GroupExtras::default(), 154 | } 155 | } 156 | 157 | /// Returns this group’s ID. 158 | pub fn gid(&self) -> gid_t { 159 | self.gid.clone() 160 | } 161 | 162 | /// Returns this group's name. 163 | pub fn name(&self) -> &str { 164 | &**self.name_arc 165 | } 166 | } 167 | 168 | impl fmt::Debug for Group { 169 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 170 | if f.alternate() { 171 | f.debug_struct("Group") 172 | .field("gid", &self.gid) 173 | .field("name_arc", &self.name_arc) 174 | .field("extras", &self.extras) 175 | .finish() 176 | } 177 | else { 178 | write!(f, "Group({}, {})", self.gid(), self.name()) 179 | } 180 | } 181 | } 182 | 183 | 184 | /// Reads data from a `*char` field in `c_passwd` or `g_group` into a UTF-8 185 | /// `String` for use in a user or group value. 186 | /// 187 | /// Although `from_utf8_lossy` returns a clone-on-write string, we immediately 188 | /// clone it anyway: the underlying buffer is managed by the C library, not by 189 | /// us, so we *need* to move data out of it before the next user gets read. 190 | unsafe fn from_raw_buf(p: *const c_char) -> String { 191 | CStr::from_ptr(p).to_string_lossy().into_owned() 192 | } 193 | 194 | /// Converts a raw pointer, which could be null, into a safe reference that 195 | /// might be `None` instead. 196 | /// 197 | /// This is basically the unstable `ptr_as_ref` feature: 198 | /// https://github.com/rust-lang/rust/issues/27780 199 | /// When that stabilises, this can be replaced. 200 | unsafe fn ptr_as_ref(pointer: *const T) -> Option { 201 | if pointer.is_null() { 202 | None 203 | } 204 | else { 205 | Some(read(pointer)) 206 | } 207 | } 208 | 209 | unsafe fn passwd_to_user(pointer: *const c_passwd) -> Option { 210 | if let Some(passwd) = ptr_as_ref(pointer) { 211 | let name = Arc::new(from_raw_buf(passwd.pw_name)); 212 | 213 | Some(User { 214 | uid: passwd.pw_uid, 215 | name_arc: name, 216 | primary_group: passwd.pw_gid, 217 | extras: os::UserExtras::from_passwd(passwd), 218 | }) 219 | } 220 | else { 221 | None 222 | } 223 | } 224 | 225 | unsafe fn struct_to_group(pointer: *const c_group) -> Option { 226 | if let Some(group) = ptr_as_ref(pointer) { 227 | let name = Arc::new(from_raw_buf(group.gr_name)); 228 | 229 | Some(Group { 230 | gid: group.gr_gid, 231 | name_arc: name, 232 | extras: os::GroupExtras::from_struct(group), 233 | }) 234 | } 235 | else { 236 | None 237 | } 238 | } 239 | 240 | /// Expand a list of group members to a vector of strings. 241 | /// 242 | /// The list of members is, in true C fashion, a pointer to a pointer of 243 | /// characters, terminated by a null pointer. We check `members[0]`, then 244 | /// `members[1]`, and so on, until that null pointer is reached. It doesn't 245 | /// specify whether we should expect a null pointer or a pointer to a null 246 | /// pointer, so we check for both here! 247 | unsafe fn members(groups: *mut *mut c_char) -> Vec { 248 | let mut members = Vec::new(); 249 | 250 | for i in 0.. { 251 | let username = groups.offset(i); 252 | 253 | if username.is_null() || (*username).is_null() { 254 | break; 255 | } 256 | else { 257 | members.push(from_raw_buf(*username)); 258 | } 259 | } 260 | 261 | members 262 | } 263 | 264 | 265 | /// Searches for a `User` with the given ID in the system’s user database. 266 | /// Returns it if one is found, otherwise returns `None`. 267 | pub fn get_user_by_uid(uid: uid_t) -> Option { 268 | unsafe { 269 | let passwd = getpwuid(uid); 270 | passwd_to_user(passwd) 271 | } 272 | } 273 | 274 | /// Searches for a `User` with the given username in the system’s user database. 275 | /// Returns it if one is found, otherwise returns `None`. 276 | pub fn get_user_by_name(username: &str) -> Option { 277 | if let Ok(username) = CString::new(username) { 278 | unsafe { 279 | let passwd = getpwnam(username.as_ptr()); 280 | passwd_to_user(passwd) 281 | } 282 | } 283 | else { 284 | // The username that was passed in contained a null character. 285 | // This will *never* find anything, so just return `None`. 286 | // (I can’t figure out a pleasant way to signal an error here) 287 | None 288 | } 289 | } 290 | 291 | /// Searches for a `Group` with the given ID in the system’s group database. 292 | /// Returns it if one is found, otherwise returns `None`. 293 | pub fn get_group_by_gid(gid: gid_t) -> Option { 294 | unsafe { 295 | let group = getgrgid(gid); 296 | struct_to_group(group) 297 | } 298 | } 299 | 300 | /// Searches for a `Group` with the given group name in the system’s group database. 301 | /// Returns it if one is found, otherwise returns `None`. 302 | pub fn get_group_by_name(group_name: &str) -> Option { 303 | if let Ok(group_name) = CString::new(group_name) { 304 | unsafe { 305 | let group = getgrnam(group_name.as_ptr()); 306 | struct_to_group(group) 307 | } 308 | } 309 | else { 310 | // The group name that was passed in contained a null character. 311 | // This will *never* find anything, so just return `None`. 312 | // (I can’t figure out a pleasant way to signal an error here) 313 | None 314 | } 315 | } 316 | 317 | /// Returns the user ID for the user running the process. 318 | pub fn get_current_uid() -> uid_t { 319 | unsafe { getuid() } 320 | } 321 | 322 | /// Returns the username of the user running the process. 323 | pub fn get_current_username() -> Option { 324 | let uid = get_current_uid(); 325 | get_user_by_uid(uid).map(|u| Arc::try_unwrap(u.name_arc).unwrap()) 326 | } 327 | 328 | /// Returns the user ID for the effective user running the process. 329 | pub fn get_effective_uid() -> uid_t { 330 | unsafe { geteuid() } 331 | } 332 | 333 | /// Returns the username of the effective user running the process. 334 | pub fn get_effective_username() -> Option { 335 | let uid = get_effective_uid(); 336 | get_user_by_uid(uid).map(|u| Arc::try_unwrap(u.name_arc).unwrap()) 337 | } 338 | 339 | /// Returns the group ID for the user running the process. 340 | pub fn get_current_gid() -> gid_t { 341 | unsafe { getgid() } 342 | } 343 | 344 | /// Returns the groupname of the user running the process. 345 | pub fn get_current_groupname() -> Option { 346 | let gid = get_current_gid(); 347 | get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name_arc).unwrap()) 348 | } 349 | 350 | /// Returns the group ID for the effective user running the process. 351 | pub fn get_effective_gid() -> gid_t { 352 | unsafe { getegid() } 353 | } 354 | 355 | /// Returns the groupname of the effective user running the process. 356 | pub fn get_effective_groupname() -> Option { 357 | let gid = get_effective_gid(); 358 | get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name_arc).unwrap()) 359 | } 360 | 361 | 362 | /// An iterator over every user present on the system. 363 | /// 364 | /// This struct actually requires no fields, but has one hidden one to make it 365 | /// `unsafe` to create. 366 | pub struct AllUsers(()); 367 | 368 | impl AllUsers { 369 | 370 | /// Creates a new iterator over every user present on the system. 371 | /// 372 | /// ## Unsafety 373 | /// 374 | /// This constructor is marked as `unsafe`, which is odd for a crate 375 | /// that's meant to be a safe interface. It *has* to be unsafe because 376 | /// we cannot guarantee that the underlying C functions, 377 | /// `getpwent`/`setpwent`/`endpwent` that iterate over the system's 378 | /// `passwd` entries, are called in a thread-safe manner. 379 | /// 380 | /// These functions [modify a global 381 | /// state](http://man7.org/linux/man-pages/man3/getpwent.3.html# 382 | /// ATTRIBUTES), and if any are used at the same time, the state could 383 | /// be reset, resulting in a data race. We cannot even place it behind 384 | /// an internal `Mutex`, as there is nothing stopping another `extern` 385 | /// function definition from calling it! 386 | /// 387 | /// So to iterate all users, construct the iterator inside an `unsafe` 388 | /// block, then make sure to not make a new instance of it until 389 | /// iteration is over. 390 | pub unsafe fn new() -> AllUsers { 391 | setpwent(); 392 | AllUsers(()) 393 | } 394 | } 395 | 396 | impl Drop for AllUsers { 397 | fn drop(&mut self) { 398 | unsafe { endpwent() }; 399 | } 400 | } 401 | 402 | impl Iterator for AllUsers { 403 | type Item = User; 404 | 405 | fn next(&mut self) -> Option { 406 | unsafe { passwd_to_user(getpwent()) } 407 | } 408 | } 409 | 410 | 411 | 412 | /// OS-specific extensions to users and groups. 413 | /// 414 | /// Every OS has a different idea of what data a user or a group comes with. 415 | /// Although they all provide a *username*, some OS’ users have an *actual name* 416 | /// too, or a set of permissions or directories or timestamps associated with 417 | /// them. 418 | /// 419 | /// This module provides extension traits for users and groups that allow 420 | /// implementors of this library to access this data *as long as a trait is 421 | /// available*, which requires the OS they’re using to support this data. 422 | /// 423 | /// It’s the same method taken by `Metadata` in the standard Rust library, 424 | /// which has a few cross-platform fields and many more OS-specific fields: 425 | /// traits in `std::os` provides access to any data that is not guaranteed to 426 | /// be there in the actual struct. 427 | pub mod os { 428 | 429 | /// Extensions to users and groups for Unix platforms. 430 | /// 431 | /// Although the `passwd` struct is common among Unix systems, its actual 432 | /// format can vary. See the definitions in the `base` module to check which 433 | /// fields are actually present. 434 | #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))] 435 | pub mod unix { 436 | use std::path::Path; 437 | 438 | use super::super::{c_passwd, c_group, members, from_raw_buf, Group}; 439 | 440 | /// Unix-specific extensions for `User`s. 441 | pub trait UserExt { 442 | 443 | /// Returns a path to this user’s home directory. 444 | fn home_dir(&self) -> &Path; 445 | 446 | /// Sets this user value’s home directory to the given string. 447 | /// Can be used to construct test users, which by default come with a 448 | /// dummy home directory string. 449 | fn with_home_dir(self, home_dir: &str) -> Self; 450 | 451 | /// Returns a path to this user’s shell. 452 | fn shell(&self) -> &Path; 453 | 454 | /// Sets this user’s shell path to the given string. 455 | /// Can be used to construct test users, which by default come with a 456 | /// dummy shell field. 457 | fn with_shell(self, shell: &str) -> Self; 458 | 459 | // TODO(ogham): Isn’t it weird that the setters take string slices, but 460 | // the getters return paths? 461 | } 462 | 463 | /// Unix-specific extensions for `Group`s. 464 | pub trait GroupExt { 465 | 466 | /// Returns a slice of the list of users that are in this group as 467 | /// their non-primary group. 468 | fn members(&self) -> &[String]; 469 | 470 | /// Adds a new member to this group. 471 | fn add_member(self, name: &str) -> Self; 472 | } 473 | 474 | /// Unix-specific fields for `User`s. 475 | #[derive(Clone, Debug)] 476 | pub struct UserExtras { 477 | 478 | /// The path to the user’s home directory. 479 | pub home_dir: String, 480 | 481 | /// The path to the user’s shell. 482 | pub shell: String, 483 | } 484 | 485 | impl Default for UserExtras { 486 | fn default() -> UserExtras { 487 | UserExtras { 488 | home_dir: String::from("/var/empty"), 489 | shell: String::from("/bin/false"), 490 | } 491 | } 492 | } 493 | 494 | impl UserExtras { 495 | /// Extract the OS-specific fields from the C `passwd` struct that 496 | /// we just read. 497 | pub unsafe fn from_passwd(passwd: c_passwd) -> UserExtras { 498 | let home_dir = from_raw_buf(passwd.pw_dir); 499 | let shell = from_raw_buf(passwd.pw_shell); 500 | 501 | UserExtras { 502 | home_dir: home_dir, 503 | shell: shell, 504 | } 505 | } 506 | } 507 | 508 | #[cfg(any(target_os = "linux"))] 509 | use super::super::User; 510 | 511 | #[cfg(any(target_os = "linux"))] 512 | impl UserExt for User { 513 | fn home_dir(&self) -> &Path { 514 | Path::new(&self.extras.home_dir) 515 | } 516 | 517 | fn with_home_dir(mut self, home_dir: &str) -> User { 518 | self.extras.home_dir = home_dir.to_owned(); 519 | self 520 | } 521 | 522 | fn shell(&self) -> &Path { 523 | Path::new(&self.extras.shell) 524 | } 525 | 526 | fn with_shell(mut self, shell: &str) -> User { 527 | self.extras.shell = shell.to_owned(); 528 | self 529 | } 530 | } 531 | 532 | /// Unix-specific fields for `Group`s. 533 | #[derive(Clone, Default, Debug)] 534 | pub struct GroupExtras { 535 | 536 | /// Vector of usernames that are members of this group. 537 | pub members: Vec, 538 | } 539 | 540 | impl GroupExtras { 541 | /// Extract the OS-specific fields from the C `group` struct that 542 | /// we just read. 543 | pub unsafe fn from_struct(group: c_group) -> GroupExtras { 544 | let members = members(group.gr_mem); 545 | 546 | GroupExtras { 547 | members: members, 548 | } 549 | } 550 | } 551 | 552 | impl GroupExt for Group { 553 | fn members(&self) -> &[String] { 554 | &*self.extras.members 555 | } 556 | 557 | fn add_member(mut self, member: &str) -> Group { 558 | self.extras.members.push(member.to_owned()); 559 | self 560 | } 561 | } 562 | } 563 | 564 | /// Extensions to users and groups for BSD platforms. 565 | /// 566 | /// These platforms have `change` and `expire` fields in their `passwd` 567 | /// C structs. 568 | #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))] 569 | pub mod bsd { 570 | use std::path::Path; 571 | use libc::time_t; 572 | use super::super::{c_passwd, User}; 573 | 574 | /// BSD-specific fields for `User`s. 575 | #[derive(Clone, Debug)] 576 | pub struct UserExtras { 577 | 578 | /// Fields specific to Unix, rather than just BSD. (This struct is 579 | /// a superset, so it has to have all the other fields in it, too). 580 | pub extras: super::unix::UserExtras, 581 | 582 | /// Password change time. 583 | pub change: time_t, 584 | 585 | /// Password expiry time. 586 | pub expire: time_t, 587 | } 588 | 589 | impl UserExtras { 590 | /// Extract the OS-specific fields from the C `passwd` struct that 591 | /// we just read. 592 | pub unsafe fn from_passwd(passwd: c_passwd) -> UserExtras { 593 | UserExtras { 594 | change: passwd.pw_change, 595 | expire: passwd.pw_expire, 596 | extras: super::unix::UserExtras::from_passwd(passwd), 597 | } 598 | } 599 | } 600 | 601 | impl super::unix::UserExt for User { 602 | fn home_dir(&self) -> &Path { 603 | Path::new(&self.extras.extras.home_dir) 604 | } 605 | 606 | fn with_home_dir(mut self, home_dir: &str) -> User { 607 | self.extras.extras.home_dir = home_dir.to_owned(); 608 | self 609 | } 610 | 611 | fn shell(&self) -> &Path { 612 | Path::new(&self.extras.extras.shell) 613 | } 614 | 615 | fn with_shell(mut self, shell: &str) -> User { 616 | self.extras.extras.shell = shell.to_owned(); 617 | self 618 | } 619 | } 620 | 621 | /// BSD-specific accessors for `User`s. 622 | pub trait UserExt { 623 | 624 | /// Returns this user's password change timestamp. 625 | fn password_change_time(&self) -> time_t; 626 | 627 | /// Returns this user's password expiry timestamp. 628 | fn password_expire_time(&self) -> time_t; 629 | } 630 | 631 | impl UserExt for User { 632 | fn password_change_time(&self) -> time_t { 633 | self.extras.change.clone() 634 | } 635 | 636 | fn password_expire_time(&self) -> time_t { 637 | self.extras.expire.clone() 638 | } 639 | } 640 | 641 | impl Default for UserExtras { 642 | fn default() -> UserExtras { 643 | UserExtras { 644 | extras: super::unix::UserExtras::default(), 645 | change: 0, 646 | expire: 0, 647 | } 648 | } 649 | } 650 | } 651 | 652 | /// Any extra fields on a `User` specific to the current platform. 653 | #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))] 654 | pub type UserExtras = bsd::UserExtras; 655 | 656 | /// Any extra fields on a `User` specific to the current platform. 657 | #[cfg(any(target_os = "linux"))] 658 | pub type UserExtras = unix::UserExtras; 659 | 660 | /// Any extra fields on a `Group` specific to the current platform. 661 | #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))] 662 | pub type GroupExtras = unix::GroupExtras; 663 | } 664 | 665 | 666 | #[cfg(test)] 667 | mod test { 668 | use super::*; 669 | 670 | #[test] 671 | fn uid() { 672 | get_current_uid(); 673 | } 674 | 675 | #[test] 676 | fn username() { 677 | let uid = get_current_uid(); 678 | assert_eq!(&*get_current_username().unwrap(), &*get_user_by_uid(uid).unwrap().name()); 679 | } 680 | 681 | #[test] 682 | fn uid_for_username() { 683 | let uid = get_current_uid(); 684 | let user = get_user_by_uid(uid).unwrap(); 685 | assert_eq!(user.uid, uid); 686 | } 687 | 688 | #[test] 689 | fn username_for_uid_for_username() { 690 | let uid = get_current_uid(); 691 | let user = get_user_by_uid(uid).unwrap(); 692 | let user2 = get_user_by_uid(user.uid).unwrap(); 693 | assert_eq!(user2.uid, uid); 694 | } 695 | 696 | #[test] 697 | fn user_info() { 698 | use base::os::unix::UserExt; 699 | 700 | let uid = get_current_uid(); 701 | let user = get_user_by_uid(uid).unwrap(); 702 | // Not a real test but can be used to verify correct results 703 | // Use with --nocapture on test executable to show output 704 | println!("HOME={:?}, SHELL={:?}", user.home_dir(), user.shell()); 705 | } 706 | 707 | #[test] 708 | fn user_by_name() { 709 | // We cannot really test for arbitrary user as they might not exist on the machine 710 | // Instead the name of the current user is used 711 | let name = get_current_username().unwrap(); 712 | let user_by_name = get_user_by_name(&name); 713 | assert!(user_by_name.is_some()); 714 | assert_eq!(user_by_name.unwrap().name(), &*name); 715 | 716 | // User names containing '\0' cannot be used (for now) 717 | let user = get_user_by_name("user\0"); 718 | assert!(user.is_none()); 719 | } 720 | 721 | #[test] 722 | fn group_by_name() { 723 | // We cannot really test for arbitrary groups as they might not exist on the machine 724 | // Instead the primary group of the current user is used 725 | let cur_uid = get_current_uid(); 726 | let cur_user = get_user_by_uid(cur_uid).unwrap(); 727 | let cur_group = get_group_by_gid(cur_user.primary_group).unwrap(); 728 | let group_by_name = get_group_by_name(&cur_group.name()); 729 | 730 | assert!(group_by_name.is_some()); 731 | assert_eq!(group_by_name.unwrap().name(), cur_group.name()); 732 | 733 | // Group names containing '\0' cannot be used (for now) 734 | let group = get_group_by_name("users\0"); 735 | assert!(group.is_none()); 736 | } 737 | } 738 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | //! A cache for users and groups provided by the OS. 2 | //! 3 | //! ## Caching, multiple threads, and mutability 4 | //! 5 | //! The `UsersCache` type is caught between a rock and a hard place when it comes 6 | //! to providing references to users and groups. 7 | //! 8 | //! Instead of returning a fresh `User` struct each time, for example, it will 9 | //! return a reference to the version it currently has in its cache. So you can 10 | //! ask for User #501 twice, and you’ll get a reference to the same value both 11 | //! time. Its methods are *idempotent* -- calling one multiple times has the 12 | //! same effect as calling one once. 13 | //! 14 | //! This works fine in theory, but in practice, the cache has to update its own 15 | //! state somehow: it contains several `HashMap`s that hold the result of user 16 | //! and group lookups. Rust provides mutability in two ways: 17 | //! 18 | //! 1. Have its methods take `&mut self`, instead of `&self`, allowing the 19 | //! internal maps to be mutated (“inherited mutability”) 20 | //! 2. Wrap the internal maps in a `RefCell`, allowing them to be modified 21 | //! (“interior mutability”). 22 | //! 23 | //! Unfortunately, Rust is also very protective of references to a mutable 24 | //! value. In this case, switching to `&mut self` would only allow for one user 25 | //! to be read at a time! 26 | //! 27 | //! ```norun 28 | //! let mut cache = UsersCache::empty_cache(); 29 | //! let uid = cache.get_current_uid(); // OK... 30 | //! let user = cache.get_user_by_uid(uid).unwrap() // OK... 31 | //! let group = cache.get_group_by_gid(user.primary_group); // No! 32 | //! ``` 33 | //! 34 | //! When we get the `user`, it returns an optional reference (which we unwrap) 35 | //! to the user’s entry in the cache. This is a reference to something contained 36 | //! in a mutable value. Then, when we want to get the user’s primary group, it 37 | //! will return *another* reference to the same mutable value. This is something 38 | //! that Rust explicitly disallows! 39 | //! 40 | //! The compiler wasn’t on our side with Option 1, so let’s try Option 2: 41 | //! changing the methods back to `&self` instead of `&mut self`, and using 42 | //! `RefCell`s internally. However, Rust is smarter than this, and knows that 43 | //! we’re just trying the same trick as earlier. A simplified implementation of 44 | //! a user cache lookup would look something like this: 45 | //! 46 | //! ```norun 47 | //! fn get_user_by_uid(&self, uid: uid_t) -> Option<&User> { 48 | //! let users = self.users.borrow_mut(); 49 | //! users.get(uid) 50 | //! } 51 | //! ``` 52 | //! 53 | //! Rust won’t allow us to return a reference like this because the `Ref` of the 54 | //! `RefCell` just gets dropped at the end of the method, meaning that our 55 | //! reference does not live long enough. 56 | //! 57 | //! So instead of doing any of that, we use `Arc` everywhere in order to get 58 | //! around all the lifetime restrictions. Returning reference-counted users and 59 | //! groups mean that we don’t have to worry about further uses of the cache, as 60 | //! the values themselves don’t count as being stored *in* the cache anymore. So 61 | //! it can be queried multiple times or go out of scope and the values it 62 | //! produces are not affected. 63 | 64 | use libc::{uid_t, gid_t}; 65 | use std::borrow::ToOwned; 66 | use std::cell::{Cell, RefCell}; 67 | use std::collections::hash_map::Entry::{Occupied, Vacant}; 68 | use std::collections::HashMap; 69 | use std::sync::Arc; 70 | 71 | use base::{User, Group, AllUsers}; 72 | use traits::{Users, Groups}; 73 | 74 | 75 | /// A producer of user and group instances that caches every result. 76 | pub struct UsersCache { 77 | users: BiMap, 78 | groups: BiMap, 79 | 80 | uid: Cell>, 81 | gid: Cell>, 82 | euid: Cell>, 83 | egid: Cell>, 84 | } 85 | 86 | /// A kinda-bi-directional HashMap that associates keys to values, and then 87 | /// strings back to keys. It doesn’t go the full route and offer 88 | /// *values*-to-keys lookup, because we only want to search based on 89 | /// usernames and group names. There wouldn’t be much point offering a “User 90 | /// to uid” map, as the uid is present in the user struct! 91 | struct BiMap { 92 | forward: RefCell< HashMap>> >, 93 | backward: RefCell< HashMap, Option> >, 94 | } 95 | 96 | // Default has to be impl'd manually here, because there's no 97 | // Default impl on User or Group, even though those types aren't 98 | // needed to produce a default instance of any HashMaps... 99 | 100 | impl Default for UsersCache { 101 | fn default() -> UsersCache { 102 | UsersCache { 103 | users: BiMap { 104 | forward: RefCell::new(HashMap::new()), 105 | backward: RefCell::new(HashMap::new()), 106 | }, 107 | 108 | groups: BiMap { 109 | forward: RefCell::new(HashMap::new()), 110 | backward: RefCell::new(HashMap::new()), 111 | }, 112 | 113 | uid: Cell::new(None), 114 | gid: Cell::new(None), 115 | euid: Cell::new(None), 116 | egid: Cell::new(None), 117 | } 118 | } 119 | } 120 | 121 | impl UsersCache { 122 | 123 | /// Creates a new empty cache. 124 | pub fn new() -> UsersCache { 125 | UsersCache::default() 126 | } 127 | 128 | /// Creates a new cache that contains all the users present on the system. 129 | /// 130 | /// This is `unsafe` because we cannot prevent data races if two caches 131 | /// were attempted to be initialised on different threads at the same time. 132 | pub unsafe fn with_all_users() -> UsersCache { 133 | let cache = UsersCache::new(); 134 | 135 | for user in AllUsers::new() { 136 | let uid = user.uid(); 137 | let user_arc = Arc::new(user); 138 | cache.users.forward.borrow_mut().insert(uid, Some(user_arc.clone())); 139 | cache.users.backward.borrow_mut().insert(user_arc.name_arc.clone(), Some(uid)); 140 | } 141 | 142 | cache 143 | } 144 | } 145 | 146 | impl Users for UsersCache { 147 | fn get_user_by_uid(&self, uid: uid_t) -> Option> { 148 | let mut users_forward = self.users.forward.borrow_mut(); 149 | 150 | match users_forward.entry(uid) { 151 | Vacant(entry) => { 152 | match super::get_user_by_uid(uid) { 153 | Some(user) => { 154 | let newsername = user.name_arc.clone(); 155 | let mut users_backward = self.users.backward.borrow_mut(); 156 | users_backward.insert(newsername, Some(uid)); 157 | 158 | let user_arc = Arc::new(user); 159 | entry.insert(Some(user_arc.clone())); 160 | Some(user_arc) 161 | }, 162 | None => { 163 | entry.insert(None); 164 | None 165 | } 166 | } 167 | }, 168 | Occupied(entry) => entry.get().clone(), 169 | } 170 | } 171 | 172 | fn get_user_by_name(&self, username: &str) -> Option> { 173 | let mut users_backward = self.users.backward.borrow_mut(); 174 | 175 | // to_owned() could change here: 176 | // https://github.com/rust-lang/rfcs/blob/master/text/0509-collections-reform-part-2.md#alternatives-to-toowned-on-entries 177 | match users_backward.entry(Arc::new(username.to_owned())) { 178 | Vacant(entry) => { 179 | match super::get_user_by_name(username) { 180 | Some(user) => { 181 | let uid = user.uid(); 182 | let user_arc = Arc::new(user); 183 | 184 | let mut users_forward = self.users.forward.borrow_mut(); 185 | users_forward.insert(uid, Some(user_arc.clone())); 186 | entry.insert(Some(uid)); 187 | 188 | Some(user_arc) 189 | }, 190 | None => { 191 | entry.insert(None); 192 | None 193 | } 194 | } 195 | }, 196 | Occupied(entry) => match *entry.get() { 197 | Some(uid) => { 198 | let users_forward = self.users.forward.borrow_mut(); 199 | users_forward[&uid].clone() 200 | } 201 | None => None, 202 | } 203 | } 204 | } 205 | 206 | fn get_current_uid(&self) -> uid_t { 207 | match self.uid.get() { 208 | Some(uid) => uid, 209 | None => { 210 | let uid = super::get_current_uid(); 211 | self.uid.set(Some(uid)); 212 | uid 213 | } 214 | } 215 | } 216 | 217 | fn get_current_username(&self) -> Option> { 218 | let uid = self.get_current_uid(); 219 | self.get_user_by_uid(uid).map(|u| u.name_arc.clone()) 220 | } 221 | 222 | fn get_effective_uid(&self) -> uid_t { 223 | match self.euid.get() { 224 | Some(uid) => uid, 225 | None => { 226 | let uid = super::get_effective_uid(); 227 | self.euid.set(Some(uid)); 228 | uid 229 | } 230 | } 231 | } 232 | 233 | fn get_effective_username(&self) -> Option> { 234 | let uid = self.get_effective_uid(); 235 | self.get_user_by_uid(uid).map(|u| u.name_arc.clone()) 236 | } 237 | } 238 | 239 | impl Groups for UsersCache { 240 | fn get_group_by_gid(&self, gid: gid_t) -> Option> { 241 | let mut groups_forward = self.groups.forward.borrow_mut(); 242 | 243 | match groups_forward.entry(gid) { 244 | Vacant(entry) => { 245 | let group = super::get_group_by_gid(gid); 246 | match group { 247 | Some(group) => { 248 | let new_group_name = group.name_arc.clone(); 249 | let mut groups_backward = self.groups.backward.borrow_mut(); 250 | groups_backward.insert(new_group_name, Some(gid)); 251 | 252 | let group_arc = Arc::new(group); 253 | entry.insert(Some(group_arc.clone())); 254 | Some(group_arc) 255 | }, 256 | None => { 257 | entry.insert(None); 258 | None 259 | } 260 | } 261 | }, 262 | Occupied(entry) => entry.get().clone(), 263 | } 264 | } 265 | 266 | fn get_group_by_name(&self, group_name: &str) -> Option> { 267 | let mut groups_backward = self.groups.backward.borrow_mut(); 268 | 269 | // to_owned() could change here: 270 | // https://github.com/rust-lang/rfcs/blob/master/text/0509-collections-reform-part-2.md#alternatives-to-toowned-on-entries 271 | match groups_backward.entry(Arc::new(group_name.to_owned())) { 272 | Vacant(entry) => { 273 | let user = super::get_group_by_name(group_name); 274 | match user { 275 | Some(group) => { 276 | let group_arc = Arc::new(group.clone()); 277 | let gid = group.gid(); 278 | 279 | let mut groups_forward = self.groups.forward.borrow_mut(); 280 | groups_forward.insert(gid, Some(group_arc.clone())); 281 | entry.insert(Some(gid)); 282 | 283 | Some(group_arc) 284 | }, 285 | None => { 286 | entry.insert(None); 287 | None 288 | } 289 | } 290 | }, 291 | Occupied(entry) => match *entry.get() { 292 | Some(gid) => { 293 | let groups_forward = self.groups.forward.borrow_mut(); 294 | groups_forward[&gid].as_ref().cloned() 295 | } 296 | None => None, 297 | } 298 | } 299 | } 300 | 301 | fn get_current_gid(&self) -> gid_t { 302 | match self.gid.get() { 303 | Some(gid) => gid, 304 | None => { 305 | let gid = super::get_current_gid(); 306 | self.gid.set(Some(gid)); 307 | gid 308 | } 309 | } 310 | } 311 | 312 | fn get_current_groupname(&self) -> Option> { 313 | let gid = self.get_current_gid(); 314 | self.get_group_by_gid(gid).map(|g| g.name_arc.clone()) 315 | } 316 | 317 | fn get_effective_gid(&self) -> gid_t { 318 | match self.egid.get() { 319 | Some(gid) => gid, 320 | None => { 321 | let gid = super::get_effective_gid(); 322 | self.egid.set(Some(gid)); 323 | gid 324 | } 325 | } 326 | } 327 | 328 | fn get_effective_groupname(&self) -> Option> { 329 | let gid = self.get_effective_gid(); 330 | self.get_group_by_gid(gid).map(|g| g.name_arc.clone()) 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "rlib"] 2 | #![crate_type = "dylib"] 3 | 4 | //! This is a library for getting information on Unix users and groups. It 5 | //! supports getting the system users, and creating your own mock tables. 6 | //! 7 | //! In Unix, each user has an individual *user ID*, and each process has an 8 | //! *effective user ID* that says which user’s permissions it is using. 9 | //! Furthermore, users can be the members of *groups*, which also have names and 10 | //! IDs. This functionality is exposed in libc, the C standard library, but as 11 | //! an unsafe Rust interface. This wrapper library provides a safe interface, 12 | //! using User and Group objects instead of low-level pointers and strings. It 13 | //! also offers basic caching functionality. 14 | //! 15 | //! It does not (yet) offer *editing* functionality; the objects returned are 16 | //! read-only. 17 | //! 18 | //! 19 | //! ## Users 20 | //! 21 | //! The function `get_current_uid` returns a `uid_t` value representing the user 22 | //! currently running the program, and the `get_user_by_uid` function scans the 23 | //! users database and returns a User object with the user’s information. This 24 | //! function returns `None` when there is no user for that ID. 25 | //! 26 | //! A `User` object has the following accessors: 27 | //! 28 | //! - **uid:** The user’s ID 29 | //! - **name:** The user’s name 30 | //! - **primary_group:** The ID of this user’s primary group 31 | //! 32 | //! Here is a complete example that prints out the current user’s name: 33 | //! 34 | //! ```rust 35 | //! use rust_users::{get_user_by_uid, get_current_uid}; 36 | //! let user = get_user_by_uid(get_current_uid()).unwrap(); 37 | //! println!("Hello, {}!", user.name()); 38 | //! ``` 39 | //! 40 | //! This code assumes (with `unwrap()`) that the user hasn’t been deleted after 41 | //! the program has started running. For arbitrary user IDs, this is **not** a 42 | //! safe assumption: it’s possible to delete a user while it’s running a 43 | //! program, or is the owner of files, or for that user to have never existed. 44 | //! So always check the return values from `user_to_uid`! 45 | //! 46 | //! There is also a `get_current_username` function, as it’s such a common 47 | //! operation that it deserves special treatment. 48 | //! 49 | //! 50 | //! ## Caching 51 | //! 52 | //! Despite the above warning, the users and groups database rarely changes. 53 | //! While a short program may only need to get user information once, a 54 | //! long-running one may need to re-query the database many times, and a 55 | //! medium-length one may get away with caching the values to save on redundant 56 | //! system calls. 57 | //! 58 | //! For this reason, this crate offers a caching interface to the database, 59 | //! which offers the same functionality while holding on to every result, 60 | //! caching the information so it can be re-used. 61 | //! 62 | //! To introduce a cache, create a new `UsersCache` and call the same 63 | //! methods on it. For example: 64 | //! 65 | //! ```rust 66 | //! use rust_users::{Users, Groups, UsersCache}; 67 | //! let mut cache = UsersCache::new(); 68 | //! let uid = cache.get_current_uid(); 69 | //! let user = cache.get_user_by_uid(uid).unwrap(); 70 | //! println!("Hello again, {}!", user.name()); 71 | //! ``` 72 | //! 73 | //! This cache is **only additive**: it’s not possible to drop it, or erase 74 | //! selected entries, as when the database may have been modified, it’s best to 75 | //! start entirely afresh. So to accomplish this, just start using a new 76 | //! `UsersCache`. 77 | //! 78 | //! 79 | //! ## Groups 80 | //! 81 | //! Finally, it’s possible to get groups in a similar manner. 82 | //! A `Group` has the following accessors: 83 | //! 84 | //! - **gid:** The group’s ID 85 | //! - **name:** The group’s name 86 | //! 87 | //! And again, a complete example: 88 | //! 89 | //! ```no_run 90 | //! use rust_users::{Users, Groups, UsersCache}; 91 | //! let mut cache = UsersCache::new(); 92 | //! let group = cache.get_group_by_name("admin").expect("No such group 'admin'!"); 93 | //! println!("The '{}' group has the ID {}", group.name(), group.gid()); 94 | //! ``` 95 | //! 96 | //! 97 | //! ## Caveats 98 | //! 99 | //! You should be prepared for the users and groups tables to be completely 100 | //! broken: IDs shouldn’t be assumed to map to actual users and groups, and 101 | //! usernames and group names aren’t guaranteed to map either! 102 | //! 103 | //! Use the mocking module to create custom tables to test your code for these 104 | //! edge cases. 105 | 106 | #![warn(missing_copy_implementations)] 107 | #![warn(missing_docs)] 108 | #![warn(trivial_casts, trivial_numeric_casts)] 109 | #![warn(unused_extern_crates, unused_qualifications)] 110 | 111 | extern crate libc; 112 | pub use libc::{uid_t, gid_t}; 113 | 114 | mod base; 115 | pub use base::{User, Group, os}; 116 | pub use base::{get_user_by_uid, get_user_by_name}; 117 | pub use base::{get_group_by_gid, get_group_by_name}; 118 | pub use base::{get_current_uid, get_current_username}; 119 | pub use base::{get_effective_uid, get_effective_username}; 120 | pub use base::{get_current_gid, get_current_groupname}; 121 | pub use base::{get_effective_gid, get_effective_groupname}; 122 | pub use base::AllUsers; 123 | 124 | 125 | pub mod cache; 126 | pub use cache::UsersCache; 127 | 128 | pub mod mock; 129 | 130 | pub mod switch; 131 | 132 | mod traits; 133 | pub use traits::{Users, Groups}; 134 | -------------------------------------------------------------------------------- /src/mock.rs: -------------------------------------------------------------------------------- 1 | //! Mockable users and groups. 2 | //! 3 | //! When you’re testing your code, you don’t want to actually rely on the 4 | //! system actually having various users and groups present - it’s much better 5 | //! to have a custom set of users that are *guaranteed* to be there, so you can 6 | //! test against them. 7 | //! 8 | //! This sub-library allows you to create these custom users and groups 9 | //! definitions, then access them using the same `Users` trait as in the main 10 | //! library, with few changes to your code. 11 | //! 12 | //! 13 | //! ## Creating Mock Users 14 | //! 15 | //! The only thing a mock users object needs to know in advance is the UID of 16 | //! the current user. Aside from that, you can add users and groups with 17 | //! `add_user` and `add_group` to the object: 18 | //! 19 | //! ```rust 20 | //! use rust_users::mock::{MockUsers, User, Group}; 21 | //! use rust_users::os::unix::{UserExt, GroupExt}; 22 | //! use std::sync::Arc; 23 | //! 24 | //! let mut users = MockUsers::with_current_uid(1000); 25 | //! let bobbins = User::new(1000, "Bobbins", 1000).with_home_dir("/home/bobbins"); 26 | //! users.add_user(bobbins); 27 | //! users.add_group(Group::new(100, "funkyppl")); 28 | //! ``` 29 | //! 30 | //! The exports get re-exported into the mock module, for simpler `use` lines. 31 | //! 32 | //! 33 | //! ## Using Mock Users 34 | //! 35 | //! To set your program up to use either type of Users object, make your 36 | //! functions and structs accept a generic parameter that implements the `Users` 37 | //! trait. Then, you can pass in a value of either Cache or Mock type. 38 | //! 39 | //! Here's a complete example: 40 | //! 41 | //! ```rust 42 | //! use rust_users::{Users, UsersCache, User}; 43 | //! use rust_users::os::unix::UserExt; 44 | //! use rust_users::mock::MockUsers; 45 | //! use std::sync::Arc; 46 | //! 47 | //! fn print_current_username(users: &mut U) { 48 | //! println!("Current user: {:?}", users.get_current_username()); 49 | //! } 50 | //! 51 | //! let mut users = MockUsers::with_current_uid(1001); 52 | //! users.add_user(User::new(1001, "fred", 101)); 53 | //! print_current_username(&mut users); 54 | //! 55 | //! let mut actual_users = UsersCache::new(); 56 | //! print_current_username(&mut actual_users); 57 | //! ``` 58 | 59 | use std::collections::HashMap; 60 | use std::sync::Arc; 61 | 62 | pub use libc::{uid_t, gid_t}; 63 | pub use base::{User, Group}; 64 | pub use traits::{Users, Groups}; 65 | 66 | 67 | /// A mocking users object that you can add your own users and groups to. 68 | pub struct MockUsers { 69 | users: HashMap>, 70 | groups: HashMap>, 71 | uid: uid_t, 72 | } 73 | 74 | impl MockUsers { 75 | 76 | /// Create a new, empty mock users object. 77 | pub fn with_current_uid(current_uid: uid_t) -> MockUsers { 78 | MockUsers { 79 | users: HashMap::new(), 80 | groups: HashMap::new(), 81 | uid: current_uid, 82 | } 83 | } 84 | 85 | /// Add a user to the users table. 86 | pub fn add_user(&mut self, user: User) -> Option> { 87 | self.users.insert(user.uid(), Arc::new(user)) 88 | } 89 | 90 | /// Add a group to the groups table. 91 | pub fn add_group(&mut self, group: Group) -> Option> { 92 | self.groups.insert(group.gid(), Arc::new(group)) 93 | } 94 | } 95 | 96 | impl Users for MockUsers { 97 | fn get_user_by_uid(&self, uid: uid_t) -> Option> { 98 | self.users.get(&uid).cloned() 99 | } 100 | 101 | fn get_user_by_name(&self, username: &str) -> Option> { 102 | self.users.values().find(|u| u.name() == username).cloned() 103 | } 104 | 105 | fn get_current_uid(&self) -> uid_t { 106 | self.uid 107 | } 108 | 109 | fn get_current_username(&self) -> Option> { 110 | self.users.get(&self.uid).map(|u| u.name_arc.clone()) 111 | } 112 | 113 | fn get_effective_uid(&self) -> uid_t { 114 | self.uid 115 | } 116 | 117 | fn get_effective_username(&self) -> Option> { 118 | self.users.get(&self.uid).map(|u| u.name_arc.clone()) 119 | } 120 | } 121 | 122 | impl Groups for MockUsers { 123 | fn get_group_by_gid(&self, gid: gid_t) -> Option> { 124 | self.groups.get(&gid).cloned() 125 | } 126 | 127 | fn get_group_by_name(&self, group_name: &str) -> Option> { 128 | self.groups.values().find(|g| g.name() == group_name).cloned() 129 | } 130 | 131 | fn get_current_gid(&self) -> uid_t { 132 | self.uid 133 | } 134 | 135 | fn get_current_groupname(&self) -> Option> { 136 | self.groups.get(&self.uid).map(|u| u.name_arc.clone()) 137 | } 138 | 139 | fn get_effective_gid(&self) -> uid_t { 140 | self.uid 141 | } 142 | 143 | fn get_effective_groupname(&self) -> Option> { 144 | self.groups.get(&self.uid).map(|u| u.name_arc.clone()) 145 | } 146 | } 147 | 148 | #[cfg(test)] 149 | mod test { 150 | use super::MockUsers; 151 | use base::{User, Group}; 152 | use traits::{Users, Groups}; 153 | use std::sync::Arc; 154 | 155 | #[test] 156 | fn current_username() { 157 | let mut users = MockUsers::with_current_uid(1337); 158 | users.add_user(User::new(1337, "fred", 101)); 159 | assert_eq!(Some(Arc::new("fred".into())), users.get_current_username()) 160 | } 161 | 162 | #[test] 163 | fn no_current_username() { 164 | let users = MockUsers::with_current_uid(1337); 165 | assert_eq!(None, users.get_current_username()) 166 | } 167 | 168 | #[test] 169 | fn uid() { 170 | let mut users = MockUsers::with_current_uid(0); 171 | users.add_user(User::new(1337, "fred", 101)); 172 | assert_eq!(Some(Arc::new("fred".into())), users.get_user_by_uid(1337).map(|u| u.name_arc.clone())) 173 | } 174 | 175 | #[test] 176 | fn username() { 177 | let mut users = MockUsers::with_current_uid(1337); 178 | users.add_user(User::new(1440, "fred", 101)); 179 | assert_eq!(Some(1440), users.get_user_by_name("fred").map(|u| u.uid())) 180 | } 181 | 182 | #[test] 183 | fn no_username() { 184 | let mut users = MockUsers::with_current_uid(1337); 185 | users.add_user(User::new(1337, "fred", 101)); 186 | assert_eq!(None, users.get_user_by_name("criminy").map(|u| u.uid())) 187 | } 188 | 189 | #[test] 190 | fn no_uid() { 191 | let users = MockUsers::with_current_uid(0); 192 | assert_eq!(None, users.get_user_by_uid(1337).map(|u| u.name_arc.clone())) 193 | } 194 | 195 | #[test] 196 | fn gid() { 197 | let mut users = MockUsers::with_current_uid(0); 198 | users.add_group(Group::new(1337, "fred")); 199 | assert_eq!(Some(Arc::new("fred".into())), users.get_group_by_gid(1337).map(|g| g.name_arc.clone())) 200 | } 201 | 202 | #[test] 203 | fn group_name() { 204 | let mut users = MockUsers::with_current_uid(0); 205 | users.add_group(Group::new(1337, "fred")); 206 | assert_eq!(Some(1337), users.get_group_by_name("fred").map(|g| g.gid())) 207 | } 208 | 209 | #[test] 210 | fn no_group_name() { 211 | let mut users = MockUsers::with_current_uid(0); 212 | users.add_group(Group::new(1337, "fred")); 213 | assert_eq!(None, users.get_group_by_name("santa").map(|g| g.gid())) 214 | } 215 | 216 | #[test] 217 | fn no_gid() { 218 | let users = MockUsers::with_current_uid(0); 219 | assert_eq!(None, users.get_group_by_gid(1337).map(|g| g.name_arc.clone())) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/switch.rs: -------------------------------------------------------------------------------- 1 | //! Functions for switching the running process’s user or group. 2 | 3 | use std::io::{Error as IOError, Result as IOResult}; 4 | use libc::{uid_t, gid_t, c_int}; 5 | 6 | use base::{get_effective_uid, get_effective_gid}; 7 | 8 | 9 | extern { 10 | fn setuid(uid: uid_t) -> c_int; 11 | fn seteuid(uid: uid_t) -> c_int; 12 | 13 | fn setgid(gid: gid_t) -> c_int; 14 | fn setegid(gid: gid_t) -> c_int; 15 | 16 | fn setreuid(ruid: uid_t, euid: uid_t) -> c_int; 17 | fn setregid(rgid: gid_t, egid: gid_t) -> c_int; 18 | } 19 | 20 | 21 | /// Sets the **current user** for the running process to the one with the 22 | /// given user ID. Uses `setuid` internally. 23 | /// 24 | /// Typically, trying to switch to anyone other than the user already running 25 | /// the process requires root privileges. 26 | pub fn set_current_uid(uid: uid_t) -> IOResult<()> { 27 | match unsafe { setuid(uid) } { 28 | 0 => Ok(()), 29 | -1 => Err(IOError::last_os_error()), 30 | n => unreachable!("setuid returned {}", n) 31 | } 32 | } 33 | 34 | /// Sets the **current group** for the running process to the one with the 35 | /// given group ID. Uses `setgid` internally. 36 | /// 37 | /// Typically, trying to switch to any group other than the group already 38 | /// running the process requires root privileges. 39 | pub fn set_current_gid(gid: gid_t) -> IOResult<()> { 40 | match unsafe { setgid(gid) } { 41 | 0 => Ok(()), 42 | -1 => Err(IOError::last_os_error()), 43 | n => unreachable!("setgid returned {}", n) 44 | } 45 | } 46 | 47 | /// Sets the **effective user** for the running process to the one with the 48 | /// given user ID. Uses `seteuid` internally. 49 | /// 50 | /// Typically, trying to switch to anyone other than the user already running 51 | /// the process requires root privileges. 52 | pub fn set_effective_uid(uid: uid_t) -> IOResult<()> { 53 | match unsafe { seteuid(uid) } { 54 | 0 => Ok(()), 55 | -1 => Err(IOError::last_os_error()), 56 | n => unreachable!("seteuid returned {}", n) 57 | } 58 | } 59 | 60 | /// Sets the **effective group** for the running process to the one with the 61 | /// given group ID. Uses `setegid` internally. 62 | /// 63 | /// Typically, trying to switch to any group other than the group already 64 | /// running the process requires root privileges. 65 | pub fn set_effective_gid(gid: gid_t) -> IOResult<()> { 66 | match unsafe { setegid(gid) } { 67 | 0 => Ok(()), 68 | -1 => Err(IOError::last_os_error()), 69 | n => unreachable!("setegid returned {}", n) 70 | } 71 | } 72 | 73 | /// Sets both the **current user** and the **effective user** for the running 74 | /// process to the ones with the given user IDs. Uses `setreuid` internally. 75 | /// 76 | /// Typically, trying to switch to anyone other than the user already running 77 | /// the process requires root privileges. 78 | pub fn set_both_uid(ruid: uid_t, euid: uid_t) -> IOResult<()> { 79 | match unsafe { setreuid(ruid, euid) } { 80 | 0 => Ok(()), 81 | -1 => Err(IOError::last_os_error()), 82 | n => unreachable!("setreuid returned {}", n) 83 | } 84 | } 85 | 86 | /// Sets both the **current group** and the **effective group** for the 87 | /// running process to the ones with the given group IDs. Uses `setregid` 88 | /// internally. 89 | /// 90 | /// Typically, trying to switch to any group other than the group already 91 | /// running the process requires root privileges. 92 | pub fn set_both_gid(rgid: gid_t, egid: gid_t) -> IOResult<()> { 93 | match unsafe { setregid(rgid, egid) } { 94 | 0 => Ok(()), 95 | -1 => Err(IOError::last_os_error()), 96 | n => unreachable!("setregid returned {}", n) 97 | } 98 | } 99 | 100 | /// Guard returned from a `switch_user_group` call. 101 | pub struct SwitchUserGuard { 102 | uid: uid_t, 103 | gid: gid_t, 104 | } 105 | 106 | impl Drop for SwitchUserGuard { 107 | fn drop(&mut self) { 108 | // Panic on error here, as failing to set values back 109 | // is a possible security breach. 110 | set_effective_uid(self.uid).unwrap(); 111 | set_effective_gid(self.gid).unwrap(); 112 | } 113 | } 114 | 115 | /// Sets the **effective user** and the **effective group** for the current 116 | /// scope. 117 | /// 118 | /// Typically, trying to switch to any user or group other than the ones already 119 | /// running the process requires root privileges. 120 | /// 121 | /// **Use with care!** Possible security issues can happen, as Rust doesn't 122 | /// guarantee running the destructor! If in doubt run `drop()` method on the 123 | /// guard value manually! 124 | /// 125 | /// ### Examples 126 | /// 127 | /// ```no_run 128 | /// use rust_users::switch::switch_user_group; 129 | /// 130 | /// { 131 | /// let _guard = switch_user_group(1001, 1001); 132 | /// // current and effective user and group ids are 1001 133 | /// } 134 | /// // back to the old values 135 | /// ``` 136 | pub fn switch_user_group(uid: uid_t, gid: gid_t) -> IOResult { 137 | let current_state = SwitchUserGuard { 138 | uid: get_effective_uid(), 139 | gid: get_effective_gid(), 140 | }; 141 | 142 | try!(set_effective_gid(gid)); 143 | try!(set_effective_uid(uid)); 144 | Ok(current_state) 145 | } 146 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | pub use libc::{uid_t, gid_t, c_int}; 4 | 5 | use super::{User, Group}; 6 | 7 | /// Trait for producers of users. 8 | pub trait Users { 9 | 10 | /// Returns a User if one exists for the given user ID; otherwise, returns None. 11 | fn get_user_by_uid(&self, uid: uid_t) -> Option>; 12 | 13 | /// Returns a User if one exists for the given username; otherwise, returns None. 14 | fn get_user_by_name(&self, username: &str) -> Option>; 15 | 16 | /// Returns the user ID for the user running the process. 17 | fn get_current_uid(&self) -> uid_t; 18 | 19 | /// Returns the username of the user running the process. 20 | fn get_current_username(&self) -> Option>; 21 | 22 | /// Returns the effective user id. 23 | fn get_effective_uid(&self) -> uid_t; 24 | 25 | /// Returns the effective username. 26 | fn get_effective_username(&self) -> Option>; 27 | } 28 | 29 | /// Trait for producers of groups. 30 | pub trait Groups { 31 | 32 | /// Returns a Group object if one exists for the given group ID; otherwise, returns None. 33 | fn get_group_by_gid(&self, gid: gid_t) -> Option>; 34 | 35 | /// Returns a Group object if one exists for the given groupname; otherwise, returns None. 36 | fn get_group_by_name(&self, group_name: &str) -> Option>; 37 | 38 | /// Returns the group ID for the user running the process. 39 | fn get_current_gid(&self) -> gid_t; 40 | 41 | /// Returns the group name of the user running the process. 42 | fn get_current_groupname(&self) -> Option>; 43 | 44 | /// Returns the effective group id. 45 | fn get_effective_gid(&self) -> gid_t; 46 | 47 | /// Returns the effective group name. 48 | fn get_effective_groupname(&self) -> Option>; 49 | } --------------------------------------------------------------------------------