summaryrefslogtreecommitdiff
path: root/usr/src/lib/passwdutil/files_attr.c
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/lib/passwdutil/files_attr.c
downloadillumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/lib/passwdutil/files_attr.c')
-rw-r--r--usr/src/lib/passwdutil/files_attr.c1265
1 files changed, 1265 insertions, 0 deletions
diff --git a/usr/src/lib/passwdutil/files_attr.c b/usr/src/lib/passwdutil/files_attr.c
new file mode 100644
index 0000000000..d5514d780d
--- /dev/null
+++ b/usr/src/lib/passwdutil/files_attr.c
@@ -0,0 +1,1265 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#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, char *dummy,
+ 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, "r")) == 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, "r");
+ 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;
+
+ /*
+ * 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
+ */
+/*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) {
+ *auth_user = strdup(user);
+ 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, "r")) == 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);
+ 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);
+ free(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) {
+ 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 [s]he logs-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, "w")) == NULL) {
+ err = PWU_OPEN_FAILED;
+ goto shadow_exit;
+ }
+
+ if ((src = fopen(SHADOW, "r")) == 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, "w")) == NULL) {
+ err = PWU_OPEN_FAILED;
+ goto passwd_exit;
+ }
+ if ((src = fopen(PASSWD, "r")) == 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, dummy, rep, buf)
+ *
+ * store the password attributes contained in "buf" in /etc/passwd and
+ * /etc/shadow. The dummy parameter is a placeholder for NIS+
+ * updates where the "oldrpc" password is passed.
+ */
+/*ARGSUSED*/
+int
+files_putpwnam(char *name, char *oldpw, char *dummy,
+ 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, "r")) == NULL) &&
+ (errno != ENOENT)) {
+ (void) unlink(HISTEMP);
+ return (PWU_OPEN_FAILED);
+ }
+ if ((dst = fdopen(tmpfd, "w")) == 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);
+}