passwd_rs/
shadow.rs

1use std::ffi::{CStr, CString};
2use std::io;
3use std::os::raw::*;
4
5/// Account status
6pub enum AccountStatus {
7	/// Account is enabled/active and password is stored in `String` attached to `Active`
8	Active(String),
9	/// Account has no password
10	NoPassword,
11	/// A regular user can't enter that account
12	NoLogin,
13	/// Unknown status
14	Unknown,
15}
16impl AccountStatus {
17	/// Convert `AccountStatus` to `String`
18	pub fn to_string(&self) -> String {
19		match self {
20			Self::Unknown => "Unknown password".to_string(),
21			Self::Active(p) => p.to_string(),
22			Self::NoLogin => "Disabled account".to_string(),
23			Self::NoPassword => "No password".to_string(),
24		}
25	}
26}
27
28/// Struct for information about shadow passwords
29pub struct Shadow {
30	/// Account name
31	pub name: String,
32	/// Password. Stored in `AccountStatus`
33	pub passwd: AccountStatus,
34
35	/// Date of last change (measured in days since 1970-01-01 00:00:00 +0000 (UTC))
36	pub last_chage: c_long,
37	/// Min number of days between changes
38	pub min: c_long,
39	/// Max number of days between changes
40	pub max: c_long,
41
42	/// Number of days before password expires to warn user to change it
43	pub warn: c_long,
44	/// Number of days after password expires until account is disabled
45	pub inactive: c_long,
46	/// Date when account expires (measured in days since 1970-01-01 00:00:00 +0000 (UTC))
47	pub expires: c_long,
48}
49
50// Raw
51#[repr(C)]
52struct Spwd {
53	sp_namp: *const c_char,
54	sp_pwdp: *const c_char,
55
56	sp_lstchg: c_long,
57	sp_min: c_long,
58	sp_max: c_long,
59	sp_warn: c_long,
60
61	sp_inact: c_long,
62	sp_expire: c_long,
63
64	sp_flag: c_ulong,
65}
66extern "C" {
67	fn getspnam(name: *const c_char) -> *const Spwd;
68}
69
70impl Shadow {
71	unsafe fn convert(raw: *const Spwd) -> io::Result<Self> {
72		if raw == std::ptr::null() {
73			return Err(io::Error::last_os_error());
74		}
75
76		let name = match CStr::from_ptr((*raw).sp_namp).to_str() {
77			Ok(o) => o.to_string(),
78			Err(_) => return Err(io::Error::from(io::ErrorKind::InvalidData)),
79		};
80
81		let passwd: AccountStatus = {
82			match CStr::from_ptr((*raw).sp_pwdp).to_str() {
83				Err(_) => AccountStatus::Unknown,
84				Ok(o) => {
85					if o.eq("*") {
86						AccountStatus::NoLogin
87					} else if o.eq("!") {
88						AccountStatus::NoPassword
89					} else {
90						AccountStatus::Active(o.to_string())
91					}
92				}
93			}
94		};
95
96		Ok(Self {
97			name,
98			passwd,
99			last_chage: (*raw).sp_lstchg,
100			min: (*raw).sp_min,
101			max: (*raw).sp_max,
102			warn: (*raw).sp_warn,
103			inactive: (*raw).sp_inact,
104			expires: (*raw).sp_expire,
105		})
106	}
107
108	/// Get information about the shadow passwords of the specified user.
109	pub fn new_from_username(name: &str) -> io::Result<Self> {
110		unsafe {
111			let ptr = match CString::new(name) {
112				Err(_) => return Err(io::Error::from(io::ErrorKind::InvalidData)),
113				Ok(o) => o,
114			};
115
116			Self::convert(getspnam(ptr.as_ptr()))
117		}
118	}
119}