1 /*
2 * Copyright (c) 2000-2003,2005,2012,2016,2019,2021 by Solar Designer
3 * Copyright (c) 2017,2018 by Dmitry V. Levin
4 * Copyright (c) 2017,2018 by Oleg Solovyov
5 * See LICENSE
6 */
7
8 #if defined(__FreeBSD__) || defined(__DragonFly__)
9 /* For vsnprintf(3) */
10 #define _XOPEN_SOURCE 600
11 #else
12 #define _XOPEN_SOURCE 500
13 #define _XOPEN_SOURCE_EXTENDED
14 #define _XOPEN_VERSION 500
15 #define _DEFAULT_SOURCE
16 #endif
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <stdarg.h>
20 #include <string.h>
21 #include <limits.h>
22 #include <unistd.h>
23 #include <pwd.h>
24 #ifdef HAVE_SHADOW
25 #include <shadow.h>
26 #endif
27 #ifdef HAVE_LIBAUDIT
28 #include <security/pam_modutil.h>
29 #include <libaudit.h>
30 #endif
31
32 #define PAM_SM_PASSWORD
33 #ifndef LINUX_PAM
34 #include <security/pam_appl.h>
35 #endif
36 #include <security/pam_modules.h>
37
38 #include "pam_macros.h"
39
40 #if !defined(PAM_EXTERN) && !defined(PAM_STATIC)
41 #define PAM_EXTERN extern
42 #endif
43
44 #if !defined(PAM_AUTHTOK_RECOVERY_ERR) && defined(PAM_AUTHTOK_RECOVER_ERR)
45 #define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR
46 #endif
47
48 #if (defined(__sun) || defined(__hpux)) && \
49 !defined(LINUX_PAM) && !defined(_OPENPAM)
50 /* Sun's PAM doesn't use const here, while Linux-PAM and OpenPAM do */
51 #define lo_const
52 #else
53 #define lo_const const
54 #endif
55 #ifdef _OPENPAM
56 /* OpenPAM doesn't use const here, while Linux-PAM does */
57 #define l_const
58 #else
59 #define l_const lo_const
60 #endif
61 typedef lo_const void *pam_item_t;
62
63 #include "passwdqc.h"
64
65 #include "passwdqc_i18n.h"
66
67 #define PROMPT_OLDPASS \
68 _("Enter current password: ")
69 #define PROMPT_NEWPASS1 \
70 _("Enter new password: ")
71 #define PROMPT_NEWPASS2 \
72 _("Re-type new password: ")
73
74 #define MESSAGE_MISCONFIGURED \
75 _("System configuration error. Please contact your administrator.")
76 #define MESSAGE_INVALID_OPTION \
77 "pam_passwdqc: %s."
78 #define MESSAGE_INTRO_PASSWORD \
79 _("\nYou can now choose the new password.\n")
80 #define MESSAGE_INTRO_BOTH \
81 _("\nYou can now choose the new password or passphrase.\n")
82
83 #define MESSAGE_EXPLAIN_PASSWORD_1_CLASS(count) \
84 P3_( \
85 "A good password should be a mix of upper and lower case letters, digits, and\n" \
86 "other characters. You can use a password containing at least %d character.\n", \
87 \
88 "A good password should be a mix of upper and lower case letters, digits, and\n" \
89 "other characters. You can use a password containing at least %d characters.\n", \
90 count), (count)
91
92 #define MESSAGE_EXPLAIN_PASSWORD_N_CLASSES(count) \
93 P3_( \
94 "A valid password should be a mix of upper and lower case letters, digits, and\n" \
95 "other characters. You can use a password containing at least %d character\n" \
96 "from at least %d of these 4 classes.\n" \
97 "An upper case letter that begins the password and a digit that ends it do not\n" \
98 "count towards the number of character classes used.\n", \
99 \
100 "A valid password should be a mix of upper and lower case letters, digits, and\n" \
101 "other characters. You can use a password containing at least %d characters\n" \
102 "from at least %d of these 4 classes.\n" \
103 "An upper case letter that begins the password and a digit that ends it do not\n" \
104 "count towards the number of character classes used.\n", \
105 count), (count)
106
107 #define MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES(count) \
108 P3_( \
109 "A valid password should be a mix of upper and lower case letters, digits, and\n" \
110 "other characters. You can use a password containing at least %d character\n" \
111 "from all of these classes.\n" \
112 "An upper case letter that begins the password and a digit that ends it do not\n" \
113 "count towards the number of character classes used.\n", \
114 \
115 "A valid password should be a mix of upper and lower case letters, digits, and\n" \
116 "other characters. You can use a password containing at least %d characters\n" \
117 "from all of these classes.\n" \
118 "An upper case letter that begins the password and a digit that ends it do not\n" \
119 "count towards the number of character classes used.\n", \
120 count), (count)
121
122 #define MESSAGE_EXPLAIN_PASSWORD_ALL_OR_3_CLASSES(count) \
123 P3_( \
124 "A valid password should be a mix of upper and lower case letters, digits, and\n" \
125 "other characters. You can use a password containing at least %d character\n" \
126 "from all of these classes, or a password containing at least %d characters\n" \
127 "from just 3 of these 4 classes.\n" \
128 "An upper case letter that begins the password and a digit that ends it do not\n" \
129 "count towards the number of character classes used.\n", \
130 \
131 "A valid password should be a mix of upper and lower case letters, digits, and\n" \
132 "other characters. You can use a password containing at least %d characters\n" \
133 "from all of these classes, or a password containing at least %d characters\n" \
134 "from just 3 of these 4 classes.\n" \
135 "An upper case letter that begins the password and a digit that ends it do not\n" \
136 "count towards the number of character classes used.\n", \
137 count), (count)
138
139 #define MESSAGE_EXPLAIN_PASSPHRASE(count) \
140 P3_(\
141 "A passphrase should be of at least %d word, %d to %d characters long, and\n" \
142 "contain enough different characters.\n", \
143 \
144 "A passphrase should be of at least %d words, %d to %d characters long, and\n" \
145 "contain enough different characters.\n", \
146 count), (count)
147
148 #define MESSAGE_RANDOM \
149 _("Alternatively, if no one else can see your terminal now, you can pick this as\n" \
150 "your password: \"%s\".\n")
151 #define MESSAGE_RANDOMONLY \
152 _("This system is configured to permit randomly generated passwords only.\n" \
153 "If no one else can see your terminal now, you can pick this as your\n" \
154 "password: \"%s\". Otherwise come back later.\n")
155 #define MESSAGE_RANDOMFAILED \
156 _("This system is configured to use randomly generated passwords only,\n" \
157 "but the attempt to generate a password has failed. This could happen\n" \
158 "for a number of reasons: you could have requested an impossible password\n" \
159 "length, or the access to kernel random number pool could have failed.")
160 #define MESSAGE_TOOLONG \
161 _("This password may be too long for some services. Choose another.")
162 #define MESSAGE_TRUNCATED \
163 _("Warning: your longer password will be truncated to 8 characters.")
164 #define MESSAGE_WEAKPASS \
165 _("Weak password: %s.")
166 #define MESSAGE_NOTRANDOM \
167 _("Sorry, you've mistyped the password that was generated for you.")
168 #define MESSAGE_MISTYPED \
169 _("Sorry, passwords do not match.")
170 #define MESSAGE_RETRY \
171 _("Try again.")
172
logaudit(pam_handle_t * pamh,int status,passwdqc_params_t * params)173 static int logaudit(pam_handle_t *pamh, int status, passwdqc_params_t *params)
174 {
175 #ifdef HAVE_LIBAUDIT
176 if (!(params->pam.flags & F_NO_AUDIT)) {
177 int rc = pam_modutil_audit_write(pamh, AUDIT_USER_CHAUTHTOK, "pam_passwdqc", status);
178 if (status == PAM_SUCCESS)
179 status = rc;
180 }
181 #else /* !HAVE_LIBAUDIT */
182 (void) pamh;
183 #endif
184 passwdqc_params_free(params);
185 return status;
186 }
187
converse(pam_handle_t * pamh,int style,l_const char * text,struct pam_response ** resp)188 static int converse(pam_handle_t *pamh, int style, l_const char *text,
189 struct pam_response **resp)
190 {
191 pam_item_t item;
192 const struct pam_conv *conv;
193 struct pam_message msg, *pmsg;
194 int status;
195
196 *resp = NULL;
197 status = pam_get_item(pamh, PAM_CONV, &item);
198 if (status != PAM_SUCCESS)
199 return status;
200 conv = item;
201
202 pmsg = &msg;
203 msg.msg_style = style;
204 msg.msg = (char *)text;
205
206 return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp,
207 conv->appdata_ptr);
208 }
209
210 #ifdef __GNUC__
211 __attribute__ ((format (printf, 3, 4)))
212 #endif
say(pam_handle_t * pamh,int style,const char * format,...)213 static int say(pam_handle_t *pamh, int style, const char *format, ...)
214 {
215 va_list args;
216 char buffer[0x800];
217 int needed;
218 struct pam_response *resp;
219 int status;
220
221 va_start(args, format);
222 needed = vsnprintf(buffer, sizeof(buffer), format, args);
223 va_end(args);
224
225 if ((unsigned int)needed < sizeof(buffer)) {
226 status = converse(pamh, style, buffer, &resp);
227 pwqc_drop_pam_reply(resp, 1);
228 } else {
229 status = PAM_ABORT;
230 }
231 _passwdqc_memzero(buffer, sizeof(buffer));
232
233 return status;
234 }
235
check_max(passwdqc_params_qc_t * qc,pam_handle_t * pamh,const char * newpass)236 static int check_max(passwdqc_params_qc_t *qc, pam_handle_t *pamh,
237 const char *newpass)
238 {
239 if (strlen(newpass) > (size_t)qc->max) {
240 if (qc->max != 8) {
241 say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG);
242 return -1;
243 }
244 say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED);
245 }
246
247 return 0;
248 }
249
check_pass(struct passwd * pw,const char * pass)250 static int check_pass(struct passwd *pw, const char *pass)
251 {
252 const char *hash;
253 int retval;
254
255 #ifdef HAVE_SHADOW
256 #ifdef __hpux
257 if (iscomsec()) {
258 #else
259 if (!strcmp(pw->pw_passwd, "x")) {
260 #endif
261 struct spwd *spw = getspnam(pw->pw_name);
262 endspent();
263 if (!spw)
264 return -1;
265 hash = NULL;
266 if (strlen(spw->sp_pwdp) >= 13) {
267 #ifdef __hpux
268 hash = bigcrypt(pass, spw->sp_pwdp);
269 #else
270 hash = crypt(pass, spw->sp_pwdp);
271 #endif
272 }
273 retval = (hash && !strcmp(hash, spw->sp_pwdp)) ? 0 : -1;
274 _passwdqc_memzero(spw->sp_pwdp, strlen(spw->sp_pwdp));
275 return retval;
276 }
277 #endif
278
279 hash = NULL;
280 if (strlen(pw->pw_passwd) >= 13)
281 hash = crypt(pass, pw->pw_passwd);
282 retval = (hash && !strcmp(hash, pw->pw_passwd)) ? 0 : -1;
283 _passwdqc_memzero(pw->pw_passwd, strlen(pw->pw_passwd));
284 return retval;
285 }
286
287 static int am_root(pam_handle_t *pamh)
288 {
289 pam_item_t item;
290 const char *service;
291
292 if (getuid() != 0)
293 return 0;
294
295 if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS)
296 return 0;
297 service = item;
298
299 return !strcmp(service, "passwd");
300 }
301
302 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
303 int argc, const char **argv)
304 {
305 passwdqc_params_t params;
306 struct pam_response *resp;
307 struct passwd *pw, fake_pw;
308 pam_item_t item;
309 const char *user, *oldpass, *newpass;
310 char *trypass, *randompass;
311 char *parse_reason;
312 const char *check_reason;
313 int ask_oldauthtok;
314 int randomonly, enforce, retries_left, retry_wanted;
315 int status;
316
317 passwdqc_params_reset(¶ms);
318 if (passwdqc_params_parse(¶ms, &parse_reason, argc, argv)) {
319 say(pamh, PAM_ERROR_MSG, am_root(pamh) ?
320 MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED,
321 parse_reason);
322 free(parse_reason);
323 return PAM_ABORT;
324 }
325 status = PAM_SUCCESS;
326
327 ask_oldauthtok = 0;
328 if (flags & PAM_PRELIM_CHECK) {
329 if (params.pam.flags & F_ASK_OLDAUTHTOK_PRELIM)
330 ask_oldauthtok = 1;
331 } else if (flags & PAM_UPDATE_AUTHTOK) {
332 if (params.pam.flags & F_ASK_OLDAUTHTOK_UPDATE)
333 ask_oldauthtok = 1;
334 } else {
335 passwdqc_params_free(¶ms);
336 return PAM_SERVICE_ERR;
337 }
338
339 if (ask_oldauthtok && !am_root(pamh)) {
340 status = converse(pamh, PAM_PROMPT_ECHO_OFF,
341 PROMPT_OLDPASS, &resp);
342
343 if (status == PAM_SUCCESS) {
344 if (resp && resp->resp) {
345 status = pam_set_item(pamh,
346 PAM_OLDAUTHTOK, resp->resp);
347 pwqc_drop_pam_reply(resp, 1);
348 } else
349 status = PAM_AUTHTOK_RECOVERY_ERR;
350 }
351
352 if (status != PAM_SUCCESS)
353 return logaudit(pamh, status, ¶ms);
354 }
355
356 if (flags & PAM_PRELIM_CHECK) {
357 passwdqc_params_free(¶ms);
358 return status;
359 }
360
361 status = pam_get_item(pamh, PAM_USER, &item);
362 if (status != PAM_SUCCESS)
363 return logaudit(pamh, status, ¶ms);
364 user = item;
365
366 status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item);
367 if (status != PAM_SUCCESS)
368 return logaudit(pamh, status, ¶ms);
369 oldpass = item;
370
371 if (params.pam.flags & F_NON_UNIX) {
372 pw = &fake_pw;
373 memset(pw, 0, sizeof(*pw));
374 pw->pw_name = (char *)user;
375 pw->pw_gecos = "";
376 pw->pw_dir = "";
377 } else {
378 /* As currently implemented, we don't avoid timing leaks for valid vs. not
379 * usernames and hashes. Normally, the username would have already been
380 * checked and determined valid, and the check_oldauthtok option is only needed
381 * on systems that happen to have similar timing leaks all over the place. */
382 pw = getpwnam(user);
383 endpwent();
384 if (!pw)
385 return logaudit(pamh, PAM_USER_UNKNOWN, ¶ms);
386 if ((params.pam.flags & F_CHECK_OLDAUTHTOK) && !am_root(pamh)
387 && (!oldpass || check_pass(pw, oldpass)))
388 status = PAM_AUTH_ERR;
389 _passwdqc_memzero(pw->pw_passwd, strlen(pw->pw_passwd));
390 if (status != PAM_SUCCESS)
391 return logaudit(pamh, status, ¶ms);
392 }
393
394 randomonly = params.qc.min[4] > params.qc.max;
395
396 if (am_root(pamh))
397 enforce = params.pam.flags & F_ENFORCE_ROOT;
398 else
399 enforce = params.pam.flags & F_ENFORCE_USERS;
400
401 if (params.pam.flags & F_USE_AUTHTOK) {
402 status = pam_get_item(pamh, PAM_AUTHTOK, &item);
403 if (status != PAM_SUCCESS)
404 return logaudit(pamh, status, ¶ms);
405 newpass = item;
406 if (!newpass ||
407 (check_max(¶ms.qc, pamh, newpass) && enforce))
408 return logaudit(pamh, PAM_AUTHTOK_ERR, ¶ms);
409 check_reason =
410 passwdqc_check(¶ms.qc, newpass, oldpass, pw);
411 if (check_reason) {
412 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS,
413 check_reason);
414 if (enforce)
415 status = PAM_AUTHTOK_ERR;
416 }
417 return logaudit(pamh, status, ¶ms);
418 }
419
420 retries_left = params.pam.retry;
421
422 retry:
423 retry_wanted = 0;
424
425 if (!randomonly &&
426 params.qc.passphrase_words && params.qc.min[2] <= params.qc.max)
427 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH);
428 else
429 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD);
430 if (status != PAM_SUCCESS)
431 return logaudit(pamh, status, ¶ms);
432
433 if (!randomonly && params.qc.min[0] == params.qc.min[4])
434 status = say(pamh, PAM_TEXT_INFO,
435 MESSAGE_EXPLAIN_PASSWORD_1_CLASS(params.qc.min[4]));
436
437 else if (!randomonly && params.qc.min[3] == params.qc.min[4])
438 status = say(pamh, PAM_TEXT_INFO,
439 MESSAGE_EXPLAIN_PASSWORD_N_CLASSES(params.qc.min[4]),
440 params.qc.min[1] != params.qc.min[3] ? 3 : 2);
441 else if (!randomonly && params.qc.min[3] == INT_MAX)
442 status = say(pamh, PAM_TEXT_INFO,
443 MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES(params.qc.min[4]));
444 else if (!randomonly) {
445 status = say(pamh, PAM_TEXT_INFO,
446 MESSAGE_EXPLAIN_PASSWORD_ALL_OR_3_CLASSES(params.qc.min[4]),
447 params.qc.min[3]);
448 }
449 if (status != PAM_SUCCESS)
450 return logaudit(pamh, status, ¶ms);
451
452 if (!randomonly &&
453 params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) {
454 status = say(pamh, PAM_TEXT_INFO,
455 MESSAGE_EXPLAIN_PASSPHRASE(params.qc.passphrase_words),
456 params.qc.min[2], params.qc.max);
457 if (status != PAM_SUCCESS)
458 return logaudit(pamh, status, ¶ms);
459 }
460
461 randompass = passwdqc_random(¶ms.qc);
462 if (randompass) {
463 status = say(pamh, PAM_TEXT_INFO, randomonly ?
464 MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
465 if (status != PAM_SUCCESS) {
466 pwqc_overwrite_string(randompass);
467 pwqc_drop_mem(randompass);
468 }
469 } else if (randomonly) {
470 say(pamh, PAM_ERROR_MSG, am_root(pamh) ?
471 MESSAGE_RANDOMFAILED : MESSAGE_MISCONFIGURED);
472 return logaudit(pamh, PAM_AUTHTOK_ERR, ¶ms);
473 }
474
475 status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp);
476 if (status == PAM_SUCCESS && (!resp || !resp->resp))
477 status = PAM_AUTHTOK_ERR;
478
479 if (status != PAM_SUCCESS) {
480 pwqc_overwrite_string(randompass);
481 pwqc_drop_mem(randompass);
482 return logaudit(pamh, status, ¶ms);
483 }
484
485 trypass = strdup(resp->resp);
486
487 pwqc_drop_pam_reply(resp, 1);
488
489 if (!trypass) {
490 pwqc_overwrite_string(randompass);
491 pwqc_drop_mem(randompass);
492 return logaudit(pamh, PAM_AUTHTOK_ERR, ¶ms);
493 }
494
495 if (check_max(¶ms.qc, pamh, trypass) && enforce) {
496 status = PAM_AUTHTOK_ERR;
497 retry_wanted = 1;
498 }
499
500 check_reason = NULL; /* unused */
501 if (status == PAM_SUCCESS &&
502 (!randompass || !strstr(trypass, randompass)) &&
503 (randomonly ||
504 (check_reason = passwdqc_check(¶ms.qc, trypass, oldpass, pw)))) {
505 if (randomonly)
506 say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM);
507 else
508 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS,
509 check_reason);
510 if (enforce) {
511 status = PAM_AUTHTOK_ERR;
512 retry_wanted = 1;
513 }
514 }
515
516 if (status == PAM_SUCCESS)
517 status = converse(pamh, PAM_PROMPT_ECHO_OFF,
518 PROMPT_NEWPASS2, &resp);
519 if (status == PAM_SUCCESS) {
520 if (resp && resp->resp) {
521 if (strcmp(trypass, resp->resp)) {
522 status = say(pamh,
523 PAM_ERROR_MSG, MESSAGE_MISTYPED);
524 if (status == PAM_SUCCESS) {
525 status = PAM_AUTHTOK_ERR;
526 retry_wanted = 1;
527 }
528 }
529 pwqc_drop_pam_reply(resp, 1);
530 } else
531 status = PAM_AUTHTOK_ERR;
532 }
533
534 if (status == PAM_SUCCESS)
535 status = pam_set_item(pamh, PAM_AUTHTOK, trypass);
536
537 pwqc_overwrite_string(randompass);
538 pwqc_drop_mem(randompass);
539
540 pwqc_overwrite_string(trypass);
541 pwqc_drop_mem(trypass);
542
543 if (retry_wanted && --retries_left > 0) {
544 status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY);
545 if (status == PAM_SUCCESS)
546 goto retry;
547 }
548
549 return logaudit(pamh, status, ¶ms);
550 }
551
552 #ifdef PAM_MODULE_ENTRY
553 PAM_MODULE_ENTRY("pam_passwdqc");
554 #elif defined(PAM_STATIC)
555 const struct pam_module _pam_passwdqc_modstruct = {
556 "pam_passwdqc",
557 NULL,
558 NULL,
559 NULL,
560 NULL,
561 NULL,
562 pam_sm_chauthtok
563 };
564 #endif
565