1#![doc = include_str!("../examples/example.rs")]
7#![cfg_attr(docsrs, feature(doc_auto_cfg))]
26
27use libpwquality_sys as sys;
28use paste::paste;
29use std::os::raw::{c_char, c_int, c_void};
30use std::{
31 ffi::{CStr, CString},
32 path::Path,
33 ptr::{null, null_mut},
34};
35
36macro_rules! define_settings {
37 ($($(#[$meta:meta])? $setting:ident),* $(,)?) => {
38 paste! {
39 #[derive(Copy, Clone, Debug)]
41 #[non_exhaustive]
42 enum Setting {
43 $(
44 $(#[$meta])?
45 $setting = sys::[<PWQ_SETTING_ $setting:snake:upper>] as isize,
46 )*
47 }
48 }
49 };
50}
51
52define_settings! {
53 DiffOk,
54 MinLength,
55 DigCredit,
56 UpCredit,
57 LowCredit,
58 OthCredit,
59 MinClass,
60 MaxRepeat,
61 DictPath,
62 MaxClassRepeat,
63 GecosCheck,
64 BadWords,
65 #[cfg(any(feature = "v1_2", feature = "vendored", feature = "vendored-cracklib"))]
66 MaxSequence,
67 #[cfg(any(feature = "v1_3", feature = "vendored", feature = "vendored-cracklib"))]
68 DictCheck,
69 #[cfg(any(feature = "v1_4", feature = "vendored", feature = "vendored-cracklib"))]
70 UserCheck,
71 #[cfg(any(feature = "v1_4", feature = "vendored", feature = "vendored-cracklib"))]
72 Enforcing,
73 #[cfg(any(feature = "v1_4_1", feature = "vendored", feature = "vendored-cracklib"))]
74 RetryTimes,
75 #[cfg(any(feature = "v1_4_1", feature = "vendored", feature = "vendored-cracklib"))]
76 EnforceRoot,
77 #[cfg(any(feature = "v1_4_1", feature = "vendored", feature = "vendored-cracklib"))]
78 LocalUsers,
79 #[cfg(any(feature = "v1_4_3", feature = "vendored", feature = "vendored-cracklib"))]
80 UserSubstr,
81}
82
83pub struct PWQError(String);
85
86impl PWQError {
87 fn new_aux(error_code: i32, aux_error: Option<*mut c_void>) -> Self {
88 unsafe {
89 let s =
90 sys::pwquality_strerror(null_mut(), 0, error_code, aux_error.unwrap_or(null_mut()))
91 .as_ref()
92 .map(|p| CStr::from_ptr(p).to_string_lossy().to_string())
93 .unwrap_or(format!("Unknown error: errcode={error_code}"));
94
95 Self(s)
96 }
97 }
98
99 fn new(error_code: i32) -> Self {
100 Self::new_aux(error_code, None)
101 }
102}
103
104impl std::error::Error for PWQError {}
105
106impl std::fmt::Debug for PWQError {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 write!(f, "PWQError: {}", self.0)
109 }
110}
111
112impl std::fmt::Display for PWQError {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(f, "{}", self.0)
115 }
116}
117
118type Result<T> = std::result::Result<T, PWQError>;
119
120macro_rules! define_getseters {
121 (#[$doc_meta:meta] $(#[$feat_meta:meta])? $func:ident, $setting:ident) => {
122 paste! {
123 $(#[$feat_meta])?
124 #[doc = " Get"]
125 #[$doc_meta]
126 pub fn [<get_ $func>] (&self) -> i32 {
127 self.get_int_value($crate::Setting::$setting)
128 }
129
130 $(#[$feat_meta])?
131 #[doc = " Set"]
132 #[$doc_meta]
133 pub fn $func(&self, value: i32) -> &Self {
134 self.set_int_value($crate::Setting::$setting, value)
135 }
136 }
137 };
138 (#[$doc_meta:meta] $(#[$feat_meta:meta])? $func:ident, $setting:ident, bool) => {
139 paste! {
140 $(#[$feat_meta])?
141 #[doc = " Get"]
142 #[$doc_meta]
143 pub fn [<get_ $func>] (&self) -> bool {
144 self.get_int_value($crate::Setting::$setting) != 0
145 }
146
147 $(#[$feat_meta])?
148 #[doc = " Set"]
149 #[$doc_meta]
150 pub fn $func(&self, value: bool) -> &Self {
151 self.set_int_value($crate::Setting::$setting, i32::from(value))
152 }
153 }
154 };
155}
156
157pub struct PWQuality {
159 pwq: *mut sys::pwquality_settings_t,
160}
161
162impl PWQuality {
163 pub fn new() -> Result<Self> {
165 unsafe {
166 let ptr = sys::pwquality_default_settings();
167
168 ptr.as_ref()
169 .ok_or(PWQError::new(sys::PWQ_ERROR_MEM_ALLOC))
170 .map(|_| Self { pwq: ptr })
171 }
172 }
173
174 pub fn read_default_config(&self) -> Result<&Self> {
176 self.read_optional_config::<&str>(None)
177 }
178
179 pub fn read_config<P: AsRef<Path>>(&self, path: P) -> Result<&Self> {
181 self.read_optional_config(Some(path))
182 }
183
184 fn read_optional_config<P: AsRef<Path>>(&self, path: Option<P>) -> Result<&Self> {
186 let mut aux_error = null_mut();
187 let c_path = path
188 .map(|p| CString::new(p.as_ref().to_string_lossy().to_string()))
189 .transpose()
190 .unwrap();
191
192 let ret = unsafe {
193 sys::pwquality_read_config(
194 self.pwq,
195 c_path.as_ref().map(|s| s.as_ptr()).unwrap_or(null()),
196 &mut aux_error,
197 )
198 };
199
200 if ret == 0 {
201 Ok(self)
202 } else {
203 Err(PWQError::new_aux(ret, Some(aux_error)))
204 }
205 }
206
207 fn set_int_value(&self, setting: Setting, value: i32) -> &Self {
209 let ret = unsafe { sys::pwquality_set_int_value(self.pwq, setting as c_int, value) };
210
211 debug_assert!(ret == 0);
212
213 self
214 }
215
216 fn get_int_value(&self, setting: Setting) -> i32 {
218 let mut value: i32 = 0;
219 let ret = unsafe { sys::pwquality_get_int_value(self.pwq, setting as c_int, &mut value) };
220
221 debug_assert!(ret == 0);
222
223 value
224 }
225
226 fn set_str_value(&self, setting: Setting, value: &str) -> Result<&Self> {
228 let value = CString::new(value).unwrap();
229 let ret =
230 unsafe { sys::pwquality_set_str_value(self.pwq, setting as c_int, value.as_ptr()) };
231
232 if ret == 0 {
233 Ok(self)
234 } else {
235 Err(PWQError::new(ret))
236 }
237 }
238
239 fn get_str_value(&self, setting: Setting) -> Result<String> {
241 let mut ptr: *const c_char = null();
242
243 let ret = unsafe { sys::pwquality_get_str_value(self.pwq, setting as c_int, &mut ptr) };
244 if ret == 0 {
245 let s = unsafe {
246 ptr.as_ref()
247 .map(|p| CStr::from_ptr(p).to_string_lossy().to_string())
248 .unwrap_or_default()
249 };
250
251 Ok(s)
252 } else {
253 Err(PWQError::new(ret))
254 }
255 }
256
257 pub fn generate(&self, bits: i32) -> Result<String> {
259 let mut ptr: *mut c_char = null_mut();
260 unsafe {
261 let ret = sys::pwquality_generate(self.pwq, bits, &mut ptr);
262
263 ptr.as_ref().ok_or(PWQError::new(ret)).map(|p| {
264 let s = CStr::from_ptr(p).to_string_lossy().to_string();
265
266 libc::free(ptr.cast());
268
269 s
270 })
271 }
272 }
273
274 pub fn check(
276 &self,
277 password: &str,
278 old_password: Option<&str>,
279 user: Option<&str>,
280 ) -> Result<i32> {
281 let c_password = CString::new(password).unwrap();
282 let mut aux_error = null_mut();
283
284 let c_old_password = old_password.map(CString::new).transpose().unwrap();
285 let c_user = user.map(CString::new).transpose().unwrap();
286
287 let ret = unsafe {
288 sys::pwquality_check(
289 self.pwq,
290 c_password.as_ptr(),
291 c_old_password
292 .as_ref()
293 .map(|s| s.as_ptr())
294 .unwrap_or(null()),
295 c_user.as_ref().map(|s| s.as_ptr()).unwrap_or(null()),
296 &mut aux_error,
297 )
298 };
299
300 if ret < 0 {
301 Err(PWQError::new_aux(ret, Some(aux_error)))
302 } else {
303 Ok(ret)
304 }
305 }
306
307 define_getseters! {
308 #[doc = " the minimum number of characters in the new password that must not be present in the old password."]
309 min_diff,
310 DiffOk
311 }
312
313 define_getseters! {
314 #[doc = " the minimum acceptable size for the new password."]
315 min_length,
316 MinLength
317 }
318
319 define_getseters! {
320 #[doc = " the maximum credit for having digits in the new password."]
321 digit_credit,
322 DigCredit
323 }
324
325 define_getseters! {
326 #[doc = " the maximum credit for having uppercase characters in the new password."]
327 uppercase_credit,
328 UpCredit
329 }
330
331 define_getseters! {
332 #[doc = " the maximum credit for having lowercase characters in the new password."]
333 lowercase_credit,
334 LowCredit
335 }
336
337 define_getseters! {
338 #[doc = " the maximum credit for having other characters in the new password."]
339 other_credit,
340 OthCredit
341 }
342
343 define_getseters! {
344 #[doc = " the minimum number of required classes of characters for the new password."]
345 min_class,
346 MinClass
347 }
348
349 define_getseters! {
350 #[doc = " the maximum number of allowed same consecutive characters in the new password."]
351 max_repeat,
352 MaxRepeat
353 }
354
355 define_getseters! {
356 #[doc = " the maximum number of allowed consecutive characters of the same class in the new password."]
357 max_class_repeat,
358 MaxClassRepeat
359 }
360
361 define_getseters! {
362 #[doc = " the maximum length of monotonic character sequences in the new password."]
363 #[cfg(any(feature = "v1_2", feature = "vendored", feature = "vendored-cracklib"))]
364 max_sequence,
365 MaxSequence
366 }
367
368 define_getseters! {
369 #[doc = " whether to perform the passwd GECOS field check."]
370 gecos_check,
371 GecosCheck,
372 bool
373 }
374
375 define_getseters! {
376 #[doc = " whether to perform the dictionary check."]
377 #[cfg(any(feature = "v1_3", feature = "vendored", feature = "vendored-cracklib"))]
378 dict_check,
379 DictCheck,
380 bool
381 }
382
383 define_getseters! {
384 #[doc = " whether to perform the user name check."]
385 #[cfg(any(feature = "v1_4", feature = "vendored", feature = "vendored-cracklib"))]
386 user_check,
387 UserCheck,
388 bool
389 }
390
391 define_getseters! {
392 #[doc = " whether the check is enforced."]
393 #[cfg(any(feature = "v1_4", feature = "vendored", feature = "vendored-cracklib"))]
394 enforcing,
395 Enforcing,
396 bool
397 }
398
399 define_getseters! {
400 #[doc = " maximum retries for the password change should be allowed."]
401 #[cfg(any(feature = "v1_4_1", feature = "vendored", feature = "vendored-cracklib"))]
402 retry_times,
403 RetryTimes
404 }
405
406 define_getseters! {
407 #[doc = " whether the check is enforced for root."]
408 #[cfg(any(feature = "v1_4_1", feature = "vendored", feature = "vendored-cracklib"))]
409 enforce_for_root,
410 EnforceRoot,
411 bool
412 }
413
414 define_getseters! {
415 #[doc = " whether to check local users only."]
416 #[cfg(any(feature = "v1_4_1", feature = "vendored", feature = "vendored-cracklib"))]
417 local_users_only,
418 LocalUsers,
419 bool
420 }
421
422 #[cfg(any(
423 feature = "v1_4_5",
424 feature = "vendored",
425 feature = "vendored-cracklib"
426 ))]
427 pub fn get_user_substr(&self) -> i32 {
429 self.get_int_value(crate::Setting::UserSubstr)
430 }
431
432 #[cfg(any(
433 feature = "v1_4_3",
434 feature = "vendored",
435 feature = "vendored-cracklib"
436 ))]
437 pub fn user_substr(&self, value: i32) -> &Self {
439 self.set_int_value(crate::Setting::UserSubstr, value)
440 }
441
442 pub fn bad_words<W>(&self, words: W) -> Result<&Self>
444 where
445 W: IntoIterator,
446 W::Item: ToString,
447 {
448 let s = words
449 .into_iter()
450 .map(|s| s.to_string())
451 .collect::<Vec<String>>()
452 .join(" ");
453
454 self.set_str_value(Setting::BadWords, &s)
455 }
456
457 pub fn get_bad_words(&self) -> Result<Vec<String>> {
459 self.get_str_value(Setting::BadWords)
460 .map(|s| s.split_whitespace().map(String::from).collect())
461 }
462
463 pub fn dict_path(&self, path: &str) -> Result<&Self> {
465 self.set_str_value(Setting::DictPath, path)
466 }
467
468 pub fn get_dict_path(&self) -> Result<String> {
470 self.get_str_value(Setting::DictPath)
471 }
472}
473
474impl Drop for PWQuality {
475 fn drop(&mut self) {
477 unsafe { sys::pwquality_free_settings(self.pwq) }
478 }
479}