xref: /freebsd/contrib/pam_modules/pam_passwdqc/pam_passwdqc.c (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
1 /*
2  * Copyright (c) 2000-2002 by Solar Designer. See LICENSE.
3  */
4 
5 #define _XOPEN_SOURCE 600
6 #define _XOPEN_SOURCE_EXTENDED
7 #define _XOPEN_VERSION 600
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 	pam_item_t item;
136 	lo_const struct pam_conv *conv;
137 	struct pam_message msg, *pmsg;
138 	int status;
139 
140 	status = pam_get_item(pamh, PAM_CONV, &item);
141 	if (status != PAM_SUCCESS)
142 		return status;
143 	conv = item;
144 
145 	pmsg = &msg;
146 	msg.msg_style = style;
147 	msg.msg = (char *)text;
148 
149 	*resp = NULL;
150 	return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp,
151 	    conv->appdata_ptr);
152 }
153 
154 #ifdef __GNUC__
155 __attribute__ ((format (printf, 3, 4)))
156 #endif
157 static int say(pam_handle_t *pamh, int style, const char *format, ...)
158 {
159 	va_list args;
160 	char buffer[0x800];
161 	int needed;
162 	struct pam_response *resp;
163 	int status;
164 
165 	va_start(args, format);
166 	needed = vsnprintf(buffer, sizeof(buffer), format, args);
167 	va_end(args);
168 
169 	if ((unsigned int)needed < sizeof(buffer)) {
170 		status = converse(pamh, style, buffer, &resp);
171 		_pam_overwrite(buffer);
172 	} else {
173 		status = PAM_ABORT;
174 		memset(buffer, 0, sizeof(buffer));
175 	}
176 
177 	return status;
178 }
179 
180 static int check_max(params_t *params, pam_handle_t *pamh, const char *newpass)
181 {
182 	if ((int)strlen(newpass) > params->qc.max) {
183 		if (params->qc.max != 8) {
184 			say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG);
185 			return -1;
186 		}
187 		say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED);
188 	}
189 
190 	return 0;
191 }
192 
193 static int parse(params_t *params, pam_handle_t *pamh,
194     int argc, const char **argv)
195 {
196 	const char *p;
197 	char *e;
198 	int i;
199 	unsigned long v;
200 
201 	while (argc) {
202 		if (!strncmp(*argv, "min=", 4)) {
203 			p = *argv + 4;
204 			for (i = 0; i < 5; i++) {
205 				if (!strncmp(p, "disabled", 8)) {
206 					v = INT_MAX;
207 					p += 8;
208 				} else {
209 					v = strtoul(p, &e, 10);
210 					p = e;
211 				}
212 				if (i < 4 && *p++ != ',') break;
213 				if (v > INT_MAX) break;
214 				if (i && (int)v > params->qc.min[i - 1]) break;
215 				params->qc.min[i] = v;
216 			}
217 			if (*p) break;
218 		} else
219 		if (!strncmp(*argv, "max=", 4)) {
220 			v = strtoul(*argv + 4, &e, 10);
221 			if (*e || v < 8 || v > INT_MAX) break;
222 			params->qc.max = v;
223 		} else
224 		if (!strncmp(*argv, "passphrase=", 11)) {
225 			v = strtoul(*argv + 11, &e, 10);
226 			if (*e || v > INT_MAX) break;
227 			params->qc.passphrase_words = v;
228 		} else
229 		if (!strncmp(*argv, "match=", 6)) {
230 			v = strtoul(*argv + 6, &e, 10);
231 			if (*e || v > INT_MAX) break;
232 			params->qc.match_length = v;
233 		} else
234 		if (!strncmp(*argv, "similar=", 8)) {
235 			if (!strcmp(*argv + 8, "permit"))
236 				params->qc.similar_deny = 0;
237 			else
238 			if (!strcmp(*argv + 8, "deny"))
239 				params->qc.similar_deny = 1;
240 			else
241 				break;
242 		} else
243 		if (!strncmp(*argv, "random=", 7)) {
244 			v = strtoul(*argv + 7, &e, 10);
245 			if (!strcmp(e, ",only")) {
246 				e += 5;
247 				params->qc.min[4] = INT_MAX;
248 			}
249 			if (*e || v > INT_MAX) break;
250 			params->qc.random_bits = v;
251 		} else
252 		if (!strncmp(*argv, "enforce=", 8)) {
253 			params->flags &= ~F_ENFORCE_MASK;
254 			if (!strcmp(*argv + 8, "users"))
255 				params->flags |= F_ENFORCE_USERS;
256 			else
257 			if (!strcmp(*argv + 8, "everyone"))
258 				params->flags |= F_ENFORCE_EVERYONE;
259 			else
260 			if (strcmp(*argv + 8, "none"))
261 				break;
262 		} else
263 		if (!strcmp(*argv, "non-unix")) {
264 			if (params->flags & F_CHECK_OLDAUTHTOK) break;
265 			params->flags |= F_NON_UNIX;
266 		} else
267 		if (!strncmp(*argv, "retry=", 6)) {
268 			v = strtoul(*argv + 6, &e, 10);
269 			if (*e || v > INT_MAX) break;
270 			params->retry = v;
271 		} else
272 		if (!strncmp(*argv, "ask_oldauthtok", 14)) {
273 			params->flags &= ~F_ASK_OLDAUTHTOK_MASK;
274 			if (params->flags & F_USE_FIRST_PASS) break;
275 			if (!strcmp(*argv + 14, "=update"))
276 				params->flags |= F_ASK_OLDAUTHTOK_UPDATE;
277 			else
278 			if (!(*argv)[14])
279 				params->flags |= F_ASK_OLDAUTHTOK_PRELIM;
280 			else
281 				break;
282 		} else
283 		if (!strcmp(*argv, "check_oldauthtok")) {
284 			if (params->flags & F_NON_UNIX) break;
285 			params->flags |= F_CHECK_OLDAUTHTOK;
286 		} else
287 		if (!strcmp(*argv, "use_first_pass")) {
288 			if (params->flags & F_ASK_OLDAUTHTOK_MASK) break;
289 			params->flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK;
290 		} else
291 		if (!strcmp(*argv, "use_authtok")) {
292 			params->flags |= F_USE_AUTHTOK;
293 		} else
294 			break;
295 		argc--; argv++;
296 	}
297 
298 	if (argc) {
299 		if (getuid() != 0) {
300 			say(pamh, PAM_ERROR_MSG, MESSAGE_MISCONFIGURED);
301 		} else {
302 			say(pamh, PAM_ERROR_MSG, MESSAGE_INVALID_OPTION, *argv);
303 		}
304 		return PAM_ABORT;
305 	}
306 
307 	return PAM_SUCCESS;
308 }
309 
310 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
311     int argc, const char **argv)
312 {
313 	params_t params;
314 	struct pam_response *resp;
315 	struct passwd *pw, fake_pw;
316 #ifdef HAVE_SHADOW
317 	struct spwd *spw;
318 #endif
319 	pam_item_t item;
320 	lo_const char *user, *oldpass, *curpass;
321 	char *newpass, *randompass;
322 	const char *reason;
323 	int ask_oldauthtok;
324 	int randomonly, enforce, retries_left, retry_wanted;
325 	int status;
326 
327 	params = defaults;
328 	status = parse(&params, pamh, argc, argv);
329 	if (status != PAM_SUCCESS)
330 		return status;
331 
332 	ask_oldauthtok = 0;
333 	if (flags & PAM_PRELIM_CHECK) {
334 		if (params.flags & F_ASK_OLDAUTHTOK_PRELIM)
335 			ask_oldauthtok = 1;
336 	} else
337 	if (flags & PAM_UPDATE_AUTHTOK) {
338 		if (params.flags & F_ASK_OLDAUTHTOK_UPDATE)
339 			ask_oldauthtok = 1;
340 	} else
341 		return PAM_SERVICE_ERR;
342 
343 	if (ask_oldauthtok && getuid() != 0) {
344 		status = converse(pamh, PAM_PROMPT_ECHO_OFF,
345 		    PROMPT_OLDPASS, &resp);
346 
347 		if (status == PAM_SUCCESS) {
348 			if (resp && resp->resp) {
349 				status = pam_set_item(pamh,
350 				    PAM_OLDAUTHTOK, resp->resp);
351 				_pam_drop_reply(resp, 1);
352 			} else
353 				status = PAM_AUTHTOK_RECOVERY_ERR;
354 		}
355 
356 		if (status != PAM_SUCCESS)
357 			return status;
358 	}
359 
360 	if (flags & PAM_PRELIM_CHECK)
361 		return status;
362 
363 	status = pam_get_item(pamh, PAM_USER, &item);
364 	if (status != PAM_SUCCESS)
365 		return status;
366 	user = item;
367 
368 	status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item);
369 	if (status != PAM_SUCCESS)
370 		return status;
371 	oldpass = item;
372 
373 	if (params.flags & F_NON_UNIX) {
374 		pw = &fake_pw;
375 		pw->pw_name = (char *)user;
376 		pw->pw_gecos = "";
377 	} else {
378 		pw = getpwnam(user);
379 		endpwent();
380 		if (!pw)
381 			return PAM_USER_UNKNOWN;
382 		if ((params.flags & F_CHECK_OLDAUTHTOK) && getuid() != 0) {
383 			if (!oldpass)
384 				status = PAM_AUTH_ERR;
385 			else
386 #ifdef HAVE_SHADOW
387 			if (!strcmp(pw->pw_passwd, "x")) {
388 				spw = getspnam(user);
389 				endspent();
390 				if (spw) {
391 					if (strcmp(crypt(oldpass, spw->sp_pwdp),
392 					    spw->sp_pwdp))
393 						status = PAM_AUTH_ERR;
394 					memset(spw->sp_pwdp, 0,
395 					    strlen(spw->sp_pwdp));
396 				} else
397 					status = PAM_AUTH_ERR;
398 			} else
399 #endif
400 			if (strcmp(crypt(oldpass, pw->pw_passwd),
401 			    pw->pw_passwd))
402 				status = PAM_AUTH_ERR;
403 		}
404 		memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
405 		if (status != PAM_SUCCESS)
406 			return status;
407 	}
408 
409 	randomonly = params.qc.min[4] > params.qc.max;
410 
411 	if (getuid() != 0)
412 		enforce = params.flags & F_ENFORCE_USERS;
413 	else
414 		enforce = params.flags & F_ENFORCE_ROOT;
415 
416 	if (params.flags & F_USE_AUTHTOK) {
417 		status = pam_get_item(pamh, PAM_AUTHTOK, &item);
418 		if (status != PAM_SUCCESS)
419 			return status;
420 		curpass = item;
421 		if (!curpass || (check_max(&params, pamh, curpass) && enforce))
422 			return PAM_AUTHTOK_ERR;
423 		reason = _passwdqc_check(&params.qc, curpass, oldpass, pw);
424 		if (reason) {
425 			say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
426 			if (enforce)
427 				status = PAM_AUTHTOK_ERR;
428 		}
429 		return status;
430 	}
431 
432 	retries_left = params.retry;
433 
434 retry:
435 	retry_wanted = 0;
436 
437 	if (!randomonly &&
438 	    params.qc.passphrase_words && params.qc.min[2] <= params.qc.max)
439 		status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH);
440 	else
441 		status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD);
442 	if (status != PAM_SUCCESS)
443 		return status;
444 
445 	if (!randomonly && params.qc.min[3] <= params.qc.min[4])
446 		status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_1,
447 		    params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
448 		    params.qc.min[3]);
449 	else
450 	if (!randomonly)
451 		status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_2,
452 		    params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
453 		    params.qc.min[3],
454 		    params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
455 		    params.qc.min[4]);
456 	if (status != PAM_SUCCESS)
457 		return status;
458 
459 	if (!randomonly &&
460 	    params.qc.passphrase_words &&
461 	    params.qc.min[2] <= params.qc.max) {
462 		status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSPHRASE,
463 		    params.qc.passphrase_words,
464 		    params.qc.min[2], params.qc.max);
465 		if (status != PAM_SUCCESS)
466 			return status;
467 	}
468 
469 	randompass = _passwdqc_random(&params.qc);
470 	if (randompass) {
471 		status = say(pamh, PAM_TEXT_INFO, randomonly ?
472 		    MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
473 		if (status != PAM_SUCCESS) {
474 			_pam_overwrite(randompass);
475 			randompass = NULL;
476 		}
477 	} else
478 	if (randomonly) {
479 		say(pamh, PAM_ERROR_MSG, getuid() != 0 ?
480 		    MESSAGE_MISCONFIGURED : MESSAGE_RANDOMFAILED);
481 		return PAM_AUTHTOK_ERR;
482 	}
483 
484 	status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp);
485 	if (status == PAM_SUCCESS && (!resp || !resp->resp))
486 		status = PAM_AUTHTOK_ERR;
487 
488 	if (status != PAM_SUCCESS) {
489 		if (randompass) _pam_overwrite(randompass);
490 		return status;
491 	}
492 
493 	newpass = strdup(resp->resp);
494 
495 	_pam_drop_reply(resp, 1);
496 
497 	if (!newpass) {
498 		if (randompass) _pam_overwrite(randompass);
499 		return status;
500 	}
501 
502 	if (check_max(&params, pamh, newpass) && enforce) {
503 		status = PAM_AUTHTOK_ERR;
504 		retry_wanted = 1;
505 	}
506 
507 	reason = NULL;
508 	if (status == PAM_SUCCESS &&
509 	    (!randompass || !strstr(newpass, randompass)) &&
510 	    (randomonly ||
511 	    (reason = _passwdqc_check(&params.qc, newpass, oldpass, pw)))) {
512 		if (randomonly)
513 			say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM);
514 		else
515 			say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
516 		if (enforce) {
517 			status = PAM_AUTHTOK_ERR;
518 			retry_wanted = 1;
519 		}
520 	}
521 
522 	if (status == PAM_SUCCESS)
523 		status = converse(pamh, PAM_PROMPT_ECHO_OFF,
524 		    PROMPT_NEWPASS2, &resp);
525 	if (status == PAM_SUCCESS) {
526 		if (resp && resp->resp) {
527 			if (strcmp(newpass, resp->resp)) {
528 				status = say(pamh,
529 				    PAM_ERROR_MSG, MESSAGE_MISTYPED);
530 				if (status == PAM_SUCCESS) {
531 					status = PAM_AUTHTOK_ERR;
532 					retry_wanted = 1;
533 				}
534 			}
535 			_pam_drop_reply(resp, 1);
536 		} else
537 			status = PAM_AUTHTOK_ERR;
538 	}
539 
540 	if (status == PAM_SUCCESS)
541 		status = pam_set_item(pamh, PAM_AUTHTOK, newpass);
542 
543 	if (randompass) _pam_overwrite(randompass);
544 	_pam_overwrite(newpass);
545 	free(newpass);
546 
547 	if (retry_wanted && --retries_left > 0) {
548 		status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY);
549 		if (status == PAM_SUCCESS)
550 			goto retry;
551 	}
552 
553 	return status;
554 }
555 
556 #ifdef PAM_MODULE_ENTRY
557 PAM_MODULE_ENTRY("pam_passwdqc");
558 #elif defined(PAM_STATIC)
559 struct pam_module _pam_passwdqc_modstruct = {
560 	"pam_passwdqc",
561 	NULL,
562 	NULL,
563 	NULL,
564 	NULL,
565 	NULL,
566 	pam_sm_chauthtok
567 };
568 #endif
569