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 
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 
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
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 
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 
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(&params);
318 	if (passwdqc_params_parse(&params, &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(&params);
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, &params);
354 	}
355 
356 	if (flags & PAM_PRELIM_CHECK) {
357 		passwdqc_params_free(&params);
358 		return status;
359 	}
360 
361 	status = pam_get_item(pamh, PAM_USER, &item);
362 	if (status != PAM_SUCCESS)
363 		return logaudit(pamh, status, &params);
364 	user = item;
365 
366 	status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item);
367 	if (status != PAM_SUCCESS)
368 		return logaudit(pamh, status, &params);
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, &params);
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, &params);
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, &params);
405 		newpass = item;
406 		if (!newpass ||
407 		    (check_max(&params.qc, pamh, newpass) && enforce))
408 			return logaudit(pamh, PAM_AUTHTOK_ERR, &params);
409 		check_reason =
410 		    passwdqc_check(&params.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, &params);
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, &params);
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, &params);
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, &params);
459 	}
460 
461 	randompass = passwdqc_random(&params.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, &params);
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, &params);
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, &params);
493 	}
494 
495 	if (check_max(&params.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(&params.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, &params);
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