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 (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T     */
  27 /*        All Rights Reserved   */
  28 
  29 /*      Copyright (c) 1987, 1988 Microsoft Corporation  */
  30 /*        All Rights Reserved   */
  31 
  32 /*
  33  * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
  34  */
  35 /*
  36  * passwd is a program whose sole purpose is to manage
  37  * the password file, map, or table. It allows system administrator
  38  * to add, change and display password attributes.
  39  * Non privileged user can change password or display
  40  * password attributes which corresponds to their login name.
  41  */
  42 
  43 #include <stdio.h>
  44 #include <pwd.h>
  45 #include <sys/types.h>
  46 #include <errno.h>
  47 #include <unistd.h>
  48 #include <stdlib.h>
  49 #include <locale.h>
  50 #include <stdarg.h>
  51 #include <errno.h>
  52 #include <string.h>
  53 #include <security/pam_appl.h>
  54 #include <security/pam_modules.h>
  55 #include <security/pam_impl.h>
  56 #include <rpcsvc/nis.h>
  57 #undef GROUP
  58 #include <syslog.h>
  59 #include <userdefs.h>
  60 #include <passwdutil.h>
  61 
  62 #include <nss_dbdefs.h>
  63 
  64 #include <deflt.h>
  65 
  66 #undef  GROUP
  67 #include <bsm/adt.h>
  68 #include <bsm/adt_event.h>
  69 
  70 /*
  71  * flags indicate password attributes to be modified
  72  */
  73 
  74 #define LFLAG 0x001             /* lock user's password  */
  75 #define DFLAG 0x002             /* delete user's  password */
  76 #define MFLAG 0x004             /* set max field -- # of days passwd is valid */
  77 #define NFLAG 0x008             /* set min field -- # of days between */
  78                                 /* password changes */
  79 #define SFLAG 0x010             /* display password attributes */
  80 #define FFLAG 0x020             /* expire  user's password */
  81 #define AFLAG 0x040             /* display password attributes for all users */
  82 #define SAFLAG (SFLAG|AFLAG)    /* display password attributes for all users */
  83 #define WFLAG 0x100             /* warn user to change passwd */
  84 #define OFLAG 0x200             /* domain name */
  85 #define EFLAG 0x400             /* change shell */
  86 #define GFLAG 0x800             /* change gecos information */
  87 #define HFLAG 0x1000            /* change home directory */
  88 #define XFLAG 0x2000            /* no login */
  89 #define UFLAG 0x4000            /* unlock user's password */
  90 #define STFLAG 0x6000           /* read the password from stdin */
  91 
  92 #define NONAGEFLAG      (EFLAG | GFLAG | HFLAG)
  93 #define AGEFLAG (LFLAG | FFLAG | MFLAG | NFLAG | WFLAG | XFLAG | UFLAG)
  94 #define MUTEXFLAG       (DFLAG | LFLAG | XFLAG | UFLAG | SAFLAG)
  95 
  96 
  97 /*
  98  * exit code
  99  */
 100 
 101 #define SUCCESS 0       /* succeeded */
 102 #define NOPERM  1       /* No permission */
 103 #define BADOPT  2       /* Invalid combination of option */
 104 #define FMERR   3       /* File/table manipulation error */
 105 #define FATAL   4       /* Old file/table can not be recovered */
 106 #define FBUSY   5       /* Lock file/table busy */
 107 #define BADSYN  6       /* Incorrect syntax */
 108 #define BADAGE  7       /* Aging is disabled  */
 109 #define NOMEM   8       /* No memory */
 110 #define SYSERR  9       /* System error */
 111 #define EXPIRED 10      /* Account expired */
 112 
 113 /*
 114  * define error messages
 115  */
 116 #define MSG_NP          "Permission denied"
 117 #define MSG_BS          "Invalid combination of options"
 118 #define MSG_FE          "Unexpected failure. Password file/table unchanged."
 119 #define MSG_FF          "Unexpected failure. Password file/table missing."
 120 #define MSG_FB          "Password file/table busy. Try again later."
 121 #define MSG_NV          "Invalid argument to option"
 122 #define MSG_AD          "Password aging is disabled"
 123 #define MSG_RS          "Cannot change from restricted shell %s\n"
 124 #define MSG_NM          "Out of memory."
 125 #define MSG_UNACCEPT    "%s is unacceptable as a new shell\n"
 126 #define MSG_UNAVAIL     "warning: %s is unavailable on this machine\n"
 127 #define MSG_COLON       "':' is not allowed.\n"
 128 #define MSG_MAXLEN      "Maximum number of characters allowed is %d."
 129 #define MSG_CONTROL     "Control characters are not allowed.\n"
 130 #define MSG_SHELL_UNCHANGED     "Login shell unchanged.\n"
 131 #define MSG_GECOS_UNCHANGED     "Finger information unchanged.\n"
 132 #define MSG_DIR_UNCHANGED       "Homedir information unchanged.\n"
 133 #define MSG_NAME        "\nName [%s]: "
 134 #define MSG_HOMEDIR     "\nHome Directory [%s]: "
 135 #define MSG_OLDSHELL    "Old shell: %s\n"
 136 #define MSG_NEWSHELL    "New shell: "
 137 #define MSG_AGAIN       "\nPlease try again\n"
 138 #define MSG_INPUTHDR    "Default values are printed inside of '[]'.\n" \
 139                         "To accept the default, type <return>.\n" \
 140                         "To have a blank entry, type the word 'none'.\n"
 141 #define MSG_UNKNOWN     "%s: User unknown: %s\n"
 142 #define MSG_ACCOUNT_EXP "User account has expired: %s\n"
 143 #define MSG_AUTHTOK_EXP "Your password has been expired for too long.\n" \
 144                         "Please contact the system administrator.\n"
 145 #define MSG_NIS_HOMEDIR "-h does not apply to NIS"
 146 #define MSG_CUR_PASS    "Enter existing login password: "
 147 #define MSG_CUR_PASS_UNAME      "Enter %s's existing login password: "
 148 #define MSG_SUCCESS     "%s: password information changed for %s\n"
 149 #define MSG_SORRY       "%s: Sorry, wrong passwd\n"
 150 #define MSG_INFO        "%s: Changing password for %s\n"
 151 
 152 
 153 /*
 154  * return code from ckarg() routine
 155  */
 156 #define FAIL            -1
 157 
 158 /*
 159  *  defind password file name
 160  */
 161 #define PASSWD                  "/etc/passwd"
 162 
 163 #define MAX_INPUT_LEN           512
 164 
 165 #define DEF_ATTEMPTS    3
 166 
 167 /* Number of characters in that make up an encrypted password (for now) */
 168 #define NUMCP                   13
 169 
 170 #ifdef DEBUG
 171 #define dprintf1        printf
 172 #else
 173 #define dprintf1(w, x)
 174 #endif
 175 
 176 extern int      optind;
 177 
 178 static int              retval = SUCCESS;
 179 static int              pam_retval = PAM_SUCCESS;
 180 static uid_t            uid;
 181 static char             *prognamep;
 182 static long             maxdate;        /* password aging information */
 183 static int              passwd_conv(int, struct pam_message **,
 184                             struct pam_response **, void *);
 185 static int              stdin_conv(int, struct pam_message **,
 186                             struct pam_response **, void *);
 187 static struct pam_conv  pam_conv = {passwd_conv, NULL};
 188 static pam_handle_t     *pamh;          /* Authentication handle */
 189 static char             *usrname;       /* user whose attribute we update */
 190 static adt_session_data_t *ah;  /* audit session handle */
 191 static adt_event_data_t *event = NULL; /* event to be generated */
 192 
 193 static pam_repository_t auth_rep;
 194 static pwu_repository_t repository;
 195 static pwu_repository_t __REPFILES = { "files", NULL, 0 };
 196 
 197 /*
 198  * Function Declarations
 199  */
 200 
 201 extern  void            setusershell(void);
 202 extern  char            *getusershell(void);
 203 extern  void            endusershell(void);
 204 
 205 static  void            passwd_exit(int retcode) __NORETURN;
 206 static  void            rusage(void);
 207 static  int             ckuid(void);
 208 static  int             ckarg(int argc, char **argv, attrlist **attributes);
 209 
 210 static  int             get_namelist(pwu_repository_t, char ***, int *);
 211 static  int             get_namelist_files(char ***, int *);
 212 static  int             get_namelist_local(char ***, int *);
 213 static  int             get_attr(char *, pwu_repository_t *, attrlist **);
 214 static  void            display_attr(char *, attrlist *);
 215 static  void            free_attr(attrlist *);
 216 static  void            attrlist_add(attrlist **, attrtype, char *);
 217 static  void            attrlist_reorder(attrlist **);
 218 static  char            *userinput(char *, pwu_repository_t *, attrtype);
 219 static  char            *getresponse(char *);
 220 
 221 /*
 222  * main():
 223  *      The main routine will call ckarg() to parse the command line
 224  *      arguments and call the appropriate functions to perform the
 225  *      tasks specified by the arguments. It allows system
 226  *      administrator to add, change and display password attributes.
 227  *      Non privileged user can change password or display
 228  *      password attributes which corresponds to their login name.
 229  */
 230 
 231 int
 232 main(int argc, char *argv[])
 233 {
 234 
 235         int     flag;
 236         char    **namelist;
 237         int     num_user;
 238         int     i;
 239         attrlist *attributes = NULL;
 240         char    *input;
 241         int     tries = 1;
 242         int     updated_reps;
 243         ssize_t s;
 244         char    st_pass[PASS_MAX];
 245 
 246         if ((prognamep = strrchr(argv[0], '/')) != NULL)
 247                 ++prognamep;
 248         else
 249                 prognamep = argv[0];
 250 
 251         auth_rep.type = NULL;
 252         auth_rep.scope = NULL;
 253         repository.type = NULL;
 254         repository.scope = NULL;
 255         repository.scope_len = 0;
 256 
 257 
 258         /* initialization for variables, set locale and textdomain  */
 259         i = 0;
 260         flag = 0;
 261 
 262         uid = getuid();         /* get the user id */
 263         (void) setlocale(LC_ALL, "");
 264 
 265 #if !defined(TEXT_DOMAIN)       /* Should be defined by cc -D */
 266 #define TEXT_DOMAIN "SYS_TEST"  /* Use this only if it weren't */
 267 #endif
 268         (void) textdomain(TEXT_DOMAIN);
 269 
 270         /*
 271          * ckarg() parses the arguments. In case of an error,
 272          * it sets the retval and returns FAIL (-1).
 273          */
 274 
 275         flag = ckarg(argc, argv, &attributes);
 276         dprintf1("flag is %0x\n", flag);
 277         if (flag == FAIL)
 278                 passwd_exit(retval);
 279 
 280         argc -= optind;
 281 
 282         if (argc < 1) {
 283                 if ((usrname = getlogin()) == NULL) {
 284                         struct passwd *pass = getpwuid(uid);
 285                         if (pass != NULL)
 286                                 usrname = pass->pw_name;
 287                         else {
 288                                 rusage();
 289                                 exit(NOPERM);
 290                         }
 291                 } else if (flag == 0) {
 292                         /*
 293                          * If flag is zero, change passwd.
 294                          * Otherwise, it will display or
 295                          * modify password aging attributes
 296                          */
 297                         (void) fprintf(stderr, gettext(MSG_INFO), prognamep,
 298                             usrname);
 299                 }
 300         } else {
 301                 usrname = argv[optind];
 302         }
 303 
 304         if (flag == STFLAG) {
 305                 if ((s = read(STDIN_FILENO, st_pass, sizeof (st_pass))) <= 0)
 306                         passwd_exit(FMERR);
 307 
 308                 st_pass[s - 1] = '\0';
 309                 if ((pam_conv.appdata_ptr = strdup(st_pass)) == NULL)
 310                         passwd_exit(FMERR);
 311                 pam_conv.conv = stdin_conv;
 312         }
 313 
 314         if (pam_start("passwd", usrname, &pam_conv, &pamh) != PAM_SUCCESS) {
 315                 passwd_exit(NOPERM);
 316         }
 317 
 318         auth_rep.type = repository.type;
 319         auth_rep.scope = repository.scope;
 320         auth_rep.scope_len = repository.scope_len;
 321 
 322         if (auth_rep.type != NULL) {
 323                 if (pam_set_item(pamh, PAM_REPOSITORY, (void *)&auth_rep)
 324                     != PAM_SUCCESS) {
 325                         passwd_exit(NOPERM);
 326                 }
 327         }
 328 
 329         if (flag ==  SAFLAG) {  /* display password attributes for all users */
 330                 retval = get_namelist(repository, &namelist, &num_user);
 331                 if (retval != SUCCESS)
 332                         (void) passwd_exit(retval);
 333 
 334                 if (num_user == 0) {
 335                         (void) fprintf(stderr, "%s: %s\n", prognamep,
 336                             gettext(MSG_FF));
 337                         passwd_exit(FATAL);
 338                 }
 339                 i = 0;
 340                 while (namelist[i] != NULL) {
 341                         (void) get_attr(namelist[i], &repository,
 342                             &attributes);
 343                         (void) display_attr(namelist[i], attributes);
 344                         (void) free(namelist[i]);
 345                         (void) free_attr(attributes);
 346                         i++;
 347                 }
 348                 (void) free(namelist);
 349                 passwd_exit(SUCCESS);
 350         } else if (flag == SFLAG) { /* display password attributes by user */
 351                 if (get_attr(usrname, &repository, &attributes) ==
 352                     PWU_SUCCESS) {
 353                         (void) display_attr(usrname, attributes);
 354                         (void) free_attr(attributes);
 355                 }
 356                 passwd_exit(SUCCESS);
 357                 /* NOT REACHED */
 358         }
 359 
 360 
 361         switch (pam_authenticate(pamh, 0)) {
 362         case PAM_SUCCESS:
 363                 break;
 364         case PAM_USER_UNKNOWN:
 365                 (void) fprintf(stderr, gettext(MSG_UNKNOWN), prognamep,
 366                     usrname);
 367                 passwd_exit(NOPERM);
 368                 break;
 369         case PAM_PERM_DENIED:
 370                 passwd_exit(NOPERM);
 371                 break;
 372         case PAM_AUTH_ERR:
 373                 (void) fprintf(stderr, gettext(MSG_SORRY), prognamep);
 374                 passwd_exit(NOPERM);
 375                 break;
 376         default:
 377                 /* system error */
 378                 passwd_exit(FMERR);
 379                 break;
 380         }
 381 
 382         if (flag == STFLAG || flag == 0) {      /* changing user password */
 383                 int     chk_authtok = 0;        /* check password strength */
 384 
 385                 dprintf1("call pam_chauthtok() repository name =%s\n",
 386                     repository.type);
 387 
 388                 /* Set up for Audit */
 389                 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
 390                         perror("adt_start_session");
 391                         passwd_exit(SYSERR);
 392                 }
 393                 if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
 394                         perror("adt_alloc_event");
 395                         passwd_exit(NOMEM);
 396                 }
 397 
 398                 /* Don't check account expiration when invoked by root */
 399                 if (ckuid() != SUCCESS) {
 400                         pam_retval = pam_acct_mgmt(pamh, PAM_SILENT);
 401                         switch (pam_retval) {
 402                         case PAM_ACCT_EXPIRED:
 403                                 (void) fprintf(stderr,
 404                                     gettext(MSG_ACCOUNT_EXP), usrname);
 405                                 passwd_exit(EXPIRED);
 406                                 break;
 407                         case PAM_AUTHTOK_EXPIRED:
 408                                 (void) fprintf(stderr,
 409                                     gettext(MSG_AUTHTOK_EXP));
 410                                 passwd_exit(NOPERM);
 411                                 break;
 412                         case PAM_NEW_AUTHTOK_REQD:
 413                                 /* valid error when changing passwords */
 414                                 break;
 415                         case PAM_SUCCESS:
 416                                 /* Ok to change password */
 417                                 break;
 418                         default:
 419                                 passwd_exit(NOPERM);
 420                         }
 421                 }
 422 
 423 
 424                 pam_retval = PAM_AUTHTOK_ERR;
 425                 tries = 1;
 426                 if (ckuid() == SUCCESS) {
 427                         /* bypass password strength checks */
 428                         chk_authtok = PAM_NO_AUTHTOK_CHECK;
 429                 }
 430 
 431                 while (pam_retval == PAM_AUTHTOK_ERR && tries <= DEF_ATTEMPTS) {
 432                         if (tries > 1)
 433                                 (void) printf(gettext(MSG_AGAIN));
 434                         pam_retval = pam_chauthtok(pamh, chk_authtok);
 435                         if (pam_retval == PAM_TRY_AGAIN) {
 436                                 (void) sleep(1);
 437                                 pam_retval = pam_chauthtok(pamh, chk_authtok);
 438                         }
 439                         tries++;
 440                 }
 441 
 442                 switch (pam_retval) {
 443                 case PAM_SUCCESS:
 444                         retval = SUCCESS;
 445                         break;
 446                 case PAM_AUTHTOK_DISABLE_AGING:
 447                         retval = BADAGE;
 448                         break;
 449                 case PAM_AUTHTOK_LOCK_BUSY:
 450                         retval = FBUSY;
 451                         break;
 452                 case PAM_TRY_AGAIN:
 453                         retval = FBUSY;
 454                         break;
 455                 case PAM_AUTHTOK_ERR:
 456                 case PAM_AUTHTOK_RECOVERY_ERR:
 457                 default:
 458                         retval = NOPERM;
 459                         break;
 460                 }
 461 
 462                 (void) passwd_exit(retval);
 463                 /* NOT REACHED */
 464         } else {                /* changing attributes */
 465                 switch (flag) {
 466                 case EFLAG:             /* changing user password attributes */
 467                         input = userinput(usrname, &repository, ATTR_SHELL);
 468                         if (input)
 469                                 attrlist_add(&attributes, ATTR_SHELL, input);
 470                         else
 471                                 (void) printf(gettext(MSG_SHELL_UNCHANGED));
 472                         break;
 473                 case GFLAG:
 474                         input = userinput(usrname, &repository, ATTR_GECOS);
 475                         if (input)
 476                                 attrlist_add(&attributes, ATTR_GECOS, input);
 477                         else
 478                                 (void) printf(gettext(MSG_GECOS_UNCHANGED));
 479                         break;
 480                 case HFLAG:
 481                         input = userinput(usrname, &repository, ATTR_HOMEDIR);
 482                         if (input)
 483                                 attrlist_add(&attributes, ATTR_HOMEDIR, input);
 484                         else
 485                                 (void) printf(gettext(MSG_DIR_UNCHANGED));
 486                         break;
 487                 }
 488 
 489                 if (attributes != NULL) {
 490                         retval = __set_authtoken_attr(usrname,
 491                             pamh->ps_item[PAM_AUTHTOK].pi_addr,
 492                             &repository, attributes, &updated_reps);
 493                         switch (retval) {
 494                         case PWU_SUCCESS:
 495                                 for (i = 1; i <= REP_LAST; i <<= 1) {
 496                                         if ((updated_reps & i) == 0)
 497                                                 continue;
 498                                         (void) printf(gettext(MSG_SUCCESS),
 499                                             prognamep, usrname);
 500                                 }
 501                                 retval = SUCCESS;
 502                                 break;
 503                         case PWU_AGING_DISABLED:
 504                                 retval = BADAGE;
 505                                 break;
 506                         default:
 507                                 retval = NOPERM;
 508                                 break;
 509                         }
 510                 } else {
 511                         retval = SUCCESS; /* nothing to change won't fail */
 512                 }
 513                 (void) passwd_exit(retval);
 514         }
 515         /* NOTREACHED */
 516         return (0);
 517 }
 518 
 519 /*
 520  * Get a line of input from the user.
 521  *
 522  * If the line is empty, or the input equals 'oldval', NULL is returned.
 523  * therwise, a malloced string containing the input (minus the trailing
 524  * newline) is returned.
 525  */
 526 char *
 527 getresponse(char *oldval)
 528 {
 529         char    resp[MAX_INPUT_LEN];
 530         char    *retval = NULL;
 531         int     resplen;
 532 
 533         (void) fgets(resp, sizeof (resp) - 1, stdin);
 534         resplen = strlen(resp) - 1;
 535         if (resp[resplen] == '\n')
 536                 resp[resplen] = '\0';
 537         if (*resp != '\0' && strcmp(resp, oldval) != 0)
 538                 retval = strdup(resp);
 539         return (retval);
 540 }
 541 
 542 /*
 543  * char *userinput(item)
 544  *
 545  * user conversation function. The old value of attribute "item" is
 546  * displayed while the user is asked to provide a new value.
 547  *
 548  * returns a malloc()-ed string if the user actualy provided input
 549  * or NULL if the user simply hit return or the input equals the old
 550  * value (not changed).
 551  */
 552 char *
 553 userinput(char *name, pwu_repository_t *rep, attrtype type)
 554 {
 555         attrlist oldattr;
 556         char *oldval;                   /* shorthand for oldattr.data.val_s */
 557         char *valid;                    /* points to valid shells */
 558         char *response;
 559         char *cp;
 560 
 561         oldattr.type = type;
 562         oldattr.next = NULL;
 563 
 564         if (__get_authtoken_attr(name, rep, &oldattr) != PWU_SUCCESS)
 565                 passwd_exit(FMERR);
 566 
 567         oldval = oldattr.data.val_s;
 568 
 569         if (type == ATTR_SHELL) {
 570                 /* No current shell: set DEFSHL as default choice */
 571                 if (*oldval == '\0') {
 572                         free(oldval);
 573                         oldval = strdup(DEFSHL);
 574                 }
 575 
 576                 if (ckuid() != SUCCESS) {
 577                         /* User must currently have a valid shell */
 578                         setusershell();
 579                         valid = getusershell();
 580                         while (valid && strcmp(valid, oldval) != 0)
 581                                 valid = getusershell();
 582                         endusershell();
 583 
 584                         if (valid == NULL) {
 585                                 (void) fprintf(stderr, gettext(MSG_RS), oldval);
 586                                 free(oldval);
 587                                 return (NULL);
 588                         }
 589                 }
 590                 (void) printf(gettext(MSG_OLDSHELL), oldval);
 591                 (void) printf(gettext(MSG_NEWSHELL));
 592                 (void) fflush(stdout);
 593 
 594                 response = getresponse(oldval);
 595                 free(oldval); /* We don't need the old value anymore */
 596 
 597                 if (response == NULL || *response == '\0')
 598                         return (NULL);
 599 
 600                 /* Make sure new shell is listed */
 601                 setusershell();
 602                 valid = getusershell();
 603                 while (valid) {
 604                         char *cp;
 605 
 606                         /* Allow user to give shell without path */
 607                         if (*response == '/') {
 608                                 cp = valid;
 609                         } else {
 610                                 if ((cp = strrchr(valid, '/')) == NULL)
 611                                         cp = valid;
 612                                 else
 613                                         cp++;
 614                         }
 615                         if (strcmp(cp, response) == 0) {
 616                                 if (*response != '/') {
 617                                         /* take shell name including path */
 618                                         free(response);
 619                                         response = strdup(valid);
 620                                 }
 621                                 break;
 622                         }
 623                         valid = getusershell();
 624                 }
 625                 endusershell();
 626 
 627                 if (valid == NULL) {    /* No valid shell matches */
 628                         (void) fprintf(stderr, gettext(MSG_UNACCEPT), response);
 629                         return (NULL);
 630                 }
 631 
 632                 if (access(response, X_OK) < 0)
 633                         (void) fprintf(stderr, gettext(MSG_UNAVAIL), response);
 634                 return (response);
 635                 /* NOT REACHED */
 636         }
 637         /*
 638          * if type == SHELL, we have returned by now. Only GECOS and
 639          * HOMEDIR get to this point.
 640          */
 641         (void) printf(gettext(MSG_INPUTHDR));
 642 
 643         /*
 644          * PRE: oldval points to malloced string with Old Value
 645          * INV: oldval remains unchanged
 646          * POST:response points to valid string or NULL.
 647          */
 648         for (;;) {
 649                 if (type == ATTR_GECOS)
 650                         (void) printf(gettext(MSG_NAME), oldval);
 651                 else if (type == ATTR_HOMEDIR)
 652                         (void) printf(gettext(MSG_HOMEDIR), oldval);
 653 
 654                 response = getresponse(oldval);
 655 
 656                 if (response && strcmp(response, "none") == 0)
 657                         *response = '\0';
 658 
 659                 /* No-change or empty string are OK */
 660                 if (response == NULL || *response == '\0')
 661                         break;
 662 
 663                 /* Check for illegal characters */
 664                 if (strchr(response, ':')) {
 665                         (void) fprintf(stderr, "%s", gettext(MSG_COLON));
 666                         free(response);
 667                 } else if (strlen(response) > MAX_INPUT_LEN - 1) {
 668                         (void) fprintf(stderr, gettext(MSG_MAXLEN),
 669                             MAX_INPUT_LEN);
 670                         free(response);
 671                 } else {
 672                         /* don't allow control characters */
 673                         for (cp = response; *cp >= 040; cp++)
 674                                 ;
 675                         if (*cp != '\0') {
 676                                 (void) fprintf(stderr, gettext(MSG_CONTROL));
 677                                 free(response);
 678                         } else
 679                                 break;  /* response is a valid string */
 680                 }
 681                 /*
 682                  * We only get here if the input was invalid.
 683                  * In that case, we again ask the user for input.
 684                  */
 685         }
 686         free(oldval);
 687         return (response);
 688 }
 689 /*
 690  * ckarg():
 691  *      This function parses and verifies the
 692  *      arguments.  It takes three parameters:
 693  *      argc => # of arguments
 694  *      argv => pointer to an argument
 695  *      attrlist => pointer to list of password attributes
 696  */
 697 
 698 static int
 699 ckarg(int argc, char **argv, attrlist **attributes)
 700 {
 701         extern char     *optarg;
 702         char            *char_p;
 703         int     opt;
 704         int     flag;
 705 
 706         flag = 0;
 707 
 708         while ((opt = getopt(argc, argv, "r:aldefghsux:n:w:N:S")) != EOF) {
 709                 switch (opt) {
 710 
 711                 case 'r': /* Repository Specified */
 712                         /* repository: this option should be specified first */
 713 
 714                         if (repository.type != NULL) {
 715                                 (void) fprintf(stderr, gettext(
 716                         "Repository is already defined or specified.\n"));
 717                                 rusage();
 718                                 retval = BADSYN;
 719                                 return (FAIL);
 720                         }
 721                         if (strcmp(optarg, "nis") == 0) {
 722                                 repository.type = optarg;
 723                         } else if (strcmp(optarg, "ldap") == 0) {
 724                                 repository.type = optarg;
 725                         } else if (strcmp(optarg, "files") == 0) {
 726                                 repository.type = optarg;
 727                         } else {
 728                                 (void) fprintf(stderr,
 729                                     gettext("invalid repository: %s\n"),
 730                                     optarg);
 731                                 rusage();
 732                                 retval = BADSYN;
 733                                 return (FAIL);
 734                         }
 735                         break;
 736 
 737                 case 'd': /* Delete Auth Token */
 738                         /* if no repository the default for -d is files */
 739                         if (repository.type == NULL)
 740                                 repository = __REPFILES;
 741 
 742                         /*
 743                          * Delete the password - only privileged processes
 744                          * can execute this for FILES or LDAP
 745                          */
 746                         if (IS_FILES(repository) == FALSE &&
 747                             IS_LDAP(repository) == FALSE) {
 748                                 (void) fprintf(stderr, gettext(
 749                                     "-d only applies to files "
 750                                     "or ldap repository\n"));
 751                                 rusage();       /* exit */
 752                                 retval = BADSYN;
 753                                 return (FAIL);
 754                         }
 755 
 756                         if (ckuid() != SUCCESS) {
 757                                 retval = NOPERM;
 758                                 return (FAIL);
 759                         }
 760                         if (flag & (LFLAG|SAFLAG|DFLAG|XFLAG|UFLAG|STFLAG)) {
 761                                 rusage();
 762                                 retval = BADOPT;
 763                                 return (FAIL);
 764                         }
 765                         flag |= DFLAG;
 766                         attrlist_add(attributes, ATTR_PASSWD, NULL);
 767                         break;
 768 
 769                 case 'N': /* set account to be "no login" */
 770 
 771                         /* if no repository the default for -N is files */
 772                         if (repository.type == NULL)
 773                                 repository = __REPFILES;
 774 
 775                         if (IS_FILES(repository) == FALSE &&
 776                             IS_LDAP(repository) == FALSE) {
 777                                 (void) fprintf(stderr, gettext(
 778                                     "-N only applies to files or ldap "
 779                                     "repository\n"));
 780                                 rusage();       /* exit */
 781                                 retval = BADOPT;
 782                                 return (FAIL);
 783                         }
 784 
 785                         /*
 786                          * Only privileged processes can execute this
 787                          * for FILES or LDAP
 788                          */
 789                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
 790                             ((retval = ckuid()) != SUCCESS))
 791                                 return (FAIL);
 792                         if (flag & (MUTEXFLAG|NONAGEFLAG|STFLAG)) {
 793                                 rusage();       /* exit */
 794                                 retval = BADOPT;
 795                                 return (FAIL);
 796                         }
 797                         flag |= XFLAG;
 798                         attrlist_add(attributes, ATTR_NOLOGIN_ACCOUNT, NULL);
 799                         break;
 800 
 801                 case 'l': /* lock the password */
 802 
 803                         /* if no repository the default for -l is files */
 804                         if (repository.type == NULL)
 805                                 repository = __REPFILES;
 806 
 807                         if (IS_FILES(repository) == FALSE &&
 808                             IS_LDAP(repository) == FALSE) {
 809                                 (void) fprintf(stderr, gettext(
 810                                     "-l only applies to files or ldap "
 811                                     "repository\n"));
 812                                 rusage();       /* exit */
 813                                 retval = BADOPT;
 814                                 return (FAIL);
 815                         }
 816 
 817                         /*
 818                          * Only privileged processes can execute this
 819                          * for FILES or LDAP
 820                          */
 821                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
 822                             ((retval = ckuid()) != SUCCESS))
 823                                 return (FAIL);
 824                         if (flag & (MUTEXFLAG|NONAGEFLAG|STFLAG)) {
 825                                 rusage();       /* exit */
 826                                 retval = BADOPT;
 827                                 return (FAIL);
 828                         }
 829                         flag |= LFLAG;
 830                         attrlist_add(attributes, ATTR_LOCK_ACCOUNT, NULL);
 831                         break;
 832 
 833                 case 'u': /* unlock the password */
 834 
 835                         /* if no repository the default for -u is files */
 836                         if (repository.type == NULL)
 837                                 repository = __REPFILES;
 838 
 839                         if (IS_FILES(repository) == FALSE &&
 840                             IS_LDAP(repository) == FALSE) {
 841                                 (void) fprintf(stderr, gettext(
 842                                     "-u only applies to files or ldap "
 843                                     "repository\n"));
 844                                 rusage();       /* exit */
 845                                 retval = BADOPT;
 846                                 return (FAIL);
 847                         }
 848 
 849                         /*
 850                          * Only privileged processes can execute this
 851                          * for FILES or LDAP
 852                          */
 853                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
 854                             ((retval = ckuid()) != SUCCESS))
 855                                 return (FAIL);
 856                         if (flag & (MUTEXFLAG|NONAGEFLAG|STFLAG)) {
 857                                 rusage();       /* exit */
 858                                 retval = BADOPT;
 859                                 return (FAIL);
 860                         }
 861                         flag |= UFLAG;
 862                         attrlist_add(attributes, ATTR_UNLOCK_ACCOUNT, NULL);
 863                         attrlist_add(attributes, ATTR_RST_FAILED_LOGINS, NULL);
 864                         break;
 865 
 866                 case 'x': /* set the max date */
 867 
 868                         /* if no repository the default for -x is files */
 869                         if (repository.type == NULL)
 870                                 repository = __REPFILES;
 871 
 872                         if (IS_FILES(repository) == FALSE &&
 873                             IS_LDAP(repository) == FALSE) {
 874                                 (void) fprintf(stderr, gettext(
 875                                     "-x only applies to files or ldap "
 876                                     "repository\n"));
 877                                 rusage();       /* exit */
 878                                 retval = BADSYN;
 879                                 return (FAIL);
 880                         }
 881 
 882                         /*
 883                          * Only privileged process can execute this
 884                          * for FILES or LDAP
 885                          */
 886                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
 887                             (ckuid() != SUCCESS)) {
 888                                 retval = NOPERM;
 889                                 return (FAIL);
 890                         }
 891                         if (flag & (SAFLAG|MFLAG|NONAGEFLAG|STFLAG)) {
 892                                 retval = BADOPT;
 893                                 return (FAIL);
 894                         }
 895                         flag |= MFLAG;
 896                         if ((int)strlen(optarg)  <= 0 ||
 897                             (maxdate = strtol(optarg, &char_p, 10)) < -1 ||
 898                             *char_p != '\0') {
 899                                 (void) fprintf(stderr, "%s: %s -x\n",
 900                                     prognamep, gettext(MSG_NV));
 901                                 retval = BADSYN;
 902                                 return (FAIL);
 903                         }
 904                         attrlist_add(attributes, ATTR_MAX, optarg);
 905                         break;
 906 
 907                 case 'n': /* set the min date */
 908 
 909                         /* if no repository the default for -n is files */
 910                         if (repository.type == NULL)
 911                                 repository = __REPFILES;
 912 
 913                         if (IS_FILES(repository) == FALSE &&
 914                             IS_LDAP(repository) == FALSE) {
 915                                 (void) fprintf(stderr, gettext(
 916                                     "-n only applies to files or ldap "
 917                                     "repository\n"));
 918                                 rusage();       /* exit */
 919                                 retval = BADSYN;
 920                                 return (FAIL);
 921                         }
 922 
 923                         /*
 924                          * Only privileged process can execute this
 925                          * for FILES or LDAP
 926                          */
 927                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
 928                             ((retval = ckuid()) != SUCCESS))
 929                                 return (FAIL);
 930                         if (flag & (SAFLAG|NFLAG|NONAGEFLAG|STFLAG)) {
 931                                 retval = BADOPT;
 932                                 return (FAIL);
 933                         }
 934                         flag |= NFLAG;
 935                         if ((int)strlen(optarg)  <= 0 ||
 936                             (strtol(optarg, &char_p, 10)) < 0 ||
 937                             *char_p != '\0') {
 938                                 (void) fprintf(stderr, "%s: %s -n\n",
 939                                     prognamep, gettext(MSG_NV));
 940                                 retval = BADSYN;
 941                                 return (FAIL);
 942                         }
 943                         attrlist_add(attributes, ATTR_MIN, optarg);
 944                         break;
 945 
 946                 case 'w': /* set the warning field */
 947 
 948                         /* if no repository the default for -w is files */
 949                         if (repository.type == NULL)
 950                                 repository = __REPFILES;
 951 
 952                         if (IS_FILES(repository) == FALSE &&
 953                             IS_LDAP(repository) == FALSE) {
 954                                 (void) fprintf(stderr, gettext(
 955                                     "-w only applies to files or ldap "
 956                                     "repository\n"));
 957                                 rusage();       /* exit */
 958                                 retval = BADSYN;
 959                                 return (FAIL);
 960                         }
 961 
 962                         /*
 963                          * Only privileged process can execute this
 964                          * for FILES or LDAP
 965                          */
 966                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
 967                             (ckuid() != SUCCESS)) {
 968                                 retval = NOPERM;
 969                                 return (FAIL);
 970                         }
 971                         if (flag & (SAFLAG|WFLAG|NONAGEFLAG|STFLAG)) {
 972                                 retval = BADOPT;
 973                                 return (FAIL);
 974                         }
 975                         flag |= WFLAG;
 976                         if ((int)strlen(optarg)  <= 0 ||
 977                             (strtol(optarg, &char_p, 10)) < 0 ||
 978                             *char_p != '\0') {
 979                                 (void) fprintf(stderr, "%s: %s -w\n",
 980                                     prognamep, gettext(MSG_NV));
 981                                 retval = BADSYN;
 982                                 return (FAIL);
 983                         }
 984                         attrlist_add(attributes, ATTR_WARN, optarg);
 985                         break;
 986 
 987                 case 's': /* display password attributes */
 988 
 989                         /* if no repository the default for -s is files */
 990                         if (repository.type == NULL)
 991                                 repository = __REPFILES;
 992 
 993 
 994                         /* display password attributes */
 995                         if (IS_FILES(repository) == FALSE &&
 996                             IS_LDAP(repository) == FALSE) {
 997                                 (void) fprintf(stderr, gettext(
 998                                     "-s only applies to files or ldap "
 999                                     "repository\n"));
1000                                 rusage();       /* exit */
1001                                 retval = BADSYN;
1002                                 return (FAIL);
1003                         }
1004 
1005                         /*
1006                          * Only privileged process can execute this
1007                          * for FILES or LDAP
1008                          */
1009                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
1010                             ((retval = ckuid()) != SUCCESS))
1011                                 return (FAIL);
1012                         if (flag && (flag != AFLAG)) {
1013                                 retval = BADOPT;
1014                                 return (FAIL);
1015                         }
1016                         flag |= SFLAG;
1017                         break;
1018 
1019                 case 'a': /* display password attributes */
1020 
1021                         /* if no repository the default for -a is files */
1022                         if (repository.type == NULL)
1023                                 repository = __REPFILES;
1024 
1025                         if (IS_FILES(repository) == FALSE &&
1026                             IS_LDAP(repository) == FALSE) {
1027                                 (void) fprintf(stderr, gettext(
1028                                     "-a only applies to files or ldap "
1029                                     "repository\n"));
1030                                 rusage();       /* exit */
1031                                 retval = BADSYN;
1032                                 return (FAIL);
1033                         }
1034 
1035                         /*
1036                          * Only privileged process can execute this
1037                          * for FILES or LDAP
1038                          */
1039                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
1040                             ((retval = ckuid()) != SUCCESS))
1041                                 return (FAIL);
1042                         if (flag && (flag != SFLAG)) {
1043                                 retval = BADOPT;
1044                                 return (FAIL);
1045                         }
1046                         flag |= AFLAG;
1047                         break;
1048 
1049                 case 'f': /* expire password attributes */
1050 
1051                         /* if no repository the default for -f is files */
1052                         if (repository.type == NULL)
1053                                 repository = __REPFILES;
1054 
1055                         if (IS_FILES(repository) == FALSE &&
1056                             IS_LDAP(repository) == FALSE) {
1057                                 (void) fprintf(stderr, gettext(
1058                                     "-f only applies to files or ldap "
1059                                     "repository\n"));
1060                                 rusage();       /* exit */
1061                                 retval = BADSYN;
1062                                 return (FAIL);
1063                         }
1064 
1065                         /*
1066                          * Only privileged process can execute this
1067                          * for FILES or LDAP
1068                          */
1069                         if ((IS_FILES(repository) || IS_LDAP(repository)) &&
1070                             ((retval = ckuid()) != SUCCESS))
1071                                 return (FAIL);
1072                         if (flag & (SAFLAG|FFLAG|NONAGEFLAG|STFLAG)) {
1073                                 retval = BADOPT;
1074                                 return (FAIL);
1075                         }
1076                         flag |= FFLAG;
1077                         attrlist_add(attributes, ATTR_EXPIRE_PASSWORD, NULL);
1078                         break;
1079 
1080                 case 'e': /* change login shell */
1081 
1082                         /* if no repository the default for -e is files */
1083                         if (repository.type == NULL)
1084                                 repository = __REPFILES;
1085 
1086                         if (flag & (EFLAG|SAFLAG|AGEFLAG|STFLAG)) {
1087                                 retval = BADOPT;
1088                                 return (FAIL);
1089                         }
1090                         flag |= EFLAG;
1091                         break;
1092 
1093                 case 'g': /* change gecos information */
1094 
1095                         /* if no repository the default for -g is files */
1096                         if (repository.type == NULL)
1097                                 repository = __REPFILES;
1098 
1099                         /*
1100                          * Only privileged process can execute this
1101                          * for FILES
1102                          */
1103                         if (IS_FILES(repository) && (ckuid() != SUCCESS)) {
1104                                 retval = NOPERM;
1105                                 return (FAIL);
1106                         }
1107                         if (flag & (GFLAG|SAFLAG|AGEFLAG|STFLAG)) {
1108                                 retval = BADOPT;
1109                                 return (FAIL);
1110                         }
1111                         flag |= GFLAG;
1112                         break;
1113 
1114                 case 'h': /* change home dir */
1115 
1116                         /* if no repository the default for -h is files */
1117                         if (repository.type == NULL)
1118                                 repository = __REPFILES;
1119                         /*
1120                          * Only privileged process can execute this
1121                          * for FILES
1122                          */
1123                         if (IS_FILES(repository) && (ckuid() != SUCCESS)) {
1124                                 retval = NOPERM;
1125                                 return (FAIL);
1126                         }
1127                         if (IS_NIS(repository)) {
1128                                 (void) fprintf(stderr, "%s\n",
1129                                     gettext(MSG_NIS_HOMEDIR));
1130                                 retval = BADSYN;
1131                                 return (FAIL);
1132                         }
1133 
1134                         if (flag & (HFLAG|SAFLAG|AGEFLAG|STFLAG)) {
1135                                 retval = BADOPT;
1136                                 return (FAIL);
1137                         }
1138                         flag |= HFLAG;
1139                         break;
1140                 case 'S':
1141                         if (ckuid() != SUCCESS) {
1142                                 retval = NOPERM;
1143                                 return (FAIL);
1144                         }
1145                         if (flag & (MUTEXFLAG|NONAGEFLAG|AGEFLAG)) {
1146                                 rusage();       /* exit */
1147                                 retval = BADOPT;
1148                                 return (FAIL);
1149                         }
1150                         flag |= STFLAG;
1151                         break;
1152                 case '?':
1153                         rusage();
1154                         retval = BADOPT;
1155                         return (FAIL);
1156                 }
1157         }
1158 
1159         argc -= optind;
1160         if (argc > 1) {
1161                 rusage();
1162                 retval = BADSYN;
1163                 return (FAIL);
1164         }
1165 
1166         /* Make sure (EXPIRE comes after (MAX comes after MIN)) */
1167         attrlist_reorder(attributes);
1168 
1169         /* If no options are specified or only the show option */
1170         /* is specified, return because no option error checking */
1171         /* is needed */
1172         if (!flag || (flag == SFLAG))
1173                 return (flag);
1174 
1175         /* AFLAG must be used with SFLAG */
1176         if (flag == AFLAG) {
1177                 rusage();
1178                 retval = BADSYN;
1179                 return (FAIL);
1180         }
1181 
1182         if (flag != SAFLAG && argc < 1) {
1183                 /*
1184                  * user name is not specified (argc<1), it can't be
1185                  * aging info update.
1186                  */
1187                 if (!(flag & NONAGEFLAG)) {
1188                         rusage();
1189                         retval = BADSYN;
1190                         return (FAIL);
1191                 }
1192         }
1193 
1194         /* user name(s) may not be specified when SAFLAG is used. */
1195         if (flag == SAFLAG && argc >= 1) {
1196                 rusage();
1197                 retval = BADSYN;
1198                 return (FAIL);
1199         }
1200 
1201         /*
1202          * If aging is being turned off (maxdate == -1), mindate may not
1203          * be specified.
1204          */
1205         if ((maxdate == -1) && (flag & NFLAG)) {
1206                 (void) fprintf(stderr, "%s: %s -n\n",
1207                     prognamep, gettext(MSG_NV));
1208                 retval = BADOPT;
1209                 return (FAIL);
1210         }
1211 
1212         return (flag);
1213 }
1214 
1215 /*
1216  *
1217  * ckuid():
1218  *      This function returns SUCCESS if the caller is root, else
1219  *      it returns NOPERM.
1220  *
1221  */
1222 
1223 static int
1224 ckuid(void)
1225 {
1226         if (uid != 0) {
1227                 return (retval = NOPERM);
1228         }
1229         return (SUCCESS);
1230 }
1231 
1232 
1233 /*
1234  * get_attr()
1235  */
1236 int
1237 get_attr(char *username, pwu_repository_t *repository, attrlist **attributes)
1238 {
1239         int res;
1240 
1241         attrlist_add(attributes, ATTR_PASSWD, NULL);
1242         attrlist_add(attributes, ATTR_LSTCHG, "0");
1243         attrlist_add(attributes, ATTR_MIN, "0");
1244         attrlist_add(attributes, ATTR_MAX, "0");
1245         attrlist_add(attributes, ATTR_WARN, "0");
1246 
1247         res = __get_authtoken_attr(username, repository, *attributes);
1248 
1249         if (res == PWU_SUCCESS) {
1250                 retval = SUCCESS;
1251                 return (PWU_SUCCESS);
1252         }
1253 
1254         if (res == PWU_NOT_FOUND)
1255                 (void) fprintf(stderr, gettext(MSG_UNKNOWN), prognamep,
1256                     username);
1257 
1258         retval = NOPERM;
1259         passwd_exit(retval);
1260         /*NOTREACHED*/
1261 }
1262 
1263 /*
1264  * display_attr():
1265  * This function prints out the password attributes of a usr
1266  * onto standand output.
1267  */
1268 void
1269 display_attr(char *usrname, attrlist *attributes)
1270 {
1271         char    *status = NULL;
1272         char    *passwd;
1273         long    lstchg;
1274         int     min = 0, max = 0, warn = 0;
1275 
1276         while (attributes) {
1277                 switch (attributes->type) {
1278                 case ATTR_PASSWD:
1279                         passwd = attributes->data.val_s;
1280                         if (passwd == NULL || *passwd == '\0')
1281                                 status = "NP  ";
1282                         else if (strncmp(passwd, LOCKSTRING,
1283                             sizeof (LOCKSTRING)-1) == 0)
1284                                 status = "LK  ";
1285                         else if (strncmp(passwd, NOLOGINSTRING,
1286                             sizeof (NOLOGINSTRING)-1) == 0)
1287                                 status = "NL  ";
1288                         else if ((strlen(passwd) == 13 && passwd[0] != '$') ||
1289                             passwd[0] == '$')
1290                                 status = "PS  ";
1291                         else
1292                                 status = "UN  ";
1293                         break;
1294                 case ATTR_LSTCHG:
1295                         lstchg = attributes->data.val_i * DAY;
1296                         break;
1297                 case ATTR_MIN:
1298                         min = attributes->data.val_i;
1299                         break;
1300                 case ATTR_MAX:
1301                         max = attributes->data.val_i;
1302                         break;
1303                 case ATTR_WARN:
1304                         warn = attributes->data.val_i;
1305                         break;
1306                 default:
1307                         break;
1308                 }
1309                 attributes = attributes->next;
1310         }
1311         (void) fprintf(stdout, "%-8s  ", usrname);
1312 
1313         if (status)
1314                 (void) fprintf(stdout, "%s  ", status);
1315 
1316         if (max != -1) {
1317                 if (lstchg == 0) {
1318                         (void) fprintf(stdout, "00/00/00  ");
1319                 } else {
1320                         struct tm *tmp;
1321                         tmp = gmtime(&lstchg);
1322                         (void) fprintf(stdout, "%.2d/%.2d/%.2d  ",
1323                             tmp->tm_mon + 1,
1324                             tmp->tm_mday,
1325                             tmp->tm_year % 100);
1326                 }
1327                 (void) fprintf(stdout, (min >= 0) ? "%4d  " : "      ", min);
1328                 (void) fprintf(stdout, "%4d  ", max);
1329                 (void) fprintf(stdout, (warn > 0) ? "%4d  " : "      ", warn);
1330         }
1331         (void) fprintf(stdout, "\n");
1332 }
1333 
1334 void
1335 free_attr(attrlist *attributes)
1336 {
1337         while (attributes) {
1338                 if (attributes->type == ATTR_PASSWD)
1339                         free(attributes->data.val_s);
1340                 attributes = attributes->next;
1341         }
1342 }
1343 
1344 /*
1345  *
1346  * get_namelist_files():
1347  *      This function gets a list of user names on the system from
1348  *      the /etc/passwd file.
1349  *
1350  */
1351 int
1352 get_namelist_files(char ***namelist_p, int *num_user)
1353 {
1354         FILE            *pwfp;
1355         struct passwd   *pwd;
1356         int             max_user;
1357         int             nuser;
1358         char    **nl;
1359 
1360         nuser = 0;
1361         errno = 0;
1362         pwd = NULL;
1363 
1364         if ((pwfp = fopen(PASSWD, "r")) == NULL)
1365                 return (NOPERM);
1366 
1367         /*
1368          * find out the actual number of entries in the PASSWD file
1369          */
1370         max_user = 1;                   /* need one slot for terminator NULL */
1371         while ((pwd = fgetpwent(pwfp)) != NULL)
1372                 max_user++;
1373 
1374         /*
1375          *      reset the file stream pointer
1376          */
1377         rewind(pwfp);
1378 
1379         nl = (char **)calloc(max_user, (sizeof (char *)));
1380         if (nl == NULL) {
1381                 (void) fclose(pwfp);
1382                 return (FMERR);
1383         }
1384 
1385         while ((pwd = fgetpwent(pwfp)) != NULL) {
1386                 if ((nl[nuser] = strdup(pwd->pw_name)) == NULL) {
1387                         (void) fclose(pwfp);
1388                         return (FMERR);
1389                 }
1390                 nuser++;
1391         }
1392 
1393         nl[nuser] = NULL;
1394         *num_user = nuser;
1395         *namelist_p = nl;
1396         (void) fclose(pwfp);
1397         return (SUCCESS);
1398 }
1399 
1400 /*
1401  * get_namelist_local
1402  *
1403  */
1404 
1405 /*
1406  * Our private version of the switch frontend for getspent.  We want
1407  * to search just the ldap sp file, so we want to bypass
1408  * normal nsswitch.conf based processing.  This implementation
1409  * compatible with version 2 of the name service switch.
1410  */
1411 #define NSS_LDAP_ONLY           "ldap"
1412 
1413 extern int str2spwd(const char *, int, void *, char *, int);
1414 
1415 static DEFINE_NSS_DB_ROOT(db_root);
1416 static DEFINE_NSS_GETENT(context);
1417 
1418 static char *local_config;
1419 static void
1420 _lc_nss_initf_shadow(nss_db_params_t *p)
1421 {
1422         p->name      = NSS_DBNAM_SHADOW;
1423         p->config_name    = NSS_DBNAM_PASSWD;        /* Use config for "passwd" */
1424         p->default_config = local_config;    /* Use ldap only */
1425         p->flags = NSS_USE_DEFAULT_CONFIG;
1426 }
1427 
1428 static void
1429 _lc_setspent(void)
1430 {
1431         nss_setent(&db_root, _lc_nss_initf_shadow, &context);
1432 }
1433 
1434 static void
1435 _lc_endspent(void)
1436 {
1437         nss_endent(&db_root, _lc_nss_initf_shadow, &context);
1438         nss_delete(&db_root);
1439 }
1440 
1441 static struct spwd *
1442 _lc_getspent_r(struct spwd *result, char *buffer, int buflen)
1443 {
1444         nss_XbyY_args_t arg;
1445         char            *nam;
1446 
1447         /* In getXXent_r(), protect the unsuspecting caller from +/- entries */
1448 
1449         do {
1450                 NSS_XbyY_INIT(&arg, result, buffer, buflen, str2spwd);
1451                         /* No key to fill in */
1452                 (void) nss_getent(&db_root, _lc_nss_initf_shadow, &context,
1453                     &arg);
1454         } while (arg.returnval != 0 &&
1455             (nam = ((struct spwd *)arg.returnval)->sp_namp) != 0 &&
1456             (*nam == '+' || *nam == '-'));
1457 
1458         return (struct spwd *)NSS_XbyY_FINI(&arg);
1459 }
1460 
1461 static nss_XbyY_buf_t *buffer;
1462 
1463 static struct spwd *
1464 _lc_getspent(void)
1465 {
1466         nss_XbyY_buf_t  *b;
1467 
1468         b = NSS_XbyY_ALLOC(&buffer, sizeof (struct spwd), NSS_BUFLEN_SHADOW);
1469 
1470         return (b == 0 ? 0 : _lc_getspent_r(b->result, b->buffer, b->buflen));
1471 }
1472 
1473 int
1474 get_namelist_local(char ***namelist_p, int *num_user)
1475 {
1476         int nuser = 0;
1477         int alloced = 100;
1478         char **nl;
1479         struct spwd *p;
1480 
1481 
1482         if ((nl = calloc(alloced, sizeof (*nl))) == NULL)
1483                 return (FMERR);
1484 
1485         (void) _lc_setspent();
1486         while ((p = _lc_getspent()) != NULL) {
1487                 if ((nl[nuser] = strdup(p->sp_namp)) == NULL) {
1488                         _lc_endspent();
1489                         return (FMERR);
1490                 }
1491                 if (++nuser == alloced) {
1492                         alloced += 100;
1493                         nl = realloc(nl, alloced * (sizeof (*nl)));
1494                         if (nl == NULL) {
1495                                 _lc_endspent();
1496                                 return (FMERR);
1497                         }
1498                 }
1499         }
1500         (void) _lc_endspent();
1501         nl[nuser] = NULL;
1502 
1503         *namelist_p = nl;
1504         *num_user = nuser;              /* including NULL */
1505 
1506         return (SUCCESS);
1507 }
1508 
1509 int
1510 get_namelist(pwu_repository_t repository, char ***namelist, int *num_user)
1511 {
1512         if (IS_LDAP(repository)) {
1513                 local_config = NSS_LDAP_ONLY;
1514                 return (get_namelist_local(namelist, num_user));
1515         } else if (IS_FILES(repository))
1516                 return (get_namelist_files(namelist, num_user));
1517 
1518         rusage();
1519         return (BADSYN);
1520 }
1521 
1522 /*
1523  *
1524  * passwd_exit():
1525  *      This function will call exit() with appropriate exit code
1526  *      according to the input "retcode" value.
1527  *      It also calls pam_end() to clean-up buffers before exit.
1528  *
1529  */
1530 
1531 void
1532 passwd_exit(int retcode)
1533 {
1534 
1535         if (pamh)
1536                 (void) pam_end(pamh, pam_retval);
1537 
1538         switch (retcode) {
1539         case SUCCESS:
1540                         break;
1541         case NOPERM:
1542                         (void) fprintf(stderr, "%s\n", gettext(MSG_NP));
1543                         break;
1544         case BADOPT:
1545                         (void) fprintf(stderr, "%s\n", gettext(MSG_BS));
1546                         break;
1547         case FMERR:
1548                         (void) fprintf(stderr, "%s\n", gettext(MSG_FE));
1549                         break;
1550         case FATAL:
1551                         (void) fprintf(stderr, "%s\n", gettext(MSG_FF));
1552                         break;
1553         case FBUSY:
1554                         (void) fprintf(stderr, "%s\n", gettext(MSG_FB));
1555                         break;
1556         case BADSYN:
1557                         (void) fprintf(stderr, "%s\n", gettext(MSG_NV));
1558                         break;
1559         case BADAGE:
1560                         (void) fprintf(stderr, "%s\n", gettext(MSG_AD));
1561                         break;
1562         case NOMEM:
1563                         (void) fprintf(stderr, "%s\n", gettext(MSG_NM));
1564                         break;
1565         default:
1566                         (void) fprintf(stderr, "%s\n", gettext(MSG_NP));
1567                         retcode = NOPERM;
1568                         break;
1569         }
1570         /* write password record */
1571         if (event != NULL) {
1572                 struct passwd *pass;
1573 
1574                 if ((pass = getpwnam(usrname)) == NULL) {
1575                         /* unlikely to ever get here, but ... */
1576                         event->adt_passwd.username = usrname;
1577                 } else if (pass->pw_uid != uid) {
1578                         /* save target user */
1579                         event->adt_passwd.uid = pass->pw_uid;
1580                         event->adt_passwd.username = pass->pw_name;
1581                 }
1582 
1583                 if (adt_put_event(event,
1584                     retcode == SUCCESS ? ADT_SUCCESS : ADT_FAILURE,
1585                     retcode == SUCCESS ? ADT_SUCCESS : ADT_FAIL_PAM +
1586                     pam_retval) != 0) {
1587                         adt_free_event(event);
1588                         (void) adt_end_session(ah);
1589                         perror("adt_put_event");
1590                         exit(retcode);
1591                 }
1592                 adt_free_event(event);
1593         }
1594         (void) adt_end_session(ah);
1595         exit(retcode);
1596 }
1597 
1598 /*
1599  *
1600  * passwd_conv():
1601  *      This is the conv (conversation) function called from
1602  *      a PAM authentication module to print error messages
1603  *      or garner information from the user.
1604  *
1605  */
1606 
1607 /*ARGSUSED*/
1608 static int
1609 passwd_conv(int num_msg, struct pam_message **msg,
1610             struct pam_response **response, void *appdata_ptr)
1611 {
1612         struct pam_message      *m;
1613         struct pam_response     *r;
1614         char                    *temp;
1615         int                     k, i;
1616 
1617         if (num_msg <= 0)
1618                 return (PAM_CONV_ERR);
1619 
1620         *response = (struct pam_response *)calloc(num_msg,
1621             sizeof (struct pam_response));
1622         if (*response == NULL)
1623                 return (PAM_BUF_ERR);
1624 
1625         k = num_msg;
1626         m = *msg;
1627         r = *response;
1628         while (k--) {
1629 
1630                 switch (m->msg_style) {
1631 
1632                 case PAM_PROMPT_ECHO_OFF:
1633                         temp = getpassphrase(m->msg);
1634                         if (temp != NULL) {
1635                                 r->resp = strdup(temp);
1636                                 (void) memset(temp, 0, strlen(temp));
1637                                 if (r->resp == NULL) {
1638                                         /* free responses */
1639                                         r = *response;
1640                                         for (i = 0; i < num_msg; i++, r++) {
1641                                                 if (r->resp)
1642                                                         free(r->resp);
1643                                         }
1644                                         free(*response);
1645                                         *response = NULL;
1646                                         return (PAM_BUF_ERR);
1647                                 }
1648                         }
1649                         m++;
1650                         r++;
1651                         break;
1652 
1653                 case PAM_PROMPT_ECHO_ON:
1654                         if (m->msg != NULL) {
1655                                 (void) fputs(m->msg, stdout);
1656                         }
1657                         r->resp = (char *)calloc(PAM_MAX_RESP_SIZE,
1658                             sizeof (char));
1659                         if (r->resp == NULL) {
1660                                 /* free responses */
1661                                 r = *response;
1662                                 for (i = 0; i < num_msg; i++, r++) {
1663                                         if (r->resp)
1664                                                 free(r->resp);
1665                                 }
1666                                 free(*response);
1667                                 *response = NULL;
1668                                 return (PAM_BUF_ERR);
1669                         }
1670                         if (fgets(r->resp, PAM_MAX_RESP_SIZE-1, stdin)) {
1671                                 int len = strlen(r->resp);
1672                                 if (r->resp[len-1] == '\n')
1673                                         r->resp[len-1] = '\0';
1674                         }
1675                         m++;
1676                         r++;
1677                         break;
1678 
1679                 case PAM_ERROR_MSG:
1680                         if (m->msg != NULL) {
1681                                 (void) fputs(m->msg, stderr);
1682                                 (void) fputs("\n", stderr);
1683                         }
1684                         m++;
1685                         r++;
1686                         break;
1687                 case PAM_TEXT_INFO:
1688                         if (m->msg != NULL) {
1689                                 (void) fputs(m->msg, stdout);
1690                                 (void) fputs("\n", stdout);
1691                         }
1692                         m++;
1693                         r++;
1694                         break;
1695 
1696                 default:
1697                         break;
1698                 }
1699         }
1700         return (PAM_SUCCESS);
1701 }
1702 
1703 /*
1704  *
1705  * stdin_conv():
1706  *      This is the conv function called for reading
1707  *      the password from standard input.
1708  *
1709  */
1710 /*ARGSUSED*/
1711 static int
1712 stdin_conv(int num_msg, struct pam_message **msg,
1713             struct pam_response **response, void *appdata_ptr)
1714 {
1715         struct pam_response *reply = NULL;
1716         int                     replies = 0;
1717 
1718         if (num_msg <= 0)
1719                 return (PAM_CONV_ERR);
1720 
1721         reply = calloc(num_msg, sizeof (struct pam_response));
1722         if (reply == NULL)
1723                 return (PAM_BUF_ERR);
1724 
1725         for (replies = 0; replies < num_msg; replies ++) {
1726                 reply[replies].resp = NULL;
1727                 reply[replies].resp_retcode = PAM_SUCCESS;
1728                 switch (msg[replies]->msg_style) {
1729                 case PAM_PROMPT_ECHO_OFF:
1730                         reply[replies].resp = strdup(appdata_ptr);
1731                         if (reply[replies].resp == NULL)
1732                                 goto err;
1733                         break;
1734                 case PAM_PROMPT_ECHO_ON:
1735                 case PAM_ERROR_MSG:
1736                 case PAM_TEXT_INFO:
1737                         reply[replies].resp = strdup("");
1738                         if (reply[replies].resp == NULL)
1739                                 goto err;
1740                         break;
1741                 default:
1742                         free(reply);
1743                         return (PAM_CONV_ERR);
1744                 }
1745         }
1746         *response = reply;
1747         return (PAM_SUCCESS);
1748 
1749 err:
1750         free(reply);
1751         return (PAM_BUF_ERR);
1752 }
1753 
1754 /*
1755  *              Utilities Functions
1756  */
1757 
1758 /*
1759  * int attrlist_add(attrlist **l, attrtype type, char *val)
1760  * add an item, with type "type" and value "val", at the tail of list l.
1761  * This functions exits the application on OutOfMem error.
1762  */
1763 void
1764 attrlist_add(attrlist **l, attrtype type, char *val)
1765 {
1766         attrlist **w;
1767 
1768         /* tail insert */
1769         for (w = l; *w != NULL; w = &(*w)->next)
1770                 ;
1771 
1772         if ((*w = malloc(sizeof (**w))) == NULL)
1773                 passwd_exit(NOMEM);
1774 
1775         (*w)->type = type;
1776         (*w)->next = NULL;
1777 
1778         switch (type) {
1779         case ATTR_MIN:
1780         case ATTR_WARN:
1781         case ATTR_MAX:
1782                 (*w)->data.val_i = atoi(val);
1783                 break;
1784         default:
1785                 (*w)->data.val_s = val;
1786                 break;
1787         }
1788 }
1789 
1790 /*
1791  * attrlist_reorder(attrlist **l)
1792  * Make sure that
1793  *      - if EXPIRE and MAX or MIN is set, EXPIRE comes after MAX/MIN
1794  *      - if both MIN and MAX are set, MAX comes before MIN.
1795  */
1796 
1797 static void
1798 attrlist_reorder(attrlist **l)
1799 {
1800         attrlist        **w;
1801         attrlist        *exp = NULL;    /* ATTR_EXPIRE_PASSWORD, if found */
1802         attrlist        *max = NULL;    /* ATTR_MAX, if found */
1803 
1804         if (*l == NULL || (*l)->next == NULL)
1805                 return;         /* order of list with <= one item is ok */
1806 
1807         /*
1808          * We simply walk the list, take off the EXPIRE and MAX items if
1809          * they appear, and put them (first MAX, them EXPIRE) at the end
1810          * of the list.
1811          */
1812         w = l;
1813         while (*w != NULL) {
1814                 if ((*w)->type == ATTR_EXPIRE_PASSWORD) {
1815                         exp = *w;
1816                         *w = (*w)->next;
1817                 } else if ((*w)->type == ATTR_MAX) {
1818                         max = *w;
1819                         *w = (*w)->next;
1820                 } else
1821                         w = &(*w)->next;
1822         }
1823 
1824         /* 'w' points to the address of the 'next' field of the last element */
1825 
1826         if (max) {
1827                 *w = max;
1828                 w = &max->next;
1829         }
1830         if (exp) {
1831                 *w = exp;
1832                 w = &exp->next;
1833         }
1834         *w = NULL;
1835 }
1836 
1837 void
1838 rusage(void)
1839 {
1840 
1841 #define MSG(a) (void) fprintf(stderr, gettext((a)));
1842 
1843         MSG("usage:\n");
1844         MSG("\tpasswd [-r files | -r nis | -r ldap] [name]\n");
1845         MSG("\tpasswd [-r files] [-egh] [name]\n");
1846         MSG("\tpasswd [-r files] -sa\n");
1847         MSG("\tpasswd [-r files] -s [name]\n");
1848         MSG("\tpasswd [-r files] [-d|-l|-N|-u] [-f] [-n min] [-w warn] "
1849             "[-x max] name\n");
1850         MSG("\tpasswd -r nis [-eg] [name]\n");
1851         MSG("\t\t[-x max] name\n");
1852         MSG("\tpasswd -r ldap [-egh] [name]\n");
1853         MSG("\tpasswd -r ldap -sa\n");
1854         MSG("\tpasswd -r ldap -s [name]\n");
1855         MSG("\tpasswd -r ldap [-l|-N|-u] [-f] [-n min] [-w warn] "
1856             "[-x max] name\n");
1857         MSG("\tpasswd -S [name]\n");
1858 #undef MSG
1859 }