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/*
2N/A * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include <k5-int.h>
2N/A#include <kadm5/admin.h>
2N/A#include <krb5.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#include <syslog.h>
2N/A#include <string.h>
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <sys/types.h>
2N/A#include <pwd.h>
2N/A#include <libintl.h>
2N/A#include <netdb.h>
2N/A#include "utils.h"
2N/A#include "krb5_repository.h"
2N/A
2N/Aextern int attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, char *,
2N/A char **, boolean_t);
2N/Aextern int krb5_verifypw(char *, char *, int);
2N/A
2N/Astatic void display_msg(pam_handle_t *, int, char *);
2N/Astatic void display_msgs(pam_handle_t *, int, int,
2N/A char msgs[][PAM_MAX_MSG_SIZE]);
2N/Astatic int krb5_changepw(pam_handle_t *, char *, char *, char *, int);
2N/A
2N/A/*
2N/A * set_ccname()
2N/A *
2N/A * set KRB5CCNAME shell var
2N/A */
2N/Astatic void
2N/Aset_ccname(
2N/A pam_handle_t *pamh,
2N/A krb5_module_data_t *kmd,
2N/A int login_result,
2N/A int debug)
2N/A{
2N/A int result;
2N/A
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): password: finalize"
2N/A " ccname env, login_result =%d, env ='%s'",
2N/A login_result, kmd->env ? kmd->env : "<null>");
2N/A
2N/A if (kmd->env) {
2N/A
2N/A if (login_result == PAM_SUCCESS) {
2N/A /*
2N/A * Put ccname into the pamh so that login
2N/A * apps can pick this up when they run
2N/A * pam_getenvlist().
2N/A */
2N/A if ((result = pam_putenv(pamh, kmd->env))
2N/A != PAM_SUCCESS) {
2N/A /* should not happen but... */
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (password):"
2N/A " pam_putenv failed: result: %d",
2N/A result);
2N/A goto cleanupccname;
2N/A }
2N/A } else {
2N/A cleanupccname:
2N/A /* for lack of a Solaris unputenv() */
2N/A krb5_unsetenv(KRB5_ENV_CCNAME);
2N/A free(kmd->env);
2N/A kmd->env = NULL;
2N/A }
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * get_set_creds()
2N/A *
2N/A * do a krb5 login to get and set krb5 creds (needed after a pw change
2N/A * on pw expire on login)
2N/A */
2N/Astatic void
2N/Aget_set_creds(
2N/A pam_handle_t *pamh,
2N/A krb5_module_data_t *kmd,
2N/A char *user,
2N/A char *newpass,
2N/A int debug)
2N/A{
2N/A int login_result;
2N/A
2N/A if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
2N/A return;
2N/A
2N/A /*
2N/A * if pw has expired, get/set krb5 creds ala auth mod
2N/A *
2N/A * pwchange verified user sufficiently, so don't request strict
2N/A * tgt verification (will cause rcache perm issues possibly anyways)
2N/A */
2N/A login_result = attempt_krb5_auth(pamh, kmd, user, &newpass, 0);
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): get_set_creds: login_result= %d",
2N/A login_result);
2N/A /*
2N/A * the krb5 login should not fail, but if so,
2N/A * warn the user they have to kinit(1)
2N/A */
2N/A if (login_result != PAM_SUCCESS) {
2N/A display_msg(pamh, PAM_TEXT_INFO,
2N/A dgettext(TEXT_DOMAIN,
2N/A "Warning: "
2N/A "Could not cache Kerberos"
2N/A " credentials, please run "
2N/A "kinit(1) or re-login\n"));
2N/A }
2N/A set_ccname(pamh, kmd, login_result, debug);
2N/A}
2N/A/*
2N/A * This is the PAM Kerberos Password Change module
2N/A *
2N/A */
2N/A
2N/Aint
2N/Apam_sm_chauthtok(
2N/A pam_handle_t *pamh,
2N/A int flags,
2N/A int argc,
2N/A const char **argv)
2N/A{
2N/A
2N/A char *user;
2N/A int err, result = PAM_AUTHTOK_ERR;
2N/A char *newpass = NULL;
2N/A char *oldpass = NULL;
2N/A int i;
2N/A int debug = 0;
2N/A uid_t pw_uid;
2N/A krb5_module_data_t *kmd = NULL;
2N/A pam_repository_t *rep_data = NULL;
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
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (password): illegal option %s",
2N/A argv[i]);
2N/A }
2N/A
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): start: flags = %x",
2N/A flags);
2N/A
2N/A (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
2N/A
2N/A if (rep_data != NULL) {
2N/A if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (auth): wrong"
2N/A "repository found (%s), returning "
2N/A "PAM_IGNORE", rep_data->type);
2N/A return (PAM_IGNORE);
2N/A }
2N/A }
2N/A
2N/A if (flags & PAM_PRELIM_CHECK) {
2N/A /* Nothing to do here */
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): prelim check");
2N/A return (PAM_IGNORE);
2N/A }
2N/A
2N/A /* make sure PAM framework is telling us to update passwords */
2N/A if (!(flags & PAM_UPDATE_AUTHTOK)) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (password): bad flags: %d",
2N/A flags);
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A
2N/A if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
2N/A != PAM_SUCCESS) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): get mod data failed %d",
2N/A err);
2N/A kmd = NULL;
2N/A }
2N/A
2N/A if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) {
2N/A /* let's make sure we know the krb5 pw has expired */
2N/A
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): kmd age status %d",
2N/A kmd ? kmd->age_status : -99);
2N/A
2N/A if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
2N/A return (PAM_IGNORE);
2N/A }
2N/A
2N/A (void) pam_get_item(pamh, PAM_USER, (void **)&user);
2N/A
2N/A if (user == NULL || *user == '\0') {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (password): username is empty");
2N/A return (PAM_USER_UNKNOWN);
2N/A }
2N/A
2N/A if (!get_pw_uid(user, &pw_uid)) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (password): can't get uid for %s", user);
2N/A return (PAM_USER_UNKNOWN);
2N/A }
2N/A
2N/A /*
2N/A * if root key exists in the keytab, it's a random key so no
2N/A * need to prompt for pw and we just return IGNORE
2N/A */
2N/A if ((strcmp(user, ROOT_UNAME) == 0) &&
2N/A key_in_keytab(user, debug)) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): "
2N/A "key for '%s' in keytab, returning IGNORE", user);
2N/A result = PAM_IGNORE;
2N/A goto out;
2N/A }
2N/A
2N/A (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpass);
2N/A
2N/A /*
2N/A * If the preauth type done didn't use a passwd just ignore the error.
2N/A */
2N/A if (newpass == NULL)
2N/A if (kmd && kmd->preauth_type == KRB_PKINIT)
2N/A return (PAM_IGNORE);
2N/A else
2N/A return (PAM_SYSTEM_ERR);
2N/A
2N/A (void) pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpass);
2N/A
2N/A if (oldpass == NULL)
2N/A if (kmd && kmd->preauth_type == KRB_PKINIT)
2N/A return (PAM_IGNORE);
2N/A else
2N/A return (PAM_SYSTEM_ERR);
2N/A
2N/A result = krb5_verifypw(user, oldpass, debug);
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): verifypw %d", result);
2N/A
2N/A /*
2N/A * If it's a bad password or general failure, we are done.
2N/A */
2N/A if (result != 0) {
2N/A /*
2N/A * if the preauth type done didn't use a passwd just ignore the
2N/A * error.
2N/A */
2N/A if (kmd && kmd->preauth_type == KRB_PKINIT)
2N/A return (PAM_IGNORE);
2N/A
2N/A if (result == 2)
2N/A display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN,
2N/A "Old Kerberos password incorrect\n"));
2N/A return (PAM_AUTHTOK_ERR);
2N/A }
2N/A
2N/A /*
2N/A * If the old password verifies try to change it regardless of the
2N/A * preauth type and do not ignore the error.
2N/A */
2N/A result = krb5_changepw(pamh, user, oldpass, newpass, debug);
2N/A if (result == PAM_SUCCESS) {
2N/A display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN,
2N/A "Kerberos password successfully changed\n"));
2N/A
2N/A get_set_creds(pamh, kmd, user, newpass, debug);
2N/A }
2N/A
2N/Aout:
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): out: returns %d",
2N/A result);
2N/A
2N/A return (result);
2N/A}
2N/A
2N/Aint
2N/Akrb5_verifypw(
2N/A char *princ_str,
2N/A char *old_password,
2N/A int debug)
2N/A{
2N/A kadm5_ret_t code;
2N/A krb5_principal princ = 0;
2N/A char admin_realm[1024];
2N/A char kprinc[2*MAXHOSTNAMELEN];
2N/A char **cpw_services;
2N/A void *server_handle;
2N/A krb5_context context;
2N/A kadm5_config_params params;
2N/A
2N/A (void) memset((char *)&params, 0, sizeof (params));
2N/A
2N/A if (code = krb5_init_secure_context(&context)) {
2N/A return (6);
2N/A }
2N/A
2N/A if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
2N/A 2*MAXHOSTNAMELEN)) != 0) {
2N/A return (code);
2N/A }
2N/A
2N/A /* Need to get a krb5_principal struct */
2N/A
2N/A code = krb5_parse_name(context, kprinc, &princ);
2N/A
2N/A if (code != 0)
2N/A return (6);
2N/A
2N/A if (strlen(old_password) == 0) {
2N/A krb5_free_principal(context, princ);
2N/A return (5);
2N/A }
2N/A
2N/A (void) strlcpy(admin_realm,
2N/A krb5_princ_realm(context, princ)->data,
2N/A sizeof (admin_realm));
2N/A
2N/A params.mask |= KADM5_CONFIG_REALM;
2N/A params.realm = admin_realm;
2N/A
2N/A
2N/A if (kadm5_get_cpw_host_srv_names(context, admin_realm, &cpw_services)) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (password): unable to get host based "
2N/A "service name for realm %s\n",
2N/A admin_realm);
2N/A krb5_free_principal(context, princ);
2N/A return (3);
2N/A }
2N/A
2N/A code = kadm5_init_with_password(context, kprinc, old_password,
2N/A cpw_services, &params, KADM5_STRUCT_VERSION,
2N/A KADM5_API_VERSION_2, NULL,
2N/A &server_handle);
2N/A free_srv_names(cpw_services);
2N/A if (code != 0) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5: krb5_verifypw: init_with_pw"
2N/A " failed: (%s)", error_message(code));
2N/A krb5_free_principal(context, princ);
2N/A return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
2N/A }
2N/A
2N/A krb5_free_principal(context, princ);
2N/A
2N/A (void) kadm5_destroy(server_handle);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Function: krb5_changepw
2N/A *
2N/A * Purpose: Initialize and call lower level routines to change a password
2N/A *
2N/A * Arguments:
2N/A *
2N/A * princ_str principal name to use, optional
2N/A * old_password old password
2N/A * new_password new password
2N/A *
2N/A * Returns:
2N/A * exit status of PAM_SUCCESS for success
2N/A * else returns PAM failure
2N/A *
2N/A * Requires:
2N/A * Passwords cannot be more than 255 characters long.
2N/A *
2N/A * Modifies:
2N/A *
2N/A * Changes the principal's password.
2N/A *
2N/A */
2N/Astatic int
2N/Akrb5_changepw(
2N/A pam_handle_t *pamh,
2N/A char *princ_str,
2N/A char *old_password,
2N/A char *new_password,
2N/A int debug)
2N/A{
2N/A kadm5_ret_t code;
2N/A krb5_principal princ = 0;
2N/A char msg_ret[1024], admin_realm[1024];
2N/A char kprinc[2*MAXHOSTNAMELEN];
2N/A char **cpw_services;
2N/A void *server_handle;
2N/A krb5_context context;
2N/A kadm5_config_params params;
2N/A
2N/A (void) memset((char *)&params, 0, sizeof (params));
2N/A
2N/A if (krb5_init_secure_context(&context) != 0)
2N/A return (PAM_SYSTEM_ERR);
2N/A
2N/A if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
2N/A 2*MAXHOSTNAMELEN)) != 0) {
2N/A return (code);
2N/A }
2N/A
2N/A /* Need to get a krb5_principal struct */
2N/A
2N/A code = krb5_parse_name(context, kprinc, &princ);
2N/A if (code != 0)
2N/A return (PAM_SYSTEM_ERR);
2N/A
2N/A if (strlen(old_password) == 0) {
2N/A krb5_free_principal(context, princ);
2N/A return (PAM_AUTHTOK_ERR);
2N/A }
2N/A
2N/A (void) snprintf(admin_realm, sizeof (admin_realm), "%s",
2N/A krb5_princ_realm(context, princ)->data);
2N/A params.mask |= KADM5_CONFIG_REALM;
2N/A params.realm = admin_realm;
2N/A
2N/A
2N/A if (kadm5_get_cpw_host_srv_names(context, admin_realm, &cpw_services)) {
2N/A __pam_log(LOG_AUTH | LOG_ERR,
2N/A "PAM-KRB5 (password):unable to get host based "
2N/A "service name for realm %s\n",
2N/A admin_realm);
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A code = kadm5_init_with_password(context, kprinc, old_password,
2N/A cpw_services, &params, KADM5_STRUCT_VERSION,
2N/A KADM5_API_VERSION_2, NULL,
2N/A &server_handle);
2N/A free_srv_names(cpw_services);
2N/A if (code != 0) {
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): changepw: "
2N/A "init_with_pw failed: (%s)", error_message(code));
2N/A krb5_free_principal(context, princ);
2N/A return ((code == KADM5_BAD_PASSWORD) ?
2N/A PAM_AUTHTOK_ERR : PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A code = kadm5_chpass_principal_util(server_handle, princ,
2N/A new_password,
2N/A NULL /* don't need pw back */,
2N/A msg_ret,
2N/A sizeof (msg_ret));
2N/A
2N/A if (code) {
2N/A char msgs[2][PAM_MAX_MSG_SIZE];
2N/A
2N/A (void) snprintf(msgs[0], PAM_MAX_MSG_SIZE, "%s",
2N/A dgettext(TEXT_DOMAIN,
2N/A "Kerberos password not changed: "));
2N/A (void) snprintf(msgs[1], PAM_MAX_MSG_SIZE, "%s", msg_ret);
2N/A
2N/A display_msgs(pamh, PAM_ERROR_MSG, 2, msgs);
2N/A }
2N/A
2N/A krb5_free_principal(context, princ);
2N/A
2N/A (void) kadm5_destroy(server_handle);
2N/A
2N/A if (debug)
2N/A __pam_log(LOG_AUTH | LOG_DEBUG,
2N/A "PAM-KRB5 (password): changepw: end %d", code);
2N/A
2N/A if (code != 0)
2N/A return (PAM_AUTHTOK_ERR);
2N/A
2N/A return (PAM_SUCCESS);
2N/A}
2N/A
2N/Astatic void
2N/Adisplay_msgs(pam_handle_t *pamh,
2N/A int msg_style, int nmsg, char msgs[][PAM_MAX_MSG_SIZE])
2N/A{
2N/A (void) __pam_display_msg(pamh, msg_style, nmsg, msgs, NULL);
2N/A}
2N/A
2N/A
2N/Astatic void
2N/Adisplay_msg(pam_handle_t *pamh, int msg_style, char *msg)
2N/A{
2N/A char pam_msg[1][PAM_MAX_MSG_SIZE];
2N/A
2N/A (void) snprintf(pam_msg[0], PAM_MAX_MSG_SIZE, "%s", msg);
2N/A display_msgs(pamh, msg_style, 1, pam_msg);
2N/A}