1 /* 2 * Copyright (c) 2000-2003,2005,2012,2016,2019,2021 by Solar Designer 3 * Copyright (c) 2017,2018 by Dmitry V. Levin 4 * Copyright (c) 2017,2018 by Oleg Solovyov 5 * See LICENSE 6 */ 7 8 #if defined(__FreeBSD__) || defined(__DragonFly__) 9 /* For vsnprintf(3) */ 10 #define _XOPEN_SOURCE 600 11 #else 12 #define _XOPEN_SOURCE 500 13 #define _XOPEN_SOURCE_EXTENDED 14 #define _XOPEN_VERSION 500 15 #define _DEFAULT_SOURCE 16 #endif 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <stdarg.h> 20 #include <string.h> 21 #include <limits.h> 22 #include <unistd.h> 23 #include <pwd.h> 24 #ifdef HAVE_SHADOW 25 #include <shadow.h> 26 #endif 27 #ifdef HAVE_LIBAUDIT 28 #include <security/pam_modutil.h> 29 #include <libaudit.h> 30 #endif 31 32 #define PAM_SM_PASSWORD 33 #ifndef LINUX_PAM 34 #include <security/pam_appl.h> 35 #endif 36 #include <security/pam_modules.h> 37 38 #include "pam_macros.h" 39 40 #if !defined(PAM_EXTERN) && !defined(PAM_STATIC) 41 #define PAM_EXTERN extern 42 #endif 43 44 #if !defined(PAM_AUTHTOK_RECOVERY_ERR) && defined(PAM_AUTHTOK_RECOVER_ERR) 45 #define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR 46 #endif 47 48 #if (defined(__sun) || defined(__hpux)) && \ 49 !defined(LINUX_PAM) && !defined(_OPENPAM) 50 /* Sun's PAM doesn't use const here, while Linux-PAM and OpenPAM do */ 51 #define lo_const 52 #else 53 #define lo_const const 54 #endif 55 #ifdef _OPENPAM 56 /* OpenPAM doesn't use const here, while Linux-PAM does */ 57 #define l_const 58 #else 59 #define l_const lo_const 60 #endif 61 typedef lo_const void *pam_item_t; 62 63 #include "passwdqc.h" 64 65 #include "passwdqc_i18n.h" 66 67 #define PROMPT_OLDPASS \ 68 _("Enter current password: ") 69 #define PROMPT_NEWPASS1 \ 70 _("Enter new password: ") 71 #define PROMPT_NEWPASS2 \ 72 _("Re-type new password: ") 73 74 #define MESSAGE_MISCONFIGURED \ 75 _("System configuration error. Please contact your administrator.") 76 #define MESSAGE_INVALID_OPTION \ 77 "pam_passwdqc: %s." 78 #define MESSAGE_INTRO_PASSWORD \ 79 _("\nYou can now choose the new password.\n") 80 #define MESSAGE_INTRO_BOTH \ 81 _("\nYou can now choose the new password or passphrase.\n") 82 83 #define MESSAGE_EXPLAIN_PASSWORD_1_CLASS(count) \ 84 P3_( \ 85 "A good password should be a mix of upper and lower case letters, digits, and\n" \ 86 "other characters. You can use a password containing at least %d character.\n", \ 87 \ 88 "A good password should be a mix of upper and lower case letters, digits, and\n" \ 89 "other characters. You can use a password containing at least %d characters.\n", \ 90 count), (count) 91 92 #define MESSAGE_EXPLAIN_PASSWORD_N_CLASSES(count) \ 93 P3_( \ 94 "A valid password should be a mix of upper and lower case letters, digits, and\n" \ 95 "other characters. You can use a password containing at least %d character\n" \ 96 "from at least %d of these 4 classes.\n" \ 97 "An upper case letter that begins the password and a digit that ends it do not\n" \ 98 "count towards the number of character classes used.\n", \ 99 \ 100 "A valid password should be a mix of upper and lower case letters, digits, and\n" \ 101 "other characters. You can use a password containing at least %d characters\n" \ 102 "from at least %d of these 4 classes.\n" \ 103 "An upper case letter that begins the password and a digit that ends it do not\n" \ 104 "count towards the number of character classes used.\n", \ 105 count), (count) 106 107 #define MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES(count) \ 108 P3_( \ 109 "A valid password should be a mix of upper and lower case letters, digits, and\n" \ 110 "other characters. You can use a password containing at least %d character\n" \ 111 "from all of these classes.\n" \ 112 "An upper case letter that begins the password and a digit that ends it do not\n" \ 113 "count towards the number of character classes used.\n", \ 114 \ 115 "A valid password should be a mix of upper and lower case letters, digits, and\n" \ 116 "other characters. You can use a password containing at least %d characters\n" \ 117 "from all of these classes.\n" \ 118 "An upper case letter that begins the password and a digit that ends it do not\n" \ 119 "count towards the number of character classes used.\n", \ 120 count), (count) 121 122 #define MESSAGE_EXPLAIN_PASSWORD_ALL_OR_3_CLASSES(count) \ 123 P3_( \ 124 "A valid password should be a mix of upper and lower case letters, digits, and\n" \ 125 "other characters. You can use a password containing at least %d character\n" \ 126 "from all of these classes, or a password containing at least %d characters\n" \ 127 "from just 3 of these 4 classes.\n" \ 128 "An upper case letter that begins the password and a digit that ends it do not\n" \ 129 "count towards the number of character classes used.\n", \ 130 \ 131 "A valid password should be a mix of upper and lower case letters, digits, and\n" \ 132 "other characters. You can use a password containing at least %d characters\n" \ 133 "from all of these classes, or a password containing at least %d characters\n" \ 134 "from just 3 of these 4 classes.\n" \ 135 "An upper case letter that begins the password and a digit that ends it do not\n" \ 136 "count towards the number of character classes used.\n", \ 137 count), (count) 138 139 #define MESSAGE_EXPLAIN_PASSPHRASE(count) \ 140 P3_(\ 141 "A passphrase should be of at least %d word, %d to %d characters long, and\n" \ 142 "contain enough different characters.\n", \ 143 \ 144 "A passphrase should be of at least %d words, %d to %d characters long, and\n" \ 145 "contain enough different characters.\n", \ 146 count), (count) 147 148 #define MESSAGE_RANDOM \ 149 _("Alternatively, if no one else can see your terminal now, you can pick this as\n" \ 150 "your password: \"%s\".\n") 151 #define MESSAGE_RANDOMONLY \ 152 _("This system is configured to permit randomly generated passwords only.\n" \ 153 "If no one else can see your terminal now, you can pick this as your\n" \ 154 "password: \"%s\". Otherwise come back later.\n") 155 #define MESSAGE_RANDOMFAILED \ 156 _("This system is configured to use randomly generated passwords only,\n" \ 157 "but the attempt to generate a password has failed. This could happen\n" \ 158 "for a number of reasons: you could have requested an impossible password\n" \ 159 "length, or the access to kernel random number pool could have failed.") 160 #define MESSAGE_TOOLONG \ 161 _("This password may be too long for some services. Choose another.") 162 #define MESSAGE_TRUNCATED \ 163 _("Warning: your longer password will be truncated to 8 characters.") 164 #define MESSAGE_WEAKPASS \ 165 _("Weak password: %s.") 166 #define MESSAGE_NOTRANDOM \ 167 _("Sorry, you've mistyped the password that was generated for you.") 168 #define MESSAGE_MISTYPED \ 169 _("Sorry, passwords do not match.") 170 #define MESSAGE_RETRY \ 171 _("Try again.") 172 173 static int logaudit(pam_handle_t *pamh, int status, passwdqc_params_t *params) 174 { 175 #ifdef HAVE_LIBAUDIT 176 if (!(params->pam.flags & F_NO_AUDIT)) { 177 int rc = pam_modutil_audit_write(pamh, AUDIT_USER_CHAUTHTOK, "pam_passwdqc", status); 178 if (status == PAM_SUCCESS) 179 status = rc; 180 } 181 #else /* !HAVE_LIBAUDIT */ 182 (void) pamh; 183 #endif 184 passwdqc_params_free(params); 185 return status; 186 } 187 188 static int converse(pam_handle_t *pamh, int style, l_const char *text, 189 struct pam_response **resp) 190 { 191 pam_item_t item; 192 const struct pam_conv *conv; 193 struct pam_message msg, *pmsg; 194 int status; 195 196 *resp = NULL; 197 status = pam_get_item(pamh, PAM_CONV, &item); 198 if (status != PAM_SUCCESS) 199 return status; 200 conv = item; 201 202 pmsg = &msg; 203 msg.msg_style = style; 204 msg.msg = (char *)text; 205 206 return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp, 207 conv->appdata_ptr); 208 } 209 210 #ifdef __GNUC__ 211 __attribute__ ((format (printf, 3, 4))) 212 #endif 213 static int say(pam_handle_t *pamh, int style, const char *format, ...) 214 { 215 va_list args; 216 char buffer[0x800]; 217 int needed; 218 struct pam_response *resp; 219 int status; 220 221 va_start(args, format); 222 needed = vsnprintf(buffer, sizeof(buffer), format, args); 223 va_end(args); 224 225 if ((unsigned int)needed < sizeof(buffer)) { 226 status = converse(pamh, style, buffer, &resp); 227 pwqc_drop_pam_reply(resp, 1); 228 } else { 229 status = PAM_ABORT; 230 } 231 _passwdqc_memzero(buffer, sizeof(buffer)); 232 233 return status; 234 } 235 236 static int check_max(passwdqc_params_qc_t *qc, pam_handle_t *pamh, 237 const char *newpass) 238 { 239 if (strlen(newpass) > (size_t)qc->max) { 240 if (qc->max != 8) { 241 say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG); 242 return -1; 243 } 244 say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED); 245 } 246 247 return 0; 248 } 249 250 static int check_pass(struct passwd *pw, const char *pass) 251 { 252 const char *hash; 253 int retval; 254 255 #ifdef HAVE_SHADOW 256 #ifdef __hpux 257 if (iscomsec()) { 258 #else 259 if (!strcmp(pw->pw_passwd, "x")) { 260 #endif 261 struct spwd *spw = getspnam(pw->pw_name); 262 endspent(); 263 if (!spw) 264 return -1; 265 hash = NULL; 266 if (strlen(spw->sp_pwdp) >= 13) { 267 #ifdef __hpux 268 hash = bigcrypt(pass, spw->sp_pwdp); 269 #else 270 hash = crypt(pass, spw->sp_pwdp); 271 #endif 272 } 273 retval = (hash && !strcmp(hash, spw->sp_pwdp)) ? 0 : -1; 274 _passwdqc_memzero(spw->sp_pwdp, strlen(spw->sp_pwdp)); 275 return retval; 276 } 277 #endif 278 279 hash = NULL; 280 if (strlen(pw->pw_passwd) >= 13) 281 hash = crypt(pass, pw->pw_passwd); 282 retval = (hash && !strcmp(hash, pw->pw_passwd)) ? 0 : -1; 283 _passwdqc_memzero(pw->pw_passwd, strlen(pw->pw_passwd)); 284 return retval; 285 } 286 287 static int am_root(pam_handle_t *pamh) 288 { 289 pam_item_t item; 290 const char *service; 291 292 if (getuid() != 0) 293 return 0; 294 295 if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS) 296 return 0; 297 service = item; 298 299 return !strcmp(service, "passwd"); 300 } 301 302 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, 303 int argc, const char **argv) 304 { 305 passwdqc_params_t params; 306 struct pam_response *resp; 307 struct passwd *pw, fake_pw; 308 pam_item_t item; 309 const char *user, *oldpass, *newpass; 310 char *trypass, *randompass; 311 char *parse_reason; 312 const char *check_reason; 313 int ask_oldauthtok; 314 int randomonly, enforce, retries_left, retry_wanted; 315 int status; 316 317 passwdqc_params_reset(¶ms); 318 if (passwdqc_params_parse(¶ms, &parse_reason, argc, argv)) { 319 say(pamh, PAM_ERROR_MSG, am_root(pamh) ? 320 MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED, 321 parse_reason); 322 free(parse_reason); 323 return PAM_ABORT; 324 } 325 status = PAM_SUCCESS; 326 327 ask_oldauthtok = 0; 328 if (flags & PAM_PRELIM_CHECK) { 329 if (params.pam.flags & F_ASK_OLDAUTHTOK_PRELIM) 330 ask_oldauthtok = 1; 331 } else if (flags & PAM_UPDATE_AUTHTOK) { 332 if (params.pam.flags & F_ASK_OLDAUTHTOK_UPDATE) 333 ask_oldauthtok = 1; 334 } else { 335 passwdqc_params_free(¶ms); 336 return PAM_SERVICE_ERR; 337 } 338 339 if (ask_oldauthtok && !am_root(pamh)) { 340 status = converse(pamh, PAM_PROMPT_ECHO_OFF, 341 PROMPT_OLDPASS, &resp); 342 343 if (status == PAM_SUCCESS) { 344 if (resp && resp->resp) { 345 status = pam_set_item(pamh, 346 PAM_OLDAUTHTOK, resp->resp); 347 pwqc_drop_pam_reply(resp, 1); 348 } else 349 status = PAM_AUTHTOK_RECOVERY_ERR; 350 } 351 352 if (status != PAM_SUCCESS) 353 return logaudit(pamh, status, ¶ms); 354 } 355 356 if (flags & PAM_PRELIM_CHECK) { 357 passwdqc_params_free(¶ms); 358 return status; 359 } 360 361 status = pam_get_item(pamh, PAM_USER, &item); 362 if (status != PAM_SUCCESS) 363 return logaudit(pamh, status, ¶ms); 364 user = item; 365 366 status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item); 367 if (status != PAM_SUCCESS) 368 return logaudit(pamh, status, ¶ms); 369 oldpass = item; 370 371 if (params.pam.flags & F_NON_UNIX) { 372 pw = &fake_pw; 373 memset(pw, 0, sizeof(*pw)); 374 pw->pw_name = (char *)user; 375 pw->pw_gecos = ""; 376 pw->pw_dir = ""; 377 } else { 378 /* As currently implemented, we don't avoid timing leaks for valid vs. not 379 * usernames and hashes. Normally, the username would have already been 380 * checked and determined valid, and the check_oldauthtok option is only needed 381 * on systems that happen to have similar timing leaks all over the place. */ 382 pw = getpwnam(user); 383 endpwent(); 384 if (!pw) 385 return logaudit(pamh, PAM_USER_UNKNOWN, ¶ms); 386 if ((params.pam.flags & F_CHECK_OLDAUTHTOK) && !am_root(pamh) 387 && (!oldpass || check_pass(pw, oldpass))) 388 status = PAM_AUTH_ERR; 389 _passwdqc_memzero(pw->pw_passwd, strlen(pw->pw_passwd)); 390 if (status != PAM_SUCCESS) 391 return logaudit(pamh, status, ¶ms); 392 } 393 394 randomonly = params.qc.min[4] > params.qc.max; 395 396 if (am_root(pamh)) 397 enforce = params.pam.flags & F_ENFORCE_ROOT; 398 else 399 enforce = params.pam.flags & F_ENFORCE_USERS; 400 401 if (params.pam.flags & F_USE_AUTHTOK) { 402 status = pam_get_item(pamh, PAM_AUTHTOK, &item); 403 if (status != PAM_SUCCESS) 404 return logaudit(pamh, status, ¶ms); 405 newpass = item; 406 if (!newpass || 407 (check_max(¶ms.qc, pamh, newpass) && enforce)) 408 return logaudit(pamh, PAM_AUTHTOK_ERR, ¶ms); 409 check_reason = 410 passwdqc_check(¶ms.qc, newpass, oldpass, pw); 411 if (check_reason) { 412 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, 413 check_reason); 414 if (enforce) 415 status = PAM_AUTHTOK_ERR; 416 } 417 return logaudit(pamh, status, ¶ms); 418 } 419 420 retries_left = params.pam.retry; 421 422 retry: 423 retry_wanted = 0; 424 425 if (!randomonly && 426 params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) 427 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH); 428 else 429 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD); 430 if (status != PAM_SUCCESS) 431 return logaudit(pamh, status, ¶ms); 432 433 if (!randomonly && params.qc.min[0] == params.qc.min[4]) 434 status = say(pamh, PAM_TEXT_INFO, 435 MESSAGE_EXPLAIN_PASSWORD_1_CLASS(params.qc.min[4])); 436 437 else if (!randomonly && params.qc.min[3] == params.qc.min[4]) 438 status = say(pamh, PAM_TEXT_INFO, 439 MESSAGE_EXPLAIN_PASSWORD_N_CLASSES(params.qc.min[4]), 440 params.qc.min[1] != params.qc.min[3] ? 3 : 2); 441 else if (!randomonly && params.qc.min[3] == INT_MAX) 442 status = say(pamh, PAM_TEXT_INFO, 443 MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES(params.qc.min[4])); 444 else if (!randomonly) { 445 status = say(pamh, PAM_TEXT_INFO, 446 MESSAGE_EXPLAIN_PASSWORD_ALL_OR_3_CLASSES(params.qc.min[4]), 447 params.qc.min[3]); 448 } 449 if (status != PAM_SUCCESS) 450 return logaudit(pamh, status, ¶ms); 451 452 if (!randomonly && 453 params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) { 454 status = say(pamh, PAM_TEXT_INFO, 455 MESSAGE_EXPLAIN_PASSPHRASE(params.qc.passphrase_words), 456 params.qc.min[2], params.qc.max); 457 if (status != PAM_SUCCESS) 458 return logaudit(pamh, status, ¶ms); 459 } 460 461 randompass = passwdqc_random(¶ms.qc); 462 if (randompass) { 463 status = say(pamh, PAM_TEXT_INFO, randomonly ? 464 MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass); 465 if (status != PAM_SUCCESS) { 466 pwqc_overwrite_string(randompass); 467 pwqc_drop_mem(randompass); 468 } 469 } else if (randomonly) { 470 say(pamh, PAM_ERROR_MSG, am_root(pamh) ? 471 MESSAGE_RANDOMFAILED : MESSAGE_MISCONFIGURED); 472 return logaudit(pamh, PAM_AUTHTOK_ERR, ¶ms); 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 pwqc_overwrite_string(randompass); 481 pwqc_drop_mem(randompass); 482 return logaudit(pamh, status, ¶ms); 483 } 484 485 trypass = strdup(resp->resp); 486 487 pwqc_drop_pam_reply(resp, 1); 488 489 if (!trypass) { 490 pwqc_overwrite_string(randompass); 491 pwqc_drop_mem(randompass); 492 return logaudit(pamh, PAM_AUTHTOK_ERR, ¶ms); 493 } 494 495 if (check_max(¶ms.qc, pamh, trypass) && enforce) { 496 status = PAM_AUTHTOK_ERR; 497 retry_wanted = 1; 498 } 499 500 check_reason = NULL; /* unused */ 501 if (status == PAM_SUCCESS && 502 (!randompass || !strstr(trypass, randompass)) && 503 (randomonly || 504 (check_reason = passwdqc_check(¶ms.qc, trypass, oldpass, pw)))) { 505 if (randomonly) 506 say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM); 507 else 508 say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, 509 check_reason); 510 if (enforce) { 511 status = PAM_AUTHTOK_ERR; 512 retry_wanted = 1; 513 } 514 } 515 516 if (status == PAM_SUCCESS) 517 status = converse(pamh, PAM_PROMPT_ECHO_OFF, 518 PROMPT_NEWPASS2, &resp); 519 if (status == PAM_SUCCESS) { 520 if (resp && resp->resp) { 521 if (strcmp(trypass, resp->resp)) { 522 status = say(pamh, 523 PAM_ERROR_MSG, MESSAGE_MISTYPED); 524 if (status == PAM_SUCCESS) { 525 status = PAM_AUTHTOK_ERR; 526 retry_wanted = 1; 527 } 528 } 529 pwqc_drop_pam_reply(resp, 1); 530 } else 531 status = PAM_AUTHTOK_ERR; 532 } 533 534 if (status == PAM_SUCCESS) 535 status = pam_set_item(pamh, PAM_AUTHTOK, trypass); 536 537 pwqc_overwrite_string(randompass); 538 pwqc_drop_mem(randompass); 539 540 pwqc_overwrite_string(trypass); 541 pwqc_drop_mem(trypass); 542 543 if (retry_wanted && --retries_left > 0) { 544 status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY); 545 if (status == PAM_SUCCESS) 546 goto retry; 547 } 548 549 return logaudit(pamh, status, ¶ms); 550 } 551 552 #ifdef PAM_MODULE_ENTRY 553 PAM_MODULE_ENTRY("pam_passwdqc"); 554 #elif defined(PAM_STATIC) 555 const struct pam_module _pam_passwdqc_modstruct = { 556 "pam_passwdqc", 557 NULL, 558 NULL, 559 NULL, 560 NULL, 561 NULL, 562 pam_sm_chauthtok 563 }; 564 #endif 565