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