diff options
Diffstat (limited to 'contrib/serf/auth/auth_spnego_gss.c')
-rw-r--r-- | contrib/serf/auth/auth_spnego_gss.c | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/contrib/serf/auth/auth_spnego_gss.c b/contrib/serf/auth/auth_spnego_gss.c new file mode 100644 index 0000000..aa3ebc6 --- /dev/null +++ b/contrib/serf/auth/auth_spnego_gss.c @@ -0,0 +1,224 @@ +/* Copyright 2009 Justin Erenkrantz and Greg Stein + * + * Licensed 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, const char *filename, + 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(verbose_flag, filename, + "%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, __FILE__, 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__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(AUTH_VERBOSE, __FILE__, "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, __FILE__, 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, __FILE__, ctx, + gss_maj_stat, gss_min_stat, + "Error during Kerberos handshake"); + return SERF_ERROR_AUTHN_FAILED; + } +} + +#endif /* SERF_USE_GSSAPI */ |