c8b001924d
Approved by: re (rgrimes)
232 lines
7.9 KiB
C
232 lines
7.9 KiB
C
/* ====================================================================
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
* ====================================================================
|
|
*/
|
|
|
|
#include "serf.h"
|
|
#include "serf_private.h"
|
|
#include "auth_spnego.h"
|
|
|
|
#ifdef SERF_USE_GSSAPI
|
|
#include <apr_strings.h>
|
|
#include <gssapi/gssapi.h>
|
|
|
|
|
|
/* This module can support all authentication mechanisms as provided by
|
|
the GSS-API implementation, but for now it only supports SPNEGO for
|
|
Negotiate.
|
|
SPNEGO can delegate authentication to Kerberos if supported by the
|
|
host. */
|
|
|
|
#ifndef GSS_SPNEGO_MECHANISM
|
|
static gss_OID_desc spnego_mech_oid = { 6, "\x2b\x06\x01\x05\x05\x02" };
|
|
#define GSS_SPNEGO_MECHANISM &spnego_mech_oid
|
|
#endif
|
|
|
|
struct serf__spnego_context_t
|
|
{
|
|
/* GSSAPI context */
|
|
gss_ctx_id_t gss_ctx;
|
|
|
|
/* Mechanism used to authenticate. */
|
|
gss_OID gss_mech;
|
|
};
|
|
|
|
static void
|
|
log_error(int verbose_flag, apr_socket_t *skt,
|
|
serf__spnego_context_t *ctx,
|
|
OM_uint32 err_maj_stat,
|
|
OM_uint32 err_min_stat,
|
|
const char *msg)
|
|
{
|
|
OM_uint32 maj_stat, min_stat;
|
|
gss_buffer_desc stat_buff;
|
|
OM_uint32 msg_ctx = 0;
|
|
|
|
if (verbose_flag) {
|
|
maj_stat = gss_display_status(&min_stat,
|
|
err_maj_stat,
|
|
GSS_C_GSS_CODE,
|
|
ctx->gss_mech,
|
|
&msg_ctx,
|
|
&stat_buff);
|
|
if (maj_stat == GSS_S_COMPLETE ||
|
|
maj_stat == GSS_S_FAILURE) {
|
|
maj_stat = gss_display_status(&min_stat,
|
|
err_min_stat,
|
|
GSS_C_MECH_CODE,
|
|
ctx->gss_mech,
|
|
&msg_ctx,
|
|
&stat_buff);
|
|
}
|
|
|
|
serf__log_skt(verbose_flag, __FILE__, skt,
|
|
"%s (%x,%d): %s\n", msg,
|
|
err_maj_stat, err_min_stat, stat_buff.value);
|
|
}
|
|
}
|
|
|
|
/* Cleans the GSS context object, when the pool used to create it gets
|
|
cleared or destroyed. */
|
|
static apr_status_t
|
|
cleanup_ctx(void *data)
|
|
{
|
|
serf__spnego_context_t *ctx = data;
|
|
|
|
if (ctx->gss_ctx != GSS_C_NO_CONTEXT) {
|
|
OM_uint32 gss_min_stat, gss_maj_stat;
|
|
|
|
gss_maj_stat = gss_delete_sec_context(&gss_min_stat, &ctx->gss_ctx,
|
|
GSS_C_NO_BUFFER);
|
|
if(GSS_ERROR(gss_maj_stat)) {
|
|
log_error(AUTH_VERBOSE, NULL, ctx,
|
|
gss_maj_stat, gss_min_stat,
|
|
"Error cleaning up GSS security context");
|
|
return SERF_ERROR_AUTHN_FAILED;
|
|
}
|
|
}
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
static apr_status_t
|
|
cleanup_sec_buffer(void *data)
|
|
{
|
|
OM_uint32 min_stat;
|
|
gss_buffer_desc *gss_buf = data;
|
|
|
|
gss_release_buffer(&min_stat, gss_buf);
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
apr_status_t
|
|
serf__spnego_create_sec_context(serf__spnego_context_t **ctx_p,
|
|
const serf__authn_scheme_t *scheme,
|
|
apr_pool_t *result_pool,
|
|
apr_pool_t *scratch_pool)
|
|
{
|
|
serf__spnego_context_t *ctx;
|
|
|
|
ctx = apr_pcalloc(result_pool, sizeof(*ctx));
|
|
|
|
ctx->gss_ctx = GSS_C_NO_CONTEXT;
|
|
ctx->gss_mech = GSS_SPNEGO_MECHANISM;
|
|
|
|
apr_pool_cleanup_register(result_pool, ctx,
|
|
cleanup_ctx,
|
|
apr_pool_cleanup_null);
|
|
|
|
*ctx_p = ctx;
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
apr_status_t
|
|
serf__spnego_reset_sec_context(serf__spnego_context_t *ctx)
|
|
{
|
|
OM_uint32 dummy_stat;
|
|
|
|
if (ctx->gss_ctx)
|
|
(void)gss_delete_sec_context(&dummy_stat, &ctx->gss_ctx,
|
|
GSS_C_NO_BUFFER);
|
|
ctx->gss_ctx = GSS_C_NO_CONTEXT;
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
apr_status_t
|
|
serf__spnego_init_sec_context(serf_connection_t *conn,
|
|
serf__spnego_context_t *ctx,
|
|
const char *service,
|
|
const char *hostname,
|
|
serf__spnego_buffer_t *input_buf,
|
|
serf__spnego_buffer_t *output_buf,
|
|
apr_pool_t *result_pool,
|
|
apr_pool_t *scratch_pool
|
|
)
|
|
{
|
|
gss_buffer_desc gss_input_buf = GSS_C_EMPTY_BUFFER;
|
|
gss_buffer_desc *gss_output_buf_p;
|
|
OM_uint32 gss_min_stat, gss_maj_stat;
|
|
gss_name_t host_gss_name;
|
|
gss_buffer_desc bufdesc;
|
|
gss_OID dummy; /* unused */
|
|
|
|
/* Get the name for the HTTP service at the target host. */
|
|
/* TODO: should be shared between multiple requests. */
|
|
bufdesc.value = apr_pstrcat(scratch_pool, service, "@", hostname, NULL);
|
|
bufdesc.length = strlen(bufdesc.value);
|
|
serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
|
|
"Get principal for %s\n", bufdesc.value);
|
|
gss_maj_stat = gss_import_name (&gss_min_stat, &bufdesc,
|
|
GSS_C_NT_HOSTBASED_SERVICE,
|
|
&host_gss_name);
|
|
if(GSS_ERROR(gss_maj_stat)) {
|
|
log_error(AUTH_VERBOSE, conn->skt, ctx,
|
|
gss_maj_stat, gss_min_stat,
|
|
"Error converting principal name to GSS internal format ");
|
|
return SERF_ERROR_AUTHN_FAILED;
|
|
}
|
|
|
|
/* If the server sent us a token, pass it to gss_init_sec_token for
|
|
validation. */
|
|
gss_input_buf.value = input_buf->value;
|
|
gss_input_buf.length = input_buf->length;
|
|
|
|
gss_output_buf_p = apr_pcalloc(result_pool, sizeof(*gss_output_buf_p));
|
|
|
|
/* Establish a security context to the server. */
|
|
gss_maj_stat = gss_init_sec_context
|
|
(&gss_min_stat, /* minor_status */
|
|
GSS_C_NO_CREDENTIAL, /* XXXXX claimant_cred_handle */
|
|
&ctx->gss_ctx, /* gssapi context handle */
|
|
host_gss_name, /* HTTP@server name */
|
|
ctx->gss_mech, /* mech_type (SPNEGO) */
|
|
GSS_C_MUTUAL_FLAG, /* ensure the peer authenticates itself */
|
|
0, /* default validity period */
|
|
GSS_C_NO_CHANNEL_BINDINGS, /* do not use channel bindings */
|
|
&gss_input_buf, /* server token, initially empty */
|
|
&dummy, /* actual mech type */
|
|
gss_output_buf_p, /* output_token */
|
|
NULL, /* ret_flags */
|
|
NULL /* not interested in remaining validity */
|
|
);
|
|
|
|
apr_pool_cleanup_register(result_pool, gss_output_buf_p,
|
|
cleanup_sec_buffer,
|
|
apr_pool_cleanup_null);
|
|
|
|
output_buf->value = gss_output_buf_p->value;
|
|
output_buf->length = gss_output_buf_p->length;
|
|
|
|
switch(gss_maj_stat) {
|
|
case GSS_S_COMPLETE:
|
|
return APR_SUCCESS;
|
|
case GSS_S_CONTINUE_NEEDED:
|
|
return APR_EAGAIN;
|
|
default:
|
|
log_error(AUTH_VERBOSE, conn->skt, ctx,
|
|
gss_maj_stat, gss_min_stat,
|
|
"Error during Kerberos handshake");
|
|
return SERF_ERROR_AUTHN_FAILED;
|
|
}
|
|
}
|
|
|
|
#endif /* SERF_USE_GSSAPI */
|