/* ==================================================================== * 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 #include /* 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 */