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 #ifdef HAVE_SHADOW 221 struct spwd *spw; 222 const char *hash; 223 int retval; 224 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; 242 } 243 #endif 244 245 return strcmp(crypt(pass, pw->pw_passwd), pw->pw_passwd) ? -1 : 0; 246 } 247 248 static int am_root(pam_handle_t *pamh) 249 { 250 pam_item_t item; 251 const char *service; 252 253 if (getuid() != 0) 254 return 0; 255 256 if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS) 257 return 0; 258 service = item; 259 260 return !strcmp(service, "passwd"); 261 } 262 263 static int parse(params_t *params, pam_handle_t *pamh, 264 int argc, const char **argv) 265 { 266 const char *p; 267 char *e; 268 int i; 269 unsigned long v; 270 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; 281 } 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; 286 } 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; 318 } 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++; 366 } 367 368 if (argc) { 369 say(pamh, PAM_ERROR_MSG, am_root(pamh) ? 370 MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED, *argv); 371 return PAM_ABORT; 372 } 373 374 return PAM_SUCCESS; 375 } 376 377 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, 378 int argc, const char **argv) 379 { 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; 390 391 params = defaults; 392 status = parse(¶ms, pamh, argc, argv); 393 if (status != PAM_SUCCESS) 394 return status; 395 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; 406 407 if (ask_oldauthtok && !am_root(pamh)) { 408 status = converse(pamh, PAM_PROMPT_ECHO_OFF, 409 PROMPT_OLDPASS, &resp); 410 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; 418 } 419 420 if (status != PAM_SUCCESS) 421 return status; 422 } 423 424 if (flags & PAM_PRELIM_CHECK) 425 return status; 426 427 status = pam_get_item(pamh, PAM_USER, &item); 428 if (status != PAM_SUCCESS) 429 return status; 430 user = item; 431 432 status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item); 433 if (status != PAM_SUCCESS) 434 return status; 435 oldpass = item; 436 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; 452 } 453 454 randomonly = params.qc.min[4] > params.qc.max; 455 456 if (am_root(pamh)) 457 enforce = params.flags & F_ENFORCE_ROOT; 458 else 459 enforce = params.flags & F_ENFORCE_USERS; 460 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(¶ms, pamh, newpass) && enforce)) 467 return PAM_AUTHTOK_ERR; 468 reason = _passwdqc_check(¶ms.qc, newpass, oldpass, pw); 469 if (reason) { 470 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason); 471 if (enforce) 472 status = PAM_AUTHTOK_ERR; 473 } 474 return status; 475 } 476 477 retries_left = params.retry; 478 479 retry: 480 retry_wanted = 0; 481 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; 489 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; 518 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; 527 } 528 529 randompass = _passwdqc_random(¶ms.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; 536 } 537 } else 538 if (randomonly) { 539 say(pamh, PAM_ERROR_MSG, am_root(pamh) ? 540 MESSAGE_RANDOMFAILED : MESSAGE_MISCONFIGURED); 541 return PAM_AUTHTOK_ERR; 542 } 543 544 status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp); 545 if (status == PAM_SUCCESS && (!resp || !resp->resp)) 546 status = PAM_AUTHTOK_ERR; 547 548 if (status != PAM_SUCCESS) { 549 pwqc_overwrite_string(randompass); 550 return status; 551 } 552 553 trypass = strdup(resp->resp); 554 555 pwqc_drop_pam_reply(resp, 1); 556 557 if (!trypass) { 558 pwqc_overwrite_string(randompass); 559 return PAM_AUTHTOK_ERR; 560 } 561 562 if (check_max(¶ms, pamh, trypass) && enforce) { 563 status = PAM_AUTHTOK_ERR; 564 retry_wanted = 1; 565 } 566 567 reason = NULL; 568 if (status == PAM_SUCCESS && 569 (!randompass || !strstr(trypass, randompass)) && 570 (randomonly || 571 (reason = _passwdqc_check(¶ms.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; 579 } 580 } 581 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; 593 } 594 } 595 pwqc_drop_pam_reply(resp, 1); 596 } else 597 status = PAM_AUTHTOK_ERR; 598 } 599 600 if (status == PAM_SUCCESS) 601 status = pam_set_item(pamh, PAM_AUTHTOK, trypass); 602 603 pwqc_overwrite_string(randompass); 604 pwqc_overwrite_string(trypass); 605 pwqc_drop_mem(trypass); 606 607 if (retry_wanted && --retries_left > 0) { 608 status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY); 609 if (status == PAM_SUCCESS) 610 goto retry; 611 } 612 613 return status; 614 } 615 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 627 }; 628 #endif 629