1 /* 2 * Copyright (c) 2000-2003,2005 by Solar Designer. See LICENSE. 3 */ 4 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 23 24 #define PAM_SM_PASSWORD 25 #ifndef LINUX_PAM 26 #include <security/pam_appl.h> 27 #endif 28 #include <security/pam_modules.h> 29 30 #include "pam_macros.h" 31 32 #if !defined(PAM_EXTERN) && !defined(PAM_STATIC) 33 #define PAM_EXTERN extern 34 #endif 35 36 #if !defined(PAM_AUTHTOK_RECOVERY_ERR) && defined(PAM_AUTHTOK_RECOVER_ERR) 37 #define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR 38 #endif 39 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; 54 55 #include "passwdqc.h" 56 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 68 69 typedef struct { 70 passwdqc_params_t qc; 71 int flags; 72 int retry; 73 } params_t; 74 75 static params_t defaults = { 76 { 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 */ 83 }, 84 F_ENFORCE_EVERYONE, /* flags */ 85 3 /* retry */ 86 }; 87 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: " 94 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." 155 156 static int converse(pam_handle_t *pamh, int style, l_const char *text, 157 struct pam_response **resp) 158 { 159 pam_item_t item; 160 const struct pam_conv *conv; 161 struct pam_message msg, *pmsg; 162 int status; 163 164 *resp = NULL; 165 status = pam_get_item(pamh, PAM_CONV, &item); 166 if (status != PAM_SUCCESS) 167 return status; 168 conv = item; 169 170 pmsg = &msg; 171 msg.msg_style = style; 172 msg.msg = (char *)text; 173 174 return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp, 175 conv->appdata_ptr); 176 } 177 178 #ifdef __GNUC__ 179 __attribute__ ((format (printf, 3, 4))) 180 #endif 181 static int say(pam_handle_t *pamh, int style, const char *format, ...) 182 { 183 va_list args; 184 char buffer[0x800]; 185 int needed; 186 struct pam_response *resp; 187 int status; 188 189 va_start(args, format); 190 needed = vsnprintf(buffer, sizeof(buffer), format, args); 191 va_end(args); 192 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)); 200 } 201 202 return status; 203 } 204 205 static int check_max(params_t *params, pam_handle_t *pamh, const char *newpass) 206 { 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; 211 } 212 say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED); 213 } 214 215 return 0; 216 } 217 218 static int check_pass(struct passwd *pw, const char *pass) 219 { 220 char *cryptpw; 221 #ifdef HAVE_SHADOW 222 struct spwd *spw; 223 const char *hash; 224 int retval; 225 226 #ifdef __hpux 227 if (iscomsec()) { 228 #else 229 if (!strcmp(pw->pw_passwd, "x")) { 230 #endif 231 spw = getspnam(pw->pw_name); 232 endspent(); 233 if (!spw) 234 return -1; 235 #ifdef __hpux 236 hash = bigcrypt(pass, spw->sp_pwdp); 237 #else 238 hash = crypt(pass, spw->sp_pwdp); 239 #endif 240 retval = (hash == NULL || strcmp(hash, spw->sp_pwdp)) ? -1 : 0; 241 memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp)); 242 return retval; 243 } 244 #endif 245 cryptpw = crypt(pass, pw->pw_passwd); 246 return (cryptpw == NULL || strcmp(cryptpw, pw->pw_passwd)) ? -1 : 0; 247 } 248 249 static int am_root(pam_handle_t *pamh) 250 { 251 pam_item_t item; 252 const char *service; 253 254 if (getuid() != 0) 255 return 0; 256 257 if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS) 258 return 0; 259 service = item; 260 261 return !strcmp(service, "passwd"); 262 } 263 264 static int parse(params_t *params, pam_handle_t *pamh, 265 int argc, const char **argv) 266 { 267 const char *p; 268 char *e; 269 int i; 270 unsigned long v; 271 272 while (argc) { 273 if (!strncmp(*argv, "min=", 4)) { 274 p = *argv + 4; 275 for (i = 0; i < 5; i++) { 276 if (!strncmp(p, "disabled", 8)) { 277 v = INT_MAX; 278 p += 8; 279 } else { 280 v = strtoul(p, &e, 10); 281 p = e; 282 } 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; 287 } 288 if (*p) break; 289 } else 290 if (!strncmp(*argv, "max=", 4)) { 291 v = strtoul(*argv + 4, &e, 10); 292 if (*e || v < 8 || v > INT_MAX) break; 293 params->qc.max = v; 294 } else 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; 299 } else 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; 304 } else 305 if (!strncmp(*argv, "similar=", 8)) { 306 if (!strcmp(*argv + 8, "permit")) 307 params->qc.similar_deny = 0; 308 else 309 if (!strcmp(*argv + 8, "deny")) 310 params->qc.similar_deny = 1; 311 else 312 break; 313 } else 314 if (!strncmp(*argv, "random=", 7)) { 315 v = strtoul(*argv + 7, &e, 10); 316 if (!strcmp(e, ",only")) { 317 e += 5; 318 params->qc.min[4] = INT_MAX; 319 } 320 if (*e || (v && v < 24) || v > 72) break; 321 params->qc.random_bits = v; 322 } else 323 if (!strncmp(*argv, "enforce=", 8)) { 324 params->flags &= ~F_ENFORCE_MASK; 325 if (!strcmp(*argv + 8, "users")) 326 params->flags |= F_ENFORCE_USERS; 327 else 328 if (!strcmp(*argv + 8, "everyone")) 329 params->flags |= F_ENFORCE_EVERYONE; 330 else 331 if (strcmp(*argv + 8, "none")) 332 break; 333 } else 334 if (!strcmp(*argv, "non-unix")) { 335 if (params->flags & F_CHECK_OLDAUTHTOK) break; 336 params->flags |= F_NON_UNIX; 337 } else 338 if (!strncmp(*argv, "retry=", 6)) { 339 v = strtoul(*argv + 6, &e, 10); 340 if (*e || v > INT_MAX) break; 341 params->retry = v; 342 } else 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; 348 else 349 if (!(*argv)[14]) 350 params->flags |= F_ASK_OLDAUTHTOK_PRELIM; 351 else 352 break; 353 } else 354 if (!strcmp(*argv, "check_oldauthtok")) { 355 if (params->flags & F_NON_UNIX) break; 356 params->flags |= F_CHECK_OLDAUTHTOK; 357 } else 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; 361 } else 362 if (!strcmp(*argv, "use_authtok")) { 363 params->flags |= F_USE_AUTHTOK; 364 } else 365 break; 366 argc--; argv++; 367 } 368 369 if (argc) { 370 say(pamh, PAM_ERROR_MSG, am_root(pamh) ? 371 MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED, *argv); 372 return PAM_ABORT; 373 } 374 375 return PAM_SUCCESS; 376 } 377 378 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, 379 int argc, const char **argv) 380 { 381 params_t params; 382 struct pam_response *resp; 383 struct passwd *pw, fake_pw; 384 pam_item_t item; 385 const char *user, *oldpass, *newpass; 386 char *trypass, *randompass; 387 const char *reason; 388 int ask_oldauthtok; 389 int randomonly, enforce, retries_left, retry_wanted; 390 int status; 391 392 params = defaults; 393 status = parse(¶ms, pamh, argc, argv); 394 if (status != PAM_SUCCESS) 395 return status; 396 397 ask_oldauthtok = 0; 398 if (flags & PAM_PRELIM_CHECK) { 399 if (params.flags & F_ASK_OLDAUTHTOK_PRELIM) 400 ask_oldauthtok = 1; 401 } else 402 if (flags & PAM_UPDATE_AUTHTOK) { 403 if (params.flags & F_ASK_OLDAUTHTOK_UPDATE) 404 ask_oldauthtok = 1; 405 } else 406 return PAM_SERVICE_ERR; 407 408 if (ask_oldauthtok && !am_root(pamh)) { 409 status = converse(pamh, PAM_PROMPT_ECHO_OFF, 410 PROMPT_OLDPASS, &resp); 411 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); 417 } else 418 status = PAM_AUTHTOK_RECOVERY_ERR; 419 } 420 421 if (status != PAM_SUCCESS) 422 return status; 423 } 424 425 if (flags & PAM_PRELIM_CHECK) 426 return status; 427 428 status = pam_get_item(pamh, PAM_USER, &item); 429 if (status != PAM_SUCCESS) 430 return status; 431 user = item; 432 433 status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item); 434 if (status != PAM_SUCCESS) 435 return status; 436 oldpass = item; 437 438 if (params.flags & F_NON_UNIX) { 439 pw = &fake_pw; 440 pw->pw_name = (char *)user; 441 pw->pw_gecos = ""; 442 } else { 443 pw = getpwnam(user); 444 endpwent(); 445 if (!pw) 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) 452 return status; 453 } 454 455 randomonly = params.qc.min[4] > params.qc.max; 456 457 if (am_root(pamh)) 458 enforce = params.flags & F_ENFORCE_ROOT; 459 else 460 enforce = params.flags & F_ENFORCE_USERS; 461 462 if (params.flags & F_USE_AUTHTOK) { 463 status = pam_get_item(pamh, PAM_AUTHTOK, &item); 464 if (status != PAM_SUCCESS) 465 return status; 466 newpass = item; 467 if (!newpass || (check_max(¶ms, pamh, newpass) && enforce)) 468 return PAM_AUTHTOK_ERR; 469 reason = _passwdqc_check(¶ms.qc, newpass, oldpass, pw); 470 if (reason) { 471 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason); 472 if (enforce) 473 status = PAM_AUTHTOK_ERR; 474 } 475 return status; 476 } 477 478 retries_left = params.retry; 479 480 retry: 481 retry_wanted = 0; 482 483 if (!randomonly && 484 params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) 485 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH); 486 else 487 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD); 488 if (status != PAM_SUCCESS) 489 return status; 490 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" : "", 495 params.qc.min[4]); 496 else 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" : "", 501 params.qc.min[4], 502 params.qc.min[1] != params.qc.min[3] ? 3 : 2); 503 else 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" : "", 508 params.qc.min[4]); 509 else 510 if (!randomonly) 511 status = say(pamh, PAM_TEXT_INFO, 512 MESSAGE_EXPLAIN_PASSWORD_ALT, 513 params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "", 514 params.qc.min[3], 515 params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "", 516 params.qc.min[4]); 517 if (status != PAM_SUCCESS) 518 return status; 519 520 if (!randomonly && 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) 527 return status; 528 } 529 530 randompass = _passwdqc_random(¶ms.qc); 531 if (randompass) { 532 status = say(pamh, PAM_TEXT_INFO, randomonly ? 533 MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass); 534 if (status != PAM_SUCCESS) { 535 pwqc_overwrite_string(randompass); 536 randompass = NULL; 537 } 538 } else 539 if (randomonly) { 540 say(pamh, PAM_ERROR_MSG, am_root(pamh) ? 541 MESSAGE_RANDOMFAILED : MESSAGE_MISCONFIGURED); 542 return PAM_AUTHTOK_ERR; 543 } 544 545 status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp); 546 if (status == PAM_SUCCESS && (!resp || !resp->resp)) 547 status = PAM_AUTHTOK_ERR; 548 549 if (status != PAM_SUCCESS) { 550 pwqc_overwrite_string(randompass); 551 return status; 552 } 553 554 trypass = strdup(resp->resp); 555 556 pwqc_drop_pam_reply(resp, 1); 557 558 if (!trypass) { 559 pwqc_overwrite_string(randompass); 560 return PAM_AUTHTOK_ERR; 561 } 562 563 if (check_max(¶ms, pamh, trypass) && enforce) { 564 status = PAM_AUTHTOK_ERR; 565 retry_wanted = 1; 566 } 567 568 reason = NULL; 569 if (status == PAM_SUCCESS && 570 (!randompass || !strstr(trypass, randompass)) && 571 (randomonly || 572 (reason = _passwdqc_check(¶ms.qc, trypass, oldpass, pw)))) { 573 if (randomonly) 574 say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM); 575 else 576 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason); 577 if (enforce) { 578 status = PAM_AUTHTOK_ERR; 579 retry_wanted = 1; 580 } 581 } 582 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)) { 589 status = say(pamh, 590 PAM_ERROR_MSG, MESSAGE_MISTYPED); 591 if (status == PAM_SUCCESS) { 592 status = PAM_AUTHTOK_ERR; 593 retry_wanted = 1; 594 } 595 } 596 pwqc_drop_pam_reply(resp, 1); 597 } else 598 status = PAM_AUTHTOK_ERR; 599 } 600 601 if (status == PAM_SUCCESS) 602 status = pam_set_item(pamh, PAM_AUTHTOK, trypass); 603 604 pwqc_overwrite_string(randompass); 605 pwqc_overwrite_string(trypass); 606 pwqc_drop_mem(trypass); 607 608 if (retry_wanted && --retries_left > 0) { 609 status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY); 610 if (status == PAM_SUCCESS) 611 goto retry; 612 } 613 614 return status; 615 } 616 617 #ifdef PAM_MODULE_ENTRY 618 PAM_MODULE_ENTRY("pam_passwdqc"); 619 #elif defined(PAM_STATIC) 620 struct pam_module _pam_passwdqc_modstruct = { 621 "pam_passwdqc", 622 NULL, 623 NULL, 624 NULL, 625 NULL, 626 NULL, 627 pam_sm_chauthtok 628 }; 629 #endif 630