OpenSolaris_b135/cmd/ssh/sshd/gss-serv.c

/*
 * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "includes.h"

#ifdef GSSAPI

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include "includes.h"
#include "ssh.h"
#include "ssh2.h"
#include "xmalloc.h"
#include "buffer.h"
#include "bufaux.h"
#include "packet.h"
#include "compat.h"
#include <openssl/evp.h>
#include "cipher.h"
#include "kex.h"
#include "auth.h"
#include "log.h"
#include "channels.h"
#include "session.h"
#include "dispatch.h"
#include "servconf.h"
#include "uidswap.h"
#include "compat.h"
#include <pwd.h>

#include "ssh-gss.h"

extern char **environ;

extern ServerOptions options;
extern uchar_t *session_id2;
extern int session_id2_len;

Gssctxt	*xxx_gssctxt;

void
ssh_gssapi_server_kex_hook(Kex *kex, char **proposal)
{
	gss_OID_set mechs = GSS_C_NULL_OID_SET;

	if (kex == NULL || !kex->server)
		fatal("INTERNAL ERROR (%s)", __func__);

	ssh_gssapi_server_mechs(&mechs);
	ssh_gssapi_modify_kex(kex, mechs, proposal);
}

void
ssh_gssapi_server_mechs(gss_OID_set *mechs)
{
	static gss_OID_set	supported = GSS_C_NULL_OID_SET;
	gss_OID_set	s, acquired, indicated = GSS_C_NULL_OID_SET;
	gss_cred_id_t	creds;
	OM_uint32	maj, min;
	int		i;

	if (!mechs) {
		(void) gss_release_oid_set(&min, &supported);
		return;
	}

	if (supported != GSS_C_NULL_OID_SET) {
		*mechs = supported;
		return;
	}

	*mechs = GSS_C_NULL_OID_SET;

	maj = gss_create_empty_oid_set(&min, &s);
	if (GSS_ERROR(maj)) {
		debug("Could not allocate GSS-API resources (%s)",
		    ssh_gssapi_last_error(NULL, &maj, &min));
		return;
	}

	maj = gss_indicate_mechs(&min, &indicated);
	if (GSS_ERROR(maj)) {
		debug("No GSS-API mechanisms are installed");
		return;
	}

	maj = gss_acquire_cred(&min, GSS_C_NO_NAME, 0, indicated,
	    GSS_C_ACCEPT, &creds, &acquired, NULL);

	if (GSS_ERROR(maj))
		debug("Failed to acquire GSS-API credentials for any "
		    "mechanisms (%s)", ssh_gssapi_last_error(NULL, &maj, &min));

	(void) gss_release_oid_set(&min, &indicated);
	(void) gss_release_cred(&min, &creds);

	if (acquired == GSS_C_NULL_OID_SET || acquired->count == 0)
		return;

	for (i = 0; i < acquired->count; i++) {
		if (ssh_gssapi_is_spnego(&acquired->elements[i]))
			continue;

		maj = gss_add_oid_set_member(&min, &acquired->elements[i], &s);
		if (GSS_ERROR(maj)) {
			debug("Could not allocate GSS-API resources (%s)",
			    ssh_gssapi_last_error(NULL, &maj, &min));
			return;
		}
	}
	(void) gss_release_oid_set(&min, &acquired);

	if (s->count) {
		supported = s;
		*mechs = s;
	}
}

/*
 * Wrapper around accept_sec_context. Requires that the context contains:
 *
 *    oid
 *    credentials	(from ssh_gssapi_acquire_cred)
 */
