1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <sys/types.h> 30 #include <fcntl.h> 31 #include <errno.h> 32 #include <stdlib.h> 33 #include <sys/stat.h> 34 #include <pwd.h> 35 #include <shadow.h> 36 #include <string.h> 37 #include <strings.h> 38 #include <stdlib.h> 39 #include <unistd.h> 40 #include <nss_dbdefs.h> 41 #include <macros.h> 42 #include <syslog.h> 43 44 #include <limits.h> /* LOGNAME_MAX -- max Solaris user name */ 45 46 #include "passwdutil.h" 47 48 int files_lock(void); 49 int files_unlock(void); 50 int files_checkhistory(char *user, char *passwd, pwu_repository_t *rep); 51 int files_getattr(char *name, attrlist *item, pwu_repository_t *rep); 52 int files_getpwnam(char *name, attrlist *items, pwu_repository_t *rep, 53 void **buf); 54 int files_update(attrlist *items, pwu_repository_t *rep, void *buf); 55 int files_putpwnam(char *name, char *oldpw, char *dummy, 56 pwu_repository_t *rep, void *buf); 57 int files_user_to_authenticate(char *name, pwu_repository_t *rep, 58 char **auth_user, int *privileged); 59 60 static int files_update_history(char *name, struct spwd *spwd); 61 62 /* 63 * files function pointer table, used by passwdutil_init to initialize 64 * the global Repository-OPerations table "rops" 65 */ 66 struct repops files_repops = { 67 files_checkhistory, 68 files_getattr, 69 files_getpwnam, 70 files_update, 71 files_putpwnam, 72 files_user_to_authenticate, 73 files_lock, 74 files_unlock 75 }; 76 77 /* 78 * this structure defines the buffer used to keep state between 79 * get/update/put calls 80 */ 81 struct pwbuf { 82 int update_history; 83 struct passwd *pwd; 84 char *pwd_scratch; 85 struct spwd *spwd; 86 char *spwd_scratch; 87 char *new_sp_pwdp; 88 }; 89 90 /* 91 * We should use sysconf, but there is no sysconf name for SHADOW 92 * so we use these from nss_dbdefs 93 */ 94 #define PWD_SCRATCH_SIZE NSS_LINELEN_PASSWD 95 #define SPW_SCRATCH_SIZE NSS_LINELEN_SHADOW 96 97 /* 98 * lock functions for files repository 99 */ 100 int 101 files_lock(void) 102 { 103 int res; 104 105 if (lckpwdf()) { 106 switch (errno) { 107 case EINTR: 108 res = PWU_BUSY; 109 break; 110 case EACCES: 111 res = PWU_DENIED; 112 break; 113 case 0: 114 res = PWU_SUCCESS; 115 break; 116 } 117 } else 118 res = PWU_SUCCESS; 119 120 return (res); 121 } 122 123 int 124 files_unlock(void) 125 { 126 if (ulckpwdf()) 127 return (PWU_SYSTEM_ERROR); 128 129 return (PWU_SUCCESS); 130 } 131 132 /* 133 * files_privileged 134 * 135 * Are we a privileged user with regard to the files repository? 136 */ 137 int 138 files_privileged(void) 139 { 140 return (getuid() == 0); 141 } 142 143 /* 144 * 145 * private_getpwnam_r() 146 * 147 * A private implementation of getpwnam_r which does *not* fall back to 148 * other services possibly defined in nsswitch.conf 149 * 150 * behaves like getpwnam_r(). 151 */ 152 struct passwd * 153 private_getpwnam_r(const char *name, struct passwd *result, char *buffer, 154 int buflen) 155 { 156 FILE *fp; 157 int found; 158 159 if ((fp = fopen(PASSWD, "r")) == NULL) 160 return (NULL); 161 162 found = 0; 163 while (!found && fgetpwent_r(fp, result, buffer, buflen) != NULL) { 164 if (strcmp(name, result->pw_name) == 0) 165 found = 1; 166 } 167 168 (void) fclose(fp); 169 170 if (!found) { 171 (void) memset(buffer, 0, buflen); 172 (void) memset(result, 0, sizeof (*result)); 173 return (NULL); 174 } 175 176 return (result); 177 } 178 179 /* 180 * private_getspnam_r() 181 * 182 * A private implementation of getspnam_r which does *not* fall back to 183 * other services possibly defined in nsswitch.conf. 184 * 185 * Behaves like getspnam_r(). Since we use fgetspent_t(), all numeric 186 * fields that are undefined in /etc/shadow will be set to -1. 187 * 188 */ 189 struct spwd * 190 private_getspnam_r(const char *name, struct spwd *result, char *buffer, 191 int buflen) 192 { 193 FILE *fp; 194 int found; 195 196 fp = fopen(SHADOW, "r"); 197 if (fp == NULL) 198 return (NULL); 199 200 found = 0; 201 while (!found && fgetspent_r(fp, result, buffer, buflen) != NULL) { 202 if (strcmp(name, result->sp_namp) == 0) 203 found = 1; 204 } 205 206 (void) fclose(fp); 207 208 if (!found) { 209 (void) memset(buffer, 0, buflen); 210 (void) memset(result, 0, sizeof (*result)); 211 return (NULL); 212 } 213 return (result); 214 } 215 216 /* 217 * files_getpwnam(name, items, rep, buf) 218 * 219 */ 220 /*ARGSUSED*/ 221 int 222 files_getpwnam(char *name, attrlist *items, pwu_repository_t *rep, void **buf) 223 { 224 attrlist *p; 225 struct pwbuf *pwbuf; 226 int err = PWU_SUCCESS; 227 228 *buf = calloc(1, sizeof (struct pwbuf)); 229 pwbuf = (struct pwbuf *)*buf; 230 231 /* 232 * determine which password structure (/etc/passwd or /etc/shadow) 233 * we need for the items we need to update 234 */ 235 for (p = items; p != NULL; p = p->next) { 236 switch (p->type) { 237 case ATTR_NAME: 238 case ATTR_UID: 239 case ATTR_GID: 240 case ATTR_AGE: 241 case ATTR_COMMENT: 242 case ATTR_GECOS: 243 case ATTR_HOMEDIR: 244 case ATTR_SHELL: 245 if (pwbuf->pwd == NULL) { 246 pwbuf->pwd = malloc(sizeof (struct passwd)); 247 if (pwbuf->pwd == NULL) { 248 err = PWU_NOMEM; 249 goto error; 250 } 251 } 252 break; 253 case ATTR_PASSWD: 254 case ATTR_PASSWD_SERVER_POLICY: 255 case ATTR_LSTCHG: 256 case ATTR_MIN: 257 case ATTR_MAX: 258 case ATTR_WARN: 259 case ATTR_INACT: 260 case ATTR_EXPIRE: 261 case ATTR_FLAG: 262 case ATTR_LOCK_ACCOUNT: 263 case ATTR_EXPIRE_PASSWORD: 264 case ATTR_FAILED_LOGINS: 265 case ATTR_INCR_FAILED_LOGINS: 266 case ATTR_RST_FAILED_LOGINS: 267 case ATTR_NOLOGIN_ACCOUNT: 268 case ATTR_UNLOCK_ACCOUNT: 269 if (pwbuf->spwd == NULL) { 270 pwbuf->spwd = malloc(sizeof (struct spwd)); 271 if (pwbuf->spwd == NULL) { 272 err = PWU_NOMEM; 273 goto error; 274 } 275 } 276 break; 277 default: 278 /* 279 * Some other repository might have different values 280 * so we ignore those. 281 */ 282 break; 283 } 284 } 285 286 if (pwbuf->pwd) { 287 if ((pwbuf->pwd_scratch = malloc(PWD_SCRATCH_SIZE)) == NULL) { 288 err = PWU_NOMEM; 289 goto error; 290 } 291 if (private_getpwnam_r(name, pwbuf->pwd, pwbuf->pwd_scratch, 292 PWD_SCRATCH_SIZE) == NULL) { 293 err = PWU_NOT_FOUND; 294 goto error; 295 } 296 } 297 298 if (pwbuf->spwd) { 299 if ((pwbuf->spwd_scratch = malloc(SPW_SCRATCH_SIZE)) == NULL) { 300 err = PWU_NOMEM; 301 goto error; 302 } 303 if (private_getspnam_r(name, pwbuf->spwd, pwbuf->spwd_scratch, 304 SPW_SCRATCH_SIZE) == NULL) { 305 err = PWU_NOT_FOUND; 306 goto error; 307 } 308 } 309 310 return (PWU_SUCCESS); 311 error: 312 if (pwbuf->pwd) free(pwbuf->pwd); 313 if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch); 314 if (pwbuf->spwd) free(pwbuf->spwd); 315 if (pwbuf->spwd_scratch) free(pwbuf->spwd_scratch); 316 free(pwbuf); 317 *buf = NULL; 318 319 return (err); 320 } 321 322 /* 323 * int files_user_to_authenticate(name, rep, auth_user, privileged) 324 * Determine which user needs to be authenticated. For files, the 325 * possible return values are: 326 * PWU_NOT_FOUND 327 * PWU_SUCCESS and (auth_user == NULL || auth_user = user) 328 * PWU_DENIED 329 */ 330 /*ARGSUSED*/ 331 int 332 files_user_to_authenticate(char *user, pwu_repository_t *rep, 333 char **auth_user, int *privileged) 334 { 335 struct pwbuf *pwbuf; 336 int res; 337 attrlist attr_tmp[1] = { { ATTR_UID, NULL, NULL } }; 338 339 /* check to see if target user is present in files */ 340 res = files_getpwnam(user, &attr_tmp[0], rep, (void **)&pwbuf); 341 if (res != PWU_SUCCESS) 342 return (res); 343 344 if (files_privileged()) { 345 *auth_user = NULL; 346 *privileged = 1; 347 res = PWU_SUCCESS; 348 } else { 349 *privileged = 0; 350 if (getuid() == pwbuf->pwd->pw_uid) { 351 *auth_user = strdup(user); 352 res = PWU_SUCCESS; 353 } else { 354 res = PWU_DENIED; 355 } 356 } 357 358 if (pwbuf->pwd) free(pwbuf->pwd); 359 if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch); 360 if (pwbuf->spwd) free(pwbuf->spwd); 361 if (pwbuf->spwd_scratch) free(pwbuf->spwd_scratch); 362 free(pwbuf); 363 364 return (res); 365 } 366 367 /* 368 * Password history file format: 369 * user:crypw1: ... crypwn: such that n <= MAXHISTORY 370 */ 371 #define HISTORY "/etc/security/passhistory" 372 #define HISTEMP "/etc/security/pwhistemp" 373 #define OHISTORY "/etc/security/opwhistory" 374 #define HISTMODE S_IRUSR /* mode to create history file */ 375 /* 376 * XXX 377 * 3*LOGNAME_MAX just in case there are long user names. 378 * Traditionally Solaris LOGNAME_MAX (_POSIX_LOGIN_NAME_MAX) is 13, 379 * but some sites often user more. 380 * If LOGNAME_MAX ever becomes reasonable (128) and actually enforced, 381 * fix up here. 382 * XXX 383 */ 384 #define MAX_LOGNAME (3 * LOGNAME_MAX) 385 386 /* 387 * files_checkhistory - check if a user's new password is in the user's 388 * old password history. 389 * 390 * Entry 391 * user = username. 392 * passwd = new clear text password. 393 * 394 * Exit 395 * PWU_SUCCESS, passwd found in user's old password history. 396 * The caller should only be interested and fail if 397 * PWU_SUCCESS is returned. 398 * PWU_NOT_FOUND, passwd not in user's old password history. 399 * PWU_errors, PWU_ errors from other routines. 400 * 401 */ 402 int 403 files_checkhistory(char *user, char *passwd, pwu_repository_t *rep) 404 { 405 attrlist attr; 406 int res; 407 408 attr.type = ATTR_HISTORY; 409 attr.data.val_s = NULL; 410 attr.next = NULL; 411 412 debug("files_checkhistory(user=%s)", user); 413 414 /* 415 * XXX 416 * This depends on the underlying files_getattr implementation 417 * treating user not found in backing store or no history as 418 * an error. 419 * XXX 420 */ 421 422 if ((res = files_getattr(user, &attr, rep)) == PWU_SUCCESS) { 423 char *s; 424 char *crypt_passwd; 425 int histsize; 426 char *last = attr.data.val_s; 427 428 if ((histsize = def_getint("HISTORY=", DEFHISTORY)) == 0) { 429 debug("files_checkhistory: no history requested"); 430 res = PWU_NOT_FOUND; 431 goto out; 432 } 433 434 debug("files_checkhistory: histsize = %d", histsize); 435 if (histsize > MAXHISTORY) 436 histsize = MAXHISTORY; 437 438 debug("line to test\n\t%s", last); 439 440 /* compare crypt_passwd to attr.data.val_s strings. */ 441 res = PWU_NOT_FOUND; 442 while ((histsize-- > 0) && 443 (((s = strtok_r(NULL, ":", &last)) != NULL) && 444 (*s != '\n'))) { 445 446 crypt_passwd = crypt(passwd, s); 447 debug("files_checkhistory: user_pw=%s, history_pw=%s", 448 crypt_passwd, s); 449 if (strcmp(crypt_passwd, s) == 0) { 450 res = PWU_SUCCESS; 451 break; 452 } 453 } 454 debug("files_checkhistory(%s, %s) = %d", user, crypt_passwd, 455 res); 456 } 457 out: 458 if (attr.data.val_s != NULL) 459 free(attr.data.val_s); 460 461 return (res); 462 } 463 464 /* 465 * files_getattr(name, items, rep) 466 * 467 * Get attributes specified in list 'items' 468 */ 469 int 470 files_getattr(char *name, attrlist *items, pwu_repository_t *rep) 471 { 472 struct pwbuf *pwbuf; 473 struct passwd *pw; 474 struct spwd *spw; 475 attrlist *w; 476 int res; 477 478 res = files_getpwnam(name, items, rep, (void **)&pwbuf); 479 if (res != PWU_SUCCESS) 480 return (res); 481 482 pw = pwbuf->pwd; 483 spw = pwbuf->spwd; 484 485 for (w = items; res == PWU_SUCCESS && w != NULL; w = w->next) { 486 switch (w->type) { 487 case ATTR_NAME: 488 if ((w->data.val_s = strdup(pw->pw_name)) == NULL) 489 res = PWU_NOMEM; 490 break; 491 case ATTR_COMMENT: 492 if ((w->data.val_s = strdup(pw->pw_comment)) == NULL) 493 res = PWU_NOMEM; 494 break; 495 case ATTR_GECOS: 496 if ((w->data.val_s = strdup(pw->pw_gecos)) == NULL) 497 res = PWU_NOMEM; 498 break; 499 case ATTR_HOMEDIR: 500 if ((w->data.val_s = strdup(pw->pw_dir)) == NULL) 501 res = PWU_NOMEM; 502 break; 503 case ATTR_SHELL: 504 if ((w->data.val_s = strdup(pw->pw_shell)) == NULL) 505 res = PWU_NOMEM; 506 break; 507 /* 508 * Nothing special needs to be done for 509 * server policy 510 */ 511 case ATTR_PASSWD: 512 case ATTR_PASSWD_SERVER_POLICY: 513 if ((w->data.val_s = strdup(spw->sp_pwdp)) == NULL) 514 res = PWU_NOMEM; 515 break; 516 case ATTR_AGE: 517 if ((w->data.val_s = strdup(pw->pw_age)) == NULL) 518 res = PWU_NOMEM; 519 break; 520 case ATTR_REP_NAME: 521 if ((w->data.val_s = strdup("files")) == NULL) 522 res = PWU_NOMEM; 523 break; 524 case ATTR_HISTORY: { 525 FILE *history; 526 char buf[MAX_LOGNAME + MAXHISTORY + 527 (MAXHISTORY * CRYPT_MAXCIPHERTEXTLEN)+1]; 528 char *s, *s1; 529 530 debug("files_getattr: Get password history for %s ", 531 name); 532 533 if ((history = fopen(HISTORY, "r")) == NULL) { 534 debug("files_getattr: %s not found", HISTORY); 535 res = PWU_OPEN_FAILED; 536 goto getattr_exit; 537 } 538 res = PWU_NOT_FOUND; 539 while ((s = fgets(buf, sizeof (buf), history)) != 540 NULL) { 541 s1 = strchr(s, ':'); 542 if (s1 != NULL) { 543 *s1 = '\0'; 544 } else { 545 res = PWU_NOT_FOUND; 546 break; 547 } 548 #ifdef DEBUG 549 debug("got history line for %s", s); 550 #endif /* DEBUG */ 551 if (strcmp(s, name) == 0) { 552 /* found user */ 553 if ((items->data.val_s = 554 strdup(s1+1)) == NULL) 555 res = PWU_NOMEM; 556 else 557 res = PWU_SUCCESS; 558 break; 559 } 560 } 561 (void) fclose(history); 562 break; 563 } 564 565 /* integer values */ 566 case ATTR_UID: 567 w->data.val_i = pw->pw_uid; 568 break; 569 case ATTR_GID: 570 w->data.val_i = pw->pw_gid; 571 break; 572 case ATTR_LSTCHG: 573 w->data.val_i = spw->sp_lstchg; 574 break; 575 case ATTR_MIN: 576 w->data.val_i = spw->sp_min; 577 break; 578 case ATTR_MAX: 579 w->data.val_i = spw->sp_max; 580 break; 581 case ATTR_WARN: 582 w->data.val_i = spw->sp_warn; 583 break; 584 case ATTR_INACT: 585 w->data.val_i = spw->sp_inact; 586 break; 587 case ATTR_EXPIRE: 588 w->data.val_i = spw->sp_expire; 589 break; 590 case ATTR_FLAG: 591 w->data.val_i = spw->sp_flag; 592 break; 593 case ATTR_FAILED_LOGINS: 594 w->data.val_i = spw->sp_flag & FAILCOUNT_MASK; 595 break; 596 default: 597 break; 598 } 599 } 600 601 getattr_exit: 602 if (pwbuf->pwd) free(pwbuf->pwd); 603 if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch); 604 if (pwbuf->spwd) free(pwbuf->spwd); 605 free(pwbuf); 606 607 return (res); 608 } 609 610 /* 611 * max_present(list) 612 * 613 * see if attribute ATTR_MAX, with value != -1, is present in 614 * attribute-list "list". 615 * 616 * returns 1 if present, 0 otherwise. 617 */ 618 static int 619 max_present(attrlist *list) 620 { 621 while (list != NULL) 622 if (list->type == ATTR_MAX && list->data.val_i != -1) 623 return (1); 624 else 625 list = list->next; 626 627 return (0); 628 } 629 630 /* 631 * files_update(items, rep, buf) 632 * 633 * update the information in buf with the attributes specified in 634 * items. 635 */ 636 /*ARGSUSED*/ 637 int 638 files_update(attrlist *items, pwu_repository_t *rep, void *buf) 639 { 640 struct pwbuf *pwbuf = (struct pwbuf *)buf; 641 struct passwd *pw; 642 struct spwd *spw; 643 attrlist *p; 644 int aging_needed = 0; 645 int aging_set = 0; 646 int disable_aging; 647 char *pword; 648 int len; 649 650 pw = pwbuf->pwd; 651 spw = pwbuf->spwd; 652 pwbuf->update_history = 0; 653 654 /* 655 * if sp_max==0 : disable passwd aging after updating the password 656 */ 657 disable_aging = (spw != NULL && spw->sp_max == 0); 658 659 for (p = items; p != NULL; p = p->next) { 660 switch (p->type) { 661 case ATTR_NAME: 662 break; /* We are able to handle this, but... */ 663 case ATTR_UID: 664 pw->pw_uid = (uid_t)p->data.val_i; 665 break; 666 case ATTR_GID: 667 pw->pw_gid = (gid_t)p->data.val_i; 668 break; 669 case ATTR_AGE: 670 pw->pw_age = p->data.val_s; 671 break; 672 case ATTR_COMMENT: 673 pw->pw_comment = p->data.val_s; 674 break; 675 case ATTR_GECOS: 676 pw->pw_gecos = p->data.val_s; 677 break; 678 case ATTR_HOMEDIR: 679 pw->pw_dir = p->data.val_s; 680 break; 681 case ATTR_SHELL: 682 pw->pw_shell = p->data.val_s; 683 break; 684 685 /* 686 * Nothing special needs to be done for 687 * server policy 688 */ 689 case ATTR_PASSWD: 690 case ATTR_PASSWD_SERVER_POLICY: 691 /* 692 * There is a special case only for files: if the 693 * password is to be deleted (-d to passwd), 694 * p->data.val_s will be NULL. 695 */ 696 if (p->data.val_s == NULL) { 697 spw->sp_pwdp = ""; 698 } else { 699 char *salt = NULL; 700 char *hash = NULL; 701 702 salt = crypt_gensalt(spw->sp_pwdp, pw); 703 704 if (salt == NULL) { 705 if (errno == ENOMEM) 706 return (PWU_NOMEM); 707 /* algorithm problem? */ 708 syslog(LOG_AUTH | LOG_ALERT, 709 "passwdutil: crypt_gensalt %m"); 710 return (PWU_UPDATE_FAILED); 711 } 712 hash = crypt(p->data.val_s, salt); 713 free(salt); 714 if (hash == NULL) { 715 errno = ENOMEM; 716 return (PWU_NOMEM); 717 } 718 pword = strdup(hash); 719 free(hash); 720 if (pword == NULL) { 721 errno = ENOMEM; 722 return (PWU_NOMEM); 723 } 724 725 if (pwbuf->new_sp_pwdp) 726 free(pwbuf->new_sp_pwdp); 727 pwbuf->new_sp_pwdp = pword; 728 spw->sp_pwdp = pword; 729 aging_needed = 1; 730 pwbuf->update_history = 1; 731 } 732 spw->sp_flag &= ~FAILCOUNT_MASK; /* reset count */ 733 spw->sp_lstchg = DAY_NOW_32; 734 break; 735 case ATTR_LOCK_ACCOUNT: 736 if (spw->sp_pwdp == NULL) { 737 spw->sp_pwdp = LOCKSTRING; 738 } else if (strncmp(spw->sp_pwdp, LOCKSTRING, 739 sizeof (LOCKSTRING)-1) != 0) { 740 len = sizeof (LOCKSTRING)-1 + 741 strlen(spw->sp_pwdp) + 1; 742 pword = malloc(len); 743 if (pword == NULL) { 744 errno = ENOMEM; 745 return (PWU_NOMEM); 746 } 747 (void) strlcpy(pword, LOCKSTRING, len); 748 (void) strlcat(pword, spw->sp_pwdp, len); 749 if (pwbuf->new_sp_pwdp) 750 free(pwbuf->new_sp_pwdp); 751 pwbuf->new_sp_pwdp = pword; 752 spw->sp_pwdp = pword; 753 } 754 spw->sp_lstchg = DAY_NOW_32; 755 break; 756 case ATTR_UNLOCK_ACCOUNT: 757 if (spw->sp_pwdp != NULL && 758 strncmp(spw->sp_pwdp, LOCKSTRING, 759 sizeof (LOCKSTRING)-1) == 0) { 760 (void) strcpy(spw->sp_pwdp, spw->sp_pwdp + 761 sizeof (LOCKSTRING)-1); 762 } 763 spw->sp_lstchg = DAY_NOW_32; 764 break; 765 case ATTR_NOLOGIN_ACCOUNT: 766 spw->sp_pwdp = NOLOGINSTRING; 767 if (pwbuf->new_sp_pwdp) { 768 free(pwbuf->new_sp_pwdp); 769 pwbuf->new_sp_pwdp = NULL; 770 } 771 spw->sp_lstchg = DAY_NOW_32; 772 break; 773 case ATTR_EXPIRE_PASSWORD: 774 spw->sp_lstchg = 0; 775 break; 776 case ATTR_LSTCHG: 777 spw->sp_lstchg = p->data.val_i; 778 break; 779 case ATTR_MIN: 780 if (spw->sp_max == -1 && 781 p->data.val_i != -1 && max_present(p->next) == 0) 782 return (PWU_AGING_DISABLED); 783 spw->sp_min = p->data.val_i; 784 aging_set = 1; 785 break; 786 case ATTR_MAX: 787 if (p->data.val_i == -1) { 788 /* Turn aging off -> Reset min and warn too */ 789 790 spw->sp_min = -1; 791 spw->sp_warn = -1; 792 } else { 793 /* Turn aging on */ 794 795 if (spw->sp_min == -1) { 796 /* 797 * If minage has not been set with 798 * a command-line option, we set it 799 * to zero. 800 */ 801 spw->sp_min = 0; 802 } 803 804 /* 805 * If aging was turned off, we update lstchg. 806 * 807 * We take care not to update lstchg if the 808 * user has no password, otherwise the user 809 * might not be required to provide a password 810 * the next time [s]he logs-in. 811 * 812 * Also, if lstchg != -1 (i.e., not set in 813 * /etc/shadow), we keep the old value. 814 */ 815 if (spw->sp_max == -1 && 816 spw->sp_pwdp != NULL && *spw->sp_pwdp && 817 spw->sp_lstchg == -1) { 818 spw->sp_lstchg = DAY_NOW_32; 819 } 820 } 821 822 spw->sp_max = p->data.val_i; 823 824 aging_set = 1; 825 826 break; 827 case ATTR_WARN: 828 if (spw->sp_max == -1 && p->data.val_i != -1 && 829 max_present(p->next) == 0) 830 return (PWU_AGING_DISABLED); 831 spw->sp_warn = p->data.val_i; 832 break; 833 case ATTR_INACT: 834 spw->sp_inact = p->data.val_i; 835 break; 836 case ATTR_EXPIRE: 837 spw->sp_expire = p->data.val_i; 838 break; 839 case ATTR_FLAG: 840 spw->sp_flag = p->data.val_i; 841 break; 842 case ATTR_INCR_FAILED_LOGINS: 843 { 844 int count = (spw->sp_flag & FAILCOUNT_MASK) + 1; 845 spw->sp_flag &= ~FAILCOUNT_MASK; 846 spw->sp_flag |= min(FAILCOUNT_MASK, count); 847 p->data.val_i = count; 848 } 849 break; 850 case ATTR_RST_FAILED_LOGINS: 851 p->data.val_i = spw->sp_flag & FAILCOUNT_MASK; 852 spw->sp_flag &= ~FAILCOUNT_MASK; 853 break; 854 default: 855 break; 856 } 857 } 858 859 /* 860 * What should the new aging values look like? 861 * 862 * There are a number of different conditions 863 * 864 * a) aging is already configured: don't touch it 865 * 866 * b) disable_aging is set: disable aging 867 * 868 * c) aging is not configured: turn on default aging; 869 * 870 * b) and c) of course only if aging_needed and !aging_set. 871 * (i.e., password changed, and aging values not changed) 872 */ 873 874 if (spw != NULL && spw->sp_max <= 0) { 875 /* a) aging not yet configured */ 876 if (aging_needed && !aging_set) { 877 if (disable_aging) { 878 /* b) turn off aging */ 879 spw->sp_min = spw->sp_max = spw->sp_warn = -1; 880 } else { 881 /* c) */ 882 turn_on_default_aging(spw); 883 } 884 } 885 } 886 887 return (PWU_SUCCESS); 888 } 889 890 /* 891 * files_update_shadow(char *name, struct spwd *spwd) 892 * 893 * update the shadow password file SHADOW to contain the spwd structure 894 * "spwd" for user "name" 895 */ 896 int 897 files_update_shadow(char *name, struct spwd *spwd) 898 { 899 struct stat64 stbuf; 900 FILE *dst; 901 FILE *src; 902 struct spwd cur; 903 char buf[SPW_SCRATCH_SIZE]; 904 int tempfd; 905 mode_t filemode; 906 int result = -1; 907 int err = PWU_SUCCESS; 908 909 /* Mode of the shadow file should be 400 or 000 */ 910 if (stat64(SHADOW, &stbuf) < 0) { 911 err = PWU_STAT_FAILED; 912 goto shadow_exit; 913 } 914 915 /* copy mode from current shadow file (0400 or 0000) */ 916 filemode = stbuf.st_mode & S_IRUSR; 917 918 /* 919 * we can't specify filemodes to fopen(), and we SHOULD NOT 920 * set umask in multi-thread safe libraries, so we use 921 * a combination of open() and fdopen() 922 */ 923 tempfd = open(SHADTEMP, O_WRONLY|O_CREAT|O_TRUNC, filemode); 924 if (tempfd < 0) { 925 err = PWU_OPEN_FAILED; 926 goto shadow_exit; 927 } 928 (void) fchown(tempfd, (uid_t)0, stbuf.st_gid); 929 930 if ((dst = fdopen(tempfd, "w")) == NULL) { 931 err = PWU_OPEN_FAILED; 932 goto shadow_exit; 933 } 934 935 if ((src = fopen(SHADOW, "r")) == NULL) { 936 err = PWU_OPEN_FAILED; 937 (void) fclose(dst); 938 (void) unlink(SHADTEMP); 939 goto shadow_exit; 940 } 941 942 /* 943 * copy old shadow to temporary file while replacing the entry 944 * that matches "name". 945 */ 946 while (fgetspent_r(src, &cur, buf, sizeof (buf)) != NULL) { 947 948 if (strcmp(cur.sp_namp, name) == 0) 949 result = putspent(spwd, dst); 950 else 951 result = putspent(&cur, dst); 952 953 if (result != 0) { 954 err = PWU_WRITE_FAILED; 955 (void) fclose(src); 956 (void) fclose(dst); 957 goto shadow_exit; 958 } 959 } 960 961 (void) fclose(src); 962 963 if (fclose(dst) != 0) { 964 /* 965 * Something went wrong (ENOSPC for example). Don't 966 * use the resulting temporary file! 967 */ 968 err = PWU_CLOSE_FAILED; 969 (void) unlink(SHADTEMP); 970 goto shadow_exit; 971 } 972 973 /* 974 * Rename stmp to shadow: 975 * 1. make sure /etc/oshadow is gone 976 * 2. ln /etc/shadow /etc/oshadow 977 * 3. mv /etc/stmp /etc/shadow 978 */ 979 if (unlink(OSHADOW) && access(OSHADOW, 0) == 0) { 980 err = PWU_UPDATE_FAILED; 981 (void) unlink(SHADTEMP); 982 goto shadow_exit; 983 } 984 985 if (link(SHADOW, OSHADOW) == -1) { 986 err = PWU_UPDATE_FAILED; 987 (void) unlink(SHADTEMP); 988 goto shadow_exit; 989 } 990 991 if (rename(SHADTEMP, SHADOW) == -1) { 992 err = PWU_UPDATE_FAILED; 993 (void) unlink(SHADTEMP); 994 goto shadow_exit; 995 } 996 (void) unlink(OSHADOW); 997 998 shadow_exit: 999 return (err); 1000 } 1001 1002 int 1003 files_update_passwd(char *name, struct passwd *pwd) 1004 { 1005 struct stat64 stbuf; 1006 FILE *src, *dst; 1007 int tempfd; 1008 struct passwd cur; 1009 char buf[PWD_SCRATCH_SIZE]; 1010 int result; 1011 int err = PWU_SUCCESS; 1012 1013 if (stat64(PASSWD, &stbuf) < 0) { 1014 err = PWU_STAT_FAILED; 1015 goto passwd_exit; 1016 } 1017 1018 /* see files_update_shadow() for open()+fdopen() rationale */ 1019 1020 if ((tempfd = open(PASSTEMP, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) { 1021 err = PWU_OPEN_FAILED; 1022 goto passwd_exit; 1023 } 1024 if ((dst = fdopen(tempfd, "w")) == NULL) { 1025 err = PWU_OPEN_FAILED; 1026 goto passwd_exit; 1027 } 1028 if ((src = fopen(PASSWD, "r")) == NULL) { 1029 err = PWU_OPEN_FAILED; 1030 (void) fclose(dst); 1031 (void) unlink(PASSTEMP); 1032 goto passwd_exit; 1033 } 1034 1035 /* 1036 * copy old password entries to temporary file while replacing 1037 * the entry that matches "name" 1038 */ 1039 while (fgetpwent_r(src, &cur, buf, sizeof (buf)) != NULL) { 1040 if (strcmp(cur.pw_name, name) == 0) 1041 result = putpwent(pwd, dst); 1042 else 1043 result = putpwent(&cur, dst); 1044 if (result != 0) { 1045 err = PWU_WRITE_FAILED; 1046 (void) fclose(src); 1047 (void) fclose(dst); 1048 goto passwd_exit; 1049 } 1050 } 1051 1052 (void) fclose(src); 1053 if (fclose(dst) != 0) { 1054 err = PWU_CLOSE_FAILED; 1055 goto passwd_exit; /* Don't trust the temporary file */ 1056 } 1057 1058 /* Rename temp to passwd */ 1059 if (unlink(OPASSWD) && access(OPASSWD, 0) == 0) { 1060 err = PWU_UPDATE_FAILED; 1061 (void) unlink(PASSTEMP); 1062 goto passwd_exit; 1063 } 1064 1065 if (link(PASSWD, OPASSWD) == -1) { 1066 err = PWU_UPDATE_FAILED; 1067 (void) unlink(PASSTEMP); 1068 goto passwd_exit; 1069 } 1070 1071 if (rename(PASSTEMP, PASSWD) == -1) { 1072 err = PWU_UPDATE_FAILED; 1073 (void) unlink(PASSTEMP); 1074 goto passwd_exit; 1075 } 1076 1077 (void) chmod(PASSWD, 0644); 1078 1079 passwd_exit: 1080 return (err); 1081 1082 } 1083 1084 /* 1085 * files_putpwnam(name, oldpw, dummy, rep, buf) 1086 * 1087 * store the password attributes contained in "buf" in /etc/passwd and 1088 * /etc/shadow. The dummy parameter is a placeholder for NIS+ 1089 * updates where the "oldrpc" password is passed. 1090 */ 1091 /*ARGSUSED*/ 1092 int 1093 files_putpwnam(char *name, char *oldpw, char *dummy, 1094 pwu_repository_t *rep, void *buf) 1095 { 1096 struct pwbuf *pwbuf = (struct pwbuf *)buf; 1097 int result = PWU_SUCCESS; 1098 1099 if (pwbuf->pwd) { 1100 result = files_update_passwd(name, pwbuf->pwd); 1101 } 1102 1103 if (result == PWU_SUCCESS && pwbuf->spwd) { 1104 if (pwbuf->update_history != 0) { 1105 debug("update_history = %d", pwbuf->update_history); 1106 result = files_update_history(name, pwbuf->spwd); 1107 } else { 1108 debug("no password change"); 1109 } 1110 if (result == PWU_SUCCESS) { 1111 result = files_update_shadow(name, pwbuf->spwd); 1112 } 1113 } 1114 1115 if (pwbuf->pwd) { 1116 (void) memset(pwbuf->pwd, 0, sizeof (struct passwd)); 1117 (void) memset(pwbuf->pwd_scratch, 0, PWD_SCRATCH_SIZE); 1118 free(pwbuf->pwd); 1119 free(pwbuf->pwd_scratch); 1120 } 1121 if (pwbuf->spwd) { 1122 (void) memset(pwbuf->spwd, 0, sizeof (struct spwd)); 1123 (void) memset(pwbuf->spwd_scratch, 0, SPW_SCRATCH_SIZE); 1124 free(pwbuf->spwd); 1125 free(pwbuf->spwd_scratch); 1126 } 1127 if (pwbuf->new_sp_pwdp) { 1128 free(pwbuf->new_sp_pwdp); 1129 } 1130 1131 return (result); 1132 } 1133 1134 /* 1135 * NOTE: This is all covered under the repository lock held for updating 1136 * passwd(4) and shadow(4). 1137 */ 1138 int 1139 files_update_history(char *name, struct spwd *spwd) 1140 { 1141 int histsize; 1142 int tmpfd; 1143 FILE *src; /* history database file */ 1144 FILE *dst; /* temp history database being updated */ 1145 struct stat64 statbuf; 1146 char buf[MAX_LOGNAME + MAXHISTORY + 1147 (MAXHISTORY * CRYPT_MAXCIPHERTEXTLEN)+1]; 1148 int found; 1149 1150 if ((histsize = def_getint("HISTORY=", DEFHISTORY)) == 0) { 1151 debug("files_update_history(%s) no history, unlinking", name); 1152 (void) unlink(HISTORY); 1153 return (PWU_SUCCESS); /* no history update defined */ 1154 } 1155 debug("files_update_history(%s, %s) histsize = %d", name, spwd->sp_pwdp, 1156 histsize); 1157 1158 if (histsize > MAXHISTORY) 1159 histsize = MAXHISTORY; 1160 if ((tmpfd = open(HISTEMP, O_WRONLY|O_CREAT|O_TRUNC, HISTMODE)) < 0) { 1161 return (PWU_OPEN_FAILED); 1162 } 1163 (void) fchown(tmpfd, (uid_t)0, (gid_t)0); 1164 1165 /* get ready to copy */ 1166 if (((src = fopen(HISTORY, "r")) == NULL) && 1167 (errno != ENOENT)) { 1168 (void) unlink(HISTEMP); 1169 return (PWU_OPEN_FAILED); 1170 } 1171 if ((dst = fdopen(tmpfd, "w")) == NULL) { 1172 (void) fclose(src); 1173 (void) unlink(HISTEMP); 1174 return (PWU_OPEN_FAILED); 1175 } 1176 1177 /* Copy and update if found. Add if not found. */ 1178 1179 found = 0; 1180 1181 while ((src != NULL) && 1182 (fgets(buf, sizeof (buf), src) != NULL)) { 1183 char *user; 1184 char *last; 1185 1186 /* get username field */ 1187 user = strtok_r(buf, ":", &last); 1188 1189 #ifdef DEBUG 1190 debug("files_update_history: read=\"%s\"", user); 1191 #endif /* DEBUG */ 1192 1193 if (strcmp(user, name) == 0) { 1194 char *crypt; 1195 int i; 1196 1197 /* found user, update */ 1198 found++; 1199 (void) fprintf(dst, "%s:%s:", name, spwd->sp_pwdp); 1200 debug("files_update_history: update user\n" 1201 "\t%s:%s:", name, spwd->sp_pwdp); 1202 1203 /* get old crypted password history */ 1204 for (i = 0; i < MAXHISTORY-1; i++) { 1205 crypt = strtok_r(NULL, ":", &last); 1206 if (crypt == NULL || 1207 *crypt == '\n') { 1208 break; 1209 } 1210 (void) fprintf(dst, "%s:", crypt); 1211 debug("\t%d = %s:", i+1, crypt); 1212 } 1213 (void) fprintf(dst, "\n"); 1214 } else { 1215 1216 /* copy other users to updated file */ 1217 (void) fprintf(dst, "%s:%s", user, last); 1218 #ifdef DEBUG 1219 debug("files_update_history: copy line %s", 1220 user); 1221 #endif /* DEBUG */ 1222 } 1223 } 1224 1225 if (found == 0) { 1226 1227 /* user not found, add to history file */ 1228 (void) fprintf(dst, "%s:%s:\n", name, spwd->sp_pwdp); 1229 debug("files_update_history: add line\n" 1230 "\t%s:%s:", name, spwd->sp_pwdp); 1231 } 1232 1233 (void) fclose(src); 1234 1235 /* If something messed up in file system, loose the update */ 1236 if (fclose(dst) != 0) { 1237 1238 debug("files_update_history: update file close failed %d", 1239 errno); 1240 (void) unlink(HISTEMP); 1241 return (PWU_CLOSE_FAILED); 1242 } 1243 1244 /* 1245 * rename history to ohistory, 1246 * rename tmp to history, 1247 * unlink ohistory. 1248 */ 1249 1250 (void) unlink(OHISTORY); 1251 1252 if (stat64(OHISTORY, &statbuf) == 0 || 1253 ((src != NULL) && (link(HISTORY, OHISTORY) != 0)) || 1254 rename(HISTEMP, HISTORY) != 0) { 1255 1256 /* old history won't go away, loose the update */ 1257 debug("files_update_history: update file rename failed %d", 1258 errno); 1259 (void) unlink(HISTEMP); 1260 return (PWU_UPDATE_FAILED); 1261 } 1262 1263 (void) unlink(OHISTORY); 1264 return (PWU_SUCCESS); 1265 } 1266