/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* Copyright (c) 1987, 1988 Microsoft Corporation */ /* All Rights Reserved */ /* * passwd is a program whose sole purpose is to manage * the password file, map, or table. It allows system administrator * to add, change and display password attributes. * Non privileged user can change password or display * password attributes which corresponds to their login name. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef GROUP #include #include #include #include #include #undef GROUP #include #include /* * flags indicate password attributes to be modified */ #define LFLAG 0x001 /* lock user's password */ #define DFLAG 0x002 /* delete user's password */ #define MFLAG 0x004 /* set max field -- # of days passwd is valid */ #define NFLAG 0x008 /* set min field -- # of days between */ /* password changes */ #define SFLAG 0x010 /* display password attributes */ #define FFLAG 0x020 /* expire user's password */ #define AFLAG 0x040 /* display password attributes for all users */ #define SAFLAG (SFLAG|AFLAG) /* display password attributes for all users */ #define WFLAG 0x100 /* warn user to change passwd */ #define OFLAG 0x200 /* domain name */ #define EFLAG 0x400 /* change shell */ #define GFLAG 0x800 /* change gecos information */ #define HFLAG 0x1000 /* change home directory */ #define XFLAG 0x2000 /* no login */ #define UFLAG 0x4000 /* unlock user's password */ #define NONAGEFLAG (EFLAG | GFLAG | HFLAG) #define AGEFLAG (LFLAG | FFLAG | MFLAG | NFLAG | WFLAG | XFLAG | UFLAG) #define MUTEXFLAG (DFLAG | LFLAG | XFLAG | UFLAG | SAFLAG) /* * exit code */ #define SUCCESS 0 /* succeeded */ #define NOPERM 1 /* No permission */ #define BADOPT 2 /* Invalid combination of option */ #define FMERR 3 /* File/table manipulation error */ #define FATAL 4 /* Old file/table can not be recovered */ #define FBUSY 5 /* Lock file/table busy */ #define BADSYN 6 /* Incorrect syntax */ #define BADAGE 7 /* Aging is disabled */ #define NOMEM 8 /* No memory */ #define SYSERR 9 /* System error */ #define EXPIRED 10 /* Account expired */ /* * define error messages */ #define MSG_NP "Permission denied" #define MSG_BS "Invalid combination of options" #define MSG_FE "Unexpected failure. Password file/table unchanged." #define MSG_FF "Unexpected failure. Password file/table missing." #define MSG_FB "Password file/table busy. Try again later." #define MSG_NV "Invalid argument to option" #define MSG_AD "Password aging is disabled" #define MSG_RS "Cannot change from restricted shell %s\n" #define MSG_NM "Out of memory." #define MSG_UNACCEPT "%s is unacceptable as a new shell\n" #define MSG_UNAVAIL "warning: %s is unavailable on this machine\n" #define MSG_COLON "':' is not allowed.\n" #define MSG_MAXLEN "Maximum number of characters allowed is %d." #define MSG_CONTROL "Control characters are not allowed.\n" #define MSG_SHELL_UNCHANGED "Login shell unchanged.\n" #define MSG_GECOS_UNCHANGED "Finger information unchanged.\n" #define MSG_DIR_UNCHANGED "Homedir information unchanged.\n" #define MSG_NAME "\nName [%s]: " #define MSG_HOMEDIR "\nHome Directory [%s]: " #define MSG_OLDSHELL "Old shell: %s\n" #define MSG_NEWSHELL "New shell: " #define MSG_AGAIN "\nPlease try again\n" #define MSG_INPUTHDR "Default values are printed inside of '[]'.\n" \ "To accept the default, type .\n" \ "To have a blank entry, type the word 'none'.\n" #define MSG_UNKNOWN "%s: User unknown: %s\n" #define MSG_ACCOUNT_EXP "User account has expired: %s\n" #define MSG_AUTHTOK_EXP "Your password has been expired for too long.\n" \ "Please contact the system administrator.\n" #define MSG_NIS_HOMEDIR "-h does not apply to NIS" #define MSG_CUR_PASS "Enter existing login password: " #define MSG_CUR_PASS_UNAME "Enter %s's existing login password: " #define MSG_SUCCESS "%s: password information changed for %s\n" #define MSG_SORRY "%s: Sorry, wrong passwd\n" #define MSG_INFO "%s: Changing password for %s\n" /* * return code from ckarg() routine */ #define FAIL -1 /* * defind password file name */ #define PASSWD "/etc/passwd" #define MAX_INPUT_LEN 512 #define DEF_ATTEMPTS 3 /* Number of characters in that make up an encrypted password (for now) */ #define NUMCP 13 #ifdef DEBUG #define dprintf1 printf #else #define dprintf1(w, x) #endif extern int optind; static int retval = SUCCESS; static int pam_retval = PAM_SUCCESS; static uid_t uid; static char *prognamep; static long maxdate; /* password aging information */ static int passwd_conv(int, const struct pam_message **, struct pam_response **, void *); static struct pam_conv pam_conv = {passwd_conv, NULL}; static pam_handle_t *pamh; /* Authentication handle */ static char *usrname; /* user whose attribute we update */ static adt_session_data_t *ah; /* audit session handle */ static adt_event_data_t *event = NULL; /* event to be generated */ static pam_repository_t auth_rep; static pwu_repository_t repository; static pwu_repository_t __REPFILES = { "files", NULL, 0 }; /* * Function Declarations */ extern void setusershell(void); extern char *getusershell(void); extern void endusershell(void); static void passwd_exit(int retcode) __NORETURN; static void rusage(void); static int ckuid(void); static int ckarg(int argc, char **argv, attrlist **attributes); static int get_namelist(pwu_repository_t, char ***, int *); static int get_namelist_files(char ***, int *); static int get_namelist_local(char ***, int *); static int get_attr(char *, pwu_repository_t *, attrlist **); static void display_attr(char *, attrlist *); static void free_attr(attrlist *); static void attrlist_add(attrlist **, attrtype, char *); static void attrlist_reorder(attrlist **); static char *userinput(char *, pwu_repository_t *, attrtype); static char *getresponse(char *); /* * main(): * The main routine will call ckarg() to parse the command line * arguments and call the appropriate functions to perform the * tasks specified by the arguments. It allows system * administrator to add, change and display password attributes. * Non privileged user can change password or display * password attributes which corresponds to their login name. */ int main(int argc, char *argv[]) { int flag; char **namelist; int num_user; int i; attrlist *attributes = NULL; char *input; int tries = 1; int updated_reps; if ((prognamep = strrchr(argv[0], '/')) != NULL) ++prognamep; else prognamep = argv[0]; auth_rep.type = NULL; auth_rep.scope = NULL; repository.type = NULL; repository.scope = NULL; repository.scope_len = 0; /* initialization for variables, set locale and textdomain */ i = 0; flag = 0; uid = getuid(); /* get the user id */ (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); /* * ckarg() parses the arguments. In case of an error, * it sets the retval and returns FAIL (-1). */ flag = ckarg(argc, argv, &attributes); dprintf1("flag is %0x\n", flag); if (flag == FAIL) passwd_exit(retval); argc -= optind; if (argc < 1) { if ((usrname = getlogin()) == NULL) { struct passwd *pass = getpwuid(uid); if (pass != NULL) usrname = pass->pw_name; else { rusage(); exit(NOPERM); } } else if (flag == 0) { /* * If flag is zero, change passwd. * Otherwise, it will display or * modify password aging attributes */ (void) fprintf(stderr, gettext(MSG_INFO), prognamep, usrname); } } else { usrname = argv[optind]; } if (pam_start("passwd", usrname, &pam_conv, &pamh) != PAM_SUCCESS) { passwd_exit(NOPERM); } auth_rep.type = repository.type; auth_rep.scope = repository.scope; auth_rep.scope_len = repository.scope_len; if (auth_rep.type != NULL) { if (pam_set_item(pamh, PAM_REPOSITORY, (void *)&auth_rep) != PAM_SUCCESS) { passwd_exit(NOPERM); } } if (flag == SAFLAG) { /* display password attributes for all users */ retval = get_namelist(repository, &namelist, &num_user); if (retval != SUCCESS) (void) passwd_exit(retval); if (num_user == 0) { (void) fprintf(stderr, "%s: %s\n", prognamep, gettext(MSG_FF)); passwd_exit(FATAL); } i = 0; while (namelist[i] != NULL) { (void) get_attr(namelist[i], &repository, &attributes); (void) display_attr(namelist[i], attributes); (void) free(namelist[i]); (void) free_attr(attributes); i++; } (void) free(namelist); passwd_exit(SUCCESS); } else if (flag == SFLAG) { /* display password attributes by user */ if (get_attr(usrname, &repository, &attributes) == PWU_SUCCESS) { (void) display_attr(usrname, attributes); (void) free_attr(attributes); } passwd_exit(SUCCESS); /* NOT REACHED */ } switch (pam_authenticate(pamh, 0)) { case PAM_SUCCESS: break; case PAM_USER_UNKNOWN: (void) fprintf(stderr, gettext(MSG_UNKNOWN), prognamep, usrname); passwd_exit(NOPERM); break; case PAM_PERM_DENIED: passwd_exit(NOPERM); break; case PAM_AUTH_ERR: (void) fprintf(stderr, gettext(MSG_SORRY), prognamep); passwd_exit(NOPERM); break; default: /* system error */ passwd_exit(FMERR); break; } if (flag == 0) { /* changing user password */ int chk_authtok = 0; /* check password strength */ dprintf1("call pam_chauthtok() repository name =%s\n", repository.type); /* Set up for Audit */ if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { perror("adt_start_session"); passwd_exit(SYSERR); } if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) { perror("adt_alloc_event"); passwd_exit(NOMEM); } /* Don't check account expiration when invoked by root */ if (ckuid() != SUCCESS) { pam_retval = pam_acct_mgmt(pamh, PAM_SILENT); switch (pam_retval) { case PAM_ACCT_EXPIRED: (void) fprintf(stderr, gettext(MSG_ACCOUNT_EXP), usrname); passwd_exit(EXPIRED); break; case PAM_AUTHTOK_EXPIRED: (void) fprintf(stderr, gettext(MSG_AUTHTOK_EXP)); passwd_exit(NOPERM); break; case PAM_NEW_AUTHTOK_REQD: /* valid error when changing passwords */ break; case PAM_SUCCESS: /* Ok to change password */ break; default: passwd_exit(NOPERM); } } pam_retval = PAM_AUTHTOK_ERR; tries = 1; if (ckuid() == SUCCESS) { /* bypass password strength checks */ chk_authtok = PAM_NO_AUTHTOK_CHECK; } while (pam_retval == PAM_AUTHTOK_ERR && tries <= DEF_ATTEMPTS) { if (tries > 1) (void) printf(gettext(MSG_AGAIN)); pam_retval = pam_chauthtok(pamh, chk_authtok); if (pam_retval == PAM_TRY_AGAIN) { (void) sleep(1); pam_retval = pam_chauthtok(pamh, chk_authtok); } tries++; } switch (pam_retval) { case PAM_SUCCESS: retval = SUCCESS; break; case PAM_AUTHTOK_DISABLE_AGING: retval = BADAGE; break; case PAM_AUTHTOK_LOCK_BUSY: retval = FBUSY; break; case PAM_TRY_AGAIN: retval = FBUSY; break; case PAM_AUTHTOK_ERR: case PAM_AUTHTOK_RECOVERY_ERR: default: retval = NOPERM; break; } (void) passwd_exit(retval); /* NOT REACHED */ } else { /* changing attributes */ switch (flag) { case EFLAG: /* changing user password attributes */ input = userinput(usrname, &repository, ATTR_SHELL); if (input) attrlist_add(&attributes, ATTR_SHELL, input); else (void) printf(gettext(MSG_SHELL_UNCHANGED)); break; case GFLAG: input = userinput(usrname, &repository, ATTR_GECOS); if (input) attrlist_add(&attributes, ATTR_GECOS, input); else (void) printf(gettext(MSG_GECOS_UNCHANGED)); break; case HFLAG: input = userinput(usrname, &repository, ATTR_HOMEDIR); if (input) attrlist_add(&attributes, ATTR_HOMEDIR, input); else (void) printf(gettext(MSG_DIR_UNCHANGED)); break; } if (attributes != NULL) { retval = __set_authtoken_attr(usrname, pamh->ps_item[PAM_AUTHTOK].pi_addr, &repository, attributes, &updated_reps); switch (retval) { case PWU_SUCCESS: for (i = 1; i <= REP_LAST; i <<= 1) { if ((updated_reps & i) == 0) continue; (void) printf(gettext(MSG_SUCCESS), prognamep, usrname); } retval = SUCCESS; break; case PWU_AGING_DISABLED: retval = BADAGE; break; default: retval = NOPERM; break; } } else { retval = SUCCESS; /* nothing to change won't fail */ } (void) passwd_exit(retval); } /* NOTREACHED */ return (0); } /* * Get a line of input from the user. * * If the line is empty, or the input equals 'oldval', NULL is returned. * therwise, a malloced string containing the input (minus the trailing * newline) is returned. */ char * getresponse(char *oldval) { char resp[MAX_INPUT_LEN]; char *retval = NULL; int resplen; (void) fgets(resp, sizeof (resp) - 1, stdin); resplen = strlen(resp) - 1; if (resp[resplen] == '\n') resp[resplen] = '\0'; if (*resp != '\0' && strcmp(resp, oldval) != 0) retval = strdup(resp); return (retval); } /* * char *userinput(item) * * user conversation function. The old value of attribute "item" is * displayed while the user is asked to provide a new value. * * returns a malloc()-ed string if the user actualy provided input * or NULL if the user simply hit return or the input equals the old * value (not changed). */ char * userinput(char *name, pwu_repository_t *rep, attrtype type) { attrlist oldattr; char *oldval; /* shorthand for oldattr.data.val_s */ char *valid; /* points to valid shells */ char *response; char *cp; oldattr.type = type; oldattr.next = NULL; if (__get_authtoken_attr(name, rep, &oldattr) != PWU_SUCCESS) passwd_exit(FMERR); oldval = oldattr.data.val_s; if (type == ATTR_SHELL) { /* No current shell: set DEFSHL as default choice */ if (*oldval == '\0') { free(oldval); oldval = strdup(DEFSHL); } if (ckuid() != SUCCESS) { /* User must currently have a valid shell */ setusershell(); valid = getusershell(); while (valid && strcmp(valid, oldval) != 0) valid = getusershell(); endusershell(); if (valid == NULL) { (void) fprintf(stderr, gettext(MSG_RS), oldval); free(oldval); return (NULL); } } (void) printf(gettext(MSG_OLDSHELL), oldval); (void) printf(gettext(MSG_NEWSHELL)); (void) fflush(stdout); response = getresponse(oldval); free(oldval); /* We don't need the old value anymore */ if (response == NULL || *response == '\0') return (NULL); /* Make sure new shell is listed */ setusershell(); valid = getusershell(); while (valid) { char *cp; /* Allow user to give shell without path */ if (*response == '/') { cp = valid; } else { if ((cp = strrchr(valid, '/')) == NULL) cp = valid; else cp++; } if (strcmp(cp, response) == 0) { if (*response != '/') { /* take shell name including path */ free(response); response = strdup(valid); } break; } valid = getusershell(); } endusershell(); if (valid == NULL) { /* No valid shell matches */ (void) fprintf(stderr, gettext(MSG_UNACCEPT), response); return (NULL); } if (access(response, X_OK) < 0) (void) fprintf(stderr, gettext(MSG_UNAVAIL), response); return (response); /* NOT REACHED */ } /* * if type == SHELL, we have returned by now. Only GECOS and * HOMEDIR get to this point. */ (void) printf(gettext(MSG_INPUTHDR)); /* * PRE: oldval points to malloced string with Old Value * INV: oldval remains unchanged * POST:response points to valid string or NULL. */ for (;;) { if (type == ATTR_GECOS) (void) printf(gettext(MSG_NAME), oldval); else if (type == ATTR_HOMEDIR) (void) printf(gettext(MSG_HOMEDIR), oldval); response = getresponse(oldval); if (response && strcmp(response, "none") == 0) *response = '\0'; /* No-change or empty string are OK */ if (response == NULL || *response == '\0') break; /* Check for illegal characters */ if (strchr(response, ':')) { (void) fprintf(stderr, "%s", gettext(MSG_COLON)); free(response); } else if (strlen(response) > MAX_INPUT_LEN - 1) { (void) fprintf(stderr, gettext(MSG_MAXLEN), MAX_INPUT_LEN); free(response); } else { /* don't allow control characters */ for (cp = response; *cp >= 040; cp++) ; if (*cp != '\0') { (void) fprintf(stderr, gettext(MSG_CONTROL)); free(response); } else break; /* response is a valid string */ } /* * We only get here if the input was invalid. * In that case, we again ask the user for input. */ } free(oldval); return (response); } /* * ckarg(): * This function parses and verifies the * arguments. It takes three parameters: * argc => # of arguments * argv => pointer to an argument * attrlist => pointer to list of password attributes */ static int ckarg(int argc, char **argv, attrlist **attributes) { extern char *optarg; char *char_p; int opt; int flag; flag = 0; while ((opt = getopt(argc, argv, "r:aldefghsux:n:w:N")) != EOF) { switch (opt) { case 'r': /* Repository Specified */ /* repository: this option should be specified first */ if (repository.type != NULL) { (void) fprintf(stderr, gettext( "Repository is already defined or specified.\n")); rusage(); retval = BADSYN; return (FAIL); } if (strcmp(optarg, "nis") == 0) { repository.type = optarg; } else if (strcmp(optarg, "ldap") == 0) { repository.type = optarg; } else if (strcmp(optarg, "files") == 0) { repository.type = optarg; } else { (void) fprintf(stderr, gettext("invalid repository: %s\n"), optarg); rusage(); retval = BADSYN; return (FAIL); } break; case 'd': /* Delete Auth Token */ /* if no repository the default for -d is files */ if (repository.type == NULL) repository = __REPFILES; /* * Delete the password - only privileged processes * can execute this for FILES or LDAP */ if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-d only applies to files " "or ldap repository\n")); rusage(); /* exit */ retval = BADSYN; return (FAIL); } if (ckuid() != SUCCESS) { retval = NOPERM; return (FAIL); } if (flag & (LFLAG|SAFLAG|DFLAG|XFLAG|UFLAG)) { rusage(); retval = BADOPT; return (FAIL); } flag |= DFLAG; attrlist_add(attributes, ATTR_PASSWD, NULL); break; case 'N': /* set account to be "no login" */ /* if no repository the default for -N is files */ if (repository.type == NULL) repository = __REPFILES; if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-N only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADOPT; return (FAIL); } /* * Only privileged processes can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && ((retval = ckuid()) != SUCCESS)) return (FAIL); if (flag & (MUTEXFLAG|NONAGEFLAG)) { rusage(); /* exit */ retval = BADOPT; return (FAIL); } flag |= XFLAG; attrlist_add(attributes, ATTR_NOLOGIN_ACCOUNT, NULL); break; case 'l': /* lock the password */ /* if no repository the default for -l is files */ if (repository.type == NULL) repository = __REPFILES; if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-l only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADOPT; return (FAIL); } /* * Only privileged processes can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && ((retval = ckuid()) != SUCCESS)) return (FAIL); if (flag & (MUTEXFLAG|NONAGEFLAG)) { rusage(); /* exit */ retval = BADOPT; return (FAIL); } flag |= LFLAG; attrlist_add(attributes, ATTR_LOCK_ACCOUNT, NULL); break; case 'u': /* unlock the password */ /* if no repository the default for -u is files */ if (repository.type == NULL) repository = __REPFILES; if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-u only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADOPT; return (FAIL); } /* * Only privileged processes can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && ((retval = ckuid()) != SUCCESS)) return (FAIL); if (flag & (MUTEXFLAG|NONAGEFLAG)) { rusage(); /* exit */ retval = BADOPT; return (FAIL); } flag |= UFLAG; attrlist_add(attributes, ATTR_UNLOCK_ACCOUNT, NULL); attrlist_add(attributes, ATTR_RST_FAILED_LOGINS, NULL); break; case 'x': /* set the max date */ /* if no repository the default for -x is files */ if (repository.type == NULL) repository = __REPFILES; if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-x only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADSYN; return (FAIL); } /* * Only privileged process can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && (ckuid() != SUCCESS)) { retval = NOPERM; return (FAIL); } if (flag & (SAFLAG|MFLAG|NONAGEFLAG)) { retval = BADOPT; return (FAIL); } flag |= MFLAG; if ((int)strlen(optarg) <= 0 || (maxdate = strtol(optarg, &char_p, 10)) < -1 || *char_p != '\0') { (void) fprintf(stderr, "%s: %s -x\n", prognamep, gettext(MSG_NV)); retval = BADSYN; return (FAIL); } attrlist_add(attributes, ATTR_MAX, optarg); break; case 'n': /* set the min date */ /* if no repository the default for -n is files */ if (repository.type == NULL) repository = __REPFILES; if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-n only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADSYN; return (FAIL); } /* * Only privileged process can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && ((retval = ckuid()) != SUCCESS)) return (FAIL); if (flag & (SAFLAG|NFLAG|NONAGEFLAG)) { retval = BADOPT; return (FAIL); } flag |= NFLAG; if ((int)strlen(optarg) <= 0 || (strtol(optarg, &char_p, 10)) < 0 || *char_p != '\0') { (void) fprintf(stderr, "%s: %s -n\n", prognamep, gettext(MSG_NV)); retval = BADSYN; return (FAIL); } attrlist_add(attributes, ATTR_MIN, optarg); break; case 'w': /* set the warning field */ /* if no repository the default for -w is files */ if (repository.type == NULL) repository = __REPFILES; if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-w only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADSYN; return (FAIL); } /* * Only privileged process can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && (ckuid() != SUCCESS)) { retval = NOPERM; return (FAIL); } if (flag & (SAFLAG|WFLAG|NONAGEFLAG)) { retval = BADOPT; return (FAIL); } flag |= WFLAG; if ((int)strlen(optarg) <= 0 || (strtol(optarg, &char_p, 10)) < 0 || *char_p != '\0') { (void) fprintf(stderr, "%s: %s -w\n", prognamep, gettext(MSG_NV)); retval = BADSYN; return (FAIL); } attrlist_add(attributes, ATTR_WARN, optarg); break; case 's': /* display password attributes */ /* if no repository the default for -s is files */ if (repository.type == NULL) repository = __REPFILES; /* display password attributes */ if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-s only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADSYN; return (FAIL); } /* * Only privileged process can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && ((retval = ckuid()) != SUCCESS)) return (FAIL); if (flag && (flag != AFLAG)) { retval = BADOPT; return (FAIL); } flag |= SFLAG; break; case 'a': /* display password attributes */ /* if no repository the default for -a is files */ if (repository.type == NULL) repository = __REPFILES; if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-a only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADSYN; return (FAIL); } /* * Only privileged process can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && ((retval = ckuid()) != SUCCESS)) return (FAIL); if (flag && (flag != SFLAG)) { retval = BADOPT; return (FAIL); } flag |= AFLAG; break; case 'f': /* expire password attributes */ /* if no repository the default for -f is files */ if (repository.type == NULL) repository = __REPFILES; if (IS_FILES(repository) == FALSE && IS_LDAP(repository) == FALSE) { (void) fprintf(stderr, gettext( "-f only applies to files or ldap " "repository\n")); rusage(); /* exit */ retval = BADSYN; return (FAIL); } /* * Only privileged process can execute this * for FILES or LDAP */ if ((IS_FILES(repository) || IS_LDAP(repository)) && ((retval = ckuid()) != SUCCESS)) return (FAIL); if (flag & (SAFLAG|FFLAG|NONAGEFLAG)) { retval = BADOPT; return (FAIL); } flag |= FFLAG; attrlist_add(attributes, ATTR_EXPIRE_PASSWORD, NULL); break; case 'e': /* change login shell */ /* if no repository the default for -e is files */ if (repository.type == NULL) repository = __REPFILES; if (flag & (EFLAG|SAFLAG|AGEFLAG)) { retval = BADOPT; return (FAIL); } flag |= EFLAG; break; case 'g': /* change gecos information */ /* if no repository the default for -g is files */ if (repository.type == NULL) repository = __REPFILES; /* * Only privileged process can execute this * for FILES */ if (IS_FILES(repository) && (ckuid() != SUCCESS)) { retval = NOPERM; return (FAIL); } if (flag & (GFLAG|SAFLAG|AGEFLAG)) { retval = BADOPT; return (FAIL); } flag |= GFLAG; break; case 'h': /* change home dir */ /* if no repository the default for -h is files */ if (repository.type == NULL) repository = __REPFILES; /* * Only privileged process can execute this * for FILES */ if (IS_FILES(repository) && (ckuid() != SUCCESS)) { retval = NOPERM; return (FAIL); } if (IS_NIS(repository)) { (void) fprintf(stderr, "%s\n", gettext(MSG_NIS_HOMEDIR)); retval = BADSYN; return (FAIL); } if (flag & (HFLAG|SAFLAG|AGEFLAG)) { retval = BADOPT; return (FAIL); } flag |= HFLAG; break; case '?': rusage(); retval = BADOPT; return (FAIL); } } argc -= optind; if (argc > 1) { rusage(); retval = BADSYN; return (FAIL); } /* Make sure (EXPIRE comes after (MAX comes after MIN)) */ attrlist_reorder(attributes); /* If no options are specified or only the show option */ /* is specified, return because no option error checking */ /* is needed */ if (!flag || (flag == SFLAG)) return (flag); /* AFLAG must be used with SFLAG */ if (flag == AFLAG) { rusage(); retval = BADSYN; return (FAIL); } if (flag != SAFLAG && argc < 1) { /* * user name is not specified (argc<1), it can't be * aging info update. */ if (!(flag & NONAGEFLAG)) { rusage(); retval = BADSYN; return (FAIL); } } /* user name(s) may not be specified when SAFLAG is used. */ if (flag == SAFLAG && argc >= 1) { rusage(); retval = BADSYN; return (FAIL); } /* * If aging is being turned off (maxdate == -1), mindate may not * be specified. */ if ((maxdate == -1) && (flag & NFLAG)) { (void) fprintf(stderr, "%s: %s -n\n", prognamep, gettext(MSG_NV)); retval = BADOPT; return (FAIL); } return (flag); } /* * * ckuid(): * This function returns SUCCESS if the caller is root, else * it returns NOPERM. * */ static int ckuid(void) { if (uid != 0) { return (retval = NOPERM); } return (SUCCESS); } /* * get_attr() */ int get_attr(char *username, pwu_repository_t *repository, attrlist **attributes) { int res; attrlist_add(attributes, ATTR_PASSWD, NULL); attrlist_add(attributes, ATTR_LSTCHG, "0"); attrlist_add(attributes, ATTR_MIN, "0"); attrlist_add(attributes, ATTR_MAX, "0"); attrlist_add(attributes, ATTR_WARN, "0"); res = __get_authtoken_attr(username, repository, *attributes); if (res == PWU_SUCCESS) { retval = SUCCESS; return (PWU_SUCCESS); } if (res == PWU_NOT_FOUND) (void) fprintf(stderr, gettext(MSG_UNKNOWN), prognamep, username); retval = NOPERM; passwd_exit(retval); /*NOTREACHED*/ } /* * display_attr(): * This function prints out the password attributes of a usr * onto standand output. */ void display_attr(char *usrname, attrlist *attributes) { char *status = NULL; char *passwd; long lstchg; int min = 0, max = 0, warn = 0; while (attributes) { switch (attributes->type) { case ATTR_PASSWD: passwd = attributes->data.val_s; if (passwd == NULL || *passwd == '\0') status = "NP "; else if (strncmp(passwd, LOCKSTRING, sizeof (LOCKSTRING)-1) == 0) status = "LK "; else if (strncmp(passwd, NOLOGINSTRING, sizeof (NOLOGINSTRING)-1) == 0) status = "NL "; else if ((strlen(passwd) == 13 && passwd[0] != '$') || passwd[0] == '$') status = "PS "; else status = "UN "; break; case ATTR_LSTCHG: lstchg = attributes->data.val_i * DAY; break; case ATTR_MIN: min = attributes->data.val_i; break; case ATTR_MAX: max = attributes->data.val_i; break; case ATTR_WARN: warn = attributes->data.val_i; break; default: break; } attributes = attributes->next; } (void) fprintf(stdout, "%-8s ", usrname); if (status) (void) fprintf(stdout, "%s ", status); if (max != -1) { if (lstchg == 0) { (void) fprintf(stdout, "00/00/00 "); } else { struct tm *tmp; tmp = gmtime(&lstchg); (void) fprintf(stdout, "%.2d/%.2d/%.2d ", tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_year % 100); } (void) fprintf(stdout, (min >= 0) ? "%4d " : " ", min); (void) fprintf(stdout, "%4d ", max); (void) fprintf(stdout, (warn > 0) ? "%4d " : " ", warn); } (void) fprintf(stdout, "\n"); } void free_attr(attrlist *attributes) { while (attributes) { if (attributes->type == ATTR_PASSWD) free(attributes->data.val_s); attributes = attributes->next; } } /* * * get_namelist_files(): * This function gets a list of user names on the system from * the /etc/passwd file. * */ int get_namelist_files(char ***namelist_p, int *num_user) { FILE *pwfp; struct passwd *pwd; int max_user; int nuser; char **nl; nuser = 0; errno = 0; pwd = NULL; if ((pwfp = fopen(PASSWD, "r")) == NULL) return (NOPERM); /* * find out the actual number of entries in the PASSWD file */ max_user = 1; /* need one slot for terminator NULL */ while ((pwd = fgetpwent(pwfp)) != NULL) max_user++; /* * reset the file stream pointer */ rewind(pwfp); nl = (char **)calloc(max_user, (sizeof (char *))); if (nl == NULL) { (void) fclose(pwfp); return (FMERR); } while ((pwd = fgetpwent(pwfp)) != NULL) { if ((nl[nuser] = strdup(pwd->pw_name)) == NULL) { (void) fclose(pwfp); return (FMERR); } nuser++; } nl[nuser] = NULL; *num_user = nuser; *namelist_p = nl; (void) fclose(pwfp); return (SUCCESS); } /* * get_namelist_local * */ /* * Our private version of the switch frontend for getspent. We want * to search just the ldap sp file, so we want to bypass * normal nsswitch.conf based processing. This implementation * compatible with version 2 of the name service switch. */ #define NSS_LDAP_ONLY "ldap" extern int str2spwd(const char *, int, void *, char *, int); static DEFINE_NSS_DB_ROOT(db_root); static DEFINE_NSS_GETENT(context); static char *local_config; static void _lc_nss_initf_shadow(nss_db_params_t *p) { p->name = NSS_DBNAM_SHADOW; p->config_name = NSS_DBNAM_PASSWD; /* Use config for "passwd" */ p->default_config = local_config; /* Use ldap only */ p->flags = NSS_USE_DEFAULT_CONFIG; } static void _lc_setspent(void) { nss_setent(&db_root, _lc_nss_initf_shadow, &context); } static void _lc_endspent(void) { nss_endent(&db_root, _lc_nss_initf_shadow, &context); nss_delete(&db_root); } static struct spwd * _lc_getspent_r(struct spwd *result, char *buffer, int buflen) { nss_XbyY_args_t arg; char *nam; /* In getXXent_r(), protect the unsuspecting caller from +/- entries */ do { NSS_XbyY_INIT(&arg, result, buffer, buflen, str2spwd); /* No key to fill in */ (void) nss_getent(&db_root, _lc_nss_initf_shadow, &context, &arg); } while (arg.returnval != 0 && (nam = ((struct spwd *)arg.returnval)->sp_namp) != 0 && (*nam == '+' || *nam == '-')); return (struct spwd *)NSS_XbyY_FINI(&arg); } static nss_XbyY_buf_t *buffer; static struct spwd * _lc_getspent(void) { nss_XbyY_buf_t *b; b = NSS_XbyY_ALLOC(&buffer, sizeof (struct spwd), NSS_BUFLEN_SHADOW); return (b == 0 ? 0 : _lc_getspent_r(b->result, b->buffer, b->buflen)); } int get_namelist_local(char ***namelist_p, int *num_user) { int nuser = 0; int alloced = 100; char **nl; struct spwd *p; if ((nl = calloc(alloced, sizeof (*nl))) == NULL) return (FMERR); (void) _lc_setspent(); while ((p = _lc_getspent()) != NULL) { if ((nl[nuser] = strdup(p->sp_namp)) == NULL) { _lc_endspent(); return (FMERR); } if (++nuser == alloced) { alloced += 100; nl = realloc(nl, alloced * (sizeof (*nl))); if (nl == NULL) { _lc_endspent(); return (FMERR); } } } (void) _lc_endspent(); nl[nuser] = NULL; *namelist_p = nl; *num_user = nuser; /* including NULL */ return (SUCCESS); } int get_namelist(pwu_repository_t repository, char ***namelist, int *num_user) { if (IS_LDAP(repository)) { local_config = NSS_LDAP_ONLY; return (get_namelist_local(namelist, num_user)); } else if (IS_FILES(repository)) return (get_namelist_files(namelist, num_user)); rusage(); return (BADSYN); } /* * * passwd_exit(): * This function will call exit() with appropriate exit code * according to the input "retcode" value. * It also calls pam_end() to clean-up buffers before exit. * */ void passwd_exit(int retcode) { if (pamh) (void) pam_end(pamh, pam_retval); switch (retcode) { case SUCCESS: break; case NOPERM: (void) fprintf(stderr, "%s\n", gettext(MSG_NP)); break; case BADOPT: (void) fprintf(stderr, "%s\n", gettext(MSG_BS)); break; case FMERR: (void) fprintf(stderr, "%s\n", gettext(MSG_FE)); break; case FATAL: (void) fprintf(stderr, "%s\n", gettext(MSG_FF)); break; case FBUSY: (void) fprintf(stderr, "%s\n", gettext(MSG_FB)); break; case BADSYN: (void) fprintf(stderr, "%s\n", gettext(MSG_NV)); break; case BADAGE: (void) fprintf(stderr, "%s\n", gettext(MSG_AD)); break; case NOMEM: (void) fprintf(stderr, "%s\n", gettext(MSG_NM)); break; default: (void) fprintf(stderr, "%s\n", gettext(MSG_NP)); retcode = NOPERM; break; } /* write password record */ if (event != NULL) { struct passwd *pass; if ((pass = getpwnam(usrname)) == NULL) { /* unlikely to ever get here, but ... */ event->adt_passwd.username = usrname; } else if (pass->pw_uid != uid) { /* save target user */ event->adt_passwd.uid = pass->pw_uid; event->adt_passwd.username = pass->pw_name; } if (adt_put_event(event, retcode == SUCCESS ? ADT_SUCCESS : ADT_FAILURE, retcode == SUCCESS ? ADT_SUCCESS : ADT_FAIL_PAM + pam_retval) != 0) { adt_free_event(event); (void) adt_end_session(ah); perror("adt_put_event"); exit(retcode); } adt_free_event(event); } (void) adt_end_session(ah); exit(retcode); } /* * * passwd_conv(): * This is the conv (conversation) function called from * a PAM authentication module to print error messages * or garner information from the user. * */ static int passwd_conv(int num_msg, const struct pam_message **msg, struct pam_response **response, void *appdata_ptr) { const struct pam_message *m; struct pam_response *r; char *temp; int k, i; if (num_msg <= 0) return (PAM_CONV_ERR); *response = (struct pam_response *)calloc(num_msg, sizeof (struct pam_response)); if (*response == NULL) return (PAM_BUF_ERR); k = num_msg; m = *msg; r = *response; while (k--) { switch (m->msg_style) { case PAM_PROMPT_ECHO_OFF: temp = getpassphrase(m->msg); if (temp != NULL) { r->resp = strdup(temp); (void) memset(temp, 0, strlen(temp)); if (r->resp == NULL) { /* free responses */ r = *response; for (i = 0; i < num_msg; i++, r++) { if (r->resp) free(r->resp); } free(*response); *response = NULL; return (PAM_BUF_ERR); } } m++; r++; break; case PAM_PROMPT_ECHO_ON: if (m->msg != NULL) { (void) fputs(m->msg, stdout); } r->resp = (char *)calloc(PAM_MAX_RESP_SIZE, sizeof (char)); if (r->resp == NULL) { /* free responses */ r = *response; for (i = 0; i < num_msg; i++, r++) { if (r->resp) free(r->resp); } free(*response); *response = NULL; return (PAM_BUF_ERR); } if (fgets(r->resp, PAM_MAX_RESP_SIZE-1, stdin)) { int len = strlen(r->resp); if (r->resp[len-1] == '\n') r->resp[len-1] = '\0'; } m++; r++; break; case PAM_ERROR_MSG: if (m->msg != NULL) { (void) fputs(m->msg, stderr); (void) fputs("\n", stderr); } m++; r++; break; case PAM_TEXT_INFO: if (m->msg != NULL) { (void) fputs(m->msg, stdout); (void) fputs("\n", stdout); } m++; r++; break; default: break; } } return (PAM_SUCCESS); } /* * Utilities Functions */ /* * int attrlist_add(attrlist **l, attrtype type, char *val) * add an item, with type "type" and value "val", at the tail of list l. * This functions exits the application on OutOfMem error. */ void attrlist_add(attrlist **l, attrtype type, char *val) { attrlist **w; /* tail insert */ for (w = l; *w != NULL; w = &(*w)->next) ; if ((*w = malloc(sizeof (**w))) == NULL) passwd_exit(NOMEM); (*w)->type = type; (*w)->next = NULL; switch (type) { case ATTR_MIN: case ATTR_WARN: case ATTR_MAX: (*w)->data.val_i = atoi(val); break; default: (*w)->data.val_s = val; break; } } /* * attrlist_reorder(attrlist **l) * Make sure that * - if EXPIRE and MAX or MIN is set, EXPIRE comes after MAX/MIN * - if both MIN and MAX are set, MAX comes before MIN. */ static void attrlist_reorder(attrlist **l) { attrlist **w; attrlist *exp = NULL; /* ATTR_EXPIRE_PASSWORD, if found */ attrlist *max = NULL; /* ATTR_MAX, if found */ if (*l == NULL || (*l)->next == NULL) return; /* order of list with <= one item is ok */ /* * We simply walk the list, take off the EXPIRE and MAX items if * they appear, and put them (first MAX, them EXPIRE) at the end * of the list. */ w = l; while (*w != NULL) { if ((*w)->type == ATTR_EXPIRE_PASSWORD) { exp = *w; *w = (*w)->next; } else if ((*w)->type == ATTR_MAX) { max = *w; *w = (*w)->next; } else w = &(*w)->next; } /* 'w' points to the address of the 'next' field of the last element */ if (max) { *w = max; w = &max->next; } if (exp) { *w = exp; w = &exp->next; } *w = NULL; } void rusage(void) { #define MSG(a) (void) fprintf(stderr, gettext((a))); MSG("usage:\n"); MSG("\tpasswd [-r files | -r nis | -r ldap] [name]\n"); MSG("\tpasswd [-r files] [-egh] [name]\n"); MSG("\tpasswd [-r files] -sa\n"); MSG("\tpasswd [-r files] -s [name]\n"); MSG("\tpasswd [-r files] [-d|-l|-N|-u] [-f] [-n min] [-w warn] " "[-x max] name\n"); MSG("\tpasswd -r nis [-eg] [name]\n"); MSG("\t\t[-x max] name\n"); MSG("\tpasswd -r ldap [-egh] [name]\n"); MSG("\tpasswd -r ldap -sa\n"); MSG("\tpasswd -r ldap -s [name]\n"); MSG("\tpasswd -r ldap [-l|-N|-u] [-f] [-n min] [-w warn] " "[-x max] name\n"); #undef MSG }