gss-serv.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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 2004 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 "monitor_wrap.h"
#include <pwd.h>
#include "ssh-gss.h"
extern char **environ;
extern ServerOptions options;
extern u_char *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, u_int *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 */
return;
}
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 */