/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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(5) and shadow(5). */ 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); }