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