/* Priviledged */
OM_uint32
ssh_gssapi_accept_ctx(Gssctxt *ctx, gss_buffer_t recv_tok,
    gss_buffer_t send_tok)
{
	/*
	 * Acquiring a cred for the ctx->desired_mech for GSS_C_NO_NAME
	 * may well be probably better than using GSS_C_NO_CREDENTIAL
	 * and then checking that ctx->desired_mech agrees with
	 * ctx->actual_mech...
	 */
	ctx->major = gss_accept_sec_context(&ctx->minor, &ctx->context,
	    GSS_C_NO_CREDENTIAL, recv_tok, GSS_C_NO_CHANNEL_BINDINGS,
	    &ctx->src_name, &ctx->actual_mech, send_tok, &ctx->flags,
	    NULL, &ctx->deleg_creds);

	if (GSS_ERROR(ctx->major))
		ssh_gssapi_error(ctx, "accepting security context");

	if (ctx->major == GSS_S_CONTINUE_NEEDED && send_tok->length == 0)
		fatal("Zero length GSS context token output when "
		    "continue needed");
	else if (GSS_ERROR(ctx->major) && send_tok->length == 0)
		debug2("Zero length GSS context error token output");

	if (ctx->major == GSS_S_COMPLETE &&
	    ctx->desired_mech != GSS_C_NULL_OID &&
	    (ctx->desired_mech->length != ctx->actual_mech->length ||
	    memcmp(ctx->desired_mech->elements, ctx->actual_mech->elements,
	    ctx->desired_mech->length) != 0)) {

		gss_OID_set supported;
		OM_uint32 min;
		int present = 0;

		debug("The client did not use the GSS-API mechanism it "
		    "asked for");

		/* Let it slide as long as the mech is supported */
		ssh_gssapi_server_mechs(&supported);
		if (supported != GSS_C_NULL_OID_SET) {
			(void) gss_test_oid_set_member(&min, ctx->actual_mech,
			    supported, &present);
		}
		if (!present)
			ctx->major = GSS_S_BAD_MECH;
	}

	if (ctx->deleg_creds)
		debug("Received delegated GSS credentials");

	if (ctx->major == GSS_S_COMPLETE) {
		ctx->major = gss_inquire_context(&ctx->minor, ctx->context,
		    NULL, &ctx->dst_name, NULL, NULL, NULL, NULL,
		    &ctx->established);

		if (GSS_ERROR(ctx->major)) {
			ssh_gssapi_error(ctx,
			    "inquiring established sec context");
			return (ctx->major);
		}

		xxx_gssctxt = ctx;
	}

	return (ctx->major);
}


/* As user - called through fatal cleanup hook */
void
ssh_gssapi_cleanup_creds(Gssctxt *ctx)
{
#ifdef HAVE_GSS_STORE_CRED
	/* pam_setcred() will take care of this */
	return;
#else
	return;
/* #error "Portability broken in cleanup of stored creds" */
#endif /* HAVE_GSS_STORE_CRED */
}

