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, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include <stdlib.h>
2N/A#include <syslog.h>
2N/A#include <errno.h>
2N/A#include <string.h>
2N/A#include <rpc/rpc.h>
2N/A#include <unistd.h>
2N/A#include <assert.h>
2N/A#include <stdarg.h>
2N/A#include <sys/types.h>
2N/A#include <sys/wait.h>
2N/A#include <limits.h>
2N/A#include <signal.h>
2N/A#include <pthread.h>
2N/A#include <synch.h>
2N/A
2N/A#include <rpcsvc/nis.h>
2N/A#include <rpcsvc/yppasswd.h>
2N/A#include <rpcsvc/ypclnt.h>
2N/A#include <rpc/key_prot.h>
2N/A#include <rpc/rpc.h>
2N/A#include <nfs/nfs.h>
2N/A#include <nfs/nfssys.h>
2N/A#include <nss_dbdefs.h>
2N/A#include <nsswitch.h>
2N/A#include <rpcsvc/nis_dhext.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 <libintl.h>
2N/A
2N/A#include <sys/mman.h>
2N/A
2N/A#include <passwdutil.h>
2N/A
2N/A#include "key_call_uid.h"
2N/A#include <shadow.h>
2N/A
2N/Aextern int _nfssys(int, void *);
2N/A
2N/A/*
2N/A * int msg(pamh, ...)
2N/A *
2N/A * display message to the user
2N/A */
2N/A/*PRINTFLIKE2*/
2N/Astatic int
2N/Amsg(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 va_end(ap);
2N/A
2N/A return (__pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages, NULL));
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Get the secret key for the given netname, key length, and algorithm
2N/A * type and send it to keyserv if the given pw decrypts it. Update the
2N/A * following counter args as necessary: get_seckey_cnt, good_pw_cnt, and
2N/A * set_seckey_cnt.
2N/A *
2N/A * Returns 0 on malloc failure, else 1.
2N/A */
2N/Astatic int
2N/Aget_and_set_seckey(
2N/A pam_handle_t *pamh, /* in */
2N/A const char *netname, /* in */
2N/A keylen_t keylen, /* in */
2N/A algtype_t algtype, /* in */
2N/A const char *pw, /* in */
2N/A uid_t uid, /* in */
2N/A gid_t gid, /* in */
2N/A int *get_seckey_cnt, /* out */
2N/A int *good_pw_cnt, /* out */
2N/A int *set_seckey_cnt, /* out */
2N/A int flags, /* in */
2N/A int debug) /* in */
2N/A{
2N/A char *skey;
2N/A int skeylen;
2N/A char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
2N/A
2N/A skeylen = BITS2NIBBLES(keylen) + 1;
2N/A
2N/A if ((skey = malloc(skeylen)) == NULL) {
2N/A return (0);
2N/A }
2N/A
2N/A if (getsecretkey_g(netname, keylen, algtype, skey, skeylen, pw)) {
2N/A (*get_seckey_cnt)++;
2N/A
2N/A if (skey[0]) {
2N/A /* password does decrypt secret key */
2N/A (*good_pw_cnt)++;
2N/A if (key_setnet_g_uid(netname, skey, keylen, NULL, 0,
2N/A algtype, uid, gid) >= 0) {
2N/A (*set_seckey_cnt)++;
2N/A } else {
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_dhkeys: "
2N/A "get_and_set_seckey: could not "
2N/A "set secret key for keytype "
2N/A "%d-%d", keylen, algtype);
2N/A }
2N/A } else {
2N/A if (pamh && !(flags & PAM_SILENT)) {
2N/A (void) snprintf(messages[0],
2N/A sizeof (messages[0]),
2N/A dgettext(TEXT_DOMAIN,
2N/A "Password does not "
2N/A "decrypt secret key (type = %d-%d) "
2N/A "for '%s'."), keylen, algtype, netname);
2N/A (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1,
2N/A messages, NULL);
2N/A }
2N/A }
2N/A } else {
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_dhkeys: get_and_set_seckey: "
2N/A "could not get secret key for keytype %d-%d",
2N/A keylen, algtype);
2N/A }
2N/A
2N/A free(skey);
2N/A
2N/A return (1);
2N/A}
2N/A
2N/A/*
2N/A * int establish_key(pamh, flags, debug, netname)
2N/A *
2N/A * This routine establishes the Secure RPC Credentials for the
2N/A * user specified in PAM_USER, using the password in PAM_AUTHTOK.
2N/A *
2N/A * Establishing RPC credentials is considered a "helper" function for the PAM
2N/A * stack so we should only return failures or PAM_IGNORE. Returning PAM_SUCCESS
2N/A * may short circuit the stack and circumvent later critical checks.
2N/A *
2N/A * we are called from pam_sm_setcred:
2N/A * 1. if we are root (uid == 0), we do nothing and return
2N/A * PAM_IGNORE.
2N/A * 2. else, we try to establish credentials.
2N/A *
2N/A * We return framework errors as appropriate such as PAM_USER_UNKNOWN,
2N/A * PAM_BUF_ERR, PAM_PERM_DENIED.
2N/A *
2N/A * If we succeed in establishing credentials we return PAM_IGNORE.
2N/A *
2N/A * If we fail to establish credentials then we return:
2N/A * - PAM_SERVICE_ERR (credentials needed) or PAM_SYSTEM_ERR
2N/A * (credentials not needed) if netname could not be created;
2N/A * - PAM_AUTH_ERR (credentials needed) or PAM_IGNORE (credentials
2N/A * not needed) if no credentials were retrieved;
2N/A * - PAM_AUTH_ERR if the password didn't decrypt the cred;
2N/A * - PAM_SYSTEM_ERR if the cred's could not be stored.
2N/A *
2N/A * This routine returns the user's netname in "netname".
2N/A *
2N/A * All tools--but the PAM stack--currently use getpass() to obtain
2N/A * the user's secure RPC password. We must make sure we don't use more than
2N/A * the first des_block (eight) characters of whatever is handed down to us.
2N/A * Therefore, we use a local variable "short_pass" to hold those 8 char's.
2N/A */
2N/Astatic int
2N/Aestablish_key(pam_handle_t *pamh, int flags, int debug, char *netname)
2N/A{
2N/A char *user;
2N/A char *passwd;
2N/A char short_pass[sizeof (des_block)+1], *short_passp;
2N/A int result;
2N/A uid_t uid;
2N/A gid_t gid;
2N/A int err;
2N/A
2N/A struct passwd pw; /* Needed to obtain uid */
2N/A char *scratch;
2N/A int scratchlen;
2N/A
2N/A mechanism_t **mechs;
2N/A mechanism_t **mpp;
2N/A int get_seckey_cnt = 0;
2N/A int set_seckey_cnt = 0;
2N/A int good_pw_cnt = 0;
2N/A int valid_mech_cnt = 0;
2N/A
2N/A (void) pam_get_item(pamh, PAM_USER, (void **)&user);
2N/A
2N/A if (user == NULL || *user == '\0') {
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_dhkeys: user NULL or empty");
2N/A return (PAM_USER_UNKNOWN);
2N/A }
2N/A
2N/A (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&passwd);
2N/A
2N/A scratchlen = sysconf(_SC_GETPW_R_SIZE_MAX);
2N/A if ((scratch = malloc(scratchlen)) == NULL)
2N/A return (PAM_BUF_ERR);
2N/A
2N/A if (getpwnam_r(user, &pw, scratch, scratchlen) == NULL) {
2N/A result = PAM_USER_UNKNOWN;
2N/A goto out;
2N/A }
2N/A
2N/A uid = pw.pw_uid;
2N/A gid = pw.pw_gid;
2N/A
2N/A /*
2N/A * We don't set credentials when root logs in.
2N/A */
2N/A if (uid == 0) {
2N/A result = PAM_IGNORE;
2N/A goto out;
2N/A }
2N/A
2N/A err = user2netname(netname, uid, NULL);
2N/A
2N/A if (err != 1) {
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_dhkeys: user2netname failed");
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A /* passwd can be NULL (no passwd or su as root) */
2N/A if (passwd) {
2N/A (void) strlcpy(short_pass, passwd, sizeof (short_pass));
2N/A short_passp = short_pass;
2N/A } else
2N/A short_passp = NULL;
2N/A
2N/A if (mechs = __nis_get_mechanisms(FALSE)) {
2N/A
2N/A for (mpp = mechs; *mpp; mpp++) {
2N/A mechanism_t *mp = *mpp;
2N/A
2N/A if (AUTH_DES_COMPAT_CHK(mp))
2N/A break; /* fall through to AUTH_DES below */
2N/A
2N/A if (!VALID_MECH_ENTRY(mp))
2N/A continue;
2N/A
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_dhkeys: trying "
2N/A "key type = %d-%d", mp->keylen,
2N/A mp->algtype);
2N/A valid_mech_cnt++;
2N/A if (!get_and_set_seckey(pamh, netname, mp->keylen,
2N/A mp->algtype, short_passp, uid, gid,
2N/A &get_seckey_cnt, &good_pw_cnt, &set_seckey_cnt,
2N/A flags, debug)) {
2N/A result = PAM_BUF_ERR;
2N/A goto out;
2N/A }
2N/A }
2N/A __nis_release_mechanisms(mechs);
2N/A /* fall through to AUTH_DES below */
2N/A } else {
2N/A /*
2N/A * No usable mechs found in security congifuration file thus
2N/A * fallback to AUTH_DES compat.
2N/A */
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_dhkeys: no valid mechs "
2N/A "found. Trying AUTH_DES.");
2N/A }
2N/A
2N/A /*
2N/A * We always perform AUTH_DES for the benefit of services like NFS
2N/A * that may depend on the classic des 192bit key being set.
2N/A */
2N/A if (!get_and_set_seckey(pamh, netname, AUTH_DES_KEYLEN,
2N/A AUTH_DES_ALGTYPE, short_passp, uid, gid, &get_seckey_cnt,
2N/A &good_pw_cnt, &set_seckey_cnt, flags, debug)) {
2N/A result = PAM_BUF_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A if (debug) {
2N/A syslog(LOG_DEBUG, "pam_dhkeys: mech key totals:\n");
2N/A syslog(LOG_DEBUG, "pam_dhkeys: %d valid mechanism(s)",
2N/A valid_mech_cnt);
2N/A syslog(LOG_DEBUG, "pam_dhkeys: %d secret key(s) retrieved",
2N/A get_seckey_cnt);
2N/A syslog(LOG_DEBUG, "pam_dhkeys: %d passwd decrypt successes",
2N/A good_pw_cnt);
2N/A syslog(LOG_DEBUG, "pam_dhkeys: %d secret key(s) set",
2N/A set_seckey_cnt);
2N/A }
2N/A
2N/A if (get_seckey_cnt == 0) { /* No credentials */
2N/A result = PAM_IGNORE;
2N/A goto out;
2N/A }
2N/A
2N/A if (good_pw_cnt == 0) { /* wrong password */
2N/A result = PAM_AUTH_ERR;
2N/A goto out;
2N/A }
2N/A
2N/A if (set_seckey_cnt == 0) {
2N/A result = PAM_SYSTEM_ERR;
2N/A goto out;
2N/A }
2N/A /* Credentials have been successfully established, return PAM_IGNORE */
2N/A result = PAM_IGNORE;
2N/Aout:
2N/A /*
2N/A * If we are authenticating we attempt to establish credentials
2N/A * where appropriate. Failure to do so is only an error if we
2N/A * definitely needed them. Thus always return PAM_IGNORE
2N/A * if we are authenticating and credentials were not needed.
2N/A */
2N/A free(scratch);
2N/A
2N/A (void) memset(short_pass, '\0', sizeof (short_pass));
2N/A
2N/A return (result);
2N/A}
2N/A
2N/A/*ARGSUSED*/
2N/Aint
2N/Apam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
2N/A{
2N/A return (PAM_IGNORE);
2N/A}
2N/A
2N/A
2N/Atypedef struct argres {
2N/A uid_t uid;
2N/A int result;
2N/A} argres_t;
2N/A
2N/A/*
2N/A * Revoke NFS DES credentials.
2N/A * NFS may not be installed so we need to deal with SIGSYS
2N/A * when we call _nfssys(); we thus call _nfssys() in a separate thread that
2N/A * is created specifically for this call. The thread specific signalmask
2N/A * is set to ignore SIGSYS. After the call to _nfssys(), the thread
2N/A * ceases to exist.
2N/A */
2N/Astatic void *
2N/Arevoke_nfs_cred(void *ap)
2N/A{
2N/A struct nfs_revauth_args nra;
2N/A sigset_t isigset;
2N/A argres_t *argres = (argres_t *)ap;
2N/A
2N/A nra.authtype = AUTH_DES;
2N/A nra.uid = argres->uid;
2N/A
2N/A (void) sigemptyset(&isigset);
2N/A (void) sigaddset(&isigset, SIGSYS);
2N/A
2N/A if (pthread_sigmask(SIG_BLOCK, &isigset, NULL) == 0) {
2N/A argres->result = _nfssys(NFS_REVAUTH, &nra);
2N/A if (argres->result < 0 && errno == ENOSYS) {
2N/A argres->result = 0;
2N/A }
2N/A } else {
2N/A argres->result = -1;
2N/A }
2N/A return (NULL);
2N/A}
2N/A
2N/Astatic int
2N/Aremove_key(pam_handle_t *pamh, int flags, int debug)
2N/A{
2N/A int result;
2N/A char *uname;
2N/A attrlist attr_pw[2];
2N/A struct pam_repository *auth_rep = NULL;
2N/A pwu_repository_t *pwu_rep;
2N/A uid_t uid;
2N/A gid_t gid;
2N/A argres_t argres;
2N/A pthread_t tid;
2N/A
2N/A (void) pam_get_item(pamh, PAM_USER, (void **)&uname);
2N/A if (uname == NULL || *uname == NULL) {
2N/A if (debug)
2N/A syslog(LOG_DEBUG,
2N/A "pam_dhkeys: user NULL or empty in remove_key()");
2N/A return (PAM_USER_UNKNOWN);
2N/A }
2N/A
2N/A if (strcmp(uname, "root") == 0) {
2N/A if ((flags & PAM_SILENT) == 0) {
2N/A char msg[3][PAM_MAX_MSG_SIZE];
2N/A (void) snprintf(msg[0], sizeof (msg[0]),
2N/A dgettext(TEXT_DOMAIN,
2N/A "removing root credentials would"
2N/A " break the rpc services that"));
2N/A (void) snprintf(msg[1], sizeof (msg[1]),
2N/A dgettext(TEXT_DOMAIN,
2N/A "use secure rpc on this host!"));
2N/A (void) snprintf(msg[2], sizeof (msg[2]),
2N/A dgettext(TEXT_DOMAIN,
2N/A "root may use keylogout -f to do"
2N/A " this (at your own risk)!"));
2N/A (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 3,
2N/A msg, NULL);
2N/A }
2N/A return (PAM_PERM_DENIED);
2N/A }
2N/A
2N/A (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
2N/A if (auth_rep != NULL) {
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 } else
2N/A pwu_rep = PWU_DEFAULT_REP;
2N/A
2N/A /* Retrieve user's uid/gid from the password repository */
2N/A attr_pw[0].type = ATTR_UID; attr_pw[0].next = &attr_pw[1];
2N/A attr_pw[1].type = ATTR_GID; attr_pw[1].next = NULL;
2N/A
2N/A result = __get_authtoken_attr(uname, pwu_rep, attr_pw);
2N/A
2N/A if (pwu_rep != PWU_DEFAULT_REP)
2N/A free(pwu_rep);
2N/A
2N/A if (result == PWU_NOT_FOUND)
2N/A return (PAM_USER_UNKNOWN);
2N/A if (result == PWU_DENIED)
2N/A return (PAM_PERM_DENIED);
2N/A if (result != PWU_SUCCESS)
2N/A return (PAM_SYSTEM_ERR);
2N/A
2N/A uid = (uid_t)attr_pw[0].data.val_i;
2N/A gid = (gid_t)attr_pw[1].data.val_i;
2N/A
2N/A (void) key_removesecret_g_uid(uid, gid);
2N/A
2N/A argres.uid = uid;
2N/A argres.result = -1;
2N/A
2N/A if (pthread_create(&tid, NULL, revoke_nfs_cred, (void *)&argres) == 0)
2N/A (void) pthread_join(tid, NULL);
2N/A
2N/A if (argres.result < 0) {
2N/A if ((flags & PAM_SILENT) == 0) {
2N/A (void) msg(pamh, dgettext(TEXT_DOMAIN,
2N/A "Warning: NFS credentials not destroyed"));
2N/A }
2N/A return (PAM_AUTH_ERR);
2N/A }
2N/A
2N/A return (PAM_IGNORE);
2N/A}
2N/A
2N/Aint
2N/Apam_sm_setcred(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 result;
2N/A char netname[MAXNETNAMELEN + 1];
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 flags |= PAM_SILENT;
2N/A }
2N/A
2N/A /* Check for invalid flags */
2N/A if (flags && (flags & PAM_ESTABLISH_CRED) == 0 &&
2N/A (flags & PAM_REINITIALIZE_CRED) == 0 &&
2N/A (flags & PAM_REFRESH_CRED) == 0 &&
2N/A (flags & PAM_DELETE_CRED) == 0 &&
2N/A (flags & PAM_SILENT) == 0) {
2N/A syslog(LOG_ERR, "pam_dhkeys: pam_setcred: illegal flags %d",
2N/A flags);
2N/A return (PAM_SYSTEM_ERR);
2N/A }
2N/A
2N/A
2N/A if ((flags & PAM_REINITIALIZE_CRED) || (flags & PAM_REFRESH_CRED)) {
2N/A /* doesn't apply to UNIX */
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_dhkeys: cred reinit/refresh "
2N/A "ignored\n");
2N/A return (PAM_IGNORE);
2N/A }
2N/A
2N/A if (flags & PAM_DELETE_CRED) {
2N/A if (debug)
2N/A syslog(LOG_DEBUG, "pam_dhkeys: removing creds\n");
2N/A result = remove_key(pamh, flags, debug);
2N/A } else {
2N/A result = establish_key(pamh, flags, debug, netname);
2N/A /* Some diagnostics */
2N/A if ((flags & PAM_SILENT) == 0) {
2N/A if (result == PAM_AUTH_ERR)
2N/A (void) msg(pamh, dgettext(TEXT_DOMAIN,
2N/A "Password does not decrypt any secret "
2N/A "keys for %s."), netname);
2N/A else if (result == PAM_SYSTEM_ERR && netname[0])
2N/A (void) msg(pamh, dgettext(TEXT_DOMAIN,
2N/A "Could not set secret key(s) for %s. "
2N/A "The key server may be down."), netname);
2N/A }
2N/A
2N/A /* Not having credentials set is not an error... */
2N/A result = PAM_IGNORE;
2N/A }
2N/A
2N/A return (result);
2N/A}
2N/A
2N/A/*ARGSUSED*/
2N/Avoid
2N/Arpc_cleanup(pam_handle_t *pamh, void *data, int pam_status)
2N/A{
2N/A if (data) {
2N/A (void) memset(data, 0, strlen(data));
2N/A free(data);
2N/A }
2N/A}
2N/A
2N/A/*ARGSUSED*/
2N/Aint
2N/Apam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
2N/A{
2N/A return (PAM_IGNORE);
2N/A}