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 41 typedef 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 57 typedef struct { 58 passwdqc_params_t qc; 59 int flags; 60 int retry; 61 } params_t; 62 63 static 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 132 static 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 157 static 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 180 static 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 193 static 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 310 PAM_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(¶ms, 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(¶ms, pamh, curpass) && enforce)) 422 return PAM_AUTHTOK_ERR; 423 reason = _passwdqc_check(¶ms.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 434 retry: 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(¶ms.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(¶ms, 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(¶ms.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 557 PAM_MODULE_ENTRY("pam_passwdqc"); 558 #elif defined(PAM_STATIC) 559 struct 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