/* 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.
*/
/*
* mod_auth_gss module
*
* Wyllys Ingersoll <wyllys.ingersoll@sun.com>
*
* Based on work by
* Daniel Kouril <kouril@users.sourceforge.net>
* James E. Robinson, III <james@ncstate.net>
* Daniel Henninger <daniel@ncsu.edu>
* Ludek Sulak <xsulak@fi.muni.cz>
*/
/*
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
*/
#include <sys/types.h>
#include <strings.h>
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "ap_config.h"
#include "apr_base64.h"
#include "apr_lib.h"
#include "apr_time.h"
#include "apr_errno.h"
#include "apr_global_mutex.h"
#include "apr_strings.h"
#include "ap_compat.h"
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_ext.h>
module auth_gss_module;
static void *gss_create_dir_config(apr_pool_t *, char *);
int gss_authenticate(request_rec *);
typedef struct {
char *gss_service_name;
char *keytab_file;
int gss_debug;
} gss_auth_config;
static const char *set_service_name(cmd_parms *cmd, void *config,
const char *name)
{
((gss_auth_config *) config)->gss_service_name = (char *)name;
return NULL;
}
static const char *set_keytab_file(cmd_parms *cmd, void *config,
const char *file)
{
((gss_auth_config *) config)->keytab_file = (char *)file;
return NULL;
}
static const char *set_gss_debug(cmd_parms *cmd, void *config,
const char *debugflag)
{
((gss_auth_config *) config)->gss_debug = atoi(debugflag);
return NULL;
}
static const command_rec gss_auth_cmds[] = {
AP_INIT_TAKE1("AuthGSSServiceName", set_service_name, NULL,
OR_AUTHCFG, "Service name used for authentication."),
AP_INIT_TAKE1("AuthGSSKeytabFile", set_keytab_file, NULL,
OR_AUTHCFG,
"Location of Kerberos V5 keytab file."),
AP_INIT_TAKE1("AuthGssDebug", set_gss_debug, NULL,
OR_AUTHCFG,
"Enable debug logging in error_log"),
{ NULL }
};
static void
gss_register_hooks(apr_pool_t *p)
{
ap_hook_check_user_id(gss_authenticate,NULL,NULL,APR_HOOK_MIDDLE);
}
module AP_MODULE_DECLARE_DATA auth_gss_module = {
STANDARD20_MODULE_STUFF,
gss_create_dir_config, /* dir config creater */
NULL, /* dir merger --- default is to override */
NULL, /* server config */
NULL, /* merge server config */
gss_auth_cmds, /* command apr_table_t */
gss_register_hooks /* register hooks */
};
typedef struct {
gss_ctx_id_t context;
gss_cred_id_t server_creds;
} gss_connection_t;
static gss_connection_t *gss_connection = NULL;
static void *
gss_create_dir_config(apr_pool_t *p, char *d)
{
gss_auth_config *rec =
(gss_auth_config *) apr_pcalloc(p, sizeof(gss_auth_config));
((gss_auth_config *)rec)->gss_service_name = "HTTP";
((gss_auth_config *)rec)->keytab_file = "/var/apache2/http.keytab";
((gss_auth_config *)rec)->gss_debug = 0;
return rec;
}
void log_rerror(const char *file, int line, int level, int status,
const request_rec *r, const char *fmt, ...)
{
char errstr[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(errstr, sizeof(errstr), fmt, ap);
va_end(ap);
ap_log_rerror(file, line, level | APLOG_NOERRNO, NULL, r, "%s", errstr);
}
/*********************************************************************
* GSSAPI Authentication
********************************************************************/
static const char *
gss_error_msg(apr_pool_t *p, OM_uint32 maj, OM_uint32 min, char *prefix)
{
OM_uint32 maj_stat, min_stat;
OM_uint32 msg_ctx = 0;
gss_buffer_desc msg;
char *err_msg = (char *)apr_pstrdup(p, prefix);
do {
maj_stat = gss_display_status (&min_stat,
maj, GSS_C_GSS_CODE,
GSS_C_NO_OID, &msg_ctx,
&msg);
if (GSS_ERROR(maj_stat))
break;
err_msg = apr_pstrcat(p, err_msg, ": ", (char*) msg.value,
NULL);
(void) gss_release_buffer(&min_stat, &msg);
maj_stat = gss_display_status (&min_stat,
min, GSS_C_MECH_CODE,
GSS_C_NULL_OID, &msg_ctx,
&msg);
if (!GSS_ERROR(maj_stat)) {
err_msg = apr_pstrcat(p, err_msg,
" (", (char*) msg.value, ")", NULL);
(void) gss_release_buffer(&min_stat, &msg);
}
} while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
return (err_msg);
}
static int
cleanup_gss_connection(void *data)
{
OM_uint32 ret;
OM_uint32 minor_status;
gss_connection_t *gss_conn = (gss_connection_t *)data;
if (data == NULL)
return 0;
if (gss_conn->context != GSS_C_NO_CONTEXT) {
(void) gss_delete_sec_context(&minor_status,
&gss_conn->context,
GSS_C_NO_BUFFER);
}
if (gss_conn->server_creds != GSS_C_NO_CREDENTIAL) {
(void) gss_release_cred(&minor_status, &gss_conn->server_creds);
}
gss_connection = NULL;
return 0;
}
static int
acquire_server_creds(request_rec *r,
gss_auth_config *conf,
gss_OID_set mechset,
gss_cred_id_t *server_creds)
{
int ret = 0;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
OM_uint32 major_status, minor_status, minor_status2;
gss_name_t server_name = GSS_C_NO_NAME;
char buf[1024];
snprintf(buf, sizeof(buf), "%s@%s",
conf->gss_service_name, r->hostname);
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"acquire_server_creds for %s", buf);
input_token.value = buf;
input_token.length = strlen(buf) + 1;
major_status = gss_import_name(&minor_status, &input_token,
GSS_C_NT_HOSTBASED_SERVICE,
&server_name);
if (GSS_ERROR(major_status)) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"%s", gss_error_msg(r->pool, major_status, minor_status,
"gss_import_name() failed"));
return (HTTP_INTERNAL_SERVER_ERROR);
}
major_status = gss_acquire_cred(&minor_status, server_name,
GSS_C_INDEFINITE,
mechset, GSS_C_ACCEPT,
server_creds, NULL, NULL);
if (GSS_ERROR(major_status)) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"%s", gss_error_msg(r->pool, major_status, minor_status,
"gss_acquire_cred() failed"));
ret = HTTP_INTERNAL_SERVER_ERROR;
}
(void) gss_release_name(&minor_status2, &server_name);
return (ret);
}
static int
authenticate_user_gss(request_rec *r, gss_auth_config *conf,
const char *auth_line, char **negotiate_ret_value)
{
int ret = 0;
OM_uint32 major_status, minor_status, minor_status2;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
const char *auth_param = NULL;
gss_name_t client_name = GSS_C_NO_NAME;
gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"authenticate_user_gss called");
*negotiate_ret_value = (char *)"";
if (gss_connection == NULL) {
gss_connection = apr_pcalloc(r->connection->pool, sizeof(*gss_connection));
if (gss_connection == NULL) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"apr_pcalloc() failed (not enough memory)");
ret = HTTP_INTERNAL_SERVER_ERROR;
goto end;
}
(void) memset(gss_connection, 0, sizeof(*gss_connection));
apr_pool_cleanup_register(r->connection->pool, gss_connection,
cleanup_gss_connection, apr_pool_cleanup_null);
}
if (conf->keytab_file) {
char *ktname;
/*
* We don't use the ap_* calls here, since the string
* passed to putenv() will become part of the enviroment
* and shouldn't be free()ed by apache.
*/
ktname = malloc(strlen("KRB5_KTNAME=") + strlen(conf->keytab_file) + 1);
if (ktname == NULL) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"malloc() failed: not enough memory");
ret = HTTP_INTERNAL_SERVER_ERROR;
goto end;
}
/*
* Put the keytab name in the environment so that Kerberos
* knows where to look later.
*/
sprintf(ktname, "KRB5_KTNAME=%s", conf->keytab_file);
putenv(ktname);
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Using keytab: %s", ktname);
}
/* ap_getword() shifts parameter */
auth_param = ap_getword_white(r->pool, &auth_line);
if (auth_param == NULL) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"No Authorization parameter in request from client");
ret = HTTP_UNAUTHORIZED;
goto end;
}
input_token.length = apr_base64_decode_len(auth_param) + 1;
input_token.value = apr_pcalloc(r->connection->pool, input_token.length);
if (input_token.value == NULL) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"apr_pcalloc() failed (not enough memory)");
ret = HTTP_INTERNAL_SERVER_ERROR;
goto end;
}
input_token.length = apr_base64_decode(input_token.value, auth_param);
if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) {
gss_OID_set_desc desiredMechs;
gss_OID_desc client_mech_desc;
gss_OID client_mechoid = &client_mech_desc;
char *mechstr = NULL;
if (!__gss_get_mech_type(client_mechoid, &input_token)) {
mechstr = (char *)__gss_oid_to_mech(client_mechoid);
}
if (mechstr == NULL) {
client_mechoid = GSS_C_NULL_OID;
mechstr = "<unknown>";
}
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Client wants GSS mech: %s", mechstr);
desiredMechs.count = 1;
desiredMechs.elements = client_mechoid;
/* Get creds using the mechanism that the client requested */
ret = acquire_server_creds(r, conf, &desiredMechs,
&gss_connection->server_creds);
if (ret)
goto end;
}
/*
* Try to display the server creds information.
*/
if (conf->gss_debug) {
gss_name_t sname;
gss_buffer_desc dname;
major_status = gss_inquire_cred(&minor_status,
gss_connection->server_creds,
&sname, NULL, NULL, NULL);
if (major_status == GSS_S_COMPLETE) {
major_status = gss_display_name(&minor_status,
sname, &dname, NULL);
}
if (major_status == GSS_S_COMPLETE) {
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"got server creds for: %.*s",
(int)dname.length,
(char *)dname.value);
(void) gss_release_name(&minor_status, &sname);
(void) gss_release_buffer(&minor_status, &dname);
}
}
major_status = gss_accept_sec_context(&minor_status,
&gss_connection->context,
gss_connection->server_creds,
&input_token,
GSS_C_NO_CHANNEL_BINDINGS,
&client_name,
NULL,
&output_token,
NULL,
NULL,
&delegated_cred);
if (output_token.length) {
char *token = NULL;
size_t len;
len = apr_base64_encode_len(output_token.length) + 1;
token = apr_pcalloc(r->connection->pool, len + 1);
if (token == NULL) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"apr_pcalloc() failed (not enough memory)");
ret = HTTP_INTERNAL_SERVER_ERROR;
gss_release_buffer(&minor_status2, &output_token);
goto end;
}
apr_base64_encode(token, output_token.value, output_token.length);
token[len] = '\0';
*negotiate_ret_value = token;
}
if (GSS_ERROR(major_status)) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"%s", gss_error_msg(r->pool, major_status, minor_status,
"gss_accept_sec_context() failed"));
/* Don't offer the Negotiate method again if call to GSS layer failed */
*negotiate_ret_value = NULL;
ret = HTTP_UNAUTHORIZED;
goto end;
}
if (major_status == GSS_S_CONTINUE_NEEDED) {
/*
* Some GSSAPI mechanisms may require multiple iterations to
* establish authentication. Most notably, when MUTUAL_AUTHENTICATION
* flag is used, multiple round trips are needed.
*/
ret = HTTP_UNAUTHORIZED;
goto end;
}
if (client_name != GSS_C_NO_NAME) {
gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
major_status = gss_display_name(&minor_status, client_name,
&name_token, NULL);
if (GSS_ERROR(major_status)) {
log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"%s", gss_error_msg(r->pool, major_status,
minor_status,
"gss_export_name() failed"));
ret = HTTP_INTERNAL_SERVER_ERROR;
goto end;
}
if (name_token.length) {
r->user = apr_pstrdup(r->pool, name_token.value);
gss_release_buffer(&minor_status, &name_token);
}
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Authenticated user: %s",
r->user ? r->user : "<unknown>");
}
r->ap_auth_type = "Negotiate";
ret = OK;
end:
if (delegated_cred)
gss_release_cred(&minor_status, &delegated_cred);
if (output_token.length)
gss_release_buffer(&minor_status, &output_token);
if (client_name != GSS_C_NO_NAME)
gss_release_name(&minor_status, &client_name);
cleanup_gss_connection(gss_connection);
return ret;
}
static int
already_succeeded(request_rec *r)
{
if (ap_is_initial_req(r) || r->ap_auth_type == NULL)
return 0;
return (strcmp(r->ap_auth_type, "Negotiate") ||
(strcmp(r->ap_auth_type, "Basic") && strchr(r->user, '@')));
}
static void
note_gss_auth_failure(request_rec *r, const gss_auth_config *conf,
char *negotiate_ret_value)
{
const char *auth_name = NULL;
int set_basic = 0;
char *negoauth_param;
/* get the user realm specified in .htaccess */
auth_name = ap_auth_name(r);
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"note_gss_auth_failure: auth_name = %s",
auth_name ? auth_name : "<undefined>");
if (negotiate_ret_value != NULL) {
negoauth_param = (*negotiate_ret_value == '\0') ? "Negotiate" :
apr_pstrcat(r->pool, "Negotiate ", negotiate_ret_value, NULL);
apr_table_add(r->err_headers_out, "WWW-Authenticate", negoauth_param);
}
}
int
gss_authenticate(request_rec *r)
{
int ret;
gss_auth_config *conf =
(gss_auth_config *) ap_get_module_config(r->per_dir_config,
&auth_gss_module);
const char *auth_type = NULL;
const char *auth_line = NULL;
const char *type = NULL;
char *negotiate_ret_value;
static int last_return = HTTP_UNAUTHORIZED;
/* get the type specified in .htaccess */
type = ap_auth_type(r);
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"gss_authenticate: type = %s", type);
if (type == NULL || (strcasecmp(type, "GSSAPI") != 0)) {
return DECLINED;
}
/* get what the user sent us in the HTTP header */
auth_line = apr_table_get(r->headers_in, "Authorization");
if (!auth_line) {
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"No authentication data found");
note_gss_auth_failure(r, conf, "\0");
return HTTP_UNAUTHORIZED;
}
auth_type = ap_getword_white(r->pool, &auth_line);
if (already_succeeded(r))
return last_return;
if (strcasecmp(auth_type, "Negotiate") == 0) {
ret = authenticate_user_gss(r, conf, auth_line, &negotiate_ret_value);
} else {
ret = HTTP_UNAUTHORIZED;
}
if (ret == HTTP_UNAUTHORIZED) {
if (conf->gss_debug)
log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Authentication failed.");
note_gss_auth_failure(r, conf, negotiate_ret_value);
}
last_return = ret;
return ret;
}