1 /* 2 * Copyright (c) 2000-2002 by Solar Designer. See LICENSE. 3 */ 4 5 #define _XOPEN_SOURCE 500 6 #define _XOPEN_SOURCE_EXTENDED 7 #define _XOPEN_VERSION 500 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 struct pam_conv *conv; 136 struct pam_message msg, *pmsg; 137 int status; 138 139 status = pam_get_item(pamh, PAM_CONV, (pam_item_t *)&conv); 140 if (status != PAM_SUCCESS) 141 return status; 142 143 pmsg = &msg; 144 msg.msg_style = style; 145 msg.msg = text; 146 147 *resp = NULL; 148 return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp, 149 conv->appdata_ptr); 150 } 151 152 #ifdef __GNUC__ 153 __attribute__ ((format (printf, 3, 4))) 154 #endif 155 static int say(pam_handle_t *pamh, int style, const char *format, ...) 156 { 157 va_list args; 158 char buffer[0x800]; 159 int needed; 160 struct pam_response *resp; 161 int status; 162 163 va_start(args, format); 164 needed = vsnprintf(buffer, sizeof(buffer), format, args); 165 va_end(args); 166 167 if ((unsigned int)needed < sizeof(buffer)) { 168 status = converse(pamh, style, buffer, &resp); 169 _pam_overwrite(buffer); 170 } else { 171 status = PAM_ABORT; 172 memset(buffer, 0, sizeof(buffer)); 173 } 174 175 return status; 176 } 177 178 static int check_max(params_t *params, pam_handle_t *pamh, const char *newpass) 179 { 180 if ((int)strlen(newpass) > params->qc.max) { 181 if (params->qc.max != 8) { 182 say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG); 183 return -1; 184 } 185 say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED); 186 } 187 188 return 0; 189 } 190 191 static int parse(params_t *params, pam_handle_t *pamh, 192 int argc, const char **argv) 193 { 194 const char *p; 195 char *e; 196 int i; 197 unsigned long v; 198 199 while (argc) { 200 if (!strncmp(*argv, "min=", 4)) { 201 p = *argv + 4; 202 for (i = 0; i < 5; i++) { 203 if (!strncmp(p, "disabled", 8)) { 204 v = INT_MAX; 205 p += 8; 206 } else { 207 v = strtoul(p, &e, 10); 208 p = e; 209 } 210 if (i < 4 && *p++ != ',') break; 211 if (v > INT_MAX) break; 212 if (i && (int)v > params->qc.min[i - 1]) break; 213 params->qc.min[i] = v; 214 } 215 if (*p) break; 216 } else 217 if (!strncmp(*argv, "max=", 4)) { 218 v = strtoul(*argv + 4, &e, 10); 219 if (*e || v < 8 || v > INT_MAX) break; 220 params->qc.max = v; 221 } else 222 if (!strncmp(*argv, "passphrase=", 11)) { 223 v = strtoul(*argv + 11, &e, 10); 224 if (*e || v > INT_MAX) break; 225 params->qc.passphrase_words = v; 226 } else 227 if (!strncmp(*argv, "match=", 6)) { 228 v = strtoul(*argv + 6, &e, 10); 229 if (*e || v > INT_MAX) break; 230 params->qc.match_length = v; 231 } else 232 if (!strncmp(*argv, "similar=", 8)) { 233 if (!strcmp(*argv + 8, "permit")) 234 params->qc.similar_deny = 0; 235 else 236 if (!strcmp(*argv + 8, "deny")) 237 params->qc.similar_deny = 1; 238 else 239 break; 240 } else 241 if (!strncmp(*argv, "random=", 7)) { 242 v = strtoul(*argv + 7, &e, 10); 243 if (!strcmp(e, ",only")) { 244 e += 5; 245 params->qc.min[4] = INT_MAX; 246 } 247 if (*e || v > INT_MAX) break; 248 params->qc.random_bits = v; 249 } else 250 if (!strncmp(*argv, "enforce=", 8)) { 251 params->flags &= ~F_ENFORCE_MASK; 252 if (!strcmp(*argv + 8, "users")) 253 params->flags |= F_ENFORCE_USERS; 254 else 255 if (!strcmp(*argv + 8, "everyone")) 256 params->flags |= F_ENFORCE_EVERYONE; 257 else 258 if (strcmp(*argv + 8, "none")) 259 break; 260 } else 261 if (!strcmp(*argv, "non-unix")) { 262 if (params->flags & F_CHECK_OLDAUTHTOK) break; 263 params->flags |= F_NON_UNIX; 264 } else 265 if (!strncmp(*argv, "retry=", 6)) { 266 v = strtoul(*argv + 6, &e, 10); 267 if (*e || v > INT_MAX) break; 268 params->retry = v; 269 } else 270 if (!strncmp(*argv, "ask_oldauthtok", 14)) { 271 params->flags &= ~F_ASK_OLDAUTHTOK_MASK; 272 if (params->flags & F_USE_FIRST_PASS) break; 273 if (!strcmp(*argv + 14, "=update")) 274 params->flags |= F_ASK_OLDAUTHTOK_UPDATE; 275 else 276 if (!(*argv)[14]) 277 params->flags |= F_ASK_OLDAUTHTOK_PRELIM; 278 else 279 break; 280 } else 281 if (!strcmp(*argv, "check_oldauthtok")) { 282 if (params->flags & F_NON_UNIX) break; 283 params->flags |= F_CHECK_OLDAUTHTOK; 284 } else 285 if (!strcmp(*argv, "use_first_pass")) { 286 if (params->flags & F_ASK_OLDAUTHTOK_MASK) break; 287 params->flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK; 288 } else 289 if (!strcmp(*argv, "use_authtok")) { 290 params->flags |= F_USE_AUTHTOK; 291 } else 292 break; 293 argc--; argv++; 294 } 295 296 if (argc) { 297 say(pamh, PAM_ERROR_MSG, getuid() != 0 ? 298 MESSAGE_MISCONFIGURED : MESSAGE_INVALID_OPTION, *argv); 299 return PAM_ABORT; 300 } 301 302 return PAM_SUCCESS; 303 } 304 305 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, 306 int argc, const char **argv) 307 { 308 params_t params; 309 struct pam_response *resp; 310 struct passwd *pw, fake_pw; 311 #ifdef HAVE_SHADOW 312 struct spwd *spw; 313 #endif 314 char *user, *oldpass, *newpass, *randompass; 315 const char *reason; 316 int ask_oldauthtok; 317 int randomonly, enforce, retries_left, retry_wanted; 318 int status; 319 320 params = defaults; 321 status = parse(¶ms, pamh, argc, argv); 322 if (status != PAM_SUCCESS) 323 return status; 324 325 ask_oldauthtok = 0; 326 if (flags & PAM_PRELIM_CHECK) { 327 if (params.flags & F_ASK_OLDAUTHTOK_PRELIM) 328 ask_oldauthtok = 1; 329 } else 330 if (flags & PAM_UPDATE_AUTHTOK) { 331 if (params.flags & F_ASK_OLDAUTHTOK_UPDATE) 332 ask_oldauthtok = 1; 333 } else 334 return PAM_SERVICE_ERR; 335 336 if (ask_oldauthtok && getuid() != 0) { 337 status = converse(pamh, PAM_PROMPT_ECHO_OFF, 338 PROMPT_OLDPASS, &resp); 339 340 if (status == PAM_SUCCESS) { 341 if (resp && resp->resp) { 342 status = pam_set_item(pamh, 343 PAM_OLDAUTHTOK, resp->resp); 344 _pam_drop_reply(resp, 1); 345 } else 346 status = PAM_AUTHTOK_RECOVERY_ERR; 347 } 348 349 if (status != PAM_SUCCESS) 350 return status; 351 } 352 353 if (flags & PAM_PRELIM_CHECK) 354 return status; 355 356 status = pam_get_item(pamh, PAM_USER, (pam_item_t *)&user); 357 if (status != PAM_SUCCESS) 358 return status; 359 360 status = pam_get_item(pamh, PAM_OLDAUTHTOK, (pam_item_t *)&oldpass); 361 if (status != PAM_SUCCESS) 362 return status; 363 364 if (params.flags & F_NON_UNIX) { 365 pw = &fake_pw; 366 pw->pw_name = user; 367 pw->pw_gecos = ""; 368 } else { 369 pw = getpwnam(user); 370 endpwent(); 371 if (!pw) 372 return PAM_USER_UNKNOWN; 373 if ((params.flags & F_CHECK_OLDAUTHTOK) && getuid() != 0) { 374 if (!oldpass) 375 status = PAM_AUTH_ERR; 376 else 377 #ifdef HAVE_SHADOW 378 if (!strcmp(pw->pw_passwd, "x")) { 379 spw = getspnam(user); 380 endspent(); 381 if (spw) { 382 if (strcmp(crypt(oldpass, spw->sp_pwdp), 383 spw->sp_pwdp)) 384 status = PAM_AUTH_ERR; 385 memset(spw->sp_pwdp, 0, 386 strlen(spw->sp_pwdp)); 387 } else 388 status = PAM_AUTH_ERR; 389 } else 390 #endif 391 if (strcmp(crypt(oldpass, pw->pw_passwd), 392 pw->pw_passwd)) 393 status = PAM_AUTH_ERR; 394 } 395 memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); 396 if (status != PAM_SUCCESS) 397 return status; 398 } 399 400 randomonly = params.qc.min[4] > params.qc.max; 401 402 if (getuid() != 0) 403 enforce = params.flags & F_ENFORCE_USERS; 404 else 405 enforce = params.flags & F_ENFORCE_ROOT; 406 407 if (params.flags & F_USE_AUTHTOK) { 408 status = pam_get_item(pamh, PAM_AUTHTOK, 409 (pam_item_t *)&newpass); 410 if (status != PAM_SUCCESS) 411 return status; 412 if (!newpass || (check_max(¶ms, pamh, newpass) && enforce)) 413 return PAM_AUTHTOK_ERR; 414 reason = _passwdqc_check(¶ms.qc, newpass, oldpass, pw); 415 if (reason) { 416 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason); 417 if (enforce) 418 status = PAM_AUTHTOK_ERR; 419 } 420 return status; 421 } 422 423 retries_left = params.retry; 424 425 retry: 426 retry_wanted = 0; 427 428 if (!randomonly && 429 params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) 430 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH); 431 else 432 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD); 433 if (status != PAM_SUCCESS) 434 return status; 435 436 if (!randomonly && params.qc.min[3] <= params.qc.min[4]) 437 status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_1, 438 params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "", 439 params.qc.min[3]); 440 else 441 if (!randomonly) 442 status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_2, 443 params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "", 444 params.qc.min[3], 445 params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "", 446 params.qc.min[4]); 447 if (status != PAM_SUCCESS) 448 return status; 449 450 if (!randomonly && 451 params.qc.passphrase_words && 452 params.qc.min[2] <= params.qc.max) { 453 status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSPHRASE, 454 params.qc.passphrase_words, 455 params.qc.min[2], params.qc.max); 456 if (status != PAM_SUCCESS) 457 return status; 458 } 459 460 randompass = _passwdqc_random(¶ms.qc); 461 if (randompass) { 462 status = say(pamh, PAM_TEXT_INFO, randomonly ? 463 MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass); 464 if (status != PAM_SUCCESS) { 465 _pam_overwrite(randompass); 466 randompass = NULL; 467 } 468 } else 469 if (randomonly) { 470 say(pamh, PAM_ERROR_MSG, getuid() != 0 ? 471 MESSAGE_MISCONFIGURED : MESSAGE_RANDOMFAILED); 472 return PAM_AUTHTOK_ERR; 473 } 474 475 status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp); 476 if (status == PAM_SUCCESS && (!resp || !resp->resp)) 477 status = PAM_AUTHTOK_ERR; 478 479 if (status != PAM_SUCCESS) { 480 if (randompass) _pam_overwrite(randompass); 481 return status; 482 } 483 484 newpass = strdup(resp->resp); 485 486 _pam_drop_reply(resp, 1); 487 488 if (!newpass) { 489 if (randompass) _pam_overwrite(randompass); 490 return PAM_AUTHTOK_ERR; 491 } 492 493 if (check_max(¶ms, pamh, newpass) && enforce) { 494 status = PAM_AUTHTOK_ERR; 495 retry_wanted = 1; 496 } 497 498 reason = NULL; 499 if (status == PAM_SUCCESS && 500 (!randompass || !strstr(newpass, randompass)) && 501 (randomonly || 502 (reason = _passwdqc_check(¶ms.qc, newpass, oldpass, pw)))) { 503 if (randomonly) 504 say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM); 505 else 506 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason); 507 if (enforce) { 508 status = PAM_AUTHTOK_ERR; 509 retry_wanted = 1; 510 } 511 } 512 513 if (status == PAM_SUCCESS) 514 status = converse(pamh, PAM_PROMPT_ECHO_OFF, 515 PROMPT_NEWPASS2, &resp); 516 if (status == PAM_SUCCESS) { 517 if (resp && resp->resp) { 518 if (strcmp(newpass, resp->resp)) { 519 status = say(pamh, 520 PAM_ERROR_MSG, MESSAGE_MISTYPED); 521 if (status == PAM_SUCCESS) { 522 status = PAM_AUTHTOK_ERR; 523 retry_wanted = 1; 524 } 525 } 526 _pam_drop_reply(resp, 1); 527 } else 528 status = PAM_AUTHTOK_ERR; 529 } 530 531 if (status == PAM_SUCCESS) 532 status = pam_set_item(pamh, PAM_AUTHTOK, newpass); 533 534 if (randompass) _pam_overwrite(randompass); 535 _pam_overwrite(newpass); 536 free(newpass); 537 538 if (retry_wanted && --retries_left > 0) { 539 status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY); 540 if (status == PAM_SUCCESS) 541 goto retry; 542 } 543 544 return status; 545 } 546 547 #ifdef PAM_MODULE_ENTRY 548 PAM_MODULE_ENTRY("pam_passwdqc"); 549 #elif defined(PAM_STATIC) 550 struct pam_module _pam_passwdqc_modstruct = { 551 "pam_passwdqc", 552 NULL, 553 NULL, 554 NULL, 555 NULL, 556 NULL, 557 pam_sm_chauthtok 558 }; 559 #endif 560