1 //! Integration with the C library’s users and groups.
2 //!
3 //! This module uses `extern` functions and types from `libc` that integrate
4 //! with the system’s C library, which integrates with the OS itself to get user
5 //! and group information. It’s where the “core” user handling is done.
6 //!
7 //!
8 //! ## Name encoding rules
9 //!
10 //! Under Unix, usernames and group names are considered to be
11 //! null-terminated, UTF-8 strings. These are `CString`s in Rust, although in
12 //! this library, they are just `String` values. Why?
13 //!
14 //! The reason is that any user or group values with invalid `CString` data
15 //! can instead just be assumed to not exist:
16 //!
17 //! - If you try to search for a user with a null character in their name,
18 //! such a user could not exist anyway — so it’s OK to return `None`.
19 //! - If the OS returns user information with a null character in a field,
20 //! then that field will just be truncated instead, which is valid behaviour
21 //! for a `CString`.
22 //!
23 //! The downside is that we use `from_utf8_lossy` instead, which has a small
24 //! runtime penalty when it calculates and scans the length of the string for
25 //! invalid characters. However, this should not be a problem when dealing with
26 //! usernames of a few bytes each.
27 //!
28 //! In short, if you want to check for null characters in user fields, your
29 //! best bet is to check for them yourself before passing strings into any
30 //! functions.
31
32 use std::ffi::{CStr, CString, OsStr, OsString};
33 use std::fmt;
34 use std::mem;
35 use std::io;
36 use std::os::unix::ffi::OsStrExt;
37 use std::ptr;
38 use std::sync::Arc;
39
40 #[cfg(feature = "logging")]
41 extern crate log;
42 #[cfg(feature = "logging")]
43 use self::log::trace;
44
45 use libc::{c_char, uid_t, gid_t, c_int};
46 use libc::passwd as c_passwd;
47 use libc::group as c_group;
48
49
50 /// Information about a particular user.
51 ///
52 /// For more information, see the [module documentation](index.html).
53 #[derive(Clone)]
54 pub struct User {
55 uid: uid_t,
56 primary_group: gid_t,
57 extras: os::UserExtras,
58 pub(crate) name_arc: Arc<OsStr>,
59 }
60
61 impl User {
62
63 /// Create a new `User` with the given user ID, name, and primary
64 /// group ID, with the rest of the fields filled with dummy values.
65 ///
66 /// This method does not actually create a new user on the system — it
67 /// should only be used for comparing users in tests.
68 ///
69 /// # Examples
70 ///
71 /// ```
72 /// use users::User;
73 ///
74 /// let user = User::new(501, "stevedore", 100);
75 /// ```
new<S: AsRef<OsStr> + ?Sized>(uid: uid_t, name: &S, primary_group: gid_t) -> Self76 pub fn new<S: AsRef<OsStr> + ?Sized>(uid: uid_t, name: &S, primary_group: gid_t) -> Self {
77 let name_arc = Arc::from(name.as_ref());
78 let extras = os::UserExtras::default();
79
80 Self { uid, name_arc, primary_group, extras }
81 }
82
83 /// Returns this user’s ID.
84 ///
85 /// # Examples
86 ///
87 /// ```
88 /// use users::User;
89 ///
90 /// let user = User::new(501, "stevedore", 100);
91 /// assert_eq!(user.uid(), 501);
92 /// ```
uid(&self) -> uid_t93 pub fn uid(&self) -> uid_t {
94 self.uid
95 }
96
97 /// Returns this user’s name.
98 ///
99 /// # Examples
100 ///
101 /// ```
102 /// use std::ffi::OsStr;
103 /// use users::User;
104 ///
105 /// let user = User::new(501, "stevedore", 100);
106 /// assert_eq!(user.name(), OsStr::new("stevedore"));
107 /// ```
name(&self) -> &OsStr108 pub fn name(&self) -> &OsStr {
109 &*self.name_arc
110 }
111
112 /// Returns the ID of this user’s primary group.
113 ///
114 /// # Examples
115 ///
116 /// ```
117 /// use users::User;
118 ///
119 /// let user = User::new(501, "stevedore", 100);
120 /// assert_eq!(user.primary_group_id(), 100);
121 /// ```
primary_group_id(&self) -> gid_t122 pub fn primary_group_id(&self) -> gid_t {
123 self.primary_group
124 }
125
126 /// Returns a list of groups this user is a member of. This involves
127 /// loading the groups list, as it is _not_ contained within this type.
128 ///
129 /// # libc functions used
130 ///
131 /// - [`getgrouplist`](https://docs.rs/libc/*/libc/fn.getgrouplist.html)
132 ///
133 /// # Examples
134 ///
135 /// ```no_run
136 /// use users::User;
137 ///
138 /// let user = User::new(501, "stevedore", 100);
139 /// for group in user.groups().expect("User not found") {
140 /// println!("User is in group: {:?}", group.name());
141 /// }
142 /// ```
groups(&self) -> Option<Vec<Group>>143 pub fn groups(&self) -> Option<Vec<Group>> {
144 get_user_groups(self.name(), self.primary_group_id())
145 }
146 }
147
148 impl fmt::Debug for User {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result149 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150 if f.alternate() {
151 f.debug_struct("User")
152 .field("uid", &self.uid)
153 .field("name_arc", &self.name_arc)
154 .field("primary_group", &self.primary_group)
155 .field("extras", &self.extras)
156 .finish()
157 }
158 else {
159 write!(f, "User({}, {})", self.uid(), self.name().to_string_lossy())
160 }
161 }
162 }
163
164
165 /// Information about a particular group.
166 ///
167 /// For more information, see the [module documentation](index.html).
168 #[derive(Clone)]
169 pub struct Group {
170 gid: gid_t,
171 extras: os::GroupExtras,
172 pub(crate) name_arc: Arc<OsStr>,
173 }
174
175 impl Group {
176
177 /// Create a new `Group` with the given group ID and name, with the
178 /// rest of the fields filled in with dummy values.
179 ///
180 /// This method does not actually create a new group on the system — it
181 /// should only be used for comparing groups in tests.
182 ///
183 /// # Examples
184 ///
185 /// ```
186 /// use users::Group;
187 ///
188 /// let group = Group::new(102, "database");
189 /// ```
new<S: AsRef<OsStr> + ?Sized>(gid: gid_t, name: &S) -> Self190 pub fn new<S: AsRef<OsStr> + ?Sized>(gid: gid_t, name: &S) -> Self {
191 let name_arc = Arc::from(name.as_ref());
192 let extras = os::GroupExtras::default();
193
194 Self { gid, name_arc, extras }
195 }
196
197 /// Returns this group’s ID.
198 ///
199 /// # Examples
200 ///
201 /// ```
202 /// use users::Group;
203 ///
204 /// let group = Group::new(102, "database");
205 /// assert_eq!(group.gid(), 102);
206 /// ```
gid(&self) -> gid_t207 pub fn gid(&self) -> gid_t {
208 self.gid
209 }
210
211 /// Returns this group’s name.
212 ///
213 /// # Examples
214 ///
215 /// ```
216 /// use std::ffi::OsStr;
217 /// use users::Group;
218 ///
219 /// let group = Group::new(102, "database");
220 /// assert_eq!(group.name(), OsStr::new("database"));
221 /// ```
name(&self) -> &OsStr222 pub fn name(&self) -> &OsStr {
223 &*self.name_arc
224 }
225 }
226
227 impl fmt::Debug for Group {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result228 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229 if f.alternate() {
230 f.debug_struct("Group")
231 .field("gid", &self.gid)
232 .field("name_arc", &self.name_arc)
233 .field("extras", &self.extras)
234 .finish()
235 }
236 else {
237 write!(f, "Group({}, {})", self.gid(), self.name().to_string_lossy())
238 }
239 }
240 }
241
242
243 /// Reads data from a `*char` field in `c_passwd` or `g_group`. The return
244 /// type will be an `Arc<OsStr>` if the text is meant to be shared in a cache,
245 /// or a plain `OsString` if it’s not.
246 ///
247 /// The underlying buffer is managed by the C library, not by us, so we *need*
248 /// to move data out of it before the next user gets read.
from_raw_buf<'a, T>(p: *const c_char) -> T where T: From<&'a OsStr>249 unsafe fn from_raw_buf<'a, T>(p: *const c_char) -> T
250 where T: From<&'a OsStr>
251 {
252 T::from(OsStr::from_bytes(CStr::from_ptr(p).to_bytes()))
253 }
254
255 /// Reads data from the `c_passwd` and returns it as a `User`.
passwd_to_user(passwd: c_passwd) -> User256 unsafe fn passwd_to_user(passwd: c_passwd) -> User {
257 #[cfg(feature = "logging")]
258 trace!("Loading user with uid {}", passwd.pw_uid);
259
260 let name = from_raw_buf(passwd.pw_name);
261
262 User {
263 uid: passwd.pw_uid,
264 name_arc: name,
265 primary_group: passwd.pw_gid,
266 extras: os::UserExtras::from_passwd(passwd),
267 }
268 }
269
270 /// Reads data from the `c_group` and returns it as a `Group`.
struct_to_group(group: c_group) -> Group271 unsafe fn struct_to_group(group: c_group) -> Group {
272 #[cfg(feature = "logging")]
273 trace!("Loading group with gid {}", group.gr_gid);
274
275 let name = from_raw_buf(group.gr_name);
276
277 Group {
278 gid: group.gr_gid,
279 name_arc: name,
280 extras: os::GroupExtras::from_struct(group),
281 }
282 }
283
284 /// Expand a list of group members to a vector of strings.
285 ///
286 /// The list of members is, in true C fashion, a pointer to a pointer of
287 /// characters, terminated by a null pointer. We check `members[0]`, then
288 /// `members[1]`, and so on, until that null pointer is reached. It doesn’t
289 /// specify whether we should expect a null pointer or a pointer to a null
290 /// pointer, so we check for both here!
members(groups: *mut *mut c_char) -> Vec<OsString>291 unsafe fn members(groups: *mut *mut c_char) -> Vec<OsString> {
292 let mut members = Vec::new();
293
294 for i in 0.. {
295 let username = groups.offset(i);
296
297 if username.is_null() || (*username).is_null() {
298 break;
299 }
300 else {
301 members.push(from_raw_buf(*username));
302 }
303 }
304
305 members
306 }
307
308
309 /// Searches for a `User` with the given ID in the system’s user database.
310 /// Returns it if one is found, otherwise returns `None`.
311 ///
312 /// # libc functions used
313 ///
314 /// - [`getpwuid_r`](https://docs.rs/libc/*/libc/fn.getpwuid_r.html)
315 ///
316 /// # Examples
317 ///
318 /// ```
319 /// use users::get_user_by_uid;
320 ///
321 /// match get_user_by_uid(501) {
322 /// Some(user) => println!("Found user {:?}", user.name()),
323 /// None => println!("User not found"),
324 /// }
325 /// ```
get_user_by_uid(uid: uid_t) -> Option<User>326 pub fn get_user_by_uid(uid: uid_t) -> Option<User> {
327 let mut passwd = unsafe { mem::zeroed::<c_passwd>() };
328 let mut buf = vec![0; 2048];
329 let mut result = ptr::null_mut::<c_passwd>();
330
331 #[cfg(feature = "logging")]
332 trace!("Running getpwuid_r for user #{}", uid);
333
334 loop {
335 let r = unsafe {
336 libc::getpwuid_r(uid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result)
337 };
338
339 if r != libc::ERANGE {
340 break;
341 }
342
343 let newsize = buf.len().checked_mul(2)?;
344 buf.resize(newsize, 0);
345 }
346
347 if result.is_null() {
348 // There is no such user, or an error has occurred.
349 // errno gets set if there’s an error.
350 return None;
351 }
352
353 if result != &mut passwd {
354 // The result of getpwuid_r should be its input passwd.
355 return None;
356 }
357
358 let user = unsafe { passwd_to_user(result.read()) };
359 Some(user)
360 }
361
362 /// Searches for a `User` with the given username in the system’s user database.
363 /// Returns it if one is found, otherwise returns `None`.
364 ///
365 /// # libc functions used
366 ///
367 /// - [`getpwnam_r`](https://docs.rs/libc/*/libc/fn.getpwnam_r.html)
368 ///
369 /// # Examples
370 ///
371 /// ```
372 /// use users::get_user_by_name;
373 ///
374 /// match get_user_by_name("stevedore") {
375 /// Some(user) => println!("Found user #{}", user.uid()),
376 /// None => println!("User not found"),
377 /// }
378 /// ```
get_user_by_name<S: AsRef<OsStr> + ?Sized>(username: &S) -> Option<User>379 pub fn get_user_by_name<S: AsRef<OsStr> + ?Sized>(username: &S) -> Option<User> {
380 let username = match CString::new(username.as_ref().as_bytes()) {
381 Ok(u) => u,
382 Err(_) => {
383 // The username that was passed in contained a null character,
384 // which will match no usernames.
385 return None;
386 }
387 };
388
389 let mut passwd = unsafe { mem::zeroed::<c_passwd>() };
390 let mut buf = vec![0; 2048];
391 let mut result = ptr::null_mut::<c_passwd>();
392
393 #[cfg(feature = "logging")]
394 trace!("Running getpwnam_r for user {:?}", username.as_ref());
395
396 loop {
397 let r = unsafe {
398 libc::getpwnam_r(username.as_ptr(), &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result)
399 };
400
401 if r != libc::ERANGE {
402 break;
403 }
404
405 let newsize = buf.len().checked_mul(2)?;
406 buf.resize(newsize, 0);
407 }
408
409 if result.is_null() {
410 // There is no such user, or an error has occurred.
411 // errno gets set if there’s an error.
412 return None;
413 }
414
415 if result != &mut passwd {
416 // The result of getpwnam_r should be its input passwd.
417 return None;
418 }
419
420 let user = unsafe { passwd_to_user(result.read()) };
421 Some(user)
422 }
423
424 /// Searches for a `Group` with the given ID in the system’s group database.
425 /// Returns it if one is found, otherwise returns `None`.
426 ///
427 /// # libc functions used
428 ///
429 /// - [`getgrgid_r`](https://docs.rs/libc/*/libc/fn.getgrgid_r.html)
430 ///
431 /// # Examples
432 ///
433 /// ```
434 /// use users::get_group_by_gid;
435 ///
436 /// match get_group_by_gid(102) {
437 /// Some(group) => println!("Found group {:?}", group.name()),
438 /// None => println!("Group not found"),
439 /// }
440 /// ```
get_group_by_gid(gid: gid_t) -> Option<Group>441 pub fn get_group_by_gid(gid: gid_t) -> Option<Group> {
442 let mut passwd = unsafe { mem::zeroed::<c_group>() };
443 let mut buf = vec![0; 2048];
444 let mut result = ptr::null_mut::<c_group>();
445
446 #[cfg(feature = "logging")]
447 trace!("Running getgruid_r for group #{}", gid);
448
449 loop {
450 let r = unsafe {
451 libc::getgrgid_r(gid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result)
452 };
453
454 if r != libc::ERANGE {
455 break;
456 }
457
458 let newsize = buf.len().checked_mul(2)?;
459 buf.resize(newsize, 0);
460 }
461
462 if result.is_null() {
463 // There is no such group, or an error has occurred.
464 // errno gets set if there’s an error.
465 return None;
466 }
467
468 if result != &mut passwd {
469 // The result of getgrgid_r should be its input struct.
470 return None;
471 }
472
473 let group = unsafe { struct_to_group(result.read()) };
474 Some(group)
475 }
476
477 /// Searches for a `Group` with the given group name in the system’s group database.
478 /// Returns it if one is found, otherwise returns `None`.
479 ///
480 /// # libc functions used
481 ///
482 /// - [`getgrnam_r`](https://docs.rs/libc/*/libc/fn.getgrnam_r.html)
483 ///
484 /// # Examples
485 ///
486 /// ```
487 /// use users::get_group_by_name;
488 ///
489 /// match get_group_by_name("db-access") {
490 /// Some(group) => println!("Found group #{}", group.gid()),
491 /// None => println!("Group not found"),
492 /// }
493 /// ```
get_group_by_name<S: AsRef<OsStr> + ?Sized>(groupname: &S) -> Option<Group>494 pub fn get_group_by_name<S: AsRef<OsStr> + ?Sized>(groupname: &S) -> Option<Group> {
495 let groupname = match CString::new(groupname.as_ref().as_bytes()) {
496 Ok(u) => u,
497 Err(_) => {
498 // The groupname that was passed in contained a null character,
499 // which will match no usernames.
500 return None;
501 }
502 };
503
504 let mut group = unsafe { mem::zeroed::<c_group>() };
505 let mut buf = vec![0; 2048];
506 let mut result = ptr::null_mut::<c_group>();
507
508 #[cfg(feature = "logging")]
509 trace!("Running getgrnam_r for group {:?}", groupname.as_ref());
510
511 loop {
512 let r = unsafe {
513 libc::getgrnam_r(groupname.as_ptr(), &mut group, buf.as_mut_ptr(), buf.len(), &mut result)
514 };
515
516 if r != libc::ERANGE {
517 break;
518 }
519
520 let newsize = buf.len().checked_mul(2)?;
521 buf.resize(newsize, 0);
522 }
523
524 if result.is_null() {
525 // There is no such group, or an error has occurred.
526 // errno gets set if there’s an error.
527 return None;
528 }
529
530 if result != &mut group {
531 // The result of getgrnam_r should be its input struct.
532 return None;
533 }
534
535 let group = unsafe { struct_to_group(result.read()) };
536 Some(group)
537 }
538
539 /// Returns the user ID for the user running the process.
540 ///
541 /// # libc functions used
542 ///
543 /// - [`getuid`](https://docs.rs/libc/*/libc/fn.getuid.html)
544 ///
545 /// # Examples
546 ///
547 /// ```
548 /// use users::get_current_uid;
549 ///
550 /// println!("The ID of the current user is {}", get_current_uid());
551 /// ```
get_current_uid() -> uid_t552 pub fn get_current_uid() -> uid_t {
553 #[cfg(feature = "logging")]
554 trace!("Running getuid");
555
556 unsafe { libc::getuid() }
557 }
558
559 /// Returns the username of the user running the process.
560 ///
561 /// This function to return `None` if the current user does not exist, which
562 /// could happed if they were deleted after the program started running.
563 ///
564 /// # libc functions used
565 ///
566 /// - [`getuid`](https://docs.rs/libc/*/libc/fn.getuid.html)
567 /// - [`getpwuid_r`](https://docs.rs/libc/*/libc/fn.getpwuid_r.html)
568 ///
569 /// # Examples
570 ///
571 /// ```
572 /// use users::get_current_username;
573 ///
574 /// match get_current_username() {
575 /// Some(uname) => println!("Running as user with name {:?}", uname),
576 /// None => println!("The current user does not exist!"),
577 /// }
578 /// ```
get_current_username() -> Option<OsString>579 pub fn get_current_username() -> Option<OsString> {
580 let uid = get_current_uid();
581 let user = get_user_by_uid(uid)?;
582
583 Some(OsString::from(&*user.name_arc))
584 }
585
586 /// Returns the user ID for the effective user running the process.
587 ///
588 /// # libc functions used
589 ///
590 /// - [`geteuid`](https://docs.rs/libc/*/libc/fn.geteuid.html)
591 ///
592 /// # Examples
593 ///
594 /// ```
595 /// use users::get_effective_uid;
596 ///
597 /// println!("The ID of the effective user is {}", get_effective_uid());
598 /// ```
get_effective_uid() -> uid_t599 pub fn get_effective_uid() -> uid_t {
600 #[cfg(feature = "logging")]
601 trace!("Running geteuid");
602
603 unsafe { libc::geteuid() }
604 }
605
606 /// Returns the username of the effective user running the process.
607 ///
608 /// # libc functions used
609 ///
610 /// - [`geteuid`](https://docs.rs/libc/*/libc/fn.geteuid.html)
611 /// - [`getpwuid_r`](https://docs.rs/libc/*/libc/fn.getpwuid_r.html)
612 ///
613 /// # Examples
614 ///
615 /// ```
616 /// use users::get_effective_username;
617 ///
618 /// match get_effective_username() {
619 /// Some(uname) => println!("Running as effective user with name {:?}", uname),
620 /// None => println!("The effective user does not exist!"),
621 /// }
622 /// ```
get_effective_username() -> Option<OsString>623 pub fn get_effective_username() -> Option<OsString> {
624 let uid = get_effective_uid();
625 let user = get_user_by_uid(uid)?;
626
627 Some(OsString::from(&*user.name_arc))
628 }
629
630 /// Returns the group ID for the user running the process.
631 ///
632 /// # libc functions used
633 ///
634 /// - [`getgid`](https://docs.rs/libc/*/libc/fn.getgid.html)
635 ///
636 /// # Examples
637 ///
638 /// ```
639 /// use users::get_current_gid;
640 ///
641 /// println!("The ID of the current group is {}", get_current_gid());
642 /// ```
get_current_gid() -> gid_t643 pub fn get_current_gid() -> gid_t {
644 #[cfg(feature = "logging")]
645 trace!("Running getgid");
646
647 unsafe { libc::getgid() }
648 }
649
650 /// Returns the groupname of the user running the process.
651 ///
652 /// # libc functions used
653 ///
654 /// - [`getgid`](https://docs.rs/libc/*/libc/fn.getgid.html)
655 /// - [`getgrgid`](https://docs.rs/libc/*/libc/fn.getgrgid.html)
656 ///
657 /// # Examples
658 ///
659 /// ```
660 /// use users::get_current_groupname;
661 ///
662 /// match get_current_groupname() {
663 /// Some(gname) => println!("Running as group with name {:?}", gname),
664 /// None => println!("The current group does not exist!"),
665 /// }
666 /// ```
get_current_groupname() -> Option<OsString>667 pub fn get_current_groupname() -> Option<OsString> {
668 let gid = get_current_gid();
669 let group = get_group_by_gid(gid)?;
670
671 Some(OsString::from(&*group.name_arc))
672 }
673
674 /// Returns the group ID for the effective user running the process.
675 ///
676 /// # libc functions used
677 ///
678 /// - [`getegid`](https://docs.rs/libc/*/libc/fn.getegid.html)
679 ///
680 /// # Examples
681 ///
682 /// ```
683 /// use users::get_effective_gid;
684 ///
685 /// println!("The ID of the effective group is {}", get_effective_gid());
686 /// ```
get_effective_gid() -> gid_t687 pub fn get_effective_gid() -> gid_t {
688 #[cfg(feature = "logging")]
689 trace!("Running getegid");
690
691 unsafe { libc::getegid() }
692 }
693
694 /// Returns the groupname of the effective user running the process.
695 ///
696 /// # libc functions used
697 ///
698 /// - [`getegid`](https://docs.rs/libc/*/libc/fn.getegid.html)
699 /// - [`getgrgid`](https://docs.rs/libc/*/libc/fn.getgrgid.html)
700 ///
701 /// # Examples
702 ///
703 /// ```
704 /// use users::get_effective_groupname;
705 ///
706 /// match get_effective_groupname() {
707 /// Some(gname) => println!("Running as effective group with name {:?}", gname),
708 /// None => println!("The effective group does not exist!"),
709 /// }
710 /// ```
get_effective_groupname() -> Option<OsString>711 pub fn get_effective_groupname() -> Option<OsString> {
712 let gid = get_effective_gid();
713 let group = get_group_by_gid(gid)?;
714
715 Some(OsString::from(&*group.name_arc))
716 }
717
718 /// Returns the group access list for the current process.
719 ///
720 /// # libc functions used
721 ///
722 /// - [`getgroups`](https://docs.rs/libc/*/libc/fn.getgroups.html)
723 ///
724 /// # Errors
725 ///
726 /// This function will return `Err` when an I/O error occurs during the
727 /// `getgroups` call.
728 ///
729 /// # Examples
730 ///
731 /// ```no_run
732 /// use users::group_access_list;
733 ///
734 /// for group in group_access_list().expect("Error looking up groups") {
735 /// println!("Process can access group #{} ({:?})", group.gid(), group.name());
736 /// }
737 /// ```
group_access_list() -> io::Result<Vec<Group>>738 pub fn group_access_list() -> io::Result<Vec<Group>> {
739 let mut buff: Vec<gid_t> = vec![0; 1024];
740
741 #[cfg(feature = "logging")]
742 trace!("Running getgroups");
743
744 let res = unsafe {
745 libc::getgroups(1024, buff.as_mut_ptr())
746 };
747
748 if res < 0 {
749 Err(io::Error::last_os_error())
750 }
751 else {
752 let mut groups = buff.into_iter()
753 .filter_map(get_group_by_gid)
754 .collect::<Vec<_>>();
755 groups.dedup_by_key(|i| i.gid());
756 Ok(groups)
757 }
758 }
759
760 /// Returns groups for a provided user name and primary group id.
761 ///
762 /// # libc functions used
763 ///
764 /// - [`getgrouplist`](https://docs.rs/libc/*/libc/fn.getgrouplist.html)
765 ///
766 /// # Examples
767 ///
768 /// ```no_run
769 /// use users::get_user_groups;
770 ///
771 /// for group in get_user_groups("stevedore", 1001).expect("Error looking up groups") {
772 /// println!("User is a member of group #{} ({:?})", group.gid(), group.name());
773 /// }
774 /// ```
get_user_groups<S: AsRef<OsStr> + ?Sized>(username: &S, gid: gid_t) -> Option<Vec<Group>>775 pub fn get_user_groups<S: AsRef<OsStr> + ?Sized>(username: &S, gid: gid_t) -> Option<Vec<Group>> {
776 // MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons
777 #[cfg(all(unix, target_os="macos"))]
778 let mut buff: Vec<i32> = vec![0; 1024];
779 #[cfg(all(unix, not(target_os="macos")))]
780 let mut buff: Vec<gid_t> = vec![0; 1024];
781
782 let name = CString::new(username.as_ref().as_bytes()).unwrap();
783 let mut count = buff.len() as c_int;
784
785 #[cfg(feature = "logging")]
786 trace!("Running getgrouplist for user {:?} and group #{}", username.as_ref(), gid);
787
788 // MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons
789 #[cfg(all(unix, target_os="macos"))]
790 let res = unsafe {
791 libc::getgrouplist(name.as_ptr(), gid as i32, buff.as_mut_ptr(), &mut count)
792 };
793
794 #[cfg(all(unix, not(target_os="macos")))]
795 let res = unsafe {
796 libc::getgrouplist(name.as_ptr(), gid, buff.as_mut_ptr(), &mut count)
797 };
798
799 if res < 0 {
800 None
801 }
802 else {
803 buff.dedup();
804 buff.into_iter()
805 .filter_map(|i| get_group_by_gid(i as gid_t))
806 .collect::<Vec<_>>()
807 .into()
808 }
809 }
810
811
812
813 /// An iterator over every user present on the system.
814 struct AllUsers;
815
816 /// Creates a new iterator over every user present on the system.
817 ///
818 /// # libc functions used
819 ///
820 /// - [`getpwent`](https://docs.rs/libc/*/libc/fn.getpwent.html)
821 /// - [`setpwent`](https://docs.rs/libc/*/libc/fn.setpwent.html)
822 /// - [`endpwent`](https://docs.rs/libc/*/libc/fn.endpwent.html)
823 ///
824 /// # Safety
825 ///
826 /// This constructor is marked as `unsafe`, which is odd for a crate
827 /// that’s meant to be a safe interface. It *has* to be unsafe because
828 /// we cannot guarantee that the underlying C functions,
829 /// `getpwent`/`setpwent`/`endpwent` that iterate over the system’s
830 /// `passwd` entries, are called in a thread-safe manner.
831 ///
832 /// These functions [modify a global
833 /// state](http://man7.org/linux/man-pages/man3/getpwent.3.html#ATTRIBUTES),
834 /// and if any are used at the same time, the state could be reset,
835 /// resulting in a data race. We cannot even place it behind an internal
836 /// `Mutex`, as there is nothing stopping another `extern` function
837 /// definition from calling it!
838 ///
839 /// So to iterate all users, construct the iterator inside an `unsafe`
840 /// block, then make sure to not make a new instance of it until
841 /// iteration is over.
842 ///
843 /// # Examples
844 ///
845 /// ```
846 /// use users::all_users;
847 ///
848 /// let iter = unsafe { all_users() };
849 /// for user in iter {
850 /// println!("User #{} ({:?})", user.uid(), user.name());
851 /// }
852 /// ```
all_users() -> impl Iterator<Item=User>853 pub unsafe fn all_users() -> impl Iterator<Item=User> {
854 #[cfg(feature = "logging")]
855 trace!("Running setpwent");
856
857 #[cfg(not(target_os = "android"))]
858 libc::setpwent();
859 AllUsers
860 }
861
862 impl Drop for AllUsers {
863 #[cfg(target_os = "android")]
drop(&mut self)864 fn drop(&mut self) {
865 // nothing to do here
866 }
867
868 #[cfg(not(target_os = "android"))]
drop(&mut self)869 fn drop(&mut self) {
870 #[cfg(feature = "logging")]
871 trace!("Running endpwent");
872
873 unsafe { libc::endpwent() };
874 }
875 }
876
877 impl Iterator for AllUsers {
878 type Item = User;
879
880 #[cfg(target_os = "android")]
next(&mut self) -> Option<User>881 fn next(&mut self) -> Option<User> {
882 None
883 }
884
885 #[cfg(not(target_os = "android"))]
next(&mut self) -> Option<User>886 fn next(&mut self) -> Option<User> {
887 #[cfg(feature = "logging")]
888 trace!("Running getpwent");
889
890 let result = unsafe { libc::getpwent() };
891
892 if result.is_null() {
893 None
894 }
895 else {
896 let user = unsafe { passwd_to_user(result.read()) };
897 Some(user)
898 }
899 }
900 }
901
902
903
904 /// OS-specific extensions to users and groups.
905 ///
906 /// Every OS has a different idea of what data a user or a group comes with.
907 /// Although they all provide a *username*, some OS’ users have an *actual name*
908 /// too, or a set of permissions or directories or timestamps associated with
909 /// them.
910 ///
911 /// This module provides extension traits for users and groups that allow
912 /// implementors of this library to access this data *as long as a trait is
913 /// available*, which requires the OS they’re using to support this data.
914 ///
915 /// It’s the same method taken by `Metadata` in the standard Rust library,
916 /// which has a few cross-platform fields and many more OS-specific fields:
917 /// traits in `std::os` provides access to any data that is not guaranteed to
918 /// be there in the actual struct.
919 pub mod os {
920
921 /// Extensions to users and groups for Unix platforms.
922 ///
923 /// Although the `passwd` struct is common among Unix systems, its actual
924 /// format can vary. See the definitions in the `base` module to check which
925 /// fields are actually present.
926 #[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 = "solaris"))]
927 pub mod unix {
928 use std::ffi::{OsStr, OsString};
929 use std::path::{Path, PathBuf};
930
931 use super::super::{c_passwd, c_group, members, from_raw_buf, Group};
932
933 /// Unix-specific extensions for `User`s.
934 pub trait UserExt {
935
936 /// Returns a path to this user’s home directory.
home_dir(&self) -> &Path937 fn home_dir(&self) -> &Path;
938
939 /// Sets this user value’s home directory to the given string.
940 /// Can be used to construct test users, which by default come with a
941 /// dummy home directory string.
with_home_dir<S: AsRef<OsStr> + ?Sized>(self, home_dir: &S) -> Self942 fn with_home_dir<S: AsRef<OsStr> + ?Sized>(self, home_dir: &S) -> Self;
943
944 /// Returns a path to this user’s shell.
shell(&self) -> &Path945 fn shell(&self) -> &Path;
946
947 /// Sets this user’s shell path to the given string.
948 /// Can be used to construct test users, which by default come with a
949 /// dummy shell field.
with_shell<S: AsRef<OsStr> + ?Sized>(self, shell: &S) -> Self950 fn with_shell<S: AsRef<OsStr> + ?Sized>(self, shell: &S) -> Self;
951
952 /// Returns the user’s encrypted password.
password(&self) -> &OsStr953 fn password(&self) -> &OsStr;
954
955 /// Sets this user’s password to the given string.
956 /// Can be used to construct tests users, which by default come with a
957 /// dummy password field.
with_password<S: AsRef<OsStr> + ?Sized>(self, password: &S) -> Self958 fn with_password<S: AsRef<OsStr> + ?Sized>(self, password: &S) -> Self;
959 }
960
961 /// Unix-specific extensions for `Group`s.
962 pub trait GroupExt {
963
964 /// Returns a slice of the list of users that are in this group as
965 /// their non-primary group.
members(&self) -> &[OsString]966 fn members(&self) -> &[OsString];
967
968 /// Adds a new member to this group.
add_member<S: AsRef<OsStr> + ?Sized>(self, name: &S) -> Self969 fn add_member<S: AsRef<OsStr> + ?Sized>(self, name: &S) -> Self;
970 }
971
972 /// Unix-specific fields for `User`s.
973 #[derive(Clone, Debug)]
974 pub struct UserExtras {
975
976 /// The path to the user’s home directory.
977 pub home_dir: PathBuf,
978
979 /// The path to the user’s shell.
980 pub shell: PathBuf,
981
982 /// The user’s encrypted password.
983 pub password: OsString,
984 }
985
986 impl Default for UserExtras {
default() -> Self987 fn default() -> Self {
988 Self {
989 home_dir: "/var/empty".into(),
990 shell: "/bin/false".into(),
991 password: "*".into(),
992 }
993 }
994 }
995
996 impl UserExtras {
997 /// Extract the OS-specific fields from the C `passwd` struct that
998 /// we just read.
from_passwd(passwd: c_passwd) -> Self999 pub(crate) unsafe fn from_passwd(passwd: c_passwd) -> Self {
1000 #[cfg(target_os = "android")]
1001 {
1002 Default::default()
1003 }
1004 #[cfg(not(target_os = "android"))]
1005 {
1006 let home_dir = from_raw_buf::<OsString>(passwd.pw_dir).into();
1007 let shell = from_raw_buf::<OsString>(passwd.pw_shell).into();
1008 let password = from_raw_buf::<OsString>(passwd.pw_passwd);
1009
1010 Self { home_dir, shell, password }
1011 }
1012 }
1013 }
1014
1015 #[cfg(any(target_os = "linux", target_os = "android", target_os = "solaris"))]
1016 use super::super::User;
1017
1018 #[cfg(any(target_os = "linux", target_os = "android", target_os = "solaris"))]
1019 impl UserExt for User {
home_dir(&self) -> &Path1020 fn home_dir(&self) -> &Path {
1021 Path::new(&self.extras.home_dir)
1022 }
1023
with_home_dir<S: AsRef<OsStr> + ?Sized>(mut self, home_dir: &S) -> Self1024 fn with_home_dir<S: AsRef<OsStr> + ?Sized>(mut self, home_dir: &S) -> Self {
1025 self.extras.home_dir = home_dir.into();
1026 self
1027 }
1028
shell(&self) -> &Path1029 fn shell(&self) -> &Path {
1030 Path::new(&self.extras.shell)
1031 }
1032
with_shell<S: AsRef<OsStr> + ?Sized>(mut self, shell: &S) -> Self1033 fn with_shell<S: AsRef<OsStr> + ?Sized>(mut self, shell: &S) -> Self {
1034 self.extras.shell = shell.into();
1035 self
1036 }
1037
password(&self) -> &OsStr1038 fn password(&self) -> &OsStr {
1039 &self.extras.password
1040 }
1041
with_password<S: AsRef<OsStr> + ?Sized>(mut self, password: &S) -> Self1042 fn with_password<S: AsRef<OsStr> + ?Sized>(mut self, password: &S) -> Self {
1043 self.extras.password = password.into();
1044 self
1045 }
1046 }
1047
1048 /// Unix-specific fields for `Group`s.
1049 #[derive(Clone, Default, Debug)]
1050 pub struct GroupExtras {
1051
1052 /// Vector of usernames that are members of this group.
1053 pub members: Vec<OsString>,
1054 }
1055
1056 impl GroupExtras {
1057 /// Extract the OS-specific fields from the C `group` struct that
1058 /// we just read.
from_struct(group: c_group) -> Self1059 pub(crate) unsafe fn from_struct(group: c_group) -> Self {
1060 Self { members: members(group.gr_mem) }
1061 }
1062 }
1063
1064 impl GroupExt for Group {
members(&self) -> &[OsString]1065 fn members(&self) -> &[OsString] {
1066 &*self.extras.members
1067 }
1068
add_member<S: AsRef<OsStr> + ?Sized>(mut self, member: &S) -> Self1069 fn add_member<S: AsRef<OsStr> + ?Sized>(mut self, member: &S) -> Self {
1070 self.extras.members.push(member.into());
1071 self
1072 }
1073 }
1074 }
1075
1076 /// Extensions to users and groups for BSD platforms.
1077 ///
1078 /// These platforms have `change` and `expire` fields in their `passwd`
1079 /// C structs.
1080 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))]
1081 pub mod bsd {
1082 use std::ffi::OsStr;
1083 use std::path::Path;
1084 use libc::time_t;
1085 use super::super::{c_passwd, User};
1086
1087 /// BSD-specific fields for `User`s.
1088 #[derive(Clone, Debug)]
1089 pub struct UserExtras {
1090
1091 /// Fields specific to Unix, rather than just BSD. (This struct is
1092 /// a superset, so it has to have all the other fields in it, too).
1093 pub extras: super::unix::UserExtras,
1094
1095 /// Password change time.
1096 pub change: time_t,
1097
1098 /// Password expiry time.
1099 pub expire: time_t,
1100 }
1101
1102 impl UserExtras {
1103 /// Extract the OS-specific fields from the C `passwd` struct that
1104 /// we just read.
from_passwd(passwd: c_passwd) -> Self1105 pub(crate) unsafe fn from_passwd(passwd: c_passwd) -> Self {
1106 Self {
1107 change: passwd.pw_change,
1108 expire: passwd.pw_expire,
1109 extras: super::unix::UserExtras::from_passwd(passwd),
1110 }
1111 }
1112 }
1113
1114 impl super::unix::UserExt for User {
home_dir(&self) -> &Path1115 fn home_dir(&self) -> &Path {
1116 Path::new(&self.extras.extras.home_dir)
1117 }
1118
with_home_dir<S: AsRef<OsStr> + ?Sized>(mut self, home_dir: &S) -> Self1119 fn with_home_dir<S: AsRef<OsStr> + ?Sized>(mut self, home_dir: &S) -> Self {
1120 self.extras.extras.home_dir = home_dir.into();
1121 self
1122 }
1123
shell(&self) -> &Path1124 fn shell(&self) -> &Path {
1125 Path::new(&self.extras.extras.shell)
1126 }
1127
with_shell<S: AsRef<OsStr> + ?Sized>(mut self, shell: &S) -> Self1128 fn with_shell<S: AsRef<OsStr> + ?Sized>(mut self, shell: &S) -> Self {
1129 self.extras.extras.shell = shell.into();
1130 self
1131 }
1132
password(&self) -> &OsStr1133 fn password(&self) -> &OsStr {
1134 &self.extras.extras.password
1135 }
1136
with_password<S: AsRef<OsStr> + ?Sized>(mut self, password: &S) -> Self1137 fn with_password<S: AsRef<OsStr> + ?Sized>(mut self, password: &S) -> Self {
1138 self.extras.extras.password = password.into();
1139 self
1140 }
1141 }
1142
1143 /// BSD-specific accessors for `User`s.
1144 pub trait UserExt {
1145
1146 /// Returns this user’s password change timestamp.
password_change_time(&self) -> time_t1147 fn password_change_time(&self) -> time_t;
1148
1149 /// Returns this user’s password expiry timestamp.
password_expire_time(&self) -> time_t1150 fn password_expire_time(&self) -> time_t;
1151 }
1152
1153 impl UserExt for User {
password_change_time(&self) -> time_t1154 fn password_change_time(&self) -> time_t {
1155 self.extras.change
1156 }
1157
password_expire_time(&self) -> time_t1158 fn password_expire_time(&self) -> time_t {
1159 self.extras.expire
1160 }
1161 }
1162
1163 impl Default for UserExtras {
default() -> Self1164 fn default() -> Self {
1165 Self {
1166 extras: super::unix::UserExtras::default(),
1167 change: 0,
1168 expire: 0,
1169 }
1170 }
1171 }
1172 }
1173
1174 /// Any extra fields on a `User` specific to the current platform.
1175 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))]
1176 pub type UserExtras = bsd::UserExtras;
1177
1178 /// Any extra fields on a `User` specific to the current platform.
1179 #[cfg(any(target_os = "linux", target_os = "android", target_os = "solaris"))]
1180 pub type UserExtras = unix::UserExtras;
1181
1182 /// Any extra fields on a `Group` specific to the current platform.
1183 #[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 = "solaris"))]
1184 pub type GroupExtras = unix::GroupExtras;
1185 }
1186
1187
1188 #[cfg(test)]
1189 mod test {
1190 use super::*;
1191
1192 #[test]
uid()1193 fn uid() {
1194 get_current_uid();
1195 }
1196
1197 #[test]
username()1198 fn username() {
1199 let uid = get_current_uid();
1200 assert_eq!(&*get_current_username().unwrap(), &*get_user_by_uid(uid).unwrap().name());
1201 }
1202
1203 #[test]
uid_for_username()1204 fn uid_for_username() {
1205 let uid = get_current_uid();
1206 let user = get_user_by_uid(uid).unwrap();
1207 assert_eq!(user.uid, uid);
1208 }
1209
1210 #[test]
username_for_uid_for_username()1211 fn username_for_uid_for_username() {
1212 let uid = get_current_uid();
1213 let user = get_user_by_uid(uid).unwrap();
1214 let user2 = get_user_by_uid(user.uid).unwrap();
1215 assert_eq!(user2.uid, uid);
1216 }
1217
1218 #[test]
user_info()1219 fn user_info() {
1220 use base::os::unix::UserExt;
1221
1222 let uid = get_current_uid();
1223 let user = get_user_by_uid(uid).unwrap();
1224 // Not a real test but can be used to verify correct results
1225 // Use with --nocapture on test executable to show output
1226 println!("HOME={:?}, SHELL={:?}, PASSWD={:?}",
1227 user.home_dir(), user.shell(), user.password());
1228 }
1229
1230 #[test]
user_by_name()1231 fn user_by_name() {
1232 // We cannot really test for arbitrary user as they might not exist on the machine
1233 // Instead the name of the current user is used
1234 let name = get_current_username().unwrap();
1235 let user_by_name = get_user_by_name(&name);
1236 assert!(user_by_name.is_some());
1237 assert_eq!(user_by_name.unwrap().name(), &*name);
1238
1239 // User names containing '\0' cannot be used (for now)
1240 let user = get_user_by_name("user\0");
1241 assert!(user.is_none());
1242 }
1243
1244 #[test]
user_get_groups()1245 fn user_get_groups() {
1246 let uid = get_current_uid();
1247 let user = get_user_by_uid(uid).unwrap();
1248 let groups = user.groups().unwrap();
1249 println!("Groups: {:?}", groups);
1250 assert!(groups.len() > 0);
1251 }
1252
1253 #[test]
group_by_name()1254 fn group_by_name() {
1255 // We cannot really test for arbitrary groups as they might not exist on the machine
1256 // Instead the primary group of the current user is used
1257 let cur_uid = get_current_uid();
1258 let cur_user = get_user_by_uid(cur_uid).unwrap();
1259 let cur_group = get_group_by_gid(cur_user.primary_group).unwrap();
1260 let group_by_name = get_group_by_name(&cur_group.name());
1261
1262 assert!(group_by_name.is_some());
1263 assert_eq!(group_by_name.unwrap().name(), cur_group.name());
1264
1265 // Group names containing '\0' cannot be used (for now)
1266 let group = get_group_by_name("users\0");
1267 assert!(group.is_none());
1268 }
1269 }
1270