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) 2004, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A
2N/A#include <sys/types.h>
2N/A#include <sys/varargs.h>
2N/A#include <string.h>
2N/A#include <syslog.h>
2N/A#include <stdlib.h>
2N/A
2N/A#include <security/pam_appl.h>
2N/A#include <security/pam_modules.h>
2N/A#include <security/pam_impl.h>
2N/A
2N/A#include <user_attr.h>
2N/A#include <secdb.h>
2N/A
2N/A#include <libintl.h>
2N/A
2N/A#include <passwdutil.h>
2N/A#include <shadow.h>
2N/A
2N/A/*PRINTFLIKE3*/
2N/Astatic void
2N/Aerror(int nowarn, pam_handle_t *pamh, char *fmt, ...)
2N/A{
2N/A va_list ap;
2N/A char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
2N/A
2N/A va_start(ap, fmt);
2N/A (void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
2N/A if (nowarn == 0)
2N/A (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages,
2N/A NULL);
2N/A va_end(ap);
2N/A}
2N/A
2N/A/*PRINTFLIKE3*/
2N/Astatic void
2N/Ainfo(int nowarn, pam_handle_t *pamh, char *fmt, ...)
2N/A{
2N/A va_list ap;
2N/A char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
2N/A
2N/A va_start(ap, fmt);
2N/A (void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
2N/A if (nowarn == 0)
2N/A (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages,
2N/A NULL);
2N/A va_end(ap);
2N/A}
2N/A
2N/A#if defined(ENABLE_AGING)
2N/A/*
2N/A * test if authtok is aged.
2N/A * returns 1 if it is, 0 otherwise
2N/A */
2N/Astatic int
2N/Aauthtok_is_aged(pam_handle_t *pamh)
2N/A{
2N/A unix_authtok_data *status;
2N/A
2N/A if (pam_get_data(pamh, UNIX_AUTHTOK_DATA,
2N/A (const void **)status) != PAM_SUCCESS)
2N/A return (0);
2N/A
2N/A return (status->age_status == PAM_NEW_AUTHTOK_REQD)
2N/A}
2N/A#endif
2N/A
2N/Aint
2N/Apam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
2N/A{
2N/A int i;
2N/A int debug = 0;
2N/A int nowarn = 0;
2N/A attrlist l;
2N/A pwu_repository_t *pwu_rep;
2N/A char *user;
2N/A char *oldpw;
2N/A char *newpw;
2N/A char *service;
2N/A struct pam_repository *auth_rep;
2N/A int res;
2N/A int updated_reps = 0;
2N/A int server_policy = 0;
2N/A userattr_t *ue;
2N/A
2N/A for (i = 0; i < argc; i++) {
2N/A if (strcmp(argv[i], "debug") == 0)
2N/A debug = 1;
2N/A else if (strcmp(argv[i], "nowarn") == 0)
2N/A nowarn = 1;
2N/A else if (strcmp(argv[i], "server_policy") == 0)
2N/A server_policy = 1;
2N/A }
2N/A
2N/A if ((flags & PAM_PRELIM_CHECK) != 0)
2N/A return (PAM_IGNORE);
2N/A
2N/A if ((flags & PAM_UPDATE_AUTHTOK) == 0)
2N/A return (PAM_SYSTEM_ERR);
2N/A
2N/A if ((flags & PAM_SILENT) != 0)
2N/A nowarn = 1;
2N/A
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_authtok_store: storing authtok");
2N/A
2N/A#if defined(ENABLE_AGING)
2N/A if ((flags & PAM_CHANGE_EXPIRED_AUTHTOK) && !authtok_is_aged(pamh)) {
2N/A syslog(LOG_DEBUG, "pam_authtok_store: System password young");
2N/A return (PAM_IGNORE);
2N/A }
2N/A#endif
2N/A
2N/A res = pam_get_item(pamh, PAM_SERVICE, (void **)&service);
2N/A if (res != PAM_SUCCESS) {
2N/A syslog(LOG_ERR, "pam_authtok_store: error getting SERVICE");
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A res = pam_get_item(pamh, PAM_USER, (void **)&user);
2N/A if (res != PAM_SUCCESS) {
2N/A syslog(LOG_ERR, "pam_authtok_store: error getting USER");
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A /*
2N/A * If the PAM_USER who's password is being changed is a role
2N/A * with roleauth=user then we ignore it because those accounts
2N/A * should not have a UNIX password. We can't return a failure
2N/A * though because the account may have other creds (eg kerberos)
2N/A * that should be getting updated in the PAM stack for password.
2N/A * We also warn the user and tell them what to do to resolve it.
2N/A */
2N/A if (user == NULL || *user == '\0') {
2N/A syslog(LOG_ERR, "pam_authtok_store: username is empty");
2N/A return (PAM_USER_UNKNOWN);
2N/A } else if ((ue = getusernam(user)) != NULL) {
2N/A char *utype, *ra;
2N/A
2N/A utype = kva_match((kva_t *)ue->attr, USERATTR_TYPE_KW);
2N/A ra = kva_match((kva_t *)ue->attr, USERATTR_ROLE_AUTH_KW);
2N/A if (utype != NULL && strcmp(utype, USERATTR_TYPE_ROLE) == 0 &&
2N/A ra != NULL && strcmp(ra, USERATTR_ROLE_AUTH_USER) == 0) {
2N/A info(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Role '%s' has roleauth=user. "
2N/A "UNIX password updates are ignored."),
2N/A service, user);
2N/A return (PAM_IGNORE);
2N/A }
2N/A }
2N/A
2N/A res = pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpw);
2N/A if (res != PAM_SUCCESS) {
2N/A syslog(LOG_ERR, "pam_authtok_store: error getting OLDAUTHTOK");
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A res = pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpw);
2N/A if (res != PAM_SUCCESS || newpw == NULL) {
2N/A /*
2N/A * A module on the stack has removed PAM_AUTHTOK. We fail
2N/A */
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A l.data.val_s = newpw;
2N/A /*
2N/A * If the server_policy option is specified,
2N/A * use the special attribute, ATTR_PASSWD_SERVER_POLICY,
2N/A * to tell the update routine for each repository
2N/A * to perform the necessary special operations.
2N/A * For now, only the LDAP routine treats this attribute
2N/A * differently that ATTR_PASSWD. It will skip the
2N/A * crypting of the password before storing it in the LDAP
2N/A * server. NIS, and FILES will handle ATTR_PASSWD_SERVER_POLICY
2N/A * the same as ATTR_PASSWD.
2N/A */
2N/A if (server_policy)
2N/A l.type = ATTR_PASSWD_SERVER_POLICY;
2N/A else
2N/A l.type = ATTR_PASSWD;
2N/A l.next = NULL;
2N/A
2N/A res = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
2N/A if (res != PAM_SUCCESS) {
2N/A syslog(LOG_ERR, "pam_authtok_store: error getting repository");
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A if (auth_rep == NULL) {
2N/A pwu_rep = PWU_DEFAULT_REP;
2N/A } else {
2N/A if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
2N/A return (PAM_BUF_ERR);
2N/A pwu_rep->type = auth_rep->type;
2N/A pwu_rep->scope = auth_rep->scope;
2N/A pwu_rep->scope_len = auth_rep->scope_len;
2N/A }
2N/A
2N/A res = __set_authtoken_attr(user, oldpw, pwu_rep, &l, &updated_reps);
2N/A
2N/A if (pwu_rep != PWU_DEFAULT_REP)
2N/A free(pwu_rep);
2N/A /*
2N/A * now map the various passwdutil return states to user messages
2N/A * and PAM return codes.
2N/A */
2N/A switch (res) {
2N/A case PWU_SUCCESS:
2N/A for (i = 1; i <= REP_LAST; i <<= 1) {
2N/A if ((updated_reps & i) == 0)
2N/A continue;
2N/A info(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: password successfully changed for %s"),
2N/A service, user);
2N/A }
2N/A res = PAM_SUCCESS;
2N/A break;
2N/A case PWU_BUSY:
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Password database busy. Try again later."),
2N/A service);
2N/A res = PAM_AUTHTOK_LOCK_BUSY;
2N/A break;
2N/A case PWU_STAT_FAILED:
2N/A syslog(LOG_ERR, "%s: stat of password file failed", service);
2N/A res = PAM_AUTHTOK_ERR;
2N/A break;
2N/A case PWU_OPEN_FAILED:
2N/A case PWU_WRITE_FAILED:
2N/A case PWU_CLOSE_FAILED:
2N/A case PWU_UPDATE_FAILED:
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Unexpected failure. Password database unchanged."),
2N/A service);
2N/A res = PAM_SYSTEM_ERR;
2N/A break;
2N/A case PWU_NOT_FOUND:
2N/A /* Different error if repository was explicitly specified */
2N/A if (auth_rep != NULL) {
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: System error: no %s password for %s."),
2N/A service, auth_rep->type, user);
2N/A } else {
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: %s does not exist."), service, user);
2N/A }
2N/A res = PAM_USER_UNKNOWN;
2N/A break;
2N/A case PWU_NOMEM:
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Internal memory allocation failure."), service);
2N/A res = PAM_BUF_ERR;
2N/A break;
2N/A case PWU_SERVER_ERROR:
2N/A res = PAM_SYSTEM_ERR;
2N/A break;
2N/A case PWU_SYSTEM_ERROR:
2N/A res = PAM_SYSTEM_ERR;
2N/A break;
2N/A case PWU_DENIED:
2N/A res = PAM_PERM_DENIED;
2N/A break;
2N/A case PWU_NO_CHANGE:
2N/A /*
2N/A * we're not changing anything.
2N/A */
2N/A info(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Password information unchanged."), service);
2N/A res = PAM_SUCCESS;
2N/A break;
2N/A case PWU_REPOSITORY_ERROR:
2N/A syslog(LOG_NOTICE, "pam_authtok_store: detected "
2N/A "unsupported configuration in /etc/nsswitch.conf.");
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: System error: repository out of range."), service);
2N/A res = PAM_SYSTEM_ERR;
2N/A break;
2N/A case PWU_PWD_TOO_SHORT:
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Password too short."), service);
2N/A res = PAM_AUTHTOK_ERR;
2N/A break;
2N/A case PWU_PWD_INVALID:
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Invalid password syntax."), service);
2N/A res = PAM_AUTHTOK_ERR;
2N/A break;
2N/A case PWU_PWD_IN_HISTORY:
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Reuse of old passwords not "
2N/A "allowed, the new password is in the history list."),
2N/A service);
2N/A res = PAM_AUTHTOK_ERR;
2N/A break;
2N/A case PWU_CHANGE_NOT_ALLOWED:
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: You may not change "
2N/A "this password."), service);
2N/A res = PAM_PERM_DENIED;
2N/A break;
2N/A case PWU_WITHIN_MIN_AGE:
2N/A error(nowarn, pamh, dgettext(TEXT_DOMAIN,
2N/A "%s: Password can not be changed yet, "
2N/A "not enough time has passed."), service);
2N/A res = PAM_PERM_DENIED;
2N/A break;
2N/A default:
2N/A res = PAM_SYSTEM_ERR;
2N/A break;
2N/A }
2N/A
2N/A return (res);
2N/A}