void
ssh_gssapi_storecreds(Gssctxt *ctx, Authctxt *authctxt)
{
#ifdef USE_PAM
	char **penv, **tmp_env;
#endif /* USE_PAM */

	if (authctxt == NULL) {
		error("Missing context while storing GSS-API credentials");
		return;
	}

	if (ctx == NULL && xxx_gssctxt == NULL)
		return;

	if (ctx == NULL)
		ctx = xxx_gssctxt;

	if (!options.gss_cleanup_creds ||
	    ctx->deleg_creds == GSS_C_NO_CREDENTIAL) {
		debug3("Not storing delegated GSS credentials"
		    " (none delegated)");
		return;
	}

	if (!authctxt->valid || authctxt->pw == NULL) {
		debug3("Not storing delegated GSS credentials"
		    " for invalid user");
		return;
	}

	debug("Storing delegated GSS-API credentials");

	/*
	 * The GSS-API has a flaw in that it does not provide a
	 * mechanism by which delegated credentials can be made
	 * available for acquisition by GSS_Acquire_cred() et. al.;
	 * gss_store_cred() is the proposed GSS-API extension for
	 * generically storing delegated credentials.
	 *
	 * gss_store_cred() does not speak to how credential stores are
	 * referenced.  Generically this may be done by switching to the
	 * user context of the user in whose default credential store we
	 * wish to place delegated credentials.  But environment
	 * variables could conceivably affect the choice of credential
	 * store as well, and perhaps in a mechanism-specific manner.
	 *
	 * SUNW -- On Solaris the euid selects the current credential
	 * store, but PAM modules could select alternate stores by
	 * setting, for example, KRB5CCNAME, so we also use the PAM
	 * environment temporarily.
	 */

#ifdef HAVE_GSS_STORE_CRED
#ifdef USE_PAM
	/*
	 * PAM may have set mechanism-specific variables (e.g.,
	 * KRB5CCNAME).  fetch_pam_environment() protects against LD_*
	 * and other environment variables.
	 */
	penv = fetch_pam_environment(authctxt);
	tmp_env = environ;
	environ = penv;
#endif /* USE_PAM */
	if (authctxt->pw->pw_uid != geteuid()) {
		temporarily_use_uid(authctxt->pw);
		ctx->major = gss_store_cred(&ctx->minor, ctx->deleg_creds,
		    GSS_C_INITIATE, GSS_C_NULL_OID, 0, ctx->default_creds,
		    NULL, NULL);
		restore_uid();
	} else {
		/* only when logging in as the privileged user used by sshd */
		ctx->major = gss_store_cred(&ctx->minor, ctx->deleg_creds,
		    GSS_C_INITIATE, GSS_C_NULL_OID, 0, ctx->default_creds,
		    NULL, NULL);
	}
#ifdef USE_PAM
	environ = tmp_env;
	free_pam_environment(penv);
#endif /* USE_PAM */
	if (GSS_ERROR(ctx->major))
		ssh_gssapi_error(ctx, "storing delegated credentials");

#else
#ifdef KRB5_GSS
#error "MIT/Heimdal krb5-specific code missing in ssh_gssapi_storecreds()"
	if (ssh_gssapi_is_krb5(ctx->mech))
		ssh_gssapi_krb5_storecreds(ctx);
#endif /* KRB5_GSS */
#ifdef GSI_GSS
#error "GSI krb5-specific code missing in ssh_gssapi_storecreds()"
	if (ssh_gssapi_is_gsi(ctx->mech))
		ssh_gssapi_krb5_storecreds(ctx);
#endif /* GSI_GSS */
/* #error "Mechanism-specific code missing in ssh_gssapi_storecreds()" */
	return;
#endif /* HAVE_GSS_STORE_CRED */
}

void
ssh_gssapi_do_child(Gssctxt *ctx, char ***envp, uint_t *envsizep)
{
	/*
	 * MIT/Heimdal/GSI specific code goes here.
	 *
	 * On Solaris there's nothing to do here as the GSS store and
	 * related environment variables are to be set by PAM, if at all
	 * (no environment variables are needed to address the default
	 * credential store -- the euid does that).
	 */
#ifdef KRB5_GSS
#error "MIT/Heimdal krb5-specific code missing in ssh_gssapi_storecreds()"
#endif /* KRB5_GSS */
#ifdef GSI_GSS
#error "GSI krb5-specific code missing in ssh_gssapi_storecreds()"
#endif /* GSI_GSS */
}

int
ssh_gssapi_userok(Gssctxt *ctx, char *user)
{
	if (ctx == NULL) {
		debug3("INTERNAL ERROR: %s", __func__);
		return (0);
	}

	if (user == NULL || *user == '\0')
		return (0);

#ifdef HAVE___GSS_USEROK
	{
		int user_ok = 0;

		ctx->major = __gss_userok(&ctx->minor, ctx->src_name, user,
		    &user_ok);
		if (GSS_ERROR(ctx->major)) {
			debug2("__GSS_userok() failed");
			return (0);
		}

		if (user_ok)
			return (1);

		/* fall through */
	}
#else
#ifdef GSSAPI_SIMPLE_USEROK
	{
		/* Mechanism-generic */
		OM_uint32	min;
		gss_buffer_desc	buf, ename1, ename2;
		gss_name_t	iname, cname;
		int		eql;

		buf.value = user;
		buf.length = strlen(user);
		ctx->major = gss_import_name(&ctx->minor, &buf,
		    GSS_C_NULL_OID, &iname);
		if (GSS_ERROR(ctx->major)) {
			ssh_gssapi_error(ctx,
			    "importing name for authorizing initiator");
			goto failed_simple_userok;
		}

		ctx->major = gss_canonicalize_name(&ctx->minor, iname,
		    ctx->actual_mech, &cname);
		(void) gss_release_name(&min, &iname);
		if (GSS_ERROR(ctx->major)) {
			ssh_gssapi_error(ctx, "canonicalizing name");
			goto failed_simple_userok;
		}

		ctx->major = gss_export_name(&ctx->minor, cname, &ename1);
		(void) gss_release_name(&min, &cname);
		if (GSS_ERROR(ctx->major)) {
			ssh_gssapi_error(ctx, "exporting name");
			goto failed_simple_userok;
		}

		ctx->major = gss_export_name(&ctx->minor, ctx->src_name,
		    &ename2);
		if (GSS_ERROR(ctx->major)) {
			ssh_gssapi_error(ctx,
			    "exporting client principal name");
			(void) gss_release_buffer(&min, &ename1);
			goto failed_simple_userok;
		}

		eql = (ename1.length == ename2.length &&
		    memcmp(ename1.value, ename2.value, ename1.length) == 0);

		(void) gss_release_buffer(&min, &ename1);
		(void) gss_release_buffer(&min, &ename2);

		if (eql)
			return (1);
		/* fall through */
	}
failed_simple_userok:
#endif /* GSSAPI_SIMPLE_USEROK */
#ifdef HAVE_GSSCRED_API
	{
		/* Mechanism-generic, Solaris-specific */
		OM_uint32	 maj;
		uid_t		 uid;
		struct passwd	*pw;

		maj = gsscred_name_to_unix_cred(ctx->src_name,
		    ctx->actual_mech, &uid, NULL, NULL, NULL);

		if (GSS_ERROR(maj))
			goto failed_simple_gsscred_userok;

		if ((pw = getpwnam(user)) == NULL)
			goto failed_simple_gsscred_userok;

		if (pw->pw_uid == uid)
			return (1);
		/* fall through */
	}

failed_simple_gsscred_userok:
#endif /* HAVE_GSSCRED_API */
#ifdef KRB5_GSS
	if (ssh_gssapi_is_krb5(ctx->mech))
		if (ssh_gssapi_krb5_userok(ctx->src_name, user))
			return (1);
#endif /* KRB5_GSS */
#ifdef GSI_GSS
	if (ssh_gssapi_is_gsi(ctx->mech))
		if (ssh_gssapi_gsi_userok(ctx->src_name, user))
			return (1);
#endif /* GSI_GSS */
#endif /* HAVE___GSS_USEROK */

	/* default to not authorized */
	return (0);
}

char *
ssh_gssapi_localname(Gssctxt *ctx)
{
	if (ctx == NULL) {
		debug3("INTERNAL ERROR: %s", __func__);
		return (NULL);
	}

	debug2("Mapping initiator GSS-API principal to local username");
#ifdef HAVE_GSSCRED_API
	{
		/* Mechanism-generic, Solaris-specific */
		OM_uint32	 maj;
		uid_t		 uid;
		struct passwd	*pw;

		if (ctx->src_name == GSS_C_NO_NAME)
			goto failed_gsscred_localname;

		maj = gsscred_name_to_unix_cred(ctx->src_name,
		    ctx->actual_mech, &uid, NULL, NULL, NULL);

		if (GSS_ERROR(maj))
			goto failed_gsscred_localname;

		if ((pw = getpwuid(uid)) == NULL)
			goto failed_gsscred_localname;

		debug2("Mapped the initiator to: %s", pw->pw_name);
		return (xstrdup(pw->pw_name));
	}
failed_gsscred_localname:
#endif /* HAVE_GSSCRED_API */
#ifdef KRB5_GSS
#error "ssh_gssapi_krb5_localname() not implemented"
	if (ssh_gssapi_is_krb5(ctx->mech))
		return (ssh_gssapi_krb5_localname(ctx->src_name));
#endif /* KRB5_GSS */
#ifdef GSI_GSS
#error "ssh_gssapi_gsi_localname() not implemented"
	if (ssh_gssapi_is_gsi(ctx->mech))
		return (ssh_gssapi_gsi_localname(ctx->src_name));
#endif /* GSI_GSS */
	return (NULL);
}
#endif /* GSSAPI */