uzers-0.12.1/.cargo_vcs_info.json0000644000000001360000000000100122650ustar { "git": { "sha1": "a0db68306aa8c7bd25549b4589a00e0fcf9189b4" }, "path_in_vcs": "" }uzers-0.12.1/.gitignore000064400000000000000000000000241046102023000130410ustar 00000000000000/target /Cargo.lock uzers-0.12.1/CHANGELOG.md000064400000000000000000000024251046102023000126710ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. ## 0.12.1 - 2024-08-03 ## Bug Fixes * Fix disabling the cache feature by @viperML in https://github.com/rustadopt/uzers-rs/pull/22 ## Updates * Update serial_test requirement from ^2.0 to ^3.1 * Update env_logger requirement from 0.7 to 0.11 ## 0.12.0 - 2024-04-23 ### What's Changed - Added explicit references to os module in docs index - Add `AllUsers` and `AllGroups` - Test CI with nss_preload - Add GECOS field on UNIX systems - Add conventional commits workflow - Split general workflow into testing and linting ## [0.11.3] - 2023-09-11 ### Bug Fixes - Fix unaligned pointer in base::members function ### Features - Add haiku support ### CI - Bump actions/checkout from 3 to 4 ## [0.11.2] - 2023-08-25 This just updates the repository URL as we moved to the newly created rustadopt organization on Github. ## [0.11.1] - 2023-08-21 This is the first version of uzers which continues the unmaintained users v0.11.0. ### Bug Fixes - Fix group listing: don't add root every time ### Features - Allow iterating all groups in the system - Add redox and illumos support ### Refactor - Reformat entire code base ### Documentation - Rename to uzers - Add this changelog ### CI - Add Github workflows uzers-0.12.1/Cargo.lock0000644000000227400000000000100102450ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "env_filter", "log", ] [[package]] name = "futures" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "lock_api" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags", ] [[package]] name = "scc" version = "2.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a870e34715d5d59c8536040d4d4e7a41af44d527dc50237036ba4090db7996fc" dependencies = [ "sdd", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "177258b64c0faaa9ffd3c65cd3262c2bc7e2588dbbd9c1641d0346145c1bbda8" [[package]] name = "serial_test" version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" dependencies = [ "futures", "log", "once_cell", "parking_lot", "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "syn" version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "uzers" version = "0.12.1" dependencies = [ "env_logger", "libc", "log", "serial_test", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" uzers-0.12.1/Cargo.toml0000644000000035100000000000100102620ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "uzers" version = "0.12.1" authors = [ "Sandro-Alessio Gierens ", "Benjamin Sago ", ] build = false exclude = ["/.github"] autobins = false autoexamples = false autotests = false autobenches = false description = "Continuation of users, a library for accessing Unix users and groups" documentation = "https://docs.rs/uzers/" readme = "README.md" license = "MIT" repository = "https://github.com/rustadopt/uzers-rs" [lib] name = "uzers" path = "src/lib.rs" [[example]] name = "example" path = "examples/example.rs" [[example]] name = "groups" path = "examples/groups.rs" [[example]] name = "list" path = "examples/list.rs" [[example]] name = "mocking" path = "examples/mocking.rs" [[example]] name = "os" path = "examples/os.rs" [[example]] name = "switching" path = "examples/switching.rs" [[example]] name = "threading" path = "examples/threading.rs" [[test]] name = "groups" path = "tests/groups.rs" [[test]] name = "users" path = "tests/users.rs" [dependencies.libc] version = "0.2" [dependencies.log] version = "0.4" optional = true default-features = false [dev-dependencies.env_logger] version = "0.11" features = [] default-features = false [dev-dependencies.serial_test] version = "^3.1" [features] cache = [] default = [ "cache", "mock", "logging", ] logging = ["log"] mock = [] test-integration = [] uzers-0.12.1/Cargo.toml.orig000064400000000000000000000013501046102023000137430ustar 00000000000000[package] name = "uzers" description = "Continuation of users, a library for accessing Unix users and groups" authors = ["Sandro-Alessio Gierens ", "Benjamin Sago "] documentation = "https://docs.rs/uzers/" exclude = [ "/.github", ] license = "MIT" readme = "README.md" repository = "https://github.com/rustadopt/uzers-rs" version = "0.12.1" [features] default = ["cache", "mock", "logging"] cache = [] mock = [] logging = ["log"] test-integration = [] [dependencies.libc] version = "0.2" [dependencies.log] version = "0.4" optional = true default-features = false [dev-dependencies.env_logger] version = "0.11" default-features = false features = [] [dev-dependencies.serial_test] version = "^3.1" uzers-0.12.1/Justfile000064400000000000000000000017431046102023000125720ustar 00000000000000all: build test all-release: build-release test-release MIN_RUST := "1.31.0" # compiles the code build: cargo +{{MIN_RUST}} build cargo +stable build # compiles the code in release mode build-release: cargo +{{MIN_RUST}} build --release --verbose cargo +stable build --release --verbose # compiles the code with every combination of feature flags build-features: cargo +{{MIN_RUST}} hack build --feature-powerset cargo +stable hack build --feature-powerset # runs unit tests test: cargo +{{MIN_RUST}} test --all -- --quiet cargo +stable test --all -- --quiet # runs unit tests in release mode test-release: cargo +{{MIN_RUST}} test --all --release --verbose cargo +stable test --all --release --verbose # runs unit tests with every combination of feature flags test-features: cargo +{{MIN_RUST}} hack test --feature-powerset --lib -- --quiet cargo +stable hack test --feature-powerset --lib -- --quiet uzers-0.12.1/LICENCE000064400000000000000000000021301046102023000120360ustar 00000000000000MIT License Copyright (c) 2019 Benjamin Sago Copyright (c) 2023 Sandro-Alessio Gierens Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. uzers-0.12.1/README.md000064400000000000000000000150001046102023000123300ustar 00000000000000# uzers-rs Adoption and continuation of the unmaintained [ogham/rust-users](https://github.com/ogham/rust-users) crate. Big shout-out to its creator [Benjamin Sago](https://github.com/ogham). This is a library for accessing Unix users and groups. It supports getting the system users and groups, storing them in a cache, and creating your own mock tables. # Installation This crate works with [Cargo](https://crates.io). Add the following to your `Cargo.toml` dependencies section: ```toml [dependencies] uzers = "0.12" ``` # Usage 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. Furthermore, users can be the members of *groups*, which also have names and IDs. This functionality is exposed in libc, the C standard library, but as an unsafe Rust interface. This wrapper library provides a safe interface, using `User` and `Group` types and functions such as `get_user_by_id` instead of low-level pointers and strings. It also offers basic caching functionality. It does not (yet) offer *editing* functionality; the values returned are read-only. ## Users 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` with the user’s information. This function returns `None` when there is no user for that ID. A `User` has the following accessors: - **uid:** The user’s ID - **name:** The user’s name - **primary_group:** The ID of this user’s primary group Here is a complete example that prints out the current user’s name: ```rust use uzers::{get_user_by_uid, get_current_uid}; let user = get_user_by_uid(get_current_uid()).unwrap(); println!("Hello, {}!", user.name()); ``` This code assumes (with `unwrap()`) that the user hasn’t been deleted after the program has started running. 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. So always check the return values! There is also a `get_current_username` function, as it’s such a common operation that it deserves special treatment. ## Caching Despite the above warning, the users and groups database rarely changes. 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. 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. To introduce a cache, create a new `UsersCache` and call the same methods on it. For example: ```rust use uzers::{Users, Groups, UsersCache}; let mut cache = UsersCache::new(); let uid = cache.get_current_uid(); let user = cache.get_user_by_uid(uid).unwrap(); println!("Hello again, {}!", user.name()); ``` This cache is **only additive**: it’s not possible to drop it, or erase selected entries, as when the database may have been modified, it’s best to start entirely afresh. So to accomplish this, just start using a new `UsersCache`. ## Groups Finally, it’s possible to get groups in a similar manner. A `Group` has the following accessors: - **gid:** The group’s ID - **name:** The group’s name And again, a complete example: ```rust use uzers::{Users, Groups, UsersCache}; let mut cache = UsersCache::new(); let group = cache.get_group_by_name("admin").expect("No such group 'admin'!"); println!("The '{}' group has the ID {}", group.name(), group.gid()); ``` ## Logging The `logging` feature, which is on by default, uses the `log` crate to record all interactions with the operating system at Trace log level. ## Caveats 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! # Mockable users and groups 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. The `mock` module 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. ## Creating mock users The only thing a mock users table needs to know in advance is the UID of the current user. Aside from that, you can add users and groups with `add_user` and `add_group` to the table: ```rust use std::sync::Arc; use uzers::mock::{MockUsers, User, Group}; use uzers::os::unix::{UserExt, GroupExt}; let mut users = MockUsers::with_current_uid(1000); let bobbins = User::new(1000, "Bobbins", 1000).with_home_dir("/home/bobbins"); users.add_user(bobbins); users.add_group(Group::new(100, "funkyppl")); ``` The exports get re-exported into the mock module, for simpler `use` lines. ## Using mock users To set your program up to use either type of `Users` table, make your functions and structs accept a generic parameter that implements the `Users` trait. Then, you can pass in a value of either OS or Mock type. Here’s a complete example: ```rust use std::sync::Arc; use uzers::{Users, UsersCache, User}; use uzers::os::unix::UserExt; use uzers::mock::MockUsers; fn print_current_username(users: &mut U) { println!("Current user: {:?}", users.get_current_username()); } let mut users = MockUsers::with_current_uid(1001); users.add_user(User::new(1001, "fred", 101)); print_current_username(&mut users); let mut actual_users = UsersCache::new(); print_current_username(&mut actual_users); ``` uzers-0.12.1/examples/example.rs000064400000000000000000000013061046102023000146740ustar 00000000000000extern crate uzers; use uzers::{Groups, Users, UsersCache}; extern crate env_logger; fn main() { env_logger::init(); let cache = UsersCache::new(); let current_uid = cache.get_current_uid(); println!("Your UID is {}", current_uid); let you = cache .get_user_by_uid(current_uid) .expect("No entry for current user!"); println!("Your username is {}", you.name().to_string_lossy()); let primary_group = cache .get_group_by_gid(you.primary_group_id()) .expect("No entry for your primary group!"); println!( "Your primary group has ID {} and name {}", primary_group.gid(), primary_group.name().to_string_lossy() ); } uzers-0.12.1/examples/groups.rs000064400000000000000000000017111046102023000145600ustar 00000000000000extern crate uzers; use uzers::{get_user_groups, group_access_list, Group, Users, UsersCache}; extern crate env_logger; fn main() { env_logger::init(); let cache = UsersCache::new(); let user = cache .get_user_by_uid(cache.get_current_uid()) .expect("No current user?"); let mut groups: Vec = get_user_groups(user.name(), user.primary_group_id()).expect("No user groups?"); groups.sort_by(|a, b| a.gid().cmp(&b.gid())); for group in groups { println!( "Group {} has name {}", group.gid(), group.name().to_string_lossy() ); } let mut groups = group_access_list().expect("Group access list"); groups.sort_by(|a, b| a.gid().cmp(&b.gid())); println!("\nGroup access list:"); for group in groups { println!( "Group {} has name {}", group.gid(), group.name().to_string_lossy() ); } } uzers-0.12.1/examples/list.rs000064400000000000000000000006071046102023000142170ustar 00000000000000extern crate uzers; use uzers::{all_users, User}; extern crate env_logger; fn main() { env_logger::init(); let mut users: Vec = unsafe { all_users() }.collect(); users.sort_by(|a, b| a.uid().cmp(&b.uid())); for user in users { println!( "User {} has name {}", user.uid(), user.name().to_string_lossy() ); } } uzers-0.12.1/examples/mocking.rs000064400000000000000000000022671046102023000146770ustar 00000000000000extern crate uzers; use uzers::mock::MockUsers; use uzers::{AllGroups, Group, Groups, UsersCache, UsersSnapshot}; fn iter_aware(g: &G) { println!("All groups:"); for group in g.get_all_groups() { println!("- {group:?}"); } no_iter(g); } fn no_iter(g: &G) { let me = g.get_current_groupname(); let root = g.get_group_by_gid(0); println!("My group is {me:?}, gid 0 is {root:?}"); } fn main() { env_logger::init(); // UsersCache can only be used with `no_iter` println!("\n--- UsersCache ---"); no_iter(&UsersCache::new()); // UsersSnapshot can be used with both `no_iter` and `iter_aware` println!("\n--- UsersSnapshot: all groups ---"); no_iter(unsafe { &UsersSnapshot::new() }); println!("\n--- UsersSnapshot: primary groups of some system users ---"); iter_aware(unsafe { &UsersSnapshot::only_users(|u| u.uid() < 10) }); // MockUsers can be used with both `no_iter` and `iter_aware` println!("\n--- MockUsers ---"); let mut mock = MockUsers::with_current_uid(1000); mock.add_group(Group::new(1000, "fred")); mock.add_group(Group::new(0, "r00t")); iter_aware(&mock); } uzers-0.12.1/examples/os.rs000064400000000000000000000030211046102023000136560ustar 00000000000000extern crate uzers; use uzers::os::unix::{GroupExt, UserExt}; use uzers::{Groups, Users, UsersCache}; //use uzers::os::bsd::UserExt as BSDUserExt; extern crate env_logger; fn main() { env_logger::init(); let cache = UsersCache::new(); let current_uid = cache.get_current_uid(); println!("Your UID is {}", current_uid); let you = cache .get_user_by_uid(current_uid) .expect("No entry for current user!"); println!("Your username is {}", you.name().to_string_lossy()); println!("Your shell is {}", you.shell().display()); println!("Your home directory is {}", you.home_dir().display()); // The two fields below are only available on BSD systems. // Linux systems don’t have the fields in their `passwd` structs! //println!("Your password change timestamp is {}", you.password_change_time()); //println!("Your password expiry timestamp is {}", you.password_expire_time()); let primary_group = cache .get_group_by_gid(you.primary_group_id()) .expect("No entry for your primary group!"); println!( "Your primary group has ID {} and name {}", primary_group.gid(), primary_group.name().to_string_lossy() ); if primary_group.members().is_empty() { println!("There are no other members of that group."); } else { for username in primary_group.members() { println!( "User {} is also a member of that group.", username.to_string_lossy() ); } } } uzers-0.12.1/examples/switching.rs000064400000000000000000000014021046102023000152350ustar 00000000000000extern crate uzers; use std::mem::drop; use uzers::switch::switch_user_group; use uzers::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid, uid_t}; extern crate env_logger; const SAMPLE_ID: uid_t = 502; fn main() { env_logger::init(); println!("\nInitial values:"); print_state(); println!("\nValues after switching:"); let guard = switch_user_group(SAMPLE_ID, SAMPLE_ID); print_state(); println!("\nValues after switching back:"); drop(guard); print_state(); } fn print_state() { println!( "Current UID/GID: {}/{}", get_current_uid(), get_current_gid() ); println!( "Effective UID/GID: {}/{}", get_effective_uid(), get_effective_gid() ); } uzers-0.12.1/examples/threading.rs000064400000000000000000000044731046102023000152160ustar 00000000000000//! This example demonstrates how to use a `UsersCache` cache in a //! multi-threaded situation. The cache uses `RefCell`s internally, so it //! is distinctly not thread-safe. Instead, you’ll need to place it within //! some kind of lock in order to have threads access it one-at-a-time. //! //! It queries all the users it can find in the range 500..510. This is the //! default uid range on my Apple laptop -- Linux starts counting from 1000, //! but I can’t include both in the range! It spawns one thread per user to //! query, with each thread accessing the same cache. //! //! Then, afterwards, it retrieves references to the users that had been //! cached earlier. // For extra fun, try uncommenting some of the lines of code below, making // the code try to access the users cache *without* a Mutex, and see it // spew compile errors at you. extern crate uzers; use uzers::{uid_t, Users, UsersCache}; extern crate env_logger; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; const LO: uid_t = 500; const HI: uid_t = 510; fn main() { env_logger::init(); // For thread-safely, our users cache needs to be within a Mutex, so // only one thread can access it once. This Mutex needs to be within an // Arc, so multiple threads can access the Mutex. let cache = Arc::new(Mutex::new(UsersCache::new())); // let cache = UsersCache::empty_cache(); // Loop over the range and query all the users in the range. Although we // could use the `&User` values returned, we just ignore them. for uid in LO..HI { let cache = Arc::clone(&cache); thread::spawn(move || { let cache = cache.lock().unwrap(); // Unlock the mutex let _ = cache.get_user_by_uid(uid); // Query our users cache! }); } // Wait for all the threads to finish. thread::sleep(Duration::from_millis(100)); // Loop over the same range and print out all the users we find. // These users will be retrieved from the cache. for uid in LO..HI { let cache = cache.lock().unwrap(); // Re-unlock the mutex if let Some(u) = cache.get_user_by_uid(uid) { // Re-query our cache! println!("User #{} is {}", u.uid(), u.name().to_string_lossy()) } else { println!("User #{} does not exist", uid); } } } uzers-0.12.1/src/base.rs000064400000000000000000001266741046102023000131440ustar 00000000000000//! Integration with the C library’s users and groups. //! //! This module uses `extern` functions and types from `libc` that integrate //! with the system’s C library, which integrates with the OS itself to get user //! and group information. It’s where the “core” user handling is done. //! //! //! ## Name encoding rules //! //! Under Unix, usernames and group names are considered to be //! null-terminated, UTF-8 strings. These are `CString`s in Rust, although in //! this library, they are just `String` values. Why? //! //! The reason is that any user or group values with invalid `CString` data //! can instead just be assumed to not exist: //! //! - If you try to search for a user with a null character in their name, //! such a user could not exist anyway — so it’s OK to return `None`. //! - If the OS returns user information with a null character in a field, //! then that field will just be truncated instead, which is valid behaviour //! for a `CString`. //! //! The downside is that we use `from_utf8_lossy` instead, which has a small //! runtime penalty when it calculates and scans the length of the string for //! invalid characters. However, this should not be a problem when dealing with //! usernames of a few bytes each. //! //! In short, if you want to check for null characters in user fields, your //! best bet is to check for them yourself before passing strings into any //! functions. use std::ffi::{CStr, CString, OsStr, OsString}; use std::fmt; use std::io; use std::mem; use std::os::unix::ffi::OsStrExt; use std::ptr; use std::sync::Arc; #[cfg(feature = "logging")] extern crate log; #[cfg(feature = "logging")] use self::log::trace; use libc::group as c_group; use libc::passwd as c_passwd; use libc::{c_char, c_int, gid_t, uid_t}; /// Information about a particular user. /// /// For more information, see the [module documentation](index.html). #[derive(Clone)] pub struct User { uid: uid_t, primary_group: gid_t, extras: os::UserExtras, pub(crate) name_arc: Arc, } impl User { /// Create a new `User` with the given user ID, name, and primary /// group ID, with the rest of the fields filled with dummy values. /// /// This method does not actually create a new user on the system — it /// should only be used for comparing users in tests. /// /// # Examples /// /// ``` /// use uzers::User; /// /// let user = User::new(501, "stevedore", 100); /// ``` pub fn new + ?Sized>(uid: uid_t, name: &S, primary_group: gid_t) -> Self { let name_arc = Arc::from(name.as_ref()); let extras = os::UserExtras::default(); Self { uid, name_arc, primary_group, extras, } } /// Returns this user’s ID. /// /// # Examples /// /// ``` /// use uzers::User; /// /// let user = User::new(501, "stevedore", 100); /// assert_eq!(user.uid(), 501); /// ``` pub fn uid(&self) -> uid_t { self.uid } /// Returns this user’s name. /// /// # Examples /// /// ``` /// use std::ffi::OsStr; /// use uzers::User; /// /// let user = User::new(501, "stevedore", 100); /// assert_eq!(user.name(), OsStr::new("stevedore")); /// ``` pub fn name(&self) -> &OsStr { &self.name_arc } /// Returns the ID of this user’s primary group. /// /// # Examples /// /// ``` /// use uzers::User; /// /// let user = User::new(501, "stevedore", 100); /// assert_eq!(user.primary_group_id(), 100); /// ``` pub fn primary_group_id(&self) -> gid_t { self.primary_group } /// Returns a list of groups this user is a member of. This involves /// loading the groups list, as it is _not_ contained within this type. /// /// # libc functions used /// /// - [`getgrouplist`](https://docs.rs/libc/*/libc/fn.getgrouplist.html) /// /// # Examples /// /// ```no_run /// use uzers::User; /// /// let user = User::new(501, "stevedore", 100); /// for group in user.groups().expect("User not found") { /// println!("User is in group: {:?}", group.name()); /// } /// ``` pub fn groups(&self) -> Option> { get_user_groups(self.name(), self.primary_group_id()) } } impl fmt::Debug for User { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if f.alternate() { f.debug_struct("User") .field("uid", &self.uid) .field("name_arc", &self.name_arc) .field("primary_group", &self.primary_group) .field("extras", &self.extras) .finish() } else { write!(f, "User({}, {})", self.uid(), self.name().to_string_lossy()) } } } /// Information about a particular group. /// /// For more information, see the [module documentation](index.html). #[derive(Clone)] pub struct Group { gid: gid_t, extras: os::GroupExtras, pub(crate) name_arc: Arc, } impl Group { /// Create a new `Group` with the given group ID and name, with the /// rest of the fields filled in with dummy values. /// /// This method does not actually create a new group on the system — it /// should only be used for comparing groups in tests. /// /// # Examples /// /// ``` /// use uzers::Group; /// /// let group = Group::new(102, "database"); /// ``` pub fn new + ?Sized>(gid: gid_t, name: &S) -> Self { let name_arc = Arc::from(name.as_ref()); let extras = os::GroupExtras::default(); Self { gid, name_arc, extras, } } /// Returns this group’s ID. /// /// # Examples /// /// ``` /// use uzers::Group; /// /// let group = Group::new(102, "database"); /// assert_eq!(group.gid(), 102); /// ``` pub fn gid(&self) -> gid_t { self.gid } /// Returns this group’s name. /// /// # Examples /// /// ``` /// use std::ffi::OsStr; /// use uzers::Group; /// /// let group = Group::new(102, "database"); /// assert_eq!(group.name(), OsStr::new("database")); /// ``` pub fn name(&self) -> &OsStr { &self.name_arc } } impl fmt::Debug for Group { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if f.alternate() { f.debug_struct("Group") .field("gid", &self.gid) .field("name_arc", &self.name_arc) .field("extras", &self.extras) .finish() } else { write!( f, "Group({}, {})", self.gid(), self.name().to_string_lossy() ) } } } /// Reads data from a `*char` field in `c_passwd` or `g_group`. The return /// type will be an `Arc` if the text is meant to be shared in a cache, /// or a plain `OsString` if it’s not. /// /// The underlying buffer is managed by the C library, not by us, so we *need* /// to move data out of it before the next user gets read. unsafe fn from_raw_buf<'a, T>(p: *const c_char) -> T where T: From<&'a OsStr>, { T::from(OsStr::from_bytes(CStr::from_ptr(p).to_bytes())) } /// Reads data from the `c_passwd` and returns it as a `User`. unsafe fn passwd_to_user(passwd: c_passwd) -> User { #[cfg(feature = "logging")] trace!("Loading user with uid {}", passwd.pw_uid); let name = from_raw_buf(passwd.pw_name); User { uid: passwd.pw_uid, name_arc: name, primary_group: passwd.pw_gid, extras: os::UserExtras::from_passwd(passwd), } } /// Reads data from the `c_group` and returns it as a `Group`. unsafe fn struct_to_group(group: c_group) -> Group { #[cfg(feature = "logging")] trace!("Loading group with gid {}", group.gr_gid); let name = from_raw_buf(group.gr_name); Group { gid: group.gr_gid, name_arc: name, extras: os::GroupExtras::from_struct(group), } } /// Expand a list of group members to a vector of strings. /// /// The list of members is, in true C fashion, a pointer to a pointer of /// characters, terminated by a null pointer. We check `members[0]`, then /// `members[1]`, and so on, until that null pointer is reached. It doesn’t /// specify whether we should expect a null pointer or a pointer to a null /// pointer, so we check for both here! unsafe fn members(groups: *mut *mut c_char) -> Vec { let mut members = Vec::new(); for i in 0.. { let username = groups.offset(i); if username.is_null() || username.read_unaligned().is_null() { break; } else { members.push(from_raw_buf(username.read_unaligned())); } } members } /// Searches for a `User` with the given ID in the system’s user database. /// Returns it if one is found, otherwise returns `None`. /// /// # libc functions used /// /// - [`getpwuid_r`](https://docs.rs/libc/*/libc/fn.getpwuid_r.html) /// /// # Examples /// /// ``` /// use uzers::get_user_by_uid; /// /// match get_user_by_uid(501) { /// Some(user) => println!("Found user {:?}", user.name()), /// None => println!("User not found"), /// } /// ``` pub fn get_user_by_uid(uid: uid_t) -> Option { let mut passwd = unsafe { mem::zeroed::() }; let mut buf = vec![0; 2048]; let mut result = ptr::null_mut::(); #[cfg(feature = "logging")] trace!("Running getpwuid_r for user #{}", uid); loop { let r = unsafe { libc::getpwuid_r(uid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result) }; if r != libc::ERANGE { break; } let newsize = buf.len().checked_mul(2)?; buf.resize(newsize, 0); } if result.is_null() { // There is no such user, or an error has occurred. // errno gets set if there’s an error. return None; } if result != &mut passwd { // The result of getpwuid_r should be its input passwd. return None; } let user = unsafe { passwd_to_user(result.read()) }; Some(user) } /// Searches for a `User` with the given username in the system’s user database. /// Returns it if one is found, otherwise returns `None`. /// /// # libc functions used /// /// - [`getpwnam_r`](https://docs.rs/libc/*/libc/fn.getpwnam_r.html) /// /// # Examples /// /// ``` /// use uzers::get_user_by_name; /// /// match get_user_by_name("stevedore") { /// Some(user) => println!("Found user #{}", user.uid()), /// None => println!("User not found"), /// } /// ``` pub fn get_user_by_name + ?Sized>(username: &S) -> Option { let username = match CString::new(username.as_ref().as_bytes()) { Ok(u) => u, Err(_) => { // The username that was passed in contained a null character, // which will match no usernames. return None; } }; let mut passwd = unsafe { mem::zeroed::() }; let mut buf = vec![0; 2048]; let mut result = ptr::null_mut::(); #[cfg(feature = "logging")] trace!("Running getpwnam_r for user {:?}", username.as_ref()); loop { let r = unsafe { libc::getpwnam_r( username.as_ptr(), &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result, ) }; if r != libc::ERANGE { break; } let newsize = buf.len().checked_mul(2)?; buf.resize(newsize, 0); } if result.is_null() { // There is no such user, or an error has occurred. // errno gets set if there’s an error. return None; } if result != &mut passwd { // The result of getpwnam_r should be its input passwd. return None; } let user = unsafe { passwd_to_user(result.read()) }; Some(user) } /// Searches for a `Group` with the given ID in the system’s group database. /// Returns it if one is found, otherwise returns `None`. /// /// # libc functions used /// /// - [`getgrgid_r`](https://docs.rs/libc/*/libc/fn.getgrgid_r.html) /// /// # Examples /// /// ``` /// use uzers::get_group_by_gid; /// /// match get_group_by_gid(102) { /// Some(group) => println!("Found group {:?}", group.name()), /// None => println!("Group not found"), /// } /// ``` pub fn get_group_by_gid(gid: gid_t) -> Option { let mut passwd = unsafe { mem::zeroed::() }; let mut buf = vec![0; 2048]; let mut result = ptr::null_mut::(); #[cfg(feature = "logging")] trace!("Running getgruid_r for group #{}", gid); loop { let r = unsafe { libc::getgrgid_r(gid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result) }; if r != libc::ERANGE { break; } let newsize = buf.len().checked_mul(2)?; buf.resize(newsize, 0); } if result.is_null() { // There is no such group, or an error has occurred. // errno gets set if there’s an error. return None; } if result != &mut passwd { // The result of getgrgid_r should be its input struct. return None; } let group = unsafe { struct_to_group(result.read()) }; Some(group) } /// Searches for a `Group` with the given group name in the system’s group database. /// Returns it if one is found, otherwise returns `None`. /// /// # libc functions used /// /// - [`getgrnam_r`](https://docs.rs/libc/*/libc/fn.getgrnam_r.html) /// /// # Examples /// /// ``` /// use uzers::get_group_by_name; /// /// match get_group_by_name("db-access") { /// Some(group) => println!("Found group #{}", group.gid()), /// None => println!("Group not found"), /// } /// ``` pub fn get_group_by_name + ?Sized>(groupname: &S) -> Option { let groupname = match CString::new(groupname.as_ref().as_bytes()) { Ok(u) => u, Err(_) => { // The groupname that was passed in contained a null character, // which will match no usernames. return None; } }; let mut group = unsafe { mem::zeroed::() }; let mut buf = vec![0; 2048]; let mut result = ptr::null_mut::(); #[cfg(feature = "logging")] trace!("Running getgrnam_r for group {:?}", groupname.as_ref()); loop { let r = unsafe { libc::getgrnam_r( groupname.as_ptr(), &mut group, buf.as_mut_ptr(), buf.len(), &mut result, ) }; if r != libc::ERANGE { break; } let newsize = buf.len().checked_mul(2)?; buf.resize(newsize, 0); } if result.is_null() { // There is no such group, or an error has occurred. // errno gets set if there’s an error. return None; } if result != &mut group { // The result of getgrnam_r should be its input struct. return None; } let group = unsafe { struct_to_group(result.read()) }; Some(group) } /// Returns the user ID for the user running the process. /// /// # libc functions used /// /// - [`getuid`](https://docs.rs/libc/*/libc/fn.getuid.html) /// /// # Examples /// /// ``` /// use uzers::get_current_uid; /// /// println!("The ID of the current user is {}", get_current_uid()); /// ``` pub fn get_current_uid() -> uid_t { #[cfg(feature = "logging")] trace!("Running getuid"); unsafe { libc::getuid() } } /// Returns the username of the user running the process. /// /// This function to return `None` if the current user does not exist, which /// could happed if they were deleted after the program started running. /// /// # libc functions used /// /// - [`getuid`](https://docs.rs/libc/*/libc/fn.getuid.html) /// - [`getpwuid_r`](https://docs.rs/libc/*/libc/fn.getpwuid_r.html) /// /// # Examples /// /// ``` /// use uzers::get_current_username; /// /// match get_current_username() { /// Some(uname) => println!("Running as user with name {:?}", uname), /// None => println!("The current user does not exist!"), /// } /// ``` pub fn get_current_username() -> Option { let uid = get_current_uid(); let user = get_user_by_uid(uid)?; Some(OsString::from(&*user.name_arc)) } /// Returns the user ID for the effective user running the process. /// /// # libc functions used /// /// - [`geteuid`](https://docs.rs/libc/*/libc/fn.geteuid.html) /// /// # Examples /// /// ``` /// use uzers::get_effective_uid; /// /// println!("The ID of the effective user is {}", get_effective_uid()); /// ``` pub fn get_effective_uid() -> uid_t { #[cfg(feature = "logging")] trace!("Running geteuid"); unsafe { libc::geteuid() } } /// Returns the username of the effective user running the process. /// /// # libc functions used /// /// - [`geteuid`](https://docs.rs/libc/*/libc/fn.geteuid.html) /// - [`getpwuid_r`](https://docs.rs/libc/*/libc/fn.getpwuid_r.html) /// /// # Examples /// /// ``` /// use uzers::get_effective_username; /// /// match get_effective_username() { /// Some(uname) => println!("Running as effective user with name {:?}", uname), /// None => println!("The effective user does not exist!"), /// } /// ``` pub fn get_effective_username() -> Option { let uid = get_effective_uid(); let user = get_user_by_uid(uid)?; Some(OsString::from(&*user.name_arc)) } /// Returns the group ID for the user running the process. /// /// # libc functions used /// /// - [`getgid`](https://docs.rs/libc/*/libc/fn.getgid.html) /// /// # Examples /// /// ``` /// use uzers::get_current_gid; /// /// println!("The ID of the current group is {}", get_current_gid()); /// ``` pub fn get_current_gid() -> gid_t { #[cfg(feature = "logging")] trace!("Running getgid"); unsafe { libc::getgid() } } /// Returns the groupname of the user running the process. /// /// # libc functions used /// /// - [`getgid`](https://docs.rs/libc/*/libc/fn.getgid.html) /// - [`getgrgid`](https://docs.rs/libc/*/libc/fn.getgrgid.html) /// /// # Examples /// /// ``` /// use uzers::get_current_groupname; /// /// match get_current_groupname() { /// Some(gname) => println!("Running as group with name {:?}", gname), /// None => println!("The current group does not exist!"), /// } /// ``` pub fn get_current_groupname() -> Option { let gid = get_current_gid(); let group = get_group_by_gid(gid)?; Some(OsString::from(&*group.name_arc)) } /// Returns the group ID for the effective user running the process. /// /// # libc functions used /// /// - [`getegid`](https://docs.rs/libc/*/libc/fn.getegid.html) /// /// # Examples /// /// ``` /// use uzers::get_effective_gid; /// /// println!("The ID of the effective group is {}", get_effective_gid()); /// ``` pub fn get_effective_gid() -> gid_t { #[cfg(feature = "logging")] trace!("Running getegid"); unsafe { libc::getegid() } } /// Returns the groupname of the effective user running the process. /// /// # libc functions used /// /// - [`getegid`](https://docs.rs/libc/*/libc/fn.getegid.html) /// - [`getgrgid`](https://docs.rs/libc/*/libc/fn.getgrgid.html) /// /// # Examples /// /// ``` /// use uzers::get_effective_groupname; /// /// match get_effective_groupname() { /// Some(gname) => println!("Running as effective group with name {:?}", gname), /// None => println!("The effective group does not exist!"), /// } /// ``` pub fn get_effective_groupname() -> Option { let gid = get_effective_gid(); let group = get_group_by_gid(gid)?; Some(OsString::from(&*group.name_arc)) } /// Returns the group access list for the current process. /// /// # libc functions used /// /// - [`getgroups`](https://docs.rs/libc/*/libc/fn.getgroups.html) /// /// # Errors /// /// This function will return `Err` when an I/O error occurs during the /// `getgroups` call. /// /// # Examples /// /// ```no_run /// use uzers::group_access_list; /// /// for group in group_access_list().expect("Error looking up groups") { /// println!("Process can access group #{} ({:?})", group.gid(), group.name()); /// } /// ``` pub fn group_access_list() -> io::Result> { let mut buff: Vec = vec![0; 1024]; #[cfg(feature = "logging")] trace!("Running getgroups"); let res = unsafe { libc::getgroups(1024, buff.as_mut_ptr()) }; if res < 0 { Err(io::Error::last_os_error()) } else { buff.truncate(res as usize); buff.sort_unstable(); buff.dedup(); let groups = buff .into_iter() .filter_map(get_group_by_gid) .collect::>(); Ok(groups) } } /// Returns groups for a provided user name and primary group id. /// /// # libc functions used /// /// - [`getgrouplist`](https://docs.rs/libc/*/libc/fn.getgrouplist.html) /// /// # Examples /// /// ```no_run /// use uzers::get_user_groups; /// /// for group in get_user_groups("stevedore", 1001).expect("Error looking up groups") { /// println!("User is a member of group #{} ({:?})", group.gid(), group.name()); /// } /// ``` pub fn get_user_groups + ?Sized>(username: &S, gid: gid_t) -> Option> { // MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons #[cfg(all(unix, target_os = "macos"))] let mut buff: Vec = vec![0; 1024]; #[cfg(all(unix, not(target_os = "macos")))] let mut buff: Vec = vec![0; 1024]; let name = CString::new(username.as_ref().as_bytes()).unwrap(); let mut count = buff.len() as c_int; #[cfg(feature = "logging")] trace!( "Running getgrouplist for user {:?} and group #{}", username.as_ref(), gid ); // MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons #[cfg(all(unix, target_os = "macos"))] let res = unsafe { libc::getgrouplist(name.as_ptr(), gid as i32, buff.as_mut_ptr(), &mut count) }; #[cfg(all(unix, not(target_os = "macos")))] let res = unsafe { libc::getgrouplist(name.as_ptr(), gid, buff.as_mut_ptr(), &mut count) }; if res < 0 { None } else { buff.truncate(count as usize); buff.sort_unstable(); buff.dedup(); // allow trivial cast: on macos i is i32, on linux it's already gid_t #[allow(trivial_numeric_casts)] buff.into_iter() .filter_map(|i| get_group_by_gid(i as gid_t)) .collect::>() .into() } } /// An iterator over every user present on the system. struct AllUsers; /// Creates a new iterator over every user present on the system. /// /// # libc functions used /// /// - [`getpwent`](https://docs.rs/libc/*/libc/fn.getpwent.html) /// - [`setpwent`](https://docs.rs/libc/*/libc/fn.setpwent.html) /// - [`endpwent`](https://docs.rs/libc/*/libc/fn.endpwent.html) /// /// # Safety /// /// This constructor is marked as `unsafe`, which is odd for a crate /// that’s meant to be a safe interface. It *has* to be unsafe because /// we cannot guarantee that the underlying C functions, /// `getpwent`/`setpwent`/`endpwent` that iterate over the system’s /// `passwd` entries, are called in a thread-safe manner. /// /// These functions [modify a global /// state](http://man7.org/linux/man-pages/man3/getpwent.3.html#ATTRIBUTES), /// and if any are used at the same time, the state could be reset, /// resulting in a data race. We cannot even place it behind an internal /// `Mutex`, as there is nothing stopping another `extern` function /// definition from calling it! /// /// So to iterate all users, construct the iterator inside an `unsafe` /// block, then make sure to not make a new instance of it until /// iteration is over. /// /// # Examples /// /// ``` /// use uzers::all_users; /// /// let iter = unsafe { all_users() }; /// for user in iter { /// println!("User #{} ({:?})", user.uid(), user.name()); /// } /// ``` pub unsafe fn all_users() -> impl Iterator { #[cfg(feature = "logging")] trace!("Running setpwent"); #[cfg(not(target_os = "android"))] libc::setpwent(); AllUsers } impl Drop for AllUsers { #[cfg(target_os = "android")] fn drop(&mut self) { // nothing to do here } #[cfg(not(target_os = "android"))] fn drop(&mut self) { #[cfg(feature = "logging")] trace!("Running endpwent"); unsafe { libc::endpwent() }; } } impl Iterator for AllUsers { type Item = User; #[cfg(target_os = "android")] fn next(&mut self) -> Option { None } #[cfg(not(target_os = "android"))] fn next(&mut self) -> Option { #[cfg(feature = "logging")] trace!("Running getpwent"); let result = unsafe { libc::getpwent() }; if result.is_null() { None } else { let user = unsafe { passwd_to_user(result.read()) }; Some(user) } } } /// An iterator over every group present on the system. struct AllGroups; /// Creates a new iterator over every group present on the system. /// /// # libc functions used /// /// - [`getgrent`](https://docs.rs/libc/*/libc/fn.getgrent.html) /// - [`setgrent`](https://docs.rs/libc/*/libc/fn.setgrent.html) /// - [`endgrent`](https://docs.rs/libc/*/libc/fn.endgrent.html) /// /// # Safety /// /// This constructor is marked as `unsafe`, which is odd for a crate /// that’s meant to be a safe interface. It *has* to be unsafe because /// we cannot guarantee that the underlying C functions, /// `getgrent`/`setgrent`/`endgrent` that iterate over the system’s /// `group` entries, are called in a thread-safe manner. /// /// These functions [modify a global /// state](http://man7.org/linux/man-pages/man3/getgrent.3.html#ATTRIBUTES), /// and if any are used at the same time, the state could be reset, /// resulting in a data race. We cannot even place it behind an internal /// `Mutex`, as there is nothing stopping another `extern` function /// definition from calling it! /// /// So to iterate all groups, construct the iterator inside an `unsafe` /// block, then make sure to not make a new instance of it until /// iteration is over. /// /// # Examples /// /// ``` /// use uzers::all_groups; /// /// let iter = unsafe { all_groups() }; /// for group in iter { /// println!("Group #{} ({:?})", group.gid(), group.name()); /// } /// ``` pub unsafe fn all_groups() -> impl Iterator { #[cfg(feature = "logging")] trace!("Running setgrent"); #[cfg(not(target_os = "android"))] libc::setgrent(); AllGroups } impl Drop for AllGroups { #[cfg(target_os = "android")] fn drop(&mut self) { // nothing to do here } #[cfg(not(target_os = "android"))] fn drop(&mut self) { #[cfg(feature = "logging")] trace!("Running endgrent"); unsafe { libc::endgrent() }; } } impl Iterator for AllGroups { type Item = Group; #[cfg(target_os = "android")] fn next(&mut self) -> Option { None } #[cfg(not(target_os = "android"))] fn next(&mut self) -> Option { #[cfg(feature = "logging")] trace!("Running getgrent"); let result = unsafe { libc::getgrent() }; if result.is_null() { None } else { let group = unsafe { struct_to_group(result.read()) }; Some(group) } } } /// OS-specific extensions to users and groups. /// /// Every OS has a different idea of what data a user or a group comes with. /// Although they all provide a *username*, some OS’ users have an *actual name* /// too, or a set of permissions or directories or timestamps associated with /// them. /// /// This module provides extension traits for users and groups that allow /// implementors of this library to access this data *as long as a trait is /// available*, which requires the OS they’re using to support this data. /// /// It’s the same method taken by `Metadata` in the standard Rust library, /// which has a few cross-platform fields and many more OS-specific fields: /// traits in `std::os` provides access to any data that is not guaranteed to /// be there in the actual struct. pub mod os { /// Extensions to users and groups for Unix platforms. /// /// Although the `passwd` struct is common among Unix systems, its actual /// format can vary. See the definitions in the `base` module to check which /// fields are actually present. #[cfg(any( target_os = "linux", target_os = "android", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd", target_os = "redox", target_os = "solaris", target_os = "illumos", target_os = "haiku" ))] pub mod unix { use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use super::super::{c_group, c_passwd, from_raw_buf, members, Group}; /// Unix-specific extensions for `User`s. pub trait UserExt { /// Returns a path to this user’s home directory. fn home_dir(&self) -> &Path; /// Sets this user value’s home directory to the given string. /// Can be used to construct test users, which by default come with a /// dummy home directory string. fn with_home_dir + ?Sized>(self, home_dir: &S) -> Self; /// Returns a path to this user’s shell. fn shell(&self) -> &Path; /// Sets this user’s shell path to the given string. /// Can be used to construct test users, which by default come with a /// dummy shell field. fn with_shell + ?Sized>(self, shell: &S) -> Self; /// Returns the user’s encrypted password. fn password(&self) -> &OsStr; /// Sets this user’s password to the given string. /// Can be used to construct tests users, which by default come with a /// dummy password field. fn with_password + ?Sized>(self, password: &S) -> Self; /// Returns the user's GECOS comment fn gecos(&self) -> &OsStr; /// Sets this user's GECOS comment to the given string. /// Can be used to construct tests users, which by default come with a /// dummy GECOS field. fn with_gecos + ?Sized>(self, gecos: &S) -> Self; } /// Unix-specific extensions for `Group`s. pub trait GroupExt { /// Returns a slice of the list of users that are in this group as /// their non-primary group. fn members(&self) -> &[OsString]; /// Adds a new member to this group. fn add_member + ?Sized>(self, name: &S) -> Self; } /// Unix-specific fields for `User`s. #[derive(Clone, Debug)] pub struct UserExtras { /// The path to the user’s home directory. pub home_dir: PathBuf, /// The path to the user’s shell. pub shell: PathBuf, /// The user’s encrypted password. pub password: OsString, /// The user's GECOS comment pub gecos: OsString, } impl Default for UserExtras { fn default() -> Self { Self { home_dir: "/var/empty".into(), shell: "/bin/false".into(), password: "*".into(), gecos: "".into(), } } } impl UserExtras { /// Extract the OS-specific fields from the C `passwd` struct that /// we just read. pub(crate) unsafe fn from_passwd(passwd: c_passwd) -> Self { #[cfg(target_os = "android")] { Default::default() } #[cfg(not(target_os = "android"))] { let home_dir = from_raw_buf::(passwd.pw_dir).into(); let shell = from_raw_buf::(passwd.pw_shell).into(); let password = from_raw_buf::(passwd.pw_passwd); let gecos = from_raw_buf::(passwd.pw_gecos); Self { home_dir, shell, password, gecos, } } } } #[cfg(any( target_os = "linux", target_os = "android", target_os = "redox", target_os = "solaris", target_os = "illumos", target_os = "haiku" ))] use super::super::User; #[cfg(any( target_os = "linux", target_os = "android", target_os = "redox", target_os = "solaris", target_os = "illumos", target_os = "haiku" ))] impl UserExt for User { fn home_dir(&self) -> &Path { Path::new(&self.extras.home_dir) } fn with_home_dir + ?Sized>(mut self, home_dir: &S) -> Self { self.extras.home_dir = home_dir.into(); self } fn shell(&self) -> &Path { Path::new(&self.extras.shell) } fn with_shell + ?Sized>(mut self, shell: &S) -> Self { self.extras.shell = shell.into(); self } fn password(&self) -> &OsStr { &self.extras.password } fn with_password + ?Sized>(mut self, password: &S) -> Self { self.extras.password = password.into(); self } fn gecos(&self) -> &OsStr { &self.extras.gecos } fn with_gecos + ?Sized>(mut self, gecos: &S) -> Self { self.extras.gecos = gecos.into(); self } } /// Unix-specific fields for `Group`s. #[derive(Clone, Default, Debug)] pub struct GroupExtras { /// Vector of usernames that are members of this group. pub members: Vec, } impl GroupExtras { /// Extract the OS-specific fields from the C `group` struct that /// we just read. pub(crate) unsafe fn from_struct(group: c_group) -> Self { Self { members: members(group.gr_mem), } } } impl GroupExt for Group { fn members(&self) -> &[OsString] { &self.extras.members } fn add_member + ?Sized>(mut self, member: &S) -> Self { self.extras.members.push(member.into()); self } } } /// Extensions to users and groups for BSD platforms. /// /// These platforms have `change` and `expire` fields in their `passwd` /// C structs. #[cfg(any( target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd" ))] pub mod bsd { use super::super::{c_passwd, User}; use libc::time_t; use std::ffi::OsStr; use std::path::Path; /// BSD-specific fields for `User`s. #[derive(Clone, Debug)] pub struct UserExtras { /// Fields specific to Unix, rather than just BSD. (This struct is /// a superset, so it has to have all the other fields in it, too). pub extras: super::unix::UserExtras, /// Password change time. pub change: time_t, /// Password expiry time. pub expire: time_t, } impl UserExtras { /// Extract the OS-specific fields from the C `passwd` struct that /// we just read. pub(crate) unsafe fn from_passwd(passwd: c_passwd) -> Self { Self { change: passwd.pw_change, expire: passwd.pw_expire, extras: super::unix::UserExtras::from_passwd(passwd), } } } impl super::unix::UserExt for User { fn home_dir(&self) -> &Path { Path::new(&self.extras.extras.home_dir) } fn with_home_dir + ?Sized>(mut self, home_dir: &S) -> Self { self.extras.extras.home_dir = home_dir.into(); self } fn shell(&self) -> &Path { Path::new(&self.extras.extras.shell) } fn with_shell + ?Sized>(mut self, shell: &S) -> Self { self.extras.extras.shell = shell.into(); self } fn password(&self) -> &OsStr { &self.extras.extras.password } fn with_password + ?Sized>(mut self, password: &S) -> Self { self.extras.extras.password = password.into(); self } fn gecos(&self) -> &OsStr { &self.extras.extras.gecos } fn with_gecos + ?Sized>(mut self, gecos: &S) -> Self { self.extras.extras.gecos = gecos.into(); self } } /// BSD-specific accessors for `User`s. pub trait UserExt { /// Returns this user’s password change timestamp. fn password_change_time(&self) -> time_t; /// Returns this user’s password expiry timestamp. fn password_expire_time(&self) -> time_t; } impl UserExt for User { fn password_change_time(&self) -> time_t { self.extras.change } fn password_expire_time(&self) -> time_t { self.extras.expire } } impl Default for UserExtras { fn default() -> Self { Self { extras: super::unix::UserExtras::default(), change: 0, expire: 0, } } } } /// Any extra fields on a `User` specific to the current platform. #[cfg(any( target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd" ))] pub type UserExtras = bsd::UserExtras; /// Any extra fields on a `User` specific to the current platform. #[cfg(any( target_os = "linux", target_os = "android", target_os = "redox", target_os = "solaris", target_os = "illumos", target_os = "haiku" ))] pub type UserExtras = unix::UserExtras; /// Any extra fields on a `Group` specific to the current platform. #[cfg(any( target_os = "linux", target_os = "android", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd", target_os = "redox", target_os = "solaris", target_os = "illumos", target_os = "haiku" ))] pub type GroupExtras = unix::GroupExtras; } #[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() { use base::os::unix::UserExt; 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={:?}, PASSWD={:?}, GECOS={:?}", user.home_dir(), user.shell(), user.password(), user.gecos() ); } #[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 user_get_groups() { let uid = get_current_uid(); let user = get_user_by_uid(uid).unwrap(); let groups = user.groups().unwrap(); println!("Groups: {:?}", groups); assert!(groups.len() > 0); } #[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()); } } #[cfg(all(test, feature = "test-integration"))] mod mocked_test { extern crate serial_test; use self::serial_test::serial; use super::{os::unix::GroupExt, *}; #[test] #[serial] fn mocked_username() { let user = get_user_by_uid(1337).unwrap(); assert_eq!(user.name(), "fred"); } #[test] #[serial] fn mocked_uid_for_uid() { let user = get_user_by_uid(1337).unwrap(); assert_eq!(user.uid, 1337); } #[test] #[serial] fn mocked_user_by_name() { let user_by_name = get_user_by_name("fred"); assert!(user_by_name.is_some()); assert_eq!(user_by_name.unwrap().name(), "fred"); // User names containing '\0' cannot be used (for now) let user = get_user_by_name("user\0"); assert!(user.is_none()); } #[test] #[serial] fn mocked_group_by_name() { let group_by_name = get_group_by_name("bosses"); assert!(group_by_name.is_some()); assert_eq!(group_by_name.unwrap().name(), "bosses"); // Group names containing '\0' cannot be used (for now) let group = get_group_by_name("users\0"); assert!(group.is_none()); } #[test] #[serial] fn mocked_group_members() { let group = get_group_by_gid(43).unwrap(); let members = group.members(); assert_eq!(members.len(), 2); assert!(members.contains(&"bob".into())); assert!(members.contains(&"martha".into())); } } uzers-0.12.1/src/cache.rs000064400000000000000000000537041046102023000132660ustar 00000000000000//! Caches for users and groups provided by the OS. //! //! Because the users table changes so infrequently, it's common for //! short-running programs to cache the results instead of getting the most //! up-to-date entries every time. This create offers two caching interfaces //! that help reduce system calls: [`UsersCache`](cache/struct.UsersCache.html) //! and [`UsersSnapshot`](cache/struct.UsersSnapshot.html). `UsersCache` is a //! lazy cache, storing answers as they arrive from the OS. `UsersSnapshot` is //! an eager cache, querying all data at once when constructed. //! //! `UsersCache` has a smaller memory and performance overhead, while //! `UsersSnapshot` offers better consistency and allows iterating over users //! and groups. //! //! ## Caching, multiple threads, and mutability //! //! The `UsersCache` type is caught between a rock and a hard place when it //! comes to providing references to users and groups. //! //! Instead of returning a fresh `User` struct each time, for example, it will //! return a reference to the version it currently has in its cache. So you can //! ask for User #501 twice, and you’ll get a reference to the same value both //! time. Its methods are *idempotent* -- calling one multiple times has the //! same effect as calling one once. //! //! This works fine in theory, but in practice, the cache has to update its own //! state somehow: it contains several `HashMap`s that hold the result of user //! and group lookups. Rust provides mutability in two ways: //! //! 1. Have its methods take `&mut self`, instead of `&self`, allowing the //! internal maps to be mutated (“inherited mutability”) //! 2. Wrap the internal maps in a `RefCell`, allowing them to be modified //! (“interior mutability”). //! //! Unfortunately, Rust is also very protective of references to a mutable //! value. In this case, switching to `&mut self` would only allow for one user //! to be read at a time! //! //! ```no_run //! use uzers::{Users, Groups, UsersCache}; //! //! let mut cache = UsersCache::new(); //! //! let uid = cache.get_current_uid(); // OK... //! let user = cache.get_user_by_uid(uid).unwrap(); // OK... //! let group = cache.get_group_by_gid(user.primary_group_id()); // No! //! ``` //! //! When we get the `user`, it returns an optional reference (which we unwrap) //! to the user’s entry in the cache. This is a reference to something contained //! in a mutable value. Then, when we want to get the user’s primary group, it //! will return *another* reference to the same mutable value. This is something //! that Rust explicitly disallows! //! //! The compiler wasn’t on our side with Option 1, so let’s try Option 2: //! changing the methods back to `&self` instead of `&mut self`, and using //! `RefCell`s internally. However, Rust is smarter than this, and knows that //! we’re just trying the same trick as earlier. A simplified implementation of //! a user cache lookup would look something like this: //! //! ```text //! fn get_user_by_uid(&self, uid: uid_t) -> Option<&User> { //! let users = self.users.borrow_mut(); //! users.get(uid) //! } //! ``` //! //! Rust won’t allow us to return a reference like this because the `Ref` of the //! `RefCell` just gets dropped at the end of the method, meaning that our //! reference does not live long enough. //! //! So instead of doing any of that, we use `Arc` everywhere in order to get //! around all the lifetime restrictions. Returning reference-counted users and //! groups mean that we don’t have to worry about further uses of the cache, as //! the values themselves don’t count as being stored *in* the cache anymore. So //! it can be queried multiple times or go out of scope and the values it //! produces are not affected. use libc::{gid_t, uid_t}; use std::cell::{Cell, RefCell}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; use std::hash::Hash; use std::ops::Deref; use std::sync::Arc; use base::{all_groups, all_users, Group, User}; use traits::{AllGroups, AllUsers, Groups, Users}; /// A producer of user and group instances that caches every result. /// /// This cache is **only additive**: it’s not possible to drop it, or erase /// selected entries, as when the database may have been modified, it’s best to /// start entirely afresh. So to accomplish this, just start using a new /// `UsersCache`. /// /// ## Example /// /// ```no_run /// use std::sync::Arc; /// use uzers::{Users, UsersCache}; /// /// let mut cache = UsersCache::new(); /// let user = cache.get_user_by_uid(502).expect("User not found"); /// let same_user = cache.get_user_by_uid(502).unwrap(); /// /// // The two returned values point to the same User /// assert!(Arc::ptr_eq(&user, &same_user)); /// ``` /// /// ## See also /// /// [`all_users`] and [`all_groups`] cannot be safely exposed in `UsersCache`, /// and lazy caching may introduce inconsistencies; see [`UsersSnapshot`] for /// an alternative. /// /// For thread safety considerations, see the /// [`users::cache` module documentation](index.html#caching-multiple-threads-and-mutability). #[derive(Default)] pub struct UsersCache { users: RefCell, Arc>>, groups: RefCell, Arc>>, uid: Cell>, gid: Cell>, euid: Cell>, egid: Cell>, } /// A kinda-bi-directional `HashMap` that associates keys to values, and /// then strings back to keys. /// /// It doesn’t go the full route and offer *values*-to-keys lookup, because we /// only want to search based on usernames and group names. There wouldn’t be /// much point offering a “User to uid” map, as the uid is present in the /// `User` struct! struct IdNameMap where I: Eq + Hash + Copy, N: Eq + Hash, { forward: HashMap>, backward: HashMap>, } impl IdNameMap where I: Eq + Hash + Copy, N: Eq + Hash, { /// Creates a new entry. fn insert(&mut self, id: I, name: N, value: V) { self.forward.insert(id, Some(value)); self.backward.insert(name, Some(id)); } } // Cannot use `#[derive(Default)]` for `IdNameMap` because [`HashMap`] requires // some of its types to implement [`Default`]. impl Default for IdNameMap where I: Eq + Hash + Copy, N: Eq + Hash, { fn default() -> Self { Self { forward: HashMap::new(), backward: HashMap::new(), } } } impl UsersCache { /// Creates a new empty cache. /// /// # Examples /// /// ``` /// use uzers::cache::UsersCache; /// /// let cache = UsersCache::new(); /// ``` pub fn new() -> Self { Self::default() } /// Creates a new cache preloaded with all users present on the system. /// /// This is a legacy method for code where `UsersCache` is required. /// Consider replacing this method with [`UsersSnapshot::new`] whereever /// possible to improve performance and consistency. /// /// Only information about *existing* users and groups is preloaded. /// Consequently, the following requests will still result in system calls: /// - current UID/GID, /// - effective UID/GID, /// - users and groups that were not preloaded. /// /// # Safety /// /// This is `unsafe` because we cannot prevent data races if two caches /// were attempted to be initialised on different threads at the same time. /// For more information, see the [`all_users` documentation](../fn.all_users.html). /// /// # Examples /// /// ``` /// use uzers::cache::UsersCache; /// /// let cache = unsafe { UsersCache::with_all_users() }; /// ``` /// /// # See also /// /// [`UsersSnapshot::new`] #[deprecated(since = "0.11.4", note = "consider using `UsersSnapshot::new` instead")] pub unsafe fn with_all_users() -> Self { let cache = Self::new(); for user in all_users() { let uid = user.uid(); let user_arc = Arc::new(user); cache.users.borrow_mut().insert( uid, Arc::clone(&user_arc.name_arc), Arc::clone(&user_arc), ); } cache } } // TODO: stop using ‘Arc::from’ with entry API // The ‘get_*_by_name’ functions below create a new Arc before even testing if // the user exists in the cache, essentially creating an unnecessary Arc. // https://internals.rust-lang.org/t/pre-rfc-abandonning-morals-in-the-name-of-performance-the-raw-entry-api/7043/51 // https://github.com/rust-lang/rfcs/pull/1769 impl Users for UsersCache { fn get_user_by_uid(&self, uid: uid_t) -> Option> { let mut users = self.users.borrow_mut(); let entry = match users.forward.entry(uid) { Vacant(e) => e, Occupied(e) => return e.get().clone(), }; if let Some(user) = super::get_user_by_uid(uid) { let newsername = Arc::clone(&user.name_arc); let user_arc = Arc::new(user); entry.insert(Some(Arc::clone(&user_arc))); users.backward.insert(newsername, Some(uid)); Some(user_arc) } else { entry.insert(None); None } } fn get_user_by_name + ?Sized>(&self, username: &S) -> Option> { let mut users = self.users.borrow_mut(); let entry = match users.backward.entry(Arc::from(username.as_ref())) { Vacant(e) => e, Occupied(e) => return (*e.get()).and_then(|uid| users.forward[&uid].clone()), }; if let Some(user) = super::get_user_by_name(username) { let uid = user.uid(); let user_arc = Arc::new(user); entry.insert(Some(uid)); users.forward.insert(uid, Some(Arc::clone(&user_arc))); Some(user_arc) } else { entry.insert(None); None } } fn get_current_uid(&self) -> uid_t { self.uid.get().unwrap_or_else(|| { let uid = super::get_current_uid(); self.uid.set(Some(uid)); uid }) } fn get_current_username(&self) -> Option> { let uid = self.get_current_uid(); self.get_user_by_uid(uid).map(|u| Arc::clone(&u.name_arc)) } fn get_effective_uid(&self) -> uid_t { self.euid.get().unwrap_or_else(|| { let uid = super::get_effective_uid(); self.euid.set(Some(uid)); uid }) } fn get_effective_username(&self) -> Option> { let uid = self.get_effective_uid(); self.get_user_by_uid(uid).map(|u| Arc::clone(&u.name_arc)) } } impl Groups for UsersCache { fn get_group_by_gid(&self, gid: gid_t) -> Option> { let mut groups = self.groups.borrow_mut(); let entry = match groups.forward.entry(gid) { Vacant(e) => e, Occupied(e) => return e.get().clone(), }; if let Some(group) = super::get_group_by_gid(gid) { let new_group_name = Arc::clone(&group.name_arc); let group_arc = Arc::new(group); entry.insert(Some(Arc::clone(&group_arc))); groups.backward.insert(new_group_name, Some(gid)); Some(group_arc) } else { entry.insert(None); None } } fn get_group_by_name + ?Sized>(&self, group_name: &S) -> Option> { let mut groups = self.groups.borrow_mut(); let entry = match groups.backward.entry(Arc::from(group_name.as_ref())) { Vacant(e) => e, Occupied(e) => { return (*e.get()).and_then(|gid| groups.forward[&gid].as_ref().cloned()) } }; if let Some(group) = super::get_group_by_name(group_name) { let group_arc = Arc::new(group.clone()); let gid = group.gid(); entry.insert(Some(gid)); groups.forward.insert(gid, Some(Arc::clone(&group_arc))); Some(group_arc) } else { entry.insert(None); None } } fn get_current_gid(&self) -> gid_t { self.gid.get().unwrap_or_else(|| { let gid = super::get_current_gid(); self.gid.set(Some(gid)); gid }) } fn get_current_groupname(&self) -> Option> { let gid = self.get_current_gid(); self.get_group_by_gid(gid).map(|g| Arc::clone(&g.name_arc)) } fn get_effective_gid(&self) -> gid_t { self.egid.get().unwrap_or_else(|| { let gid = super::get_effective_gid(); self.egid.set(Some(gid)); gid }) } fn get_effective_groupname(&self) -> Option> { let gid = self.get_effective_gid(); self.get_group_by_gid(gid).map(|g| Arc::clone(&g.name_arc)) } } /// A container of user and group instances. /// /// Included users and groups are determined by the method used to construct /// the snapshot: /// - [`UsersSnapshot::new()`] includes all system users and groups, /// - [`UsersSnapshot::only_users()`] filters users and includes only their /// primary groups, /// - [`UsersSnapshot::filtered()`] filters users and groups separately. /// /// This cache is **immutable**: it's not possible to alter or refresh it in any /// way after creation. Create a new `UsersSnapshot` to see changes in the /// underlying system database. /// /// ## Examples /// /// ```no_run /// use std::sync::Arc; /// use uzers::{Users, UsersSnapshot}; /// /// let cache = unsafe { UsersSnapshot::new() }; /// let user = cache.get_user_by_uid(502).expect("User not found"); /// let same_user = cache.get_user_by_uid(502).unwrap(); /// /// // The two returned values point to the same User /// assert!(Arc::ptr_eq(&user, &same_user)); /// ``` /// /// ```no_run /// use uzers::{AllUsers, UsersSnapshot}; /// /// // Exclude MacOS system users /// let cache = unsafe { UsersSnapshot::only_users(|u| u.uid() >= 500) }; /// /// // Users and groups can be iterated /// let user_count = cache.get_all_users().count(); /// ``` /// /// ## See also /// /// Unless iteration is required, [`UsersCache`] is likely safer, easier and /// faster. /// /// For thread safety considerations, see the /// [`users::cache` module documentation](index.html#caching-multiple-threads-and-mutability). #[derive(Default)] pub struct UsersSnapshot { users: IdNameMap, Arc>, groups: IdNameMap, Arc>, uid: uid_t, gid: gid_t, euid: uid_t, egid: gid_t, } impl UsersSnapshot { /// Creates a new snapshot containing provided users and groups. pub(crate) fn from( users: U, groups: G, current_uid: uid_t, current_gid: gid_t, effective_uid: uid_t, effective_gid: gid_t, ) -> Self where U: Iterator, G: Iterator, { let mut user_map = IdNameMap::default(); for user in users { user_map.insert(user.uid(), Arc::clone(&user.name_arc), Arc::from(user)); } let mut group_map = IdNameMap::default(); for group in groups { group_map.insert(group.gid(), Arc::clone(&group.name_arc), Arc::from(group)); } Self { users: user_map, groups: group_map, uid: current_uid, gid: current_gid, euid: effective_uid, egid: effective_gid, } } /// Creates a new snapshot containing all system users and groups that pass /// the filter. /// /// # Safety /// /// This is `unsafe` because we cannot prevent data races if two caches /// were attempted to be initialised on different threads at the same time. /// For more information, see the [`all_users` documentation](../fn.all_users.html). /// /// Note that this method uses both [`all_users`] and [`all_groups`]. /// /// # Examples /// /// ``` /// use uzers::cache::UsersSnapshot; /// /// // Exclude Linux system users, include all groups /// let snapshot = unsafe { /// UsersSnapshot::filtered(|u| u.uid() >= 1000, |_| true) /// }; /// ``` /// /// # See also /// /// - [`UsersSnapshot::only_users()`] - if only primary groups of users are /// needed /// - [`UsersSnapshot::new()`] - if no filtering is needed pub unsafe fn filtered(user_filter: U, group_filter: G) -> Self where U: FnMut(&User) -> bool, G: FnMut(&Group) -> bool, { Self::from( all_users().filter(user_filter), all_groups().filter(group_filter), super::get_current_uid(), super::get_current_gid(), super::get_effective_uid(), super::get_effective_gid(), ) } /// Creates a new snapshot containing all system users that pass the filter /// and their primary groups. /// /// Note that some primary groups may be missing on the system. /// /// # Safety /// /// This is `unsafe` because we cannot prevent data races if two caches /// were attempted to be initialised on different threads at the same time. /// For more information, see the [`all_users` documentation](../fn.all_users.html). /// /// Note that this method uses both [`all_users`] and [`all_groups`]. /// /// # Examples /// /// ``` /// use uzers::cache::UsersSnapshot; /// /// // Include Linux system users and their primary groups /// let snapshot = unsafe { UsersSnapshot::only_users(|u| u.uid() < 1000) }; /// ``` /// /// # See also /// /// - [`UsersSnapshot::filtered()`] - for more elaborate group filtering /// - [`UsersSnapshot::new()`] - if no filtering is needed pub unsafe fn only_users(user_filter: F) -> Self where F: FnMut(&User) -> bool, { let users = all_users().filter(user_filter).collect::>(); let primary_groups = users .iter() .map(User::primary_group_id) .collect::>(); let groups = all_groups() .filter(|g| primary_groups.contains(&g.gid())) .collect::>(); Self::from( users.into_iter(), groups.into_iter(), super::get_current_uid(), super::get_current_gid(), super::get_effective_uid(), super::get_effective_gid(), ) } /// Creates a new snapshot containing all system users and groups. /// /// # Safety /// /// This is `unsafe` because we cannot prevent data races if two caches /// were attempted to be initialised on different threads at the same time. /// For more information, see the [`all_users` documentation](../fn.all_users.html). /// /// Note that this method uses both [`all_users`] and [`all_groups`]. /// /// # Examples /// /// ``` /// use uzers::cache::UsersSnapshot; /// /// let snapshot = unsafe { UsersSnapshot::new() }; /// ``` /// /// # See also /// /// [`UsersSnapshot::only_users()`], [`UsersSnapshot::filtered()`] provide /// performance benefits if only some users and groups will be needed. pub unsafe fn new() -> Self { Self::filtered(|_| true, |_| true) } } impl AllUsers for UsersSnapshot { type UserIter<'a> = std::iter::FilterMap< std::collections::hash_map::Values<'a, uid_t, Option>>, for<'b> fn(&'b Option>) -> Option<&'b User>, >; fn get_all_users(&self) -> Self::UserIter<'_> { fn get_user(x: &Option>) -> Option<&User> { x.as_ref().map(Arc::deref) } self.users.forward.values().filter_map(get_user) } } impl Users for UsersSnapshot { fn get_user_by_uid(&self, uid: uid_t) -> Option> { self.users.forward.get(&uid)?.as_ref().cloned() } fn get_user_by_name + ?Sized>(&self, username: &S) -> Option> { let name_arc = Arc::from(username.as_ref()); let uid = self.users.backward.get(&name_arc)?.as_ref()?; self.get_user_by_uid(*uid) } fn get_current_uid(&self) -> uid_t { self.uid } fn get_current_username(&self) -> Option> { self.get_user_by_uid(self.uid) .map(|u| Arc::clone(&u.name_arc)) } fn get_effective_uid(&self) -> uid_t { self.euid } fn get_effective_username(&self) -> Option> { self.get_user_by_uid(self.euid) .map(|u| Arc::clone(&u.name_arc)) } } impl AllGroups for UsersSnapshot { type GroupIter<'a> = std::iter::FilterMap< std::collections::hash_map::Values<'a, gid_t, Option>>, for<'b> fn(&'b Option>) -> Option<&'b Group>, >; fn get_all_groups(&self) -> Self::GroupIter<'_> { fn get_group(x: &Option>) -> Option<&Group> { x.as_ref().map(Arc::deref) } self.groups.forward.values().filter_map(get_group) } } impl Groups for UsersSnapshot { fn get_group_by_gid(&self, gid: gid_t) -> Option> { self.groups.forward.get(&gid)?.as_ref().cloned() } fn get_group_by_name + ?Sized>(&self, group_name: &S) -> Option> { let name_arc = Arc::from(group_name.as_ref()); let gid = self.groups.backward.get(&name_arc)?.as_ref()?; self.get_group_by_gid(*gid) } fn get_current_gid(&self) -> gid_t { self.gid } fn get_current_groupname(&self) -> Option> { self.get_group_by_gid(self.gid) .map(|g| Arc::clone(&g.name_arc)) } fn get_effective_gid(&self) -> gid_t { self.egid } fn get_effective_groupname(&self) -> Option> { self.get_group_by_gid(self.egid) .map(|g| Arc::clone(&g.name_arc)) } } uzers-0.12.1/src/lib.rs000064400000000000000000000135221046102023000127630ustar 00000000000000#![warn(missing_copy_implementations)] #![warn(missing_docs)] #![warn(nonstandard_style)] #![warn(trivial_numeric_casts)] #![warn(unreachable_pub)] #![warn(unused)] //! This is a library for getting information on Unix users and groups. It //! supports getting the system users, and creating your own mock tables. //! //! 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. //! Furthermore, users can be the members of *groups*, which also have names and //! IDs. This functionality is exposed in libc, the C standard library, but as //! an unsafe Rust interface. This wrapper library provides a safe interface, //! using [`User`](struct.user.html) and [`Group`](struct.group.html) types //! and functions such as [`get_user_by_uid`](fn.get_user_by_uid.html) instead //! of low-level pointers and strings. It also offers basic caching //! functionality. //! //! It does not (yet) offer *editing* functionality; the values returned are //! read-only. //! //! //! ## Users //! //! The function [`get_current_uid`](fn.get_current_uid.html) returns a //! `uid_t` value representing the user currently running the program, and the //! [`get_user_by_uid`](fn.get_user_by_uid.html) function scans the users //! database and returns a `User` with the user’s information. This function //! returns `None` when there is no user for that ID. The `uid_t` type is //! re-exported from the libc crate. //! //! A [`User`](struct.User.html) value has the following accessors: //! //! - **uid:** The user’s ID //! - **name:** The user’s name //! - **primary_group:** The ID of this user’s primary group //! - [OS-specific extensions](os/index.html) such as home directories //! //! Here is a complete example that prints out the current user’s name: //! //! ``` //! use uzers::{get_user_by_uid, get_current_uid}; //! //! let user = get_user_by_uid(get_current_uid()).unwrap(); //! println!("Hello, {}!", user.name().to_string_lossy()); //! ``` //! //! This code assumes (with `unwrap()`) that the user hasn’t been deleted after //! the program has started running. 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. //! So always check the return values! //! //! There is also a [`get_current_username`](fn.get_current_username.html) //! function, as it’s such a common operation that it deserves special //! treatment. //! //! //! ## Caching //! //! Despite the above warning, the users and groups database rarely changes. //! 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. //! //! For this reason, this create offers two caching interfaces that expose the //! same functionality while reducing system calls: //! [`UsersCache`](cache/struct.UsersCache.html) and //! [`UsersSnapshot`](cache/struct.UsersSnapshot.html). `UsersCache` is a lazy //! cache, storing answers as they arrive from the OS. `UsersSnapshot` is an //! eager cache, querying all data at once when constructed. //! //! `UsersCache` has a smaller memory and performance overhead, while //! `UsersSnapshot` offers better consistency and allows iterating over users //! and groups. //! //! For example, to introduce a lazy cache, create a new //! [`UsersCache`](cache/struct.UsersCache.html). It has functions with the //! same names as the ones from earlier: //! //! ``` //! use uzers::{Users, Groups, UsersCache}; //! //! let mut cache = UsersCache::new(); //! let uid = cache.get_current_uid(); //! let user = cache.get_user_by_uid(uid).unwrap(); //! println!("Hello again, {}!", user.name().to_string_lossy()); //! ``` //! //! See documentation for [`UsersCache`](cache/struct.UsersCache.html) and //! [`UsersSnapshot`](cache/struct.UsersSnapshot.html) for more details. //! //! //! ## Groups //! //! Finally, it’s possible to get groups in a similar manner. //! A [`Group`](struct.Group.html) has the following accessors: //! //! - **gid:** The group’s ID //! - **name:** The group’s name //! - [OS-specific extensions](os/index.html) such as lists of members //! //! And again, a complete example: //! //! ```no_run //! use uzers::{Users, Groups, UsersCache}; //! //! let mut cache = UsersCache::new(); //! let group = cache.get_group_by_name("admin").expect("No such group 'admin'!"); //! println!("The '{}' group has the ID {}", group.name().to_string_lossy(), group.gid()); //! ``` //! //! //! ## Logging //! //! The `logging` feature, which is on by default, uses the `log` crate to //! record all interactions with the operating system. //! //! //! ## Caveats //! //! 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! //! //! Use the [`mock`](mock/index.html) module to create custom tables to test //! your code for these edge cases. extern crate libc; pub use libc::{gid_t, uid_t}; mod base; pub use base::{all_groups, all_users}; pub use base::{get_current_gid, get_current_groupname}; pub use base::{get_current_uid, get_current_username}; pub use base::{get_effective_gid, get_effective_groupname}; pub use base::{get_effective_uid, get_effective_username}; pub use base::{get_group_by_gid, get_group_by_name}; pub use base::{get_user_by_name, get_user_by_uid}; pub use base::{get_user_groups, group_access_list}; pub use base::{os, Group, User}; #[cfg(feature = "cache")] pub mod cache; #[cfg(feature = "cache")] pub use cache::{UsersCache, UsersSnapshot}; #[cfg(feature = "mock")] pub mod mock; pub mod switch; mod traits; pub use traits::{AllGroups, AllUsers, Groups, Users}; uzers-0.12.1/src/mock.rs000064400000000000000000000232571046102023000131540ustar 00000000000000//! Mockable users and groups. //! //! 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. //! //! This module 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. //! //! //! ## Creating Mock Users //! //! The only thing a mock users table needs to know in advance is the UID of //! the current user. Aside from that, you can add users and groups with //! `add_user` and `add_group` to the table: //! //! ``` //! use std::sync::Arc; //! use uzers::mock::{MockUsers, User, Group}; //! use uzers::os::unix::{UserExt, GroupExt}; //! //! let mut users = MockUsers::with_current_uid(1000); //! let bobbins = User::new(1000, "Bobbins", 1000).with_home_dir("/home/bobbins"); //! users.add_user(bobbins); //! users.add_group(Group::new(100, "funkyppl")); //! ``` //! //! The exports get re-exported into the mock module, for simpler `use` lines. //! //! //! ## Using Mock Users //! //! To set your program up to use either type of `Users` table, make your //! functions and structs accept a generic parameter that implements the `Users` //! trait. Then, you can pass in a value of either Cache, Snapshot or Mock type. //! //! Here’s a complete example: //! //! ``` //! use std::sync::Arc; //! use uzers::{Users, UsersCache, User}; //! use uzers::os::unix::UserExt; //! use uzers::mock::MockUsers; //! //! fn print_current_username(users: &mut U) { //! println!("Current user: {:?}", users.get_current_username()); //! } //! //! let mut users = MockUsers::with_current_uid(1001); //! users.add_user(User::new(1001, "fred", 101)); //! print_current_username(&mut users); //! //! let mut actual_users = UsersCache::new(); //! print_current_username(&mut actual_users); //! ``` //! //! Include `AllUsers` in generic parameter bounds if iteration is required: //! //! ``` //! use uzers::{AllUsers, User, Users, UsersSnapshot}; //! use uzers::mock::MockUsers; //! //! fn print_all_users(users: &U) { //! println!("All users:"); //! for user in users.get_all_users() { //! println!("- {:?}", user.name()); //! } //! } //! //! let mut users = MockUsers::with_current_uid(1001); //! users.add_user(User::new(1001, "fred", 101)); //! print_all_users(&users); //! //! let actual_users = unsafe { UsersSnapshot::new() }; //! print_all_users(&users); use std::collections::HashMap; use std::ffi::OsStr; use std::ops::Deref; use std::sync::Arc; pub use base::{Group, User}; pub use libc::{gid_t, uid_t}; pub use traits::{AllGroups, AllUsers, Groups, Users}; /// A mocking users table that you can add your own users and groups to. pub struct MockUsers { users: HashMap>, groups: HashMap>, uid: uid_t, } impl MockUsers { /// Create a new, empty mock users table. pub fn with_current_uid(current_uid: uid_t) -> Self { Self { users: HashMap::new(), groups: HashMap::new(), uid: current_uid, } } /// Add a user to the users table. pub fn add_user(&mut self, user: User) -> Option> { self.users.insert(user.uid(), Arc::new(user)) } /// Add a group to the groups table. pub fn add_group(&mut self, group: Group) -> Option> { self.groups.insert(group.gid(), Arc::new(group)) } } impl Users for MockUsers { fn get_user_by_uid(&self, uid: uid_t) -> Option> { self.users.get(&uid).cloned() } fn get_user_by_name + ?Sized>(&self, username: &S) -> Option> { self.users .values() .find(|u| u.name() == username.as_ref()) .cloned() } fn get_current_uid(&self) -> uid_t { self.uid } fn get_current_username(&self) -> Option> { self.users .get(&self.uid) .cloned() .map(|u| Arc::clone(&u.name_arc)) } fn get_effective_uid(&self) -> uid_t { self.uid } fn get_effective_username(&self) -> Option> { self.users .get(&self.uid) .cloned() .map(|u| Arc::clone(&u.name_arc)) } } impl Groups for MockUsers { fn get_group_by_gid(&self, gid: gid_t) -> Option> { self.groups.get(&gid).cloned() } fn get_group_by_name + ?Sized>(&self, group_name: &S) -> Option> { self.groups .values() .find(|g| g.name() == group_name.as_ref()) .cloned() } fn get_current_gid(&self) -> uid_t { self.uid } fn get_current_groupname(&self) -> Option> { self.groups .get(&self.uid) .cloned() .map(|u| Arc::clone(&u.name_arc)) } fn get_effective_gid(&self) -> uid_t { self.uid } fn get_effective_groupname(&self) -> Option> { self.groups .get(&self.uid) .cloned() .map(|u| Arc::clone(&u.name_arc)) } } impl AllUsers for MockUsers { type UserIter<'a> = std::iter::Map< std::collections::hash_map::Values<'a, uid_t, Arc>, for<'b> fn(&'b Arc) -> &'b User, >; fn get_all_users(&self) -> Self::UserIter<'_> { self.users.values().map(Arc::deref) } } impl AllGroups for MockUsers { type GroupIter<'a> = std::iter::Map< std::collections::hash_map::Values<'a, gid_t, Arc>, for<'b> fn(&'b Arc) -> &'b Group, >; fn get_all_groups(&self) -> Self::GroupIter<'_> { self.groups.values().map(Arc::deref) } } #[cfg(test)] mod test { use super::MockUsers; use base::{Group, User}; use traits::{AllGroups, AllUsers, Groups, Users}; use std::ffi::OsStr; use std::sync::Arc; #[test] fn current_username() { let mut users = MockUsers::with_current_uid(1337); users.add_user(User::new(1337, "fred", 101)); assert_eq!( Some(Arc::from(OsStr::new("fred"))), users.get_current_username() ) } #[test] fn no_current_username() { let users = MockUsers::with_current_uid(1337); assert_eq!(None, users.get_current_username()) } #[test] fn uid() { let mut users = MockUsers::with_current_uid(0); users.add_user(User::new(1337, "fred", 101)); assert_eq!( Some(Arc::from(OsStr::new("fred"))), users.get_user_by_uid(1337).map(|u| Arc::clone(&u.name_arc)) ) } #[test] fn username() { let mut users = MockUsers::with_current_uid(1337); users.add_user(User::new(1440, "fred", 101)); assert_eq!(Some(1440), users.get_user_by_name("fred").map(|u| u.uid())) } #[test] fn no_username() { let mut users = MockUsers::with_current_uid(1337); users.add_user(User::new(1337, "fred", 101)); assert_eq!(None, users.get_user_by_name("criminy").map(|u| u.uid())) } #[test] fn no_uid() { let users = MockUsers::with_current_uid(0); assert_eq!( None, users.get_user_by_uid(1337).map(|u| Arc::clone(&u.name_arc)) ) } #[test] fn all_users() { let mut users = MockUsers::with_current_uid(1337); users.add_user(User::new(1337, "fred", 101)); assert_eq!( vec![1337], users.get_all_users().map(|u| u.uid()).collect::>() ) } #[test] fn gid() { let mut users = MockUsers::with_current_uid(0); users.add_group(Group::new(1337, "fred")); assert_eq!( Some(Arc::from(OsStr::new("fred"))), users .get_group_by_gid(1337) .map(|g| Arc::clone(&g.name_arc)) ) } #[test] fn group_name() { let mut users = MockUsers::with_current_uid(0); users.add_group(Group::new(1337, "fred")); assert_eq!(Some(1337), users.get_group_by_name("fred").map(|g| g.gid())) } #[test] fn no_group_name() { let mut users = MockUsers::with_current_uid(0); users.add_group(Group::new(1337, "fred")); assert_eq!(None, users.get_group_by_name("santa").map(|g| g.gid())) } #[test] fn no_gid() { let users = MockUsers::with_current_uid(0); assert_eq!( None, users .get_group_by_gid(1337) .map(|g| Arc::clone(&g.name_arc)) ) } #[test] fn all_groups() { let mut users = MockUsers::with_current_uid(1337); users.add_group(Group::new(1337, "fred")); assert_eq!( vec![1337], users.get_all_groups().map(|g| g.gid()).collect::>() ) } #[test] fn gecos() { use crate::os::unix::UserExt; let mut users = MockUsers::with_current_uid(1337); users.add_user(User::new(1337, "fred", 101).with_gecos("Fred Santa")); assert_eq!( Some("Fred Santa".to_string()), users .get_user_by_uid(1337) .map(|u| u.gecos().to_string_lossy().to_string()) ); } #[test] fn no_gecos() { use crate::os::unix::UserExt; let mut users = MockUsers::with_current_uid(1337); users.add_user(User::new(1337, "fred", 101)); assert_eq!( Some("".to_string()), users .get_user_by_uid(1337) .map(|u| u.gecos().to_string_lossy().to_string()) ); } } uzers-0.12.1/src/switch.rs000064400000000000000000000163701046102023000135220ustar 00000000000000//! Functions for switching the running process’s user or group. use libc::{c_int, gid_t, uid_t}; use std::io; use base::{get_effective_gid, get_effective_uid}; // NOTE: for whatever reason, it seems these are not available in libc on BSD platforms, so they // need to be included manually extern "C" { fn setreuid(ruid: uid_t, euid: uid_t) -> c_int; fn setregid(rgid: gid_t, egid: gid_t) -> c_int; } /// Sets the **current user** for the running process to the one with the /// given user ID. /// /// Typically, trying to switch to anyone other than the user already running /// the process requires root privileges. /// /// # libc functions used /// /// - [`setuid`](https://docs.rs/libc/*/libc/fn.setuid.html) /// /// # Errors /// /// This function will return `Err` when an I/O error occurs during the /// `setuid` call. /// /// # Examples /// /// ```no_run /// use uzers::switch::set_current_uid; /// /// set_current_uid(1001); /// // current user ID is 1001 /// ``` pub fn set_current_uid(uid: uid_t) -> io::Result<()> { match unsafe { libc::setuid(uid) } { 0 => Ok(()), -1 => Err(io::Error::last_os_error()), n => unreachable!("setuid returned {}", n), } } /// Sets the **current group** for the running process to the one with the /// given group ID. /// /// Typically, trying to switch to any group other than the group already /// running the process requires root privileges. /// /// # libc functions used /// /// - [`setgid`](https://docs.rs/libc/*/libc/fn.setgid.html) /// /// # Errors /// /// This function will return `Err` when an I/O error occurs during the /// `setgid` call. /// /// # Examples /// /// ```no_run /// use uzers::switch::set_current_gid; /// /// set_current_gid(1001); /// // current group ID is 1001 /// ``` pub fn set_current_gid(gid: gid_t) -> io::Result<()> { match unsafe { libc::setgid(gid) } { 0 => Ok(()), -1 => Err(io::Error::last_os_error()), n => unreachable!("setgid returned {}", n), } } /// Sets the **effective user** for the running process to the one with the /// given user ID. /// /// Typically, trying to switch to anyone other than the user already running /// the process requires root privileges. /// /// # libc functions used /// /// - [`seteuid`](https://docs.rs/libc/*/libc/fn.seteuid.html) /// /// # Errors /// /// This function will return `Err` when an I/O error occurs during the /// `seteuid` call. /// /// # Examples /// /// ```no_run /// use uzers::switch::set_effective_uid; /// /// set_effective_uid(1001); /// // current effective user ID is 1001 /// ``` pub fn set_effective_uid(uid: uid_t) -> io::Result<()> { match unsafe { libc::seteuid(uid) } { 0 => Ok(()), -1 => Err(io::Error::last_os_error()), n => unreachable!("seteuid returned {}", n), } } /// Sets the **effective group** for the running process to the one with the /// given group ID. /// /// Typically, trying to switch to any group other than the group already /// running the process requires root privileges. /// /// # libc functions used /// /// - [`setegid`](https://docs.rs/libc/*/libc/fn.setegid.html) /// /// # Errors /// /// This function will return `Err` when an I/O error occurs during the /// `setegid` call. /// /// # Examples /// /// ```no_run /// use uzers::switch::set_effective_gid; /// /// set_effective_gid(1001); /// // current effective group ID is 1001 /// ``` pub fn set_effective_gid(gid: gid_t) -> io::Result<()> { match unsafe { libc::setegid(gid) } { 0 => Ok(()), -1 => Err(io::Error::last_os_error()), n => unreachable!("setegid returned {}", n), } } /// Sets both the **current user** and the **effective user** for the running /// process to the ones with the given user IDs. /// /// Typically, trying to switch to anyone other than the user already running /// the process requires root privileges. /// /// # libc functions used /// /// - [`setreuid`](https://docs.rs/libc/*/libc/fn.setreuid.html) /// /// # Errors /// /// This function will return `Err` when an I/O error occurs during the /// `setreuid` call. /// /// # Examples /// /// ```no_run /// use uzers::switch::set_both_uid; /// /// set_both_uid(1001, 1001); /// // current user ID and effective user ID are 1001 /// ``` pub fn set_both_uid(ruid: uid_t, euid: uid_t) -> io::Result<()> { match unsafe { setreuid(ruid, euid) } { 0 => Ok(()), -1 => Err(io::Error::last_os_error()), n => unreachable!("setreuid returned {}", n), } } /// Sets both the **current group** and the **effective group** for the /// running process to the ones with the given group IDs. /// /// Typically, trying to switch to any group other than the group already /// running the process requires root privileges. /// /// # libc functions used /// /// - [`setregid`](https://docs.rs/libc/*/libc/fn.setregid.html) /// /// # Errors /// /// This function will return `Err` when an I/O error occurs during the /// `setregid` call. /// /// # Examples /// /// ```no_run /// use uzers::switch::set_both_gid; /// /// set_both_gid(1001, 1001); /// // current user ID and effective group ID are 1001 /// ``` pub fn set_both_gid(rgid: gid_t, egid: gid_t) -> io::Result<()> { match unsafe { setregid(rgid, egid) } { 0 => Ok(()), -1 => Err(io::Error::last_os_error()), n => unreachable!("setregid returned {}", n), } } /// Guard returned from a `switch_user_group` call. pub struct SwitchUserGuard { uid: uid_t, gid: gid_t, } impl Drop for SwitchUserGuard { fn drop(&mut self) { set_effective_gid(self.gid).expect("Failed to set effective gid"); set_effective_uid(self.uid).expect("Failed to set effective uid"); } } /// Sets the **effective user** and the **effective group** for the current /// scope. /// /// Typically, trying to switch to any user or group other than the ones already /// running the process requires root privileges. /// /// # Security considerations /// /// - Because Rust does not guarantee running the destructor, it’s a good idea /// to call [`std::mem::drop`](https://doc.rust-lang.org/std/mem/fn.drop.html) /// on the guard manually in security-sensitive situations. /// - This function switches the group before the user to prevent the user’s /// privileges being dropped before trying to change the group (look up /// `POS36-C`). /// - This function will panic upon failing to set either walue, so the /// program does not continue executing with too many privileges. /// /// # libc functions used /// /// - [`seteuid`](https://docs.rs/libc/*/libc/fn.seteuid.html) /// - [`setegid`](https://docs.rs/libc/*/libc/fn.setegid.html) /// /// # Errors /// /// This function will return `Err` when an I/O error occurs during either /// the `seteuid` or `setegid` calls. /// /// # Examples /// /// ```no_run /// use uzers::switch::switch_user_group; /// use std::mem::drop; /// /// { /// let guard = switch_user_group(1001, 1001); /// // current and effective user and group IDs are 1001 /// drop(guard); /// } /// // back to the old values /// ``` pub fn switch_user_group(uid: uid_t, gid: gid_t) -> io::Result { let current_state = SwitchUserGuard { gid: get_effective_gid(), uid: get_effective_uid(), }; set_effective_gid(gid)?; set_effective_uid(uid)?; Ok(current_state) } uzers-0.12.1/src/traits.rs000064400000000000000000000044761046102023000135330ustar 00000000000000use std::ffi::OsStr; use std::sync::Arc; use libc::{gid_t, uid_t}; use base::{Group, User}; /// Trait for producers of users. pub trait Users { /// Returns a `User` if one exists for the given user ID; otherwise, returns `None`. fn get_user_by_uid(&self, uid: uid_t) -> Option>; /// Returns a `User` if one exists for the given username; otherwise, returns `None`. fn get_user_by_name + ?Sized>(&self, username: &S) -> Option>; /// Returns the user ID for the user running the process. fn get_current_uid(&self) -> uid_t; /// Returns the username of the user running the process. fn get_current_username(&self) -> Option>; /// Returns the effective user id. fn get_effective_uid(&self) -> uid_t; /// Returns the effective username. fn get_effective_username(&self) -> Option>; } /// Trait for producers of groups. pub trait Groups { /// Returns a `Group` if one exists for the given group ID; otherwise, returns `None`. fn get_group_by_gid(&self, gid: gid_t) -> Option>; /// Returns a `Group` if one exists for the given groupname; otherwise, returns `None`. fn get_group_by_name + ?Sized>(&self, group_name: &S) -> Option>; /// Returns the group ID for the user running the process. fn get_current_gid(&self) -> gid_t; /// Returns the group name of the user running the process. fn get_current_groupname(&self) -> Option>; /// Returns the effective group id. fn get_effective_gid(&self) -> gid_t; /// Returns the effective group name. fn get_effective_groupname(&self) -> Option>; } /// Trait for providers of user iterators. pub trait AllUsers { /// [`User`] iterator returned by [`get_all_users`][Self::get_all_users]. type UserIter<'a>: Iterator where Self: 'a; /// Creates a new iterator over every user. fn get_all_users(&self) -> Self::UserIter<'_>; } /// Trait for providers of group iterators. pub trait AllGroups { /// [`Group`] iterator returned by [`get_all_groups`][Self::get_all_groups]. type GroupIter<'a>: Iterator where Self: 'a; /// Creates a new iterator over every group. fn get_all_groups(&self) -> Self::GroupIter<'_>; } uzers-0.12.1/tests/fixtures/group000064400000000000000000000000521046102023000151440ustar 00000000000000bosses:x:42: contributors:x:43:bob,martha uzers-0.12.1/tests/fixtures/passwd000064400000000000000000000000661046102023000153160ustar 00000000000000fred:x:1337:42:Fred Santa:/home/fred:/usr/bin/nologin uzers-0.12.1/tests/groups.rs000064400000000000000000000005311046102023000141030ustar 00000000000000extern crate uzers; #[cfg(feature = "test-integration")] mod integration { #[test] fn test_group_by_name() { let group = uzers::get_group_by_name("bosses"); assert_eq!(group.is_some(), true); let group = group.unwrap(); assert_eq!(group.gid(), 42); assert_eq!(group.name(), "bosses"); } } uzers-0.12.1/tests/users.rs000064400000000000000000000010021046102023000137170ustar 00000000000000extern crate uzers; #[cfg(feature = "test-integration")] mod integration { use std::path::PathBuf; use uzers::os::unix::UserExt; #[test] fn test_user_by_name() { let user = uzers::get_user_by_name("fred"); assert_eq!(user.is_some(), true); let user = user.unwrap(); assert_eq!(user.uid(), 1337); assert_eq!(user.name(), "fred"); assert_eq!(user.primary_group_id(), 42); assert_eq!(user.home_dir(), PathBuf::from("/home/fred")); } }