2 * Copyright (c) 2000-2003,2005 by Solar Designer. See LICENSE.
5 #if defined(__FreeBSD__) || defined(__DragonFly__)
7 #define _XOPEN_SOURCE 600
9 #define _XOPEN_SOURCE 500
10 #define _XOPEN_SOURCE_EXTENDED
11 #define _XOPEN_VERSION 500
24 #define PAM_SM_PASSWORD
26 #include <security/pam_appl.h>
28 #include <security/pam_modules.h>
30 #include "pam_macros.h"
32 #if !defined(PAM_EXTERN) && !defined(PAM_STATIC)
33 #define PAM_EXTERN extern
36 #if !defined(PAM_AUTHTOK_RECOVERY_ERR) && defined(PAM_AUTHTOK_RECOVER_ERR)
37 #define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR
40 #if (defined(__sun) || defined(__hpux)) && \
41 !defined(LINUX_PAM) && !defined(_OPENPAM)
42 /* Sun's PAM doesn't use const here, while Linux-PAM and OpenPAM do */
45 #define lo_const const
48 /* OpenPAM doesn't use const here, while Linux-PAM does */
51 #define l_const lo_const
53 typedef lo_const
void *pam_item_t
;
57 #define F_ENFORCE_MASK 0x00000003
58 #define F_ENFORCE_USERS 0x00000001
59 #define F_ENFORCE_ROOT 0x00000002
60 #define F_ENFORCE_EVERYONE F_ENFORCE_MASK
61 #define F_NON_UNIX 0x00000004
62 #define F_ASK_OLDAUTHTOK_MASK 0x00000030
63 #define F_ASK_OLDAUTHTOK_PRELIM 0x00000010
64 #define F_ASK_OLDAUTHTOK_UPDATE 0x00000020
65 #define F_CHECK_OLDAUTHTOK 0x00000040
66 #define F_USE_FIRST_PASS 0x00000100
67 #define F_USE_AUTHTOK 0x00000200
75 static params_t defaults
= {
77 {INT_MAX
, 24, 11, 8, 7}, /* min */
79 3, /* passphrase_words */
84 F_ENFORCE_EVERYONE
, /* flags */
88 #define PROMPT_OLDPASS \
89 "Enter current password: "
90 #define PROMPT_NEWPASS1 \
91 "Enter new password: "
92 #define PROMPT_NEWPASS2 \
93 "Re-type new password: "
95 #define MESSAGE_MISCONFIGURED \
96 "System configuration error. Please contact your administrator."
97 #define MESSAGE_INVALID_OPTION \
98 "pam_passwdqc: Invalid option: \"%s\"."
99 #define MESSAGE_INTRO_PASSWORD \
100 "\nYou can now choose the new password.\n"
101 #define MESSAGE_INTRO_BOTH \
102 "\nYou can now choose the new password or passphrase.\n"
103 #define MESSAGE_EXPLAIN_PASSWORD_1CLASS \
104 "A good password should be a mix of upper and lower case letters,\n" \
105 "digits, and other characters. You can use a%s %d character long\n" \
107 #define MESSAGE_EXPLAIN_PASSWORD_CLASSES \
108 "A valid password should be a mix of upper and lower case letters,\n" \
109 "digits, and other characters. You can use a%s %d character long\n" \
110 "password with characters from at least %d of these 4 classes.\n" \
111 "An upper case letter that begins the password and a digit that\n" \
112 "ends it do not count towards the number of character classes used.\n"
113 #define MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES \
114 "A valid password should be a mix of upper and lower case letters,\n" \
115 "digits, and other characters. You can use a%s %d character long\n" \
116 "password with characters from all of these classes. An upper\n" \
117 "case letter that begins the password and a digit that ends it do\n" \
118 "not count towards the number of character classes used.\n"
119 #define MESSAGE_EXPLAIN_PASSWORD_ALT \
120 "A valid password should be a mix of upper and lower case letters,\n" \
121 "digits, and other characters. You can use a%s %d character long\n" \
122 "password with characters from at least 3 of these 4 classes, or\n" \
123 "a%s %d character long password containing characters from all the\n" \
124 "classes. An upper case letter that begins the password and a\n" \
125 "digit that ends it do not count towards the number of character\n" \
127 #define MESSAGE_EXPLAIN_PASSPHRASE \
128 "A passphrase should be of at least %d words, %d to %d characters\n" \
129 "long, and contain enough different characters.\n"
130 #define MESSAGE_RANDOM \
131 "Alternatively, if noone else can see your terminal now, you can\n" \
132 "pick this as your password: \"%s\".\n"
133 #define MESSAGE_RANDOMONLY \
134 "This system is configured to permit randomly generated passwords\n" \
135 "only. If noone else can see your terminal now, you can pick this\n" \
136 "as your password: \"%s\". Otherwise, come back later.\n"
137 #define MESSAGE_RANDOMFAILED \
138 "This system is configured to use randomly generated passwords\n" \
139 "only, but the attempt to generate a password has failed. This\n" \
140 "could happen for a number of reasons: you could have requested\n" \
141 "an impossible password length, or the access to kernel random\n" \
142 "number pool could have failed."
143 #define MESSAGE_TOOLONG \
144 "This password may be too long for some services. Choose another."
145 #define MESSAGE_TRUNCATED \
146 "Warning: your longer password will be truncated to 8 characters."
147 #define MESSAGE_WEAKPASS \
149 #define MESSAGE_NOTRANDOM \
150 "Sorry, you've mistyped the password that was generated for you."
151 #define MESSAGE_MISTYPED \
152 "Sorry, passwords do not match."
153 #define MESSAGE_RETRY \
156 static int converse(pam_handle_t
*pamh
, int style
, l_const
char *text
,
157 struct pam_response
**resp
)
160 const struct pam_conv
*conv
;
161 struct pam_message msg
, *pmsg
;
165 status
= pam_get_item(pamh
, PAM_CONV
, &item
);
166 if (status
!= PAM_SUCCESS
)
171 msg
.msg_style
= style
;
172 msg
.msg
= (char *)text
;
174 return conv
->conv(1, (lo_const
struct pam_message
**)&pmsg
, resp
,
179 __attribute__ ((format (printf
, 3, 4)))
181 static int say(pam_handle_t
*pamh
, int style
, const char *format
, ...)
186 struct pam_response
*resp
;
189 va_start(args
, format
);
190 needed
= vsnprintf(buffer
, sizeof(buffer
), format
, args
);
193 if ((unsigned int)needed
< sizeof(buffer
)) {
194 status
= converse(pamh
, style
, buffer
, &resp
);
195 pwqc_overwrite_string(buffer
);
196 pwqc_drop_pam_reply(resp
, 1);
199 memset(buffer
, 0, sizeof(buffer
));
205 static int check_max(params_t
*params
, pam_handle_t
*pamh
, const char *newpass
)
207 if ((int)strlen(newpass
) > params
->qc
.max
) {
208 if (params
->qc
.max
!= 8) {
209 say(pamh
, PAM_ERROR_MSG
, MESSAGE_TOOLONG
);
212 say(pamh
, PAM_TEXT_INFO
, MESSAGE_TRUNCATED
);
218 static int check_pass(struct passwd
*pw
, const char *pass
)
229 if (!strcmp(pw
->pw_passwd
, "x")) {
231 spw
= getspnam(pw
->pw_name
);
236 hash
= bigcrypt(pass
, spw
->sp_pwdp
);
238 hash
= crypt(pass
, spw
->sp_pwdp
);
240 retval
= (hash
== NULL
|| strcmp(hash
, spw
->sp_pwdp
)) ? -1 : 0;
241 memset(spw
->sp_pwdp
, 0, strlen(spw
->sp_pwdp
));
245 cryptpw
= crypt(pass
, pw
->pw_passwd
);
246 return (cryptpw
== NULL
|| strcmp(cryptpw
, pw
->pw_passwd
)) ? -1 : 0;
249 static int am_root(pam_handle_t
*pamh
)
257 if (pam_get_item(pamh
, PAM_SERVICE
, &item
) != PAM_SUCCESS
)
261 return !strcmp(service
, "passwd");
264 static int parse(params_t
*params
, pam_handle_t
*pamh
,
265 int argc
, const char **argv
)
273 if (!strncmp(*argv
, "min=", 4)) {
275 for (i
= 0; i
< 5; i
++) {
276 if (!strncmp(p
, "disabled", 8)) {
280 v
= strtoul(p
, &e
, 10);
283 if (i
< 4 && *p
++ != ',') break;
284 if (v
> INT_MAX
) break;
285 if (i
&& (int)v
> params
->qc
.min
[i
- 1]) break;
286 params
->qc
.min
[i
] = v
;
290 if (!strncmp(*argv
, "max=", 4)) {
291 v
= strtoul(*argv
+ 4, &e
, 10);
292 if (*e
|| v
< 8 || v
> INT_MAX
) break;
295 if (!strncmp(*argv
, "passphrase=", 11)) {
296 v
= strtoul(*argv
+ 11, &e
, 10);
297 if (*e
|| v
> INT_MAX
) break;
298 params
->qc
.passphrase_words
= v
;
300 if (!strncmp(*argv
, "match=", 6)) {
301 v
= strtoul(*argv
+ 6, &e
, 10);
302 if (*e
|| v
> INT_MAX
) break;
303 params
->qc
.match_length
= v
;
305 if (!strncmp(*argv
, "similar=", 8)) {
306 if (!strcmp(*argv
+ 8, "permit"))
307 params
->qc
.similar_deny
= 0;
309 if (!strcmp(*argv
+ 8, "deny"))
310 params
->qc
.similar_deny
= 1;
314 if (!strncmp(*argv
, "random=", 7)) {
315 v
= strtoul(*argv
+ 7, &e
, 10);
316 if (!strcmp(e
, ",only")) {
318 params
->qc
.min
[4] = INT_MAX
;
320 if (*e
|| (v
&& v
< 24) || v
> 72) break;
321 params
->qc
.random_bits
= v
;
323 if (!strncmp(*argv
, "enforce=", 8)) {
324 params
->flags
&= ~F_ENFORCE_MASK
;
325 if (!strcmp(*argv
+ 8, "users"))
326 params
->flags
|= F_ENFORCE_USERS
;
328 if (!strcmp(*argv
+ 8, "everyone"))
329 params
->flags
|= F_ENFORCE_EVERYONE
;
331 if (strcmp(*argv
+ 8, "none"))
334 if (!strcmp(*argv
, "non-unix")) {
335 if (params
->flags
& F_CHECK_OLDAUTHTOK
) break;
336 params
->flags
|= F_NON_UNIX
;
338 if (!strncmp(*argv
, "retry=", 6)) {
339 v
= strtoul(*argv
+ 6, &e
, 10);
340 if (*e
|| v
> INT_MAX
) break;
343 if (!strncmp(*argv
, "ask_oldauthtok", 14)) {
344 params
->flags
&= ~F_ASK_OLDAUTHTOK_MASK
;
345 if (params
->flags
& F_USE_FIRST_PASS
) break;
346 if (!strcmp(*argv
+ 14, "=update"))
347 params
->flags
|= F_ASK_OLDAUTHTOK_UPDATE
;
350 params
->flags
|= F_ASK_OLDAUTHTOK_PRELIM
;
354 if (!strcmp(*argv
, "check_oldauthtok")) {
355 if (params
->flags
& F_NON_UNIX
) break;
356 params
->flags
|= F_CHECK_OLDAUTHTOK
;
358 if (!strcmp(*argv
, "use_first_pass")) {
359 if (params
->flags
& F_ASK_OLDAUTHTOK_MASK
) break;
360 params
->flags
|= F_USE_FIRST_PASS
| F_USE_AUTHTOK
;
362 if (!strcmp(*argv
, "use_authtok")) {
363 params
->flags
|= F_USE_AUTHTOK
;
370 say(pamh
, PAM_ERROR_MSG
, am_root(pamh
) ?
371 MESSAGE_INVALID_OPTION
: MESSAGE_MISCONFIGURED
, *argv
);
378 PAM_EXTERN
int pam_sm_chauthtok(pam_handle_t
*pamh
, int flags
,
379 int argc
, const char **argv
)
382 struct pam_response
*resp
;
383 struct passwd
*pw
, fake_pw
;
385 const char *user
, *oldpass
, *newpass
;
386 char *trypass
, *randompass
;
389 int randomonly
, enforce
, retries_left
, retry_wanted
;
393 status
= parse(¶ms
, pamh
, argc
, argv
);
394 if (status
!= PAM_SUCCESS
)
398 if (flags
& PAM_PRELIM_CHECK
) {
399 if (params
.flags
& F_ASK_OLDAUTHTOK_PRELIM
)
402 if (flags
& PAM_UPDATE_AUTHTOK
) {
403 if (params
.flags
& F_ASK_OLDAUTHTOK_UPDATE
)
406 return PAM_SERVICE_ERR
;
408 if (ask_oldauthtok
&& !am_root(pamh
)) {
409 status
= converse(pamh
, PAM_PROMPT_ECHO_OFF
,
410 PROMPT_OLDPASS
, &resp
);
412 if (status
== PAM_SUCCESS
) {
413 if (resp
&& resp
->resp
) {
414 status
= pam_set_item(pamh
,
415 PAM_OLDAUTHTOK
, resp
->resp
);
416 pwqc_drop_pam_reply(resp
, 1);
418 status
= PAM_AUTHTOK_RECOVERY_ERR
;
421 if (status
!= PAM_SUCCESS
)
425 if (flags
& PAM_PRELIM_CHECK
)
428 status
= pam_get_item(pamh
, PAM_USER
, &item
);
429 if (status
!= PAM_SUCCESS
)
433 status
= pam_get_item(pamh
, PAM_OLDAUTHTOK
, &item
);
434 if (status
!= PAM_SUCCESS
)
438 if (params
.flags
& F_NON_UNIX
) {
440 pw
->pw_name
= (char *)user
;
446 return PAM_USER_UNKNOWN
;
447 if ((params
.flags
& F_CHECK_OLDAUTHTOK
) && !am_root(pamh
) &&
448 (!oldpass
|| check_pass(pw
, oldpass
)))
449 status
= PAM_AUTH_ERR
;
450 memset(pw
->pw_passwd
, 0, strlen(pw
->pw_passwd
));
451 if (status
!= PAM_SUCCESS
)
455 randomonly
= params
.qc
.min
[4] > params
.qc
.max
;
458 enforce
= params
.flags
& F_ENFORCE_ROOT
;
460 enforce
= params
.flags
& F_ENFORCE_USERS
;
462 if (params
.flags
& F_USE_AUTHTOK
) {
463 status
= pam_get_item(pamh
, PAM_AUTHTOK
, &item
);
464 if (status
!= PAM_SUCCESS
)
467 if (!newpass
|| (check_max(¶ms
, pamh
, newpass
) && enforce
))
468 return PAM_AUTHTOK_ERR
;
469 reason
= _passwdqc_check(¶ms
.qc
, newpass
, oldpass
, pw
);
471 say(pamh
, PAM_ERROR_MSG
, MESSAGE_WEAKPASS
, reason
);
473 status
= PAM_AUTHTOK_ERR
;
478 retries_left
= params
.retry
;
484 params
.qc
.passphrase_words
&& params
.qc
.min
[2] <= params
.qc
.max
)
485 status
= say(pamh
, PAM_TEXT_INFO
, MESSAGE_INTRO_BOTH
);
487 status
= say(pamh
, PAM_TEXT_INFO
, MESSAGE_INTRO_PASSWORD
);
488 if (status
!= PAM_SUCCESS
)
491 if (!randomonly
&& params
.qc
.min
[0] == params
.qc
.min
[4])
492 status
= say(pamh
, PAM_TEXT_INFO
,
493 MESSAGE_EXPLAIN_PASSWORD_1CLASS
,
494 params
.qc
.min
[4] == 8 || params
.qc
.min
[4] == 11 ? "n" : "",
497 if (!randomonly
&& params
.qc
.min
[3] == params
.qc
.min
[4])
498 status
= say(pamh
, PAM_TEXT_INFO
,
499 MESSAGE_EXPLAIN_PASSWORD_CLASSES
,
500 params
.qc
.min
[4] == 8 || params
.qc
.min
[4] == 11 ? "n" : "",
502 params
.qc
.min
[1] != params
.qc
.min
[3] ? 3 : 2);
504 if (!randomonly
&& params
.qc
.min
[3] == INT_MAX
)
505 status
= say(pamh
, PAM_TEXT_INFO
,
506 MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES
,
507 params
.qc
.min
[4] == 8 || params
.qc
.min
[4] == 11 ? "n" : "",
511 status
= say(pamh
, PAM_TEXT_INFO
,
512 MESSAGE_EXPLAIN_PASSWORD_ALT
,
513 params
.qc
.min
[3] == 8 || params
.qc
.min
[3] == 11 ? "n" : "",
515 params
.qc
.min
[4] == 8 || params
.qc
.min
[4] == 11 ? "n" : "",
517 if (status
!= PAM_SUCCESS
)
521 params
.qc
.passphrase_words
&&
522 params
.qc
.min
[2] <= params
.qc
.max
) {
523 status
= say(pamh
, PAM_TEXT_INFO
, MESSAGE_EXPLAIN_PASSPHRASE
,
524 params
.qc
.passphrase_words
,
525 params
.qc
.min
[2], params
.qc
.max
);
526 if (status
!= PAM_SUCCESS
)
530 randompass
= _passwdqc_random(¶ms
.qc
);
532 status
= say(pamh
, PAM_TEXT_INFO
, randomonly
?
533 MESSAGE_RANDOMONLY
: MESSAGE_RANDOM
, randompass
);
534 if (status
!= PAM_SUCCESS
) {
535 pwqc_overwrite_string(randompass
);
540 say(pamh
, PAM_ERROR_MSG
, am_root(pamh
) ?
541 MESSAGE_RANDOMFAILED
: MESSAGE_MISCONFIGURED
);
542 return PAM_AUTHTOK_ERR
;
545 status
= converse(pamh
, PAM_PROMPT_ECHO_OFF
, PROMPT_NEWPASS1
, &resp
);
546 if (status
== PAM_SUCCESS
&& (!resp
|| !resp
->resp
))
547 status
= PAM_AUTHTOK_ERR
;
549 if (status
!= PAM_SUCCESS
) {
550 pwqc_overwrite_string(randompass
);
554 trypass
= strdup(resp
->resp
);
556 pwqc_drop_pam_reply(resp
, 1);
559 pwqc_overwrite_string(randompass
);
560 return PAM_AUTHTOK_ERR
;
563 if (check_max(¶ms
, pamh
, trypass
) && enforce
) {
564 status
= PAM_AUTHTOK_ERR
;
569 if (status
== PAM_SUCCESS
&&
570 (!randompass
|| !strstr(trypass
, randompass
)) &&
572 (reason
= _passwdqc_check(¶ms
.qc
, trypass
, oldpass
, pw
)))) {
574 say(pamh
, PAM_ERROR_MSG
, MESSAGE_NOTRANDOM
);
576 say(pamh
, PAM_ERROR_MSG
, MESSAGE_WEAKPASS
, reason
);
578 status
= PAM_AUTHTOK_ERR
;
583 if (status
== PAM_SUCCESS
)
584 status
= converse(pamh
, PAM_PROMPT_ECHO_OFF
,
585 PROMPT_NEWPASS2
, &resp
);
586 if (status
== PAM_SUCCESS
) {
587 if (resp
&& resp
->resp
) {
588 if (strcmp(trypass
, resp
->resp
)) {
590 PAM_ERROR_MSG
, MESSAGE_MISTYPED
);
591 if (status
== PAM_SUCCESS
) {
592 status
= PAM_AUTHTOK_ERR
;
596 pwqc_drop_pam_reply(resp
, 1);
598 status
= PAM_AUTHTOK_ERR
;
601 if (status
== PAM_SUCCESS
)
602 status
= pam_set_item(pamh
, PAM_AUTHTOK
, trypass
);
604 pwqc_overwrite_string(randompass
);
605 pwqc_overwrite_string(trypass
);
606 pwqc_drop_mem(trypass
);
608 if (retry_wanted
&& --retries_left
> 0) {
609 status
= say(pamh
, PAM_TEXT_INFO
, MESSAGE_RETRY
);
610 if (status
== PAM_SUCCESS
)
617 #ifdef PAM_MODULE_ENTRY
618 PAM_MODULE_ENTRY("pam_passwdqc");
619 #elif defined(PAM_STATIC)
620 struct pam_module _pam_passwdqc_modstruct
= {