| /* |
| * 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 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| * Copyright (c) 2016 by Delphix. All rights reserved. |
| */ |
| |
| #include <sys/types.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <pwd.h> |
| #include <shadow.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <nss_dbdefs.h> |
| #include <macros.h> |
| #include <syslog.h> |
| |
| #include <limits.h> /* LOGNAME_MAX -- max Solaris user name */ |
| |
| #include "passwdutil.h" |
| |
| int files_lock(void); |
| int files_unlock(void); |
| int files_checkhistory(char *user, char *passwd, pwu_repository_t *rep); |
| int files_getattr(char *name, attrlist *item, pwu_repository_t *rep); |
| int files_getpwnam(char *name, attrlist *items, pwu_repository_t *rep, |
| void **buf); |
| int files_update(attrlist *items, pwu_repository_t *rep, void *buf); |
| int files_putpwnam(char *name, char *oldpw, pwu_repository_t *rep, void *buf); |
| int files_user_to_authenticate(char *name, pwu_repository_t *rep, |
| char **auth_user, int *privileged); |
| |
| static int files_update_history(char *name, struct spwd *spwd); |
| |
| /* |
| * files function pointer table, used by passwdutil_init to initialize |
| * the global Repository-OPerations table "rops" |
| */ |
| struct repops files_repops = { |
| files_checkhistory, |
| files_getattr, |
| files_getpwnam, |
| files_update, |
| files_putpwnam, |
| files_user_to_authenticate, |
| files_lock, |
| files_unlock |
| }; |
| |
| /* |
| * this structure defines the buffer used to keep state between |
| * get/update/put calls |
| */ |
| struct pwbuf { |
| int update_history; |
| struct passwd *pwd; |
| char *pwd_scratch; |
| struct spwd *spwd; |
| char *spwd_scratch; |
| char *new_sp_pwdp; |
| }; |
| |
| /* |
| * We should use sysconf, but there is no sysconf name for SHADOW |
| * so we use these from nss_dbdefs |
| */ |
| #define PWD_SCRATCH_SIZE NSS_LINELEN_PASSWD |
| #define SPW_SCRATCH_SIZE NSS_LINELEN_SHADOW |
| |
| /* |
| * lock functions for files repository |
| */ |
| int |
| files_lock(void) |
| { |
| int res; |
| |
| if (lckpwdf()) { |
| switch (errno) { |
| case EINTR: |
| res = PWU_BUSY; |
| break; |
| case EACCES: |
| res = PWU_DENIED; |
| break; |
| case 0: |
| res = PWU_SUCCESS; |
| break; |
| } |
| } else |
| res = PWU_SUCCESS; |
| |
| return (res); |
| } |
| |
| int |
| files_unlock(void) |
| { |
| if (ulckpwdf()) |
| return (PWU_SYSTEM_ERROR); |
| |
| return (PWU_SUCCESS); |
| } |
| |
| /* |
| * files_privileged |
| * |
| * Are we a privileged user with regard to the files repository? |
| */ |
| int |
| files_privileged(void) |
| { |
| return (getuid() == 0); |
| } |
| |
| /* |
| * |
| * private_getpwnam_r() |
| * |
| * A private implementation of getpwnam_r which does *not* fall back to |
| * other services possibly defined in nsswitch.conf |
| * |
| * behaves like getpwnam_r(). |
| */ |
| struct passwd * |
| private_getpwnam_r(const char *name, struct passwd *result, char *buffer, |
| int buflen) |
| { |
| FILE *fp; |
| int found; |
| |
| if ((fp = fopen(PASSWD, "rF")) == NULL) |
| return (NULL); |
| |
| found = 0; |
| while (!found && fgetpwent_r(fp, result, buffer, buflen) != NULL) { |
| if (strcmp(name, result->pw_name) == 0) |
| found = 1; |
| } |
| |
| (void) fclose(fp); |
| |
| if (!found) { |
| (void) memset(buffer, 0, buflen); |
| (void) memset(result, 0, sizeof (*result)); |
| return (NULL); |
| } |
| |
| return (result); |
| } |
| |
| /* |
| * private_getspnam_r() |
| * |
| * A private implementation of getspnam_r which does *not* fall back to |
| * other services possibly defined in nsswitch.conf. |
| * |
| * Behaves like getspnam_r(). Since we use fgetspent_t(), all numeric |
| * fields that are undefined in /etc/shadow will be set to -1. |
| * |
| */ |
| struct spwd * |
| private_getspnam_r(const char *name, struct spwd *result, char *buffer, |
| int buflen) |
| { |
| FILE *fp; |
| int found; |
| |
| fp = fopen(SHADOW, "rF"); |
| if (fp == NULL) |
| return (NULL); |
| |
| found = 0; |
| while (!found && fgetspent_r(fp, result, buffer, buflen) != NULL) { |
| if (strcmp(name, result->sp_namp) == 0) |
| found = 1; |
| } |
| |
| (void) fclose(fp); |
| |
| if (!found) { |
| (void) memset(buffer, 0, buflen); |
| (void) memset(result, 0, sizeof (*result)); |
| return (NULL); |
| } |
| return (result); |
| } |
| |
| /* |
| * files_getpwnam(name, items, rep, buf) |
| * |
| */ |
| /*ARGSUSED*/ |
| int |
| files_getpwnam(char *name, attrlist *items, pwu_repository_t *rep, void **buf) |
| { |
| attrlist *p; |
| struct pwbuf *pwbuf; |
| int err = PWU_SUCCESS; |
| |
| *buf = calloc(1, sizeof (struct pwbuf)); |
| pwbuf = (struct pwbuf *)*buf; |
| if (pwbuf == NULL) |
| return (PWU_NOMEM); |
| |
| /* |
| * determine which password structure (/etc/passwd or /etc/shadow) |
| * we need for the items we need to update |
| */ |
| for (p = items; p != NULL; p = p->next) { |
| switch (p->type) { |
| case ATTR_NAME: |
| case ATTR_UID: |
| case ATTR_GID: |
| case ATTR_AGE: |
| case ATTR_COMMENT: |
| case ATTR_GECOS: |
| case ATTR_HOMEDIR: |
| case ATTR_SHELL: |
| if (pwbuf->pwd == NULL) { |
| pwbuf->pwd = malloc(sizeof (struct passwd)); |
| if (pwbuf->pwd == NULL) { |
| err = PWU_NOMEM; |
| goto error; |
| } |
| } |
| break; |
| case ATTR_PASSWD: |
| case ATTR_PASSWD_SERVER_POLICY: |
| case ATTR_LSTCHG: |
| case ATTR_MIN: |
| case ATTR_MAX: |
| case ATTR_WARN: |
| case ATTR_INACT: |
| case ATTR_EXPIRE: |
| case ATTR_FLAG: |
| case ATTR_LOCK_ACCOUNT: |
| case ATTR_EXPIRE_PASSWORD: |
| case ATTR_FAILED_LOGINS: |
| case ATTR_INCR_FAILED_LOGINS: |
| case ATTR_RST_FAILED_LOGINS: |
| case ATTR_NOLOGIN_ACCOUNT: |
| case ATTR_UNLOCK_ACCOUNT: |
| if (pwbuf->spwd == NULL) { |
| pwbuf->spwd = malloc(sizeof (struct spwd)); |
| if (pwbuf->spwd == NULL) { |
| err = PWU_NOMEM; |
| goto error; |
| } |
| } |
| break; |
| default: |
| /* |
| * Some other repository might have different values |
| * so we ignore those. |
| */ |
| break; |
| } |
| } |
| |
| if (pwbuf->pwd) { |
| if ((pwbuf->pwd_scratch = malloc(PWD_SCRATCH_SIZE)) == NULL) { |
| err = PWU_NOMEM; |
| goto error; |
| } |
| if (private_getpwnam_r(name, pwbuf->pwd, pwbuf->pwd_scratch, |
| PWD_SCRATCH_SIZE) == NULL) { |
| err = PWU_NOT_FOUND; |
| goto error; |
| } |
| } |
| |
| if (pwbuf->spwd) { |
| if ((pwbuf->spwd_scratch = malloc(SPW_SCRATCH_SIZE)) == NULL) { |
| err = PWU_NOMEM; |
| goto error; |
| } |
| if (private_getspnam_r(name, pwbuf->spwd, pwbuf->spwd_scratch, |
| SPW_SCRATCH_SIZE) == NULL) { |
| err = PWU_NOT_FOUND; |
| goto error; |
| } |
| } |
| |
| return (PWU_SUCCESS); |
| error: |
| if (pwbuf->pwd) free(pwbuf->pwd); |
| if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch); |
| if (pwbuf->spwd) free(pwbuf->spwd); |
| if (pwbuf->spwd_scratch) free(pwbuf->spwd_scratch); |
| free(pwbuf); |
| *buf = NULL; |
| |
| return (err); |
| } |
| |
| /* |
| * int files_user_to_authenticate(name, rep, auth_user, privileged) |
| * Determine which user needs to be authenticated. For files, the |
| * possible return values are: |
| * PWU_NOT_FOUND |
| * PWU_SUCCESS and (auth_user == NULL || auth_user = user) |
| * PWU_DENIED |
| * PWU_NOMEM |
| */ |
| /*ARGSUSED*/ |
| int |
| files_user_to_authenticate(char *user, pwu_repository_t *rep, |
| char **auth_user, int *privileged) |
| { |
| struct pwbuf *pwbuf; |
| int res; |
| attrlist attr_tmp[1] = { { ATTR_UID, NULL, NULL } }; |
| |
| /* check to see if target user is present in files */ |
| res = files_getpwnam(user, &attr_tmp[0], rep, (void **)&pwbuf); |
| if (res != PWU_SUCCESS) |
| return (res); |
| |
| if (files_privileged()) { |
| *auth_user = NULL; |
| *privileged = 1; |
| res = PWU_SUCCESS; |
| } else { |
| *privileged = 0; |
| if (getuid() == pwbuf->pwd->pw_uid) { |
| if ((*auth_user = strdup(user)) == NULL) { |
| res = PWU_NOMEM; |
| } else { |
| res = PWU_SUCCESS; |
| } |
| } else { |
| res = PWU_DENIED; |
| } |
| } |
| |
| if (pwbuf->pwd) free(pwbuf->pwd); |
| if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch); |
| if (pwbuf->spwd) free(pwbuf->spwd); |
| if (pwbuf->spwd_scratch) free(pwbuf->spwd_scratch); |
| free(pwbuf); |
| |
| return (res); |
| } |
| |
| /* |
| * Password history file format: |
| * user:crypw1: ... crypwn: such that n <= MAXHISTORY |
| */ |
| #define HISTORY "/etc/security/passhistory" |
| #define HISTEMP "/etc/security/pwhistemp" |
| #define OHISTORY "/etc/security/opwhistory" |
| #define HISTMODE S_IRUSR /* mode to create history file */ |
| /* |
| * XXX |
| * 3*LOGNAME_MAX just in case there are long user names. |
| * Traditionally Solaris LOGNAME_MAX (_POSIX_LOGIN_NAME_MAX) is 13, |
| * but some sites often user more. |
| * If LOGNAME_MAX ever becomes reasonable (128) and actually enforced, |
| * fix up here. |
| * XXX |
| */ |
| #define MAX_LOGNAME (3 * LOGNAME_MAX) |
| |
| /* |
| * files_checkhistory - check if a user's new password is in the user's |
| * old password history. |
| * |
| * Entry |
| * user = username. |
| * passwd = new clear text password. |
| * |
| * Exit |
| * PWU_SUCCESS, passwd found in user's old password history. |
| * The caller should only be interested and fail if |
| * PWU_SUCCESS is returned. |
| * PWU_NOT_FOUND, passwd not in user's old password history. |
| * PWU_errors, PWU_ errors from other routines. |
| * |
| */ |
| int |
| files_checkhistory(char *user, char *passwd, pwu_repository_t *rep) |
| { |
| attrlist attr; |
| int res; |
| |
| attr.type = ATTR_HISTORY; |
| attr.data.val_s = NULL; |
| attr.next = NULL; |
| |
| debug("files_checkhistory(user=%s)", user); |
| |
| /* |
| * XXX |
| * This depends on the underlying files_getattr implementation |
| * treating user not found in backing store or no history as |
| * an error. |
| * XXX |
| */ |
| |
| if ((res = files_getattr(user, &attr, rep)) == PWU_SUCCESS) { |
| char *s; |
| char *crypt_passwd; |
| int histsize; |
| char *last = attr.data.val_s; |
| |
| if ((histsize = def_getint("HISTORY=", DEFHISTORY)) == 0) { |
| debug("files_checkhistory: no history requested"); |
| res = PWU_NOT_FOUND; |
| goto out; |
| } |
| |
| debug("files_checkhistory: histsize = %d", histsize); |
| if (histsize > MAXHISTORY) |
| histsize = MAXHISTORY; |
| |
| debug("line to test\n\t%s", last); |
| |
| /* compare crypt_passwd to attr.data.val_s strings. */ |
| res = PWU_NOT_FOUND; |
| while ((histsize-- > 0) && |
| (((s = strtok_r(NULL, ":", &last)) != NULL) && |
| (*s != '\n'))) { |
| |
| crypt_passwd = crypt(passwd, s); |
| debug("files_checkhistory: user_pw=%s, history_pw=%s", |
| crypt_passwd, s); |
| if (strcmp(crypt_passwd, s) == 0) { |
| res = PWU_SUCCESS; |
| break; |
| } |
| } |
| debug("files_checkhistory(%s, %s) = %d", user, crypt_passwd, |
| res); |
| } |
| out: |
| if (attr.data.val_s != NULL) |
| free(attr.data.val_s); |
| |
| return (res); |
| } |
| |
| /* |
| * files_getattr(name, items, rep) |
| * |
| * Get attributes specified in list 'items' |
| */ |
| int |
| files_getattr(char *name, attrlist *items, pwu_repository_t *rep) |
| { |
| struct pwbuf *pwbuf; |
| struct passwd *pw; |
| struct spwd *spw; |
| attrlist *w; |
| int res; |
| |
| res = files_getpwnam(name, items, rep, (void **)&pwbuf); |
| if (res != PWU_SUCCESS) |
| return (res); |
| |
| pw = pwbuf->pwd; |
| spw = pwbuf->spwd; |
| |
| for (w = items; res == PWU_SUCCESS && w != NULL; w = w->next) { |
| switch (w->type) { |
| case ATTR_NAME: |
| if ((w->data.val_s = strdup(pw->pw_name)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_COMMENT: |
| if ((w->data.val_s = strdup(pw->pw_comment)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_GECOS: |
| if ((w->data.val_s = strdup(pw->pw_gecos)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_HOMEDIR: |
| if ((w->data.val_s = strdup(pw->pw_dir)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_SHELL: |
| if ((w->data.val_s = strdup(pw->pw_shell)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| /* |
| * Nothing special needs to be done for |
| * server policy |
| */ |
| case ATTR_PASSWD: |
| case ATTR_PASSWD_SERVER_POLICY: |
| if ((w->data.val_s = strdup(spw->sp_pwdp)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_AGE: |
| if ((w->data.val_s = strdup(pw->pw_age)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_REP_NAME: |
| if ((w->data.val_s = strdup("files")) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_HISTORY: { |
| FILE *history; |
| char buf[MAX_LOGNAME + MAXHISTORY + |
| (MAXHISTORY * CRYPT_MAXCIPHERTEXTLEN)+1]; |
| char *s, *s1; |
| |
| debug("files_getattr: Get password history for %s ", |
| name); |
| |
| if ((history = fopen(HISTORY, "rF")) == NULL) { |
| debug("files_getattr: %s not found", HISTORY); |
| res = PWU_OPEN_FAILED; |
| goto getattr_exit; |
| } |
| res = PWU_NOT_FOUND; |
| while ((s = fgets(buf, sizeof (buf), history)) != |
| NULL) { |
| s1 = strchr(s, ':'); |
| if (s1 != NULL) { |
| *s1 = '\0'; |
| } else { |
| res = PWU_NOT_FOUND; |
| break; |
| } |
| #ifdef DEBUG |
| debug("got history line for %s", s); |
| #endif /* DEBUG */ |
| if (strcmp(s, name) == 0) { |
| /* found user */ |
| if ((items->data.val_s = |
| strdup(s1+1)) == NULL) |
| res = PWU_NOMEM; |
| else |
| res = PWU_SUCCESS; |
| break; |
| } |
| } |
| (void) fclose(history); |
| break; |
| } |
| |
| /* integer values */ |
| case ATTR_UID: |
| w->data.val_i = pw->pw_uid; |
| break; |
| case ATTR_GID: |
| w->data.val_i = pw->pw_gid; |
| break; |
| case ATTR_LSTCHG: |
| w->data.val_i = spw->sp_lstchg; |
| break; |
| case ATTR_MIN: |
| w->data.val_i = spw->sp_min; |
| break; |
| case ATTR_MAX: |
| w->data.val_i = spw->sp_max; |
| break; |
| case ATTR_WARN: |
| w->data.val_i = spw->sp_warn; |
| break; |
| case ATTR_INACT: |
| w->data.val_i = spw->sp_inact; |
| break; |
| case ATTR_EXPIRE: |
| w->data.val_i = spw->sp_expire; |
| break; |
| case ATTR_FLAG: |
| w->data.val_i = spw->sp_flag; |
| break; |
| case ATTR_FAILED_LOGINS: |
| w->data.val_i = spw->sp_flag & FAILCOUNT_MASK; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| getattr_exit: |
| if (pwbuf->pwd) free(pwbuf->pwd); |
| if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch); |
| if (pwbuf->spwd) free(pwbuf->spwd); |
| if (pwbuf->spwd_scratch) free(pwbuf->spwd_scratch); |
| free(pwbuf); |
| |
| return (res); |
| } |
| |
| /* |
| * max_present(list) |
| * |
| * see if attribute ATTR_MAX, with value != -1, is present in |
| * attribute-list "list". |
| * |
| * returns 1 if present, 0 otherwise. |
| */ |
| static int |
| max_present(attrlist *list) |
| { |
| while (list != NULL) |
| if (list->type == ATTR_MAX && list->data.val_i != -1) |
| return (1); |
| else |
| list = list->next; |
| |
| return (0); |
| } |
| |
| /* |
| * files_update(items, rep, buf) |
| * |
| * update the information in buf with the attributes specified in |
| * items. |
| */ |
| /*ARGSUSED*/ |
| int |
| files_update(attrlist *items, pwu_repository_t *rep, void *buf) |
| { |
| struct pwbuf *pwbuf = (struct pwbuf *)buf; |
| struct passwd *pw; |
| struct spwd *spw; |
| attrlist *p; |
| int aging_needed = 0; |
| int aging_set = 0; |
| int disable_aging; |
| char *pword; |
| int len; |
| |
| pw = pwbuf->pwd; |
| spw = pwbuf->spwd; |
| pwbuf->update_history = 0; |
| |
| /* |
| * if sp_max==0 : disable passwd aging after updating the password |
| */ |
| disable_aging = (spw != NULL && spw->sp_max == 0); |
| |
| for (p = items; p != NULL; p = p->next) { |
| switch (p->type) { |
| case ATTR_NAME: |
| break; /* We are able to handle this, but... */ |
| case ATTR_UID: |
| pw->pw_uid = (uid_t)p->data.val_i; |
| break; |
| case ATTR_GID: |
| pw->pw_gid = (gid_t)p->data.val_i; |
| break; |
| case ATTR_AGE: |
| pw->pw_age = p->data.val_s; |
| break; |
| case ATTR_COMMENT: |
| pw->pw_comment = p->data.val_s; |
| break; |
| case ATTR_GECOS: |
| pw->pw_gecos = p->data.val_s; |
| break; |
| case ATTR_HOMEDIR: |
| pw->pw_dir = p->data.val_s; |
| break; |
| case ATTR_SHELL: |
| pw->pw_shell = p->data.val_s; |
| break; |
| |
| /* |
| * Nothing special needs to be done for |
| * server policy |
| */ |
| case ATTR_PASSWD: |
| case ATTR_PASSWD_SERVER_POLICY: |
| /* |
| * There is a special case only for files: if the |
| * password is to be deleted (-d to passwd), |
| * p->data.val_s will be NULL. |
| */ |
| if (p->data.val_s == NULL) { |
| spw->sp_pwdp = ""; |
| } else { |
| char *salt = NULL; |
| char *hash = NULL; |
| |
| salt = crypt_gensalt(spw->sp_pwdp, pw); |
| |
| if (salt == NULL) { |
| if (errno == ENOMEM) |
| return (PWU_NOMEM); |
| /* algorithm problem? */ |
| syslog(LOG_AUTH | LOG_ALERT, |
| "passwdutil: crypt_gensalt %m"); |
| return (PWU_UPDATE_FAILED); |
| } |
| hash = crypt(p->data.val_s, salt); |
| free(salt); |
| if (hash == NULL) { |
| errno = ENOMEM; |
| return (PWU_NOMEM); |
| } |
| pword = strdup(hash); |
| if (pword == NULL) { |
| errno = ENOMEM; |
| return (PWU_NOMEM); |
| } |
| |
| if (pwbuf->new_sp_pwdp) |
| free(pwbuf->new_sp_pwdp); |
| pwbuf->new_sp_pwdp = pword; |
| spw->sp_pwdp = pword; |
| aging_needed = 1; |
| pwbuf->update_history = 1; |
| } |
| spw->sp_flag &= ~FAILCOUNT_MASK; /* reset count */ |
| spw->sp_lstchg = DAY_NOW_32; |
| break; |
| case ATTR_LOCK_ACCOUNT: |
| if (spw->sp_pwdp == NULL) { |
| spw->sp_pwdp = LOCKSTRING; |
| } else if ((strncmp(spw->sp_pwdp, LOCKSTRING, |
| sizeof (LOCKSTRING)-1) != 0) && |
| (strcmp(spw->sp_pwdp, NOLOGINSTRING) != 0)) { |
| len = sizeof (LOCKSTRING)-1 + |
| strlen(spw->sp_pwdp) + 1; |
| pword = malloc(len); |
| if (pword == NULL) { |
| errno = ENOMEM; |
| return (PWU_NOMEM); |
| } |
| (void) strlcpy(pword, LOCKSTRING, len); |
| (void) strlcat(pword, spw->sp_pwdp, len); |
| if (pwbuf->new_sp_pwdp) |
| free(pwbuf->new_sp_pwdp); |
| pwbuf->new_sp_pwdp = pword; |
| spw->sp_pwdp = pword; |
| } |
| spw->sp_lstchg = DAY_NOW_32; |
| break; |
| case ATTR_UNLOCK_ACCOUNT: |
| if (spw->sp_pwdp != NULL && |
| strncmp(spw->sp_pwdp, LOCKSTRING, |
| sizeof (LOCKSTRING)-1) == 0) { |
| (void) strcpy(spw->sp_pwdp, spw->sp_pwdp + |
| sizeof (LOCKSTRING)-1); |
| } |
| spw->sp_lstchg = DAY_NOW_32; |
| break; |
| case ATTR_NOLOGIN_ACCOUNT: |
| spw->sp_pwdp = NOLOGINSTRING; |
| if (pwbuf->new_sp_pwdp) { |
| free(pwbuf->new_sp_pwdp); |
| pwbuf->new_sp_pwdp = NULL; |
| } |
| spw->sp_lstchg = DAY_NOW_32; |
| break; |
| case ATTR_EXPIRE_PASSWORD: |
| spw->sp_lstchg = 0; |
| break; |
| case ATTR_LSTCHG: |
| spw->sp_lstchg = p->data.val_i; |
| break; |
| case ATTR_MIN: |
| if (spw->sp_max == -1 && |
| p->data.val_i != -1 && max_present(p->next) == 0) |
| return (PWU_AGING_DISABLED); |
| spw->sp_min = p->data.val_i; |
| aging_set = 1; |
| break; |
| case ATTR_MAX: |
| if (p->data.val_i == -1) { |
| /* Turn aging off -> Reset min and warn too */ |
| |
| spw->sp_min = -1; |
| spw->sp_warn = -1; |
| } else { |
| /* Turn aging on */ |
| |
| if (spw->sp_min == -1) { |
| /* |
| * If minage has not been set with |
| * a command-line option, we set it |
| * to zero. |
| */ |
| spw->sp_min = 0; |
| } |
| |
| /* |
| * If aging was turned off, we update lstchg. |
| * |
| * We take care not to update lstchg if the |
| * user has no password, otherwise the user |
| * might not be required to provide a password |
| * the next time they log-in. |
| * |
| * Also, if lstchg != -1 (i.e., not set in |
| * /etc/shadow), we keep the old value. |
| */ |
| if (spw->sp_max == -1 && |
| spw->sp_pwdp != NULL && *spw->sp_pwdp && |
| spw->sp_lstchg == -1) { |
| spw->sp_lstchg = DAY_NOW_32; |
| } |
| } |
| |
| spw->sp_max = p->data.val_i; |
| |
| aging_set = 1; |
| |
| break; |
| case ATTR_WARN: |
| if (spw->sp_max == -1 && p->data.val_i != -1 && |
| max_present(p->next) == 0) |
| return (PWU_AGING_DISABLED); |
| spw->sp_warn = p->data.val_i; |
| break; |
| case ATTR_INACT: |
| spw->sp_inact = p->data.val_i; |
| break; |
| case ATTR_EXPIRE: |
| spw->sp_expire = p->data.val_i; |
| break; |
| case ATTR_FLAG: |
| spw->sp_flag = p->data.val_i; |
| break; |
| case ATTR_INCR_FAILED_LOGINS: |
| { |
| int count = (spw->sp_flag & FAILCOUNT_MASK) + 1; |
| spw->sp_flag &= ~FAILCOUNT_MASK; |
| spw->sp_flag |= min(FAILCOUNT_MASK, count); |
| p->data.val_i = count; |
| } |
| break; |
| case ATTR_RST_FAILED_LOGINS: |
| p->data.val_i = spw->sp_flag & FAILCOUNT_MASK; |
| spw->sp_flag &= ~FAILCOUNT_MASK; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * What should the new aging values look like? |
| * |
| * There are a number of different conditions |
| * |
| * a) aging is already configured: don't touch it |
| * |
| * b) disable_aging is set: disable aging |
| * |
| * c) aging is not configured: turn on default aging; |
| * |
| * b) and c) of course only if aging_needed and !aging_set. |
| * (i.e., password changed, and aging values not changed) |
| */ |
| |
| if (spw != NULL && spw->sp_max <= 0) { |
| /* a) aging not yet configured */ |
| if (aging_needed && !aging_set) { |
| if (disable_aging) { |
| /* b) turn off aging */ |
| spw->sp_min = spw->sp_max = spw->sp_warn = -1; |
| } else { |
| /* c) */ |
| turn_on_default_aging(spw); |
| } |
| } |
| } |
| |
| return (PWU_SUCCESS); |
| } |
| |
| /* |
| * files_update_shadow(char *name, struct spwd *spwd) |
| * |
| * update the shadow password file SHADOW to contain the spwd structure |
| * "spwd" for user "name" |
| */ |
| int |
| files_update_shadow(char *name, struct spwd *spwd) |
| { |
| struct stat64 stbuf; |
| FILE *dst; |
| FILE *src; |
| struct spwd cur; |
| char buf[SPW_SCRATCH_SIZE]; |
| int tempfd; |
| mode_t filemode; |
| int result = -1; |
| int err = PWU_SUCCESS; |
| |
| /* Mode of the shadow file should be 400 or 000 */ |
| if (stat64(SHADOW, &stbuf) < 0) { |
| err = PWU_STAT_FAILED; |
| goto shadow_exit; |
| } |
| |
| /* copy mode from current shadow file (0400 or 0000) */ |
| filemode = stbuf.st_mode & S_IRUSR; |
| |
| /* |
| * we can't specify filemodes to fopen(), and we SHOULD NOT |
| * set umask in multi-thread safe libraries, so we use |
| * a combination of open() and fdopen() |
| */ |
| tempfd = open(SHADTEMP, O_WRONLY|O_CREAT|O_TRUNC, filemode); |
| if (tempfd < 0) { |
| err = PWU_OPEN_FAILED; |
| goto shadow_exit; |
| } |
| (void) fchown(tempfd, (uid_t)0, stbuf.st_gid); |
| |
| if ((dst = fdopen(tempfd, "wF")) == NULL) { |
| err = PWU_OPEN_FAILED; |
| goto shadow_exit; |
| } |
| |
| if ((src = fopen(SHADOW, "rF")) == NULL) { |
| err = PWU_OPEN_FAILED; |
| (void) fclose(dst); |
| (void) unlink(SHADTEMP); |
| goto shadow_exit; |
| } |
| |
| /* |
| * copy old shadow to temporary file while replacing the entry |
| * that matches "name". |
| */ |
| while (fgetspent_r(src, &cur, buf, sizeof (buf)) != NULL) { |
| |
| if (strcmp(cur.sp_namp, name) == 0) |
| result = putspent(spwd, dst); |
| else |
| result = putspent(&cur, dst); |
| |
| if (result != 0) { |
| err = PWU_WRITE_FAILED; |
| (void) fclose(src); |
| (void) fclose(dst); |
| goto shadow_exit; |
| } |
| } |
| |
| (void) fclose(src); |
| |
| if (fclose(dst) != 0) { |
| /* |
| * Something went wrong (ENOSPC for example). Don't |
| * use the resulting temporary file! |
| */ |
| err = PWU_CLOSE_FAILED; |
| (void) unlink(SHADTEMP); |
| goto shadow_exit; |
| } |
| |
| /* |
| * Rename stmp to shadow: |
| * 1. make sure /etc/oshadow is gone |
| * 2. ln /etc/shadow /etc/oshadow |
| * 3. mv /etc/stmp /etc/shadow |
| */ |
| if (unlink(OSHADOW) && access(OSHADOW, 0) == 0) { |
| err = PWU_UPDATE_FAILED; |
| (void) unlink(SHADTEMP); |
| goto shadow_exit; |
| } |
| |
| if (link(SHADOW, OSHADOW) == -1) { |
| err = PWU_UPDATE_FAILED; |
| (void) unlink(SHADTEMP); |
| goto shadow_exit; |
| } |
| |
| if (rename(SHADTEMP, SHADOW) == -1) { |
| err = PWU_UPDATE_FAILED; |
| (void) unlink(SHADTEMP); |
| goto shadow_exit; |
| } |
| (void) unlink(OSHADOW); |
| |
| shadow_exit: |
| return (err); |
| } |
| |
| int |
| files_update_passwd(char *name, struct passwd *pwd) |
| { |
| struct stat64 stbuf; |
| FILE *src, *dst; |
| int tempfd; |
| struct passwd cur; |
| char buf[PWD_SCRATCH_SIZE]; |
| int result; |
| int err = PWU_SUCCESS; |
| |
| if (stat64(PASSWD, &stbuf) < 0) { |
| err = PWU_STAT_FAILED; |
| goto passwd_exit; |
| } |
| |
| /* see files_update_shadow() for open()+fdopen() rationale */ |
| |
| if ((tempfd = open(PASSTEMP, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) { |
| err = PWU_OPEN_FAILED; |
| goto passwd_exit; |
| } |
| if ((dst = fdopen(tempfd, "wF")) == NULL) { |
| err = PWU_OPEN_FAILED; |
| goto passwd_exit; |
| } |
| if ((src = fopen(PASSWD, "rF")) == NULL) { |
| err = PWU_OPEN_FAILED; |
| (void) fclose(dst); |
| (void) unlink(PASSTEMP); |
| goto passwd_exit; |
| } |
| |
| /* |
| * copy old password entries to temporary file while replacing |
| * the entry that matches "name" |
| */ |
| while (fgetpwent_r(src, &cur, buf, sizeof (buf)) != NULL) { |
| if (strcmp(cur.pw_name, name) == 0) |
| result = putpwent(pwd, dst); |
| else |
| result = putpwent(&cur, dst); |
| if (result != 0) { |
| err = PWU_WRITE_FAILED; |
| (void) fclose(src); |
| (void) fclose(dst); |
| goto passwd_exit; |
| } |
| } |
| |
| (void) fclose(src); |
| if (fclose(dst) != 0) { |
| err = PWU_CLOSE_FAILED; |
| goto passwd_exit; /* Don't trust the temporary file */ |
| } |
| |
| /* Rename temp to passwd */ |
| if (unlink(OPASSWD) && access(OPASSWD, 0) == 0) { |
| err = PWU_UPDATE_FAILED; |
| (void) unlink(PASSTEMP); |
| goto passwd_exit; |
| } |
| |
| if (link(PASSWD, OPASSWD) == -1) { |
| err = PWU_UPDATE_FAILED; |
| (void) unlink(PASSTEMP); |
| goto passwd_exit; |
| } |
| |
| if (rename(PASSTEMP, PASSWD) == -1) { |
| err = PWU_UPDATE_FAILED; |
| (void) unlink(PASSTEMP); |
| goto passwd_exit; |
| } |
| |
| (void) chmod(PASSWD, 0644); |
| |
| passwd_exit: |
| return (err); |
| |
| } |
| |
| /* |
| * files_putpwnam(name, oldpw, rep, buf) |
| * |
| * store the password attributes contained in "buf" in /etc/passwd and |
| * /etc/shadow. |
| */ |
| /*ARGSUSED*/ |
| int |
| files_putpwnam(char *name, char *oldpw, pwu_repository_t *rep, void *buf) |
| { |
| struct pwbuf *pwbuf = (struct pwbuf *)buf; |
| int result = PWU_SUCCESS; |
| |
| if (pwbuf->pwd) { |
| result = files_update_passwd(name, pwbuf->pwd); |
| } |
| |
| if (result == PWU_SUCCESS && pwbuf->spwd) { |
| if (pwbuf->update_history != 0) { |
| debug("update_history = %d", pwbuf->update_history); |
| result = files_update_history(name, pwbuf->spwd); |
| } else { |
| debug("no password change"); |
| } |
| if (result == PWU_SUCCESS) { |
| result = files_update_shadow(name, pwbuf->spwd); |
| } |
| } |
| |
| if (pwbuf->pwd) { |
| (void) memset(pwbuf->pwd, 0, sizeof (struct passwd)); |
| (void) memset(pwbuf->pwd_scratch, 0, PWD_SCRATCH_SIZE); |
| free(pwbuf->pwd); |
| free(pwbuf->pwd_scratch); |
| } |
| if (pwbuf->spwd) { |
| (void) memset(pwbuf->spwd, 0, sizeof (struct spwd)); |
| (void) memset(pwbuf->spwd_scratch, 0, SPW_SCRATCH_SIZE); |
| free(pwbuf->spwd); |
| free(pwbuf->spwd_scratch); |
| } |
| if (pwbuf->new_sp_pwdp) { |
| free(pwbuf->new_sp_pwdp); |
| } |
| |
| return (result); |
| } |
| |
| /* |
| * NOTE: This is all covered under the repository lock held for updating |
| * passwd(4) and shadow(4). |
| */ |
| int |
| files_update_history(char *name, struct spwd *spwd) |
| { |
| int histsize; |
| int tmpfd; |
| FILE *src; /* history database file */ |
| FILE *dst; /* temp history database being updated */ |
| struct stat64 statbuf; |
| char buf[MAX_LOGNAME + MAXHISTORY + |
| (MAXHISTORY * CRYPT_MAXCIPHERTEXTLEN)+1]; |
| int found; |
| |
| if ((histsize = def_getint("HISTORY=", DEFHISTORY)) == 0) { |
| debug("files_update_history(%s) no history, unlinking", name); |
| (void) unlink(HISTORY); |
| return (PWU_SUCCESS); /* no history update defined */ |
| } |
| debug("files_update_history(%s, %s) histsize = %d", name, spwd->sp_pwdp, |
| histsize); |
| |
| if (histsize > MAXHISTORY) |
| histsize = MAXHISTORY; |
| if ((tmpfd = open(HISTEMP, O_WRONLY|O_CREAT|O_TRUNC, HISTMODE)) < 0) { |
| return (PWU_OPEN_FAILED); |
| } |
| (void) fchown(tmpfd, (uid_t)0, (gid_t)0); |
| |
| /* get ready to copy */ |
| if (((src = fopen(HISTORY, "rF")) == NULL) && |
| (errno != ENOENT)) { |
| (void) unlink(HISTEMP); |
| return (PWU_OPEN_FAILED); |
| } |
| if ((dst = fdopen(tmpfd, "wF")) == NULL) { |
| (void) fclose(src); |
| (void) unlink(HISTEMP); |
| return (PWU_OPEN_FAILED); |
| } |
| |
| /* Copy and update if found. Add if not found. */ |
| |
| found = 0; |
| |
| while ((src != NULL) && |
| (fgets(buf, sizeof (buf), src) != NULL)) { |
| char *user; |
| char *last; |
| |
| /* get username field */ |
| user = strtok_r(buf, ":", &last); |
| |
| #ifdef DEBUG |
| debug("files_update_history: read=\"%s\"", user); |
| #endif /* DEBUG */ |
| |
| if (strcmp(user, name) == 0) { |
| char *crypt; |
| int i; |
| |
| /* found user, update */ |
| found++; |
| (void) fprintf(dst, "%s:%s:", name, spwd->sp_pwdp); |
| debug("files_update_history: update user\n" |
| "\t%s:%s:", name, spwd->sp_pwdp); |
| |
| /* get old crypted password history */ |
| for (i = 0; i < MAXHISTORY-1; i++) { |
| crypt = strtok_r(NULL, ":", &last); |
| if (crypt == NULL || |
| *crypt == '\n') { |
| break; |
| } |
| (void) fprintf(dst, "%s:", crypt); |
| debug("\t%d = %s:", i+1, crypt); |
| } |
| (void) fprintf(dst, "\n"); |
| } else { |
| |
| /* copy other users to updated file */ |
| (void) fprintf(dst, "%s:%s", user, last); |
| #ifdef DEBUG |
| debug("files_update_history: copy line %s", |
| user); |
| #endif /* DEBUG */ |
| } |
| } |
| |
| if (found == 0) { |
| |
| /* user not found, add to history file */ |
| (void) fprintf(dst, "%s:%s:\n", name, spwd->sp_pwdp); |
| debug("files_update_history: add line\n" |
| "\t%s:%s:", name, spwd->sp_pwdp); |
| } |
| |
| (void) fclose(src); |
| |
| /* If something messed up in file system, loose the update */ |
| if (fclose(dst) != 0) { |
| |
| debug("files_update_history: update file close failed %d", |
| errno); |
| (void) unlink(HISTEMP); |
| return (PWU_CLOSE_FAILED); |
| } |
| |
| /* |
| * rename history to ohistory, |
| * rename tmp to history, |
| * unlink ohistory. |
| */ |
| |
| (void) unlink(OHISTORY); |
| |
| if (stat64(OHISTORY, &statbuf) == 0 || |
| ((src != NULL) && (link(HISTORY, OHISTORY) != 0)) || |
| rename(HISTEMP, HISTORY) != 0) { |
| |
| /* old history won't go away, loose the update */ |
| debug("files_update_history: update file rename failed %d", |
| errno); |
| (void) unlink(HISTEMP); |
| return (PWU_UPDATE_FAILED); |
| } |
| |
| (void) unlink(OHISTORY); |
| return (PWU_SUCCESS); |
| } |