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
41typedef 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
57typedef struct {
58	passwdqc_params_t qc;
59	int flags;
60	int retry;
61} params_t;
62
63static 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
132static 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
157static 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
180static 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
193static 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
310PAM_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
434retry:
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
557PAM_MODULE_ENTRY("pam_passwdqc");
558#elif defined(PAM_STATIC)
559struct 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