fungus/sys/user.rs
1use crate::{
2 errors::*,
3 sys::{self, PathExt},
4};
5use std::{io, iter, mem, path::PathBuf, ptr};
6
7// Implementation in Rust for the XDB Base Directory Specification
8// https://wiki.archlinux.org/index.php/XDG_Base_Directory
9// -------------------------------------------------------------------------------------------------
10
11/// Returns the full path to the current user's home directory.
12///
13/// Alternate implementation as the Rust std::env::home_dir implementation which has be deprecated
14/// https://doc.rust-lang.org/std/env/fn.home_dir.html
15///
16/// ### Examples
17/// ```
18/// use fungus::prelude::*;
19///
20/// assert!(user::home_dir().is_ok());
21/// ```
22pub fn home_dir() -> FuResult<PathBuf> {
23 let home = sys::var("HOME")?;
24 let dir = PathBuf::from(home);
25 Ok(dir)
26}
27
28/// Returns the full path to the current user's config directory.
29/// Where user-specific configurations should be written (analogous to /etc).
30/// Defaults to $HOME/.config.
31///
32/// ### Examples
33/// ```
34/// use fungus::prelude::*;
35///
36/// assert!(user::config_dir().is_ok());
37/// ```
38pub fn config_dir() -> FuResult<PathBuf> {
39 Ok(match sys::var("XDG_CONFIG_HOME") {
40 Ok(x) => PathBuf::from(x),
41 Err(_) => home_dir()?.mash(".config"),
42 })
43}
44
45/// Returns the full path to the current user's cache directory.
46/// Where user-specific non-essential (cached) data should be written (analogous to /var/cache).
47/// Defaults to $HOME/.cache.
48///
49/// ### Examples
50/// ```
51/// use fungus::prelude::*;
52///
53/// assert!(user::cache_dir().is_ok());
54/// ```
55pub fn cache_dir() -> FuResult<PathBuf> {
56 Ok(match sys::var("XDG_CACHE_HOME") {
57 Ok(x) => PathBuf::from(x),
58 Err(_) => home_dir()?.mash(".cache"),
59 })
60}
61
62/// Returns the full path to the current user's data directory.
63/// Where user-specific data files should be written (analogous to /usr/share).
64/// Defaults to $HOME/.local/share
65///
66/// ### Examples
67/// ```
68/// use fungus::prelude::*;
69///
70/// assert!(user::data_dir().is_ok());
71/// ```
72pub fn data_dir() -> FuResult<PathBuf> {
73 Ok(match sys::var("XDG_DATA_HOME") {
74 Ok(x) => PathBuf::from(x),
75 Err(_) => home_dir()?.mash(".local/share"),
76 })
77}
78
79/// Returns the full path to the current user's runtime directory.
80/// Used for non-essential, user-specific data files such as sockets, named pipes, etc.
81/// Must be owned by the user with an access mode of 0700.
82/// Filesystem fully featured by standards of OS.
83/// Must be on the local filesystem.
84/// May be subject to periodic cleanup.
85/// Modified every 6 hours or set sticky bit if persistence is desired.
86/// Can only exist for the duration of the user's login.
87/// Should not store large files as it may be mounted as a tmpfs.
88///
89/// Defaults to /tmp if $XDG_RUNTIME_DIR is not set
90///
91/// ### Examples
92/// ```
93/// use fungus::prelude::*;
94///
95/// println!("runtime directory of the current user: {:?}", user::runtime_dir());
96/// ```
97pub fn runtime_dir() -> PathBuf {
98 match sys::var("XDG_RUNTIME_DIR") {
99 Ok(x) => PathBuf::from(x),
100 Err(_) => PathBuf::from("/tmp"),
101 }
102}
103
104/// Returns the full path to a newly created directory in `/tmp` that can be used for temporary
105/// work. The returned path will be checked for uniqueness and created with a random suffix and
106/// the given `prefix`. It is up to the calling code to ensure the directory returned is
107/// properly cleaned up when done with.
108///
109/// ### Examples
110/// ```
111/// use fungus::prelude::*;
112///
113/// let tmpdir = user::temp_dir("foo").unwrap();
114/// assert_eq!(tmpdir.exists(), true);
115/// {
116/// let _defer = defer(|| sys::remove_all(&tmpdir).unwrap());
117/// }
118/// assert_eq!(tmpdir.exists(), false);
119/// ```
120pub fn temp_dir<T: AsRef<str>>(prefix: T) -> FuResult<PathBuf> {
121 loop {
122 let suffix: String = iter::repeat_with(fastrand::alphanumeric).take(8).collect();
123 let dir = PathBuf::from(format!("/tmp/{}-{}", prefix.as_ref(), suffix));
124 if !dir.exists() {
125 return sys::mkdir(&dir);
126 }
127 }
128}
129
130/// Returns the current user's data directories.
131/// List of directories seperated by : (analogous to PATH).
132/// Defaults to /usr/local/share:/usr/share.
133///
134/// ### Examples
135/// ```
136/// use fungus::prelude::*;
137///
138/// assert!(user::data_dirs().is_ok());
139/// ```
140pub fn data_dirs() -> FuResult<Vec<PathBuf>> {
141 Ok(match sys::var("XDG_DATA_DIRS") {
142 Ok(x) => sys::parse_paths(x)?,
143 Err(_) => vec![PathBuf::from("/usr/local/share:/usr/share")],
144 })
145}
146
147/// Returns the current user's config directories.
148/// List of directories seperated by : (analogous to PATH).
149/// Defaults to /etc/xdg
150///
151/// ### Examples
152/// ```
153/// use fungus::prelude::*;
154///
155/// assert!(user::config_dirs().is_ok());
156/// ```
157pub fn config_dirs() -> FuResult<Vec<PathBuf>> {
158 Ok(match sys::var("XDG_CONFIG_DIRS") {
159 Ok(x) => sys::parse_paths(x)?,
160 Err(_) => vec![PathBuf::from("/etc/xdg")],
161 })
162}
163
164/// Returns the current user's path directories.
165/// List of directories seperated by :
166///
167/// ### Examples
168/// ```
169/// use fungus::prelude::*;
170///
171/// assert!(user::path_dirs().is_ok());
172/// ```
173pub fn path_dirs() -> FuResult<Vec<PathBuf>> {
174 sys::parse_paths(sys::var("PATH")?)
175}
176
177// User functions
178// -------------------------------------------------------------------------------------------------
179
180/// User provides options for a specific user.
181#[derive(Debug, Clone, Default)]
182pub struct User {
183 pub uid: u32, // user id
184 pub gid: u32, // user group id
185 pub name: String, // user name
186 pub home: PathBuf, // user home
187 pub shell: PathBuf, // user shell
188 pub ruid: u32, // real user id behind sudo
189 pub rgid: u32, // real user group id behind sudo
190 pub realname: String, // real user name behind sudo
191 pub realhome: PathBuf, // real user home behind sudo
192 pub realshell: PathBuf, // real user shell behind sudo
193}
194
195impl User {
196 /// Returns true if the user is root
197 ///
198 /// ### Examples
199 /// ```
200 /// use fungus::prelude::*;
201 ///
202 /// assert_eq!(user::current().unwrap().is_root(), false);
203 /// ```
204 pub fn is_root(&self) -> bool {
205 self.uid == 0
206 }
207}
208
209/// Get the current user
210///
211/// ### Examples
212/// ```
213/// use fungus::prelude::*;
214///
215/// assert!(user::current().is_ok());
216/// ```
217pub fn current() -> FuResult<User> {
218 let user = lookup(unsafe { libc::getuid() })?;
219 Ok(user)
220}
221
222/// Switches back to the original user under the sudo mask with no way to go back.
223///
224/// ### Examples
225/// ```
226/// use fungus::prelude::*;
227///
228/// assert!(user::drop_sudo().is_ok());
229/// ```
230pub fn drop_sudo() -> FuResult<()> {
231 match getuid() {
232 0 => {
233 let (ruid, rgid) = getrids(0, 0);
234 switchuser(ruid, ruid, ruid, rgid, rgid, rgid)
235 },
236 _ => Ok(()),
237 }
238}
239
240/// Returns the user ID for the current user.
241///
242/// ### Examples
243/// ```
244/// use fungus::prelude::*;
245///
246/// assert!(user::getuid() != 0);
247/// ```
248pub fn getuid() -> u32 {
249 unsafe { libc::getuid() }
250}
251
252/// Returns the group ID for the current user.
253///
254/// ### Examples
255/// ```
256/// use fungus::prelude::*;
257///
258/// assert!(user::getgid() != 0);
259/// ```
260pub fn getgid() -> u32 {
261 unsafe { libc::getgid() }
262}
263
264/// Returns the user effective ID for the current user.
265///
266/// ### Examples
267/// ```
268/// use fungus::prelude::*;
269///
270/// assert!(user::geteuid() != 0);
271/// ```
272pub fn geteuid() -> u32 {
273 unsafe { libc::geteuid() }
274}
275
276/// Returns the group effective ID for the current user.
277///
278/// ### Examples
279/// ```
280/// use fungus::prelude::*;
281///
282/// assert!(user::getegid() != 0);
283/// ```
284pub fn getegid() -> u32 {
285 unsafe { libc::getegid() }
286}
287
288/// Returns the real IDs for the given user.
289///
290/// ### Examples
291/// ```
292/// use fungus::prelude::*;
293///
294/// assert_eq!(user::getrids(user::getuid(), user::getgid()), (user::getuid(), user::getgid()));
295/// ```
296pub fn getrids(uid: u32, gid: u32) -> (u32, u32) {
297 match uid {
298 0 => match (sys::var("SUDO_UID"), sys::var("SUDO_GID")) {
299 (Ok(u), Ok(g)) => match (u.parse::<u32>(), g.parse::<u32>()) {
300 (Ok(u), Ok(g)) => (u, g),
301 _ => (uid, gid),
302 },
303 _ => (uid, gid),
304 },
305 _ => (uid, gid),
306 }
307}
308
309/// Return true if the current user is the root user.
310///
311/// ### Examples
312/// ```
313/// use fungus::prelude::*;
314///
315/// assert_eq!(user::is_root(), false);
316/// ```
317pub fn is_root() -> bool {
318 getuid() == 0
319}
320
321/// Lookup a user by user id
322///
323/// ### Examples
324/// ```
325/// use fungus::prelude::*;
326///
327/// assert!(user::lookup(user::getuid()).is_ok());
328/// ```
329pub fn lookup(uid: u32) -> FuResult<User> {
330 // Get the libc::passwd by user id
331 let mut buf = vec![0; 2048];
332 let mut res = ptr::null_mut::<libc::passwd>();
333 let mut passwd = unsafe { mem::zeroed::<libc::passwd>() };
334 unsafe {
335 libc::getpwuid_r(uid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut res);
336 }
337 if res.is_null() || res != &mut passwd {
338 return Err(UserError::does_not_exist_by_id(uid).into());
339 }
340
341 // Convert libc::passwd object into a User object
342 //----------------------------------------------------------------------------------------------
343 let gid = passwd.pw_gid;
344
345 // User name for the lookedup user. We always want this and it should always exist.
346 let username = unsafe { sys::libc::to_string(passwd.pw_name)? };
347
348 // Will almost always be a single 'x' as the passwd is in the shadow database
349 // let userpwd = unsafe { crate::sys::libc::to_string(passwd.pw_passwd)? };
350
351 // User home directory e.g. '/home/<user>'. Might be a null pointer indicating the system default
352 // should be used
353 let userhome = unsafe { sys::libc::to_string(passwd.pw_dir) }.unwrap_or_default();
354
355 // User shell e.g. '/bin/bash'. Might be a null pointer indicating the system default should be used
356 let usershell = unsafe { sys::libc::to_string(passwd.pw_shell) }.unwrap_or_default();
357
358 // A string container user contextual information, possibly real name or phone number.
359 // let usergecos = unsafe { crate::sys::libc::to_string(passwd.pw_gecos)? };
360
361 // Get the user's real ids as well if applicable
362 let (ruid, rgid) = getrids(uid, gid);
363 let realuser = if uid != ruid {
364 lookup(ruid)?
365 } else {
366 User {
367 uid,
368 gid,
369 name: username.to_string(),
370 home: PathBuf::from(&userhome),
371 shell: PathBuf::from(&usershell),
372 ..Default::default()
373 }
374 };
375 Ok(User {
376 uid,
377 gid,
378 name: username,
379 home: PathBuf::from(&userhome),
380 shell: PathBuf::from(&usershell),
381 ruid,
382 rgid,
383 realname: realuser.name,
384 realhome: realuser.home,
385 realshell: realuser.shell,
386 })
387}
388
389/// Returns the current user's name.
390///
391/// ### Examples
392/// ```
393/// use fungus::prelude::*;
394///
395/// println!("current user name: {:?}", user::name().unwrap());
396/// ```
397pub fn name() -> FuResult<String> {
398 Ok(current()?.name)
399}
400
401/// Switches back to the original user under the sudo mask. Preserves the ability to raise sudo
402/// again.
403///
404/// ### Examples
405/// ```ignore
406/// use fungus::prelude::*;
407///
408/// assert!(user::pause_sudo().is_ok());
409/// ```
410pub fn pause_sudo() -> FuResult<()> {
411 match getuid() {
412 0 => {
413 let (ruid, rgid) = getrids(0, 0);
414 switchuser(ruid, ruid, 0, rgid, rgid, 0)
415 },
416 _ => Ok(()),
417 }
418}
419
420/// Set the user ID for the current user.
421///
422/// ### Examples
423/// ```ignore
424/// use fungus::prelude::*;
425///
426/// assert!(user::setuid(user::getuid()).is_ok());
427/// ```
428pub fn setuid(uid: u32) -> FuResult<()> {
429 match unsafe { libc::setuid(uid) } {
430 0 => Ok(()),
431 _ => Err(io::Error::last_os_error().into()),
432 }
433}
434
435/// Set the user effective ID for the current user.
436///
437/// ### Examples
438/// ```ignore
439/// use fungus::prelude::*;
440///
441/// assert!(user::seteuid(user::geteuid()).is_ok());
442/// ```
443pub fn seteuid(euid: u32) -> FuResult<()> {
444 match unsafe { libc::seteuid(euid) } {
445 0 => Ok(()),
446 _ => Err(io::Error::last_os_error().into()),
447 }
448}
449
450/// Set the group ID for the current user.
451///
452/// ### Examples
453/// ```ignore
454/// use fungus::prelude::*;
455///
456/// assert!(user::setgid(user::getgid()).is_ok());
457/// ```
458pub fn setgid(gid: u32) -> FuResult<()> {
459 match unsafe { libc::setgid(gid) } {
460 0 => Ok(()),
461 _ => Err(io::Error::last_os_error().into()),
462 }
463}
464
465/// Set the group effective ID for the current user.
466///
467/// ### Examples
468/// ```ignore
469/// use fungus::prelude::*;
470///
471/// assert!(user::setegid(user::getegid()).is_ok());
472/// ```
473pub fn setegid(egid: u32) -> FuResult<()> {
474 match unsafe { libc::setegid(egid) } {
475 0 => Ok(()),
476 _ => Err(io::Error::last_os_error().into()),
477 }
478}
479
480/// Switches back to sudo root. Returns and error if not allowed.
481///
482/// ### Examples
483/// ```ignore
484/// use fungus::prelude::*;
485///
486/// user:sudo().unwrap();
487/// ```
488pub fn sudo() -> FuResult<()> {
489 switchuser(0, 0, 0, 0, 0, 0)
490}
491
492/// Switches to another use by setting the real, effective and saved user and group ids.
493///
494/// ### Examples
495/// ```ignore
496/// use fungus::prelude::*;
497///
498/// // Switch to user 1000 but preserve root priviledeges to switch again
499/// user::switchuser(1000, 1000, 0, 1000, 1000, 0);
500///
501/// // Switch to user 1000 and drop root priviledges permanantely
502/// user::switchuser(1000, 1000, 1000, 1000, 1000, 1000);
503/// ```
504pub fn switchuser(ruid: u32, euid: u32, suid: u32, rgid: u32, egid: u32, sgid: u32) -> FuResult<()> {
505 // Best practice to drop the group first
506 match unsafe { libc::setresgid(rgid, egid, sgid) } {
507 0 => match unsafe { libc::setresuid(ruid, euid, suid) } {
508 0 => Ok(()),
509 _ => Err(io::Error::last_os_error().into()),
510 },
511 _ => Err(io::Error::last_os_error().into()),
512 }
513}
514
515// Unit tests
516// -------------------------------------------------------------------------------------------------
517#[cfg(test)]
518mod tests {
519 use crate::prelude::*;
520
521 #[test]
522 fn test_user_home() {
523 let home_str = sys::var("HOME").unwrap();
524 let home_path = PathBuf::from(home_str);
525 let home_dir = home_path.parent().unwrap();
526 assert_eq!(home_dir.to_path_buf(), user::home_dir().unwrap().dir().unwrap());
527 }
528
529 #[test]
530 fn test_user_libc() {
531 assert!(user::pause_sudo().is_ok());
532 assert!(user::drop_sudo().is_ok());
533 assert!(user::getuid() != 0);
534 assert!(user::getgid() != 0);
535 assert!(user::geteuid() != 0);
536 assert!(user::getegid() != 0);
537 assert_eq!(user::getrids(user::getuid(), user::getgid()), (user::getuid(), user::getgid()));
538 assert_eq!(user::is_root(), false);
539 assert!(user::lookup(user::getuid()).is_ok());
540 assert!(user::name().unwrap() != "".to_string());
541 assert!(user::current().is_ok());
542 assert_eq!(user::current().unwrap().is_root(), false);
543 // assert!(user::sudo().is_err());
544 // assert!(user::setegid(user::getegid()).is_ok());
545 // assert!(user::setgid(user::getgid()).is_ok());
546 // assert!(user::seteuid(user::geteuid()).is_ok());
547 // assert!(user::setuid(user::getuid()).is_ok());
548 }
549
550 #[test]
551 fn test_user_dirs() {
552 assert!(user::home_dir().is_ok());
553 assert!(user::config_dir().is_ok());
554 assert!(user::cache_dir().is_ok());
555 assert!(user::data_dir().is_ok());
556 user::runtime_dir();
557 assert!(user::data_dirs().is_ok());
558 assert!(user::config_dirs().is_ok());
559 assert!(user::path_dirs().is_ok());
560
561 let tmpdir = user::temp_dir("test_user_dirs").unwrap();
562 assert_eq!(tmpdir.exists(), true);
563 {
564 let _defer = defer(|| sys::remove_all(&tmpdir).unwrap());
565 }
566 assert_eq!(tmpdir.exists(), false);
567 }
568
569 #[test]
570 fn test_temp_dir() {
571 let tmpdir = user::temp_dir("foo").unwrap();
572 assert_eq!(tmpdir.exists(), true);
573 assert!(sys::remove_all(&tmpdir).is_ok());
574 assert_eq!(tmpdir.exists(), false);
575 }
576}