build - Separate out CFLAGS for kernel & module build
[dragonfly.git] / contrib / pam_passwdqc / pam_passwdqc.c
blob789e23924ba5039d23a50d8bb983298f5672a34d
1 /*
2 * Copyright (c) 2000-2003,2005 by Solar Designer. See LICENSE.
3 */
5 #if defined(__FreeBSD__) || defined(__DragonFly__)
6 /* For vsnprintf(3) */
7 #define _XOPEN_SOURCE 600
8 #else
9 #define _XOPEN_SOURCE 500
10 #define _XOPEN_SOURCE_EXTENDED
11 #define _XOPEN_VERSION 500
12 #endif
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <stdarg.h>
16 #include <string.h>
17 #include <limits.h>
18 #include <unistd.h>
19 #include <pwd.h>
20 #ifdef HAVE_SHADOW
21 #include <shadow.h>
22 #endif
24 #define PAM_SM_PASSWORD
25 #ifndef LINUX_PAM
26 #include <security/pam_appl.h>
27 #endif
28 #include <security/pam_modules.h>
30 #include "pam_macros.h"
32 #if !defined(PAM_EXTERN) && !defined(PAM_STATIC)
33 #define PAM_EXTERN extern
34 #endif
36 #if !defined(PAM_AUTHTOK_RECOVERY_ERR) && defined(PAM_AUTHTOK_RECOVER_ERR)
37 #define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR
38 #endif
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 */
43 #define lo_const
44 #else
45 #define lo_const const
46 #endif
47 #ifdef _OPENPAM
48 /* OpenPAM doesn't use const here, while Linux-PAM does */
49 #define l_const
50 #else
51 #define l_const lo_const
52 #endif
53 typedef lo_const void *pam_item_t;
55 #include "passwdqc.h"
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
69 typedef struct {
70 passwdqc_params_t qc;
71 int flags;
72 int retry;
73 } params_t;
75 static params_t defaults = {
77 {INT_MAX, 24, 11, 8, 7}, /* min */
78 40, /* max */
79 3, /* passphrase_words */
80 4, /* match_length */
81 1, /* similar_deny */
82 42 /* random_bits */
84 F_ENFORCE_EVERYONE, /* flags */
85 3 /* retry */
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" \
106 "password.\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" \
126 "classes used.\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 \
148 "Weak password: %s."
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 \
154 "Try again."
156 static int converse(pam_handle_t *pamh, int style, l_const char *text,
157 struct pam_response **resp)
159 pam_item_t item;
160 const struct pam_conv *conv;
161 struct pam_message msg, *pmsg;
162 int status;
164 *resp = NULL;
165 status = pam_get_item(pamh, PAM_CONV, &item);
166 if (status != PAM_SUCCESS)
167 return status;
168 conv = item;
170 pmsg = &msg;
171 msg.msg_style = style;
172 msg.msg = (char *)text;
174 return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp,
175 conv->appdata_ptr);
178 #ifdef __GNUC__
179 __attribute__ ((format (printf, 3, 4)))
180 #endif
181 static int say(pam_handle_t *pamh, int style, const char *format, ...)
183 va_list args;
184 char buffer[0x800];
185 int needed;
186 struct pam_response *resp;
187 int status;
189 va_start(args, format);
190 needed = vsnprintf(buffer, sizeof(buffer), format, args);
191 va_end(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);
197 } else {
198 status = PAM_ABORT;
199 memset(buffer, 0, sizeof(buffer));
202 return status;
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);
210 return -1;
212 say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED);
215 return 0;
218 static int check_pass(struct passwd *pw, const char *pass)
220 #ifdef HAVE_SHADOW
221 struct spwd *spw;
222 const char *hash;
223 int retval;
225 #ifdef __hpux
226 if (iscomsec()) {
227 #else
228 if (!strcmp(pw->pw_passwd, "x")) {
229 #endif
230 spw = getspnam(pw->pw_name);
231 endspent();
232 if (!spw)
233 return -1;
234 #ifdef __hpux
235 hash = bigcrypt(pass, spw->sp_pwdp);
236 #else
237 hash = crypt(pass, spw->sp_pwdp);
238 #endif
239 retval = strcmp(hash, spw->sp_pwdp) ? -1 : 0;
240 memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp));
241 return retval;
243 #endif
245 return strcmp(crypt(pass, pw->pw_passwd), pw->pw_passwd) ? -1 : 0;
248 static int am_root(pam_handle_t *pamh)
250 pam_item_t item;
251 const char *service;
253 if (getuid() != 0)
254 return 0;
256 if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS)
257 return 0;
258 service = item;
260 return !strcmp(service, "passwd");
263 static int parse(params_t *params, pam_handle_t *pamh,
264 int argc, const char **argv)
266 const char *p;
267 char *e;
268 int i;
269 unsigned long v;
271 while (argc) {
272 if (!strncmp(*argv, "min=", 4)) {
273 p = *argv + 4;
274 for (i = 0; i < 5; i++) {
275 if (!strncmp(p, "disabled", 8)) {
276 v = INT_MAX;
277 p += 8;
278 } else {
279 v = strtoul(p, &e, 10);
280 p = e;
282 if (i < 4 && *p++ != ',') break;
283 if (v > INT_MAX) break;
284 if (i && (int)v > params->qc.min[i - 1]) break;
285 params->qc.min[i] = v;
287 if (*p) break;
288 } else
289 if (!strncmp(*argv, "max=", 4)) {
290 v = strtoul(*argv + 4, &e, 10);
291 if (*e || v < 8 || v > INT_MAX) break;
292 params->qc.max = v;
293 } else
294 if (!strncmp(*argv, "passphrase=", 11)) {
295 v = strtoul(*argv + 11, &e, 10);
296 if (*e || v > INT_MAX) break;
297 params->qc.passphrase_words = v;
298 } else
299 if (!strncmp(*argv, "match=", 6)) {
300 v = strtoul(*argv + 6, &e, 10);
301 if (*e || v > INT_MAX) break;
302 params->qc.match_length = v;
303 } else
304 if (!strncmp(*argv, "similar=", 8)) {
305 if (!strcmp(*argv + 8, "permit"))
306 params->qc.similar_deny = 0;
307 else
308 if (!strcmp(*argv + 8, "deny"))
309 params->qc.similar_deny = 1;
310 else
311 break;
312 } else
313 if (!strncmp(*argv, "random=", 7)) {
314 v = strtoul(*argv + 7, &e, 10);
315 if (!strcmp(e, ",only")) {
316 e += 5;
317 params->qc.min[4] = INT_MAX;
319 if (*e || (v && v < 24) || v > 72) break;
320 params->qc.random_bits = v;
321 } else
322 if (!strncmp(*argv, "enforce=", 8)) {
323 params->flags &= ~F_ENFORCE_MASK;
324 if (!strcmp(*argv + 8, "users"))
325 params->flags |= F_ENFORCE_USERS;
326 else
327 if (!strcmp(*argv + 8, "everyone"))
328 params->flags |= F_ENFORCE_EVERYONE;
329 else
330 if (strcmp(*argv + 8, "none"))
331 break;
332 } else
333 if (!strcmp(*argv, "non-unix")) {
334 if (params->flags & F_CHECK_OLDAUTHTOK) break;
335 params->flags |= F_NON_UNIX;
336 } else
337 if (!strncmp(*argv, "retry=", 6)) {
338 v = strtoul(*argv + 6, &e, 10);
339 if (*e || v > INT_MAX) break;
340 params->retry = v;
341 } else
342 if (!strncmp(*argv, "ask_oldauthtok", 14)) {
343 params->flags &= ~F_ASK_OLDAUTHTOK_MASK;
344 if (params->flags & F_USE_FIRST_PASS) break;
345 if (!strcmp(*argv + 14, "=update"))
346 params->flags |= F_ASK_OLDAUTHTOK_UPDATE;
347 else
348 if (!(*argv)[14])
349 params->flags |= F_ASK_OLDAUTHTOK_PRELIM;
350 else
351 break;
352 } else
353 if (!strcmp(*argv, "check_oldauthtok")) {
354 if (params->flags & F_NON_UNIX) break;
355 params->flags |= F_CHECK_OLDAUTHTOK;
356 } else
357 if (!strcmp(*argv, "use_first_pass")) {
358 if (params->flags & F_ASK_OLDAUTHTOK_MASK) break;
359 params->flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK;
360 } else
361 if (!strcmp(*argv, "use_authtok")) {
362 params->flags |= F_USE_AUTHTOK;
363 } else
364 break;
365 argc--; argv++;
368 if (argc) {
369 say(pamh, PAM_ERROR_MSG, am_root(pamh) ?
370 MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED, *argv);
371 return PAM_ABORT;
374 return PAM_SUCCESS;
377 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
378 int argc, const char **argv)
380 params_t params;
381 struct pam_response *resp;
382 struct passwd *pw, fake_pw;
383 pam_item_t item;
384 const char *user, *oldpass, *newpass;
385 char *trypass, *randompass;
386 const char *reason;
387 int ask_oldauthtok;
388 int randomonly, enforce, retries_left, retry_wanted;
389 int status;
391 params = defaults;
392 status = parse(&params, pamh, argc, argv);
393 if (status != PAM_SUCCESS)
394 return status;
396 ask_oldauthtok = 0;
397 if (flags & PAM_PRELIM_CHECK) {
398 if (params.flags & F_ASK_OLDAUTHTOK_PRELIM)
399 ask_oldauthtok = 1;
400 } else
401 if (flags & PAM_UPDATE_AUTHTOK) {
402 if (params.flags & F_ASK_OLDAUTHTOK_UPDATE)
403 ask_oldauthtok = 1;
404 } else
405 return PAM_SERVICE_ERR;
407 if (ask_oldauthtok && !am_root(pamh)) {
408 status = converse(pamh, PAM_PROMPT_ECHO_OFF,
409 PROMPT_OLDPASS, &resp);
411 if (status == PAM_SUCCESS) {
412 if (resp && resp->resp) {
413 status = pam_set_item(pamh,
414 PAM_OLDAUTHTOK, resp->resp);
415 pwqc_drop_pam_reply(resp, 1);
416 } else
417 status = PAM_AUTHTOK_RECOVERY_ERR;
420 if (status != PAM_SUCCESS)
421 return status;
424 if (flags & PAM_PRELIM_CHECK)
425 return status;
427 status = pam_get_item(pamh, PAM_USER, &item);
428 if (status != PAM_SUCCESS)
429 return status;
430 user = item;
432 status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item);
433 if (status != PAM_SUCCESS)
434 return status;
435 oldpass = item;
437 if (params.flags & F_NON_UNIX) {
438 pw = &fake_pw;
439 pw->pw_name = (char *)user;
440 pw->pw_gecos = "";
441 } else {
442 pw = getpwnam(user);
443 endpwent();
444 if (!pw)
445 return PAM_USER_UNKNOWN;
446 if ((params.flags & F_CHECK_OLDAUTHTOK) && !am_root(pamh) &&
447 (!oldpass || check_pass(pw, oldpass)))
448 status = PAM_AUTH_ERR;
449 memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
450 if (status != PAM_SUCCESS)
451 return status;
454 randomonly = params.qc.min[4] > params.qc.max;
456 if (am_root(pamh))
457 enforce = params.flags & F_ENFORCE_ROOT;
458 else
459 enforce = params.flags & F_ENFORCE_USERS;
461 if (params.flags & F_USE_AUTHTOK) {
462 status = pam_get_item(pamh, PAM_AUTHTOK, &item);
463 if (status != PAM_SUCCESS)
464 return status;
465 newpass = item;
466 if (!newpass || (check_max(&params, pamh, newpass) && enforce))
467 return PAM_AUTHTOK_ERR;
468 reason = _passwdqc_check(&params.qc, newpass, oldpass, pw);
469 if (reason) {
470 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
471 if (enforce)
472 status = PAM_AUTHTOK_ERR;
474 return status;
477 retries_left = params.retry;
479 retry:
480 retry_wanted = 0;
482 if (!randomonly &&
483 params.qc.passphrase_words && params.qc.min[2] <= params.qc.max)
484 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH);
485 else
486 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD);
487 if (status != PAM_SUCCESS)
488 return status;
490 if (!randomonly && params.qc.min[0] == params.qc.min[4])
491 status = say(pamh, PAM_TEXT_INFO,
492 MESSAGE_EXPLAIN_PASSWORD_1CLASS,
493 params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
494 params.qc.min[4]);
495 else
496 if (!randomonly && params.qc.min[3] == params.qc.min[4])
497 status = say(pamh, PAM_TEXT_INFO,
498 MESSAGE_EXPLAIN_PASSWORD_CLASSES,
499 params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
500 params.qc.min[4],
501 params.qc.min[1] != params.qc.min[3] ? 3 : 2);
502 else
503 if (!randomonly && params.qc.min[3] == INT_MAX)
504 status = say(pamh, PAM_TEXT_INFO,
505 MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES,
506 params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
507 params.qc.min[4]);
508 else
509 if (!randomonly)
510 status = say(pamh, PAM_TEXT_INFO,
511 MESSAGE_EXPLAIN_PASSWORD_ALT,
512 params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
513 params.qc.min[3],
514 params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
515 params.qc.min[4]);
516 if (status != PAM_SUCCESS)
517 return status;
519 if (!randomonly &&
520 params.qc.passphrase_words &&
521 params.qc.min[2] <= params.qc.max) {
522 status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSPHRASE,
523 params.qc.passphrase_words,
524 params.qc.min[2], params.qc.max);
525 if (status != PAM_SUCCESS)
526 return status;
529 randompass = _passwdqc_random(&params.qc);
530 if (randompass) {
531 status = say(pamh, PAM_TEXT_INFO, randomonly ?
532 MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
533 if (status != PAM_SUCCESS) {
534 pwqc_overwrite_string(randompass);
535 randompass = NULL;
537 } else
538 if (randomonly) {
539 say(pamh, PAM_ERROR_MSG, am_root(pamh) ?
540 MESSAGE_RANDOMFAILED : MESSAGE_MISCONFIGURED);
541 return PAM_AUTHTOK_ERR;
544 status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp);
545 if (status == PAM_SUCCESS && (!resp || !resp->resp))
546 status = PAM_AUTHTOK_ERR;
548 if (status != PAM_SUCCESS) {
549 pwqc_overwrite_string(randompass);
550 return status;
553 trypass = strdup(resp->resp);
555 pwqc_drop_pam_reply(resp, 1);
557 if (!trypass) {
558 pwqc_overwrite_string(randompass);
559 return PAM_AUTHTOK_ERR;
562 if (check_max(&params, pamh, trypass) && enforce) {
563 status = PAM_AUTHTOK_ERR;
564 retry_wanted = 1;
567 reason = NULL;
568 if (status == PAM_SUCCESS &&
569 (!randompass || !strstr(trypass, randompass)) &&
570 (randomonly ||
571 (reason = _passwdqc_check(&params.qc, trypass, oldpass, pw)))) {
572 if (randomonly)
573 say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM);
574 else
575 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
576 if (enforce) {
577 status = PAM_AUTHTOK_ERR;
578 retry_wanted = 1;
582 if (status == PAM_SUCCESS)
583 status = converse(pamh, PAM_PROMPT_ECHO_OFF,
584 PROMPT_NEWPASS2, &resp);
585 if (status == PAM_SUCCESS) {
586 if (resp && resp->resp) {
587 if (strcmp(trypass, resp->resp)) {
588 status = say(pamh,
589 PAM_ERROR_MSG, MESSAGE_MISTYPED);
590 if (status == PAM_SUCCESS) {
591 status = PAM_AUTHTOK_ERR;
592 retry_wanted = 1;
595 pwqc_drop_pam_reply(resp, 1);
596 } else
597 status = PAM_AUTHTOK_ERR;
600 if (status == PAM_SUCCESS)
601 status = pam_set_item(pamh, PAM_AUTHTOK, trypass);
603 pwqc_overwrite_string(randompass);
604 pwqc_overwrite_string(trypass);
605 pwqc_drop_mem(trypass);
607 if (retry_wanted && --retries_left > 0) {
608 status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY);
609 if (status == PAM_SUCCESS)
610 goto retry;
613 return status;
616 #ifdef PAM_MODULE_ENTRY
617 PAM_MODULE_ENTRY("pam_passwdqc");
618 #elif defined(PAM_STATIC)
619 struct pam_module _pam_passwdqc_modstruct = {
620 "pam_passwdqc",
621 NULL,
622 NULL,
623 NULL,
624 NULL,
625 NULL,
626 pam_sm_chauthtok
628 #endif