2 * Wyllys Ingersoll <wyllys.ingersoll@sun.com>
5 * Daniel Kouril <kouril@users.sourceforge.net>
6 * James E. Robinson, III <james@ncstate.net>
7 * Daniel Henninger <daniel@ncsu.edu>
8 * Ludek Sulak <xsulak@fi.muni.cz>
11 /* ====================================================================
12 * The Apache Software License, Version 1.1
14 * Copyright (c) 2000-2003 The Apache Software Foundation. All rights
17 * Redistribution and use in source and binary forms, with or without
18 * modification, are permitted provided that the following conditions
21 * 1. Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
24 * 2. Redistributions in binary form must reproduce the above copyright
25 * notice, this list of conditions and the following disclaimer in
26 * the documentation and/or other materials provided with the
29 * 3. The end-user documentation included with the redistribution,
30 * if any, must include the following acknowledgment:
31 * "This product includes software developed by the
32 * Apache Software Foundation (http://www.apache.org/)."
33 * Alternately, this acknowledgment may appear in the software itself,
34 * if and wherever such third-party acknowledgments normally appear.
36 * 4. The names "Apache" and "Apache Software Foundation" must
37 * not be used to endorse or promote products derived from this
38 * software without prior written permission. For written
39 * permission, please contact apache@apache.org.
41 * 5. Products derived from this software may not be called "Apache",
42 * nor may "Apache" appear in their name, without prior written
43 * permission of the Apache Software Foundation.
45 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
46 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
47 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
48 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
49 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
50 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
51 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
52 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
53 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
54 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
55 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57 * ====================================================================
59 * This software consists of voluntary contributions made by many
60 * individuals on behalf of the Apache Software Foundation. For more
61 * information on the Apache Software Foundation, please see
62 * <http://www.apache.org/>.
64 * Portions of this software are based upon public domain software
65 * originally written at the National Center for Supercomputing Applications,
66 * University of Illinois, Urbana-Champaign.
70 * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
74 #include <sys/types.h>
78 #include "http_config.h"
79 #include "http_core.h"
81 #include "http_protocol.h"
82 #include "http_request.h"
83 #include "ap_config.h"
84 #include "apr_base64.h"
87 #include "apr_errno.h"
88 #include "apr_global_mutex.h"
89 #include "apr_strings.h"
90 #include "ap_compat.h"
92 #include <gssapi/gssapi.h>
93 #include <gssapi/gssapi_ext.h>
95 module auth_gss_module
;
97 static void *gss_create_dir_config(apr_pool_t
*, char *);
99 int gss_authenticate(request_rec
*);
102 char *gss_service_name
;
107 static const char *set_service_name(cmd_parms
*cmd
, void *config
,
110 ((gss_auth_config
*) config
)->gss_service_name
= (char *)name
;
114 static const char *set_keytab_file(cmd_parms
*cmd
, void *config
,
117 ((gss_auth_config
*) config
)->keytab_file
= (char *)file
;
121 static const char *set_gss_debug(cmd_parms
*cmd
, void *config
,
122 const char *debugflag
)
124 ((gss_auth_config
*) config
)->gss_debug
= atoi(debugflag
);
128 static const command_rec gss_auth_cmds
[] = {
129 AP_INIT_TAKE1("AuthGSSServiceName", set_service_name
, NULL
,
130 OR_AUTHCFG
, "Service name used for authentication."),
132 AP_INIT_TAKE1("AuthGSSKeytabFile", set_keytab_file
, NULL
,
134 "Location of Kerberos V5 keytab file."),
136 AP_INIT_TAKE1("AuthGssDebug", set_gss_debug
, NULL
,
138 "Enable debug logging in error_log"),
143 gss_register_hooks(apr_pool_t
*p
)
145 ap_hook_check_user_id(gss_authenticate
,NULL
,NULL
,APR_HOOK_MIDDLE
);
148 module AP_MODULE_DECLARE_DATA auth_gss_module
= {
149 STANDARD20_MODULE_STUFF
,
150 gss_create_dir_config
, /* dir config creater */
151 NULL
, /* dir merger --- default is to override */
152 NULL
, /* server config */
153 NULL
, /* merge server config */
154 gss_auth_cmds
, /* command apr_table_t */
155 gss_register_hooks
/* register hooks */
159 gss_ctx_id_t context
;
160 gss_cred_id_t server_creds
;
163 static gss_connection_t
*gss_connection
= NULL
;
166 gss_create_dir_config(apr_pool_t
*p
, char *d
)
168 gss_auth_config
*rec
=
169 (gss_auth_config
*) apr_pcalloc(p
, sizeof(gss_auth_config
));
171 ((gss_auth_config
*)rec
)->gss_service_name
= "HTTP";
172 ((gss_auth_config
*)rec
)->keytab_file
= "/var/apache2/http.keytab";
173 ((gss_auth_config
*)rec
)->gss_debug
= 0;
178 #define log_rerror ap_log_rerror
180 /*********************************************************************
181 * GSSAPI Authentication
182 ********************************************************************/
184 gss_error_msg(apr_pool_t
*p
, OM_uint32 maj
, OM_uint32 min
, char *prefix
)
186 OM_uint32 maj_stat
, min_stat
;
187 OM_uint32 msg_ctx
= 0;
190 char *err_msg
= (char *)apr_pstrdup(p
, prefix
);
193 maj_stat
= gss_display_status (&min_stat
,
195 GSS_C_NO_OID
, &msg_ctx
,
197 if (GSS_ERROR(maj_stat
))
200 err_msg
= apr_pstrcat(p
, err_msg
, ": ", (char*) msg
.value
,
202 (void) gss_release_buffer(&min_stat
, &msg
);
204 maj_stat
= gss_display_status (&min_stat
,
205 min
, GSS_C_MECH_CODE
,
206 GSS_C_NULL_OID
, &msg_ctx
,
208 if (!GSS_ERROR(maj_stat
)) {
209 err_msg
= apr_pstrcat(p
, err_msg
,
210 " (", (char*) msg
.value
, ")", NULL
);
211 (void) gss_release_buffer(&min_stat
, &msg
);
213 } while (!GSS_ERROR(maj_stat
) && msg_ctx
!= 0);
219 cleanup_gss_connection(void *data
)
222 OM_uint32 minor_status
;
223 gss_connection_t
*gss_conn
= (gss_connection_t
*)data
;
228 if (gss_conn
->context
!= GSS_C_NO_CONTEXT
) {
229 (void) gss_delete_sec_context(&minor_status
,
234 if (gss_conn
->server_creds
!= GSS_C_NO_CREDENTIAL
) {
235 (void) gss_release_cred(&minor_status
, &gss_conn
->server_creds
);
238 gss_connection
= NULL
;
244 acquire_server_creds(request_rec
*r
,
245 gss_auth_config
*conf
,
247 gss_cred_id_t
*server_creds
)
250 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
251 OM_uint32 major_status
, minor_status
, minor_status2
;
252 gss_name_t server_name
= GSS_C_NO_NAME
;
255 snprintf(buf
, sizeof(buf
), "%s@%s",
256 conf
->gss_service_name
, r
->hostname
);
259 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
260 "acquire_server_creds for %s", buf
);
262 input_token
.value
= buf
;
263 input_token
.length
= strlen(buf
) + 1;
265 major_status
= gss_import_name(&minor_status
, &input_token
,
266 GSS_C_NT_HOSTBASED_SERVICE
,
269 if (GSS_ERROR(major_status
)) {
270 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
271 "%s", gss_error_msg(r
->pool
, major_status
, minor_status
,
272 "gss_import_name() failed"));
273 return (HTTP_INTERNAL_SERVER_ERROR
);
276 major_status
= gss_acquire_cred(&minor_status
, server_name
,
278 mechset
, GSS_C_ACCEPT
,
279 server_creds
, NULL
, NULL
);
281 if (GSS_ERROR(major_status
)) {
282 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
283 "%s", gss_error_msg(r
->pool
, major_status
, minor_status
,
284 "gss_acquire_cred() failed"));
285 ret
= HTTP_INTERNAL_SERVER_ERROR
;
287 (void) gss_release_name(&minor_status2
, &server_name
);
293 authenticate_user_gss(request_rec
*r
, gss_auth_config
*conf
,
294 const char *auth_line
, char **negotiate_ret_value
)
297 OM_uint32 major_status
, minor_status
, minor_status2
;
298 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
299 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
300 const char *auth_param
= NULL
;
301 gss_name_t client_name
= GSS_C_NO_NAME
;
302 gss_cred_id_t delegated_cred
= GSS_C_NO_CREDENTIAL
;
305 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
306 "authenticate_user_gss called");
308 *negotiate_ret_value
= (char *)"";
310 if (gss_connection
== NULL
) {
311 gss_connection
= apr_pcalloc(r
->connection
->pool
, sizeof(*gss_connection
));
312 if (gss_connection
== NULL
) {
313 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
314 "apr_pcalloc() failed (not enough memory)");
315 ret
= HTTP_INTERNAL_SERVER_ERROR
;
318 (void) memset(gss_connection
, 0, sizeof(*gss_connection
));
319 apr_pool_cleanup_register(r
->connection
->pool
, gss_connection
,
320 cleanup_gss_connection
, apr_pool_cleanup_null
);
323 if (conf
->keytab_file
) {
326 * We don't use the ap_* calls here, since the string
327 * passed to putenv() will become part of the enviroment
328 * and shouldn't be free()ed by apache.
330 ktname
= malloc(strlen("KRB5_KTNAME=") + strlen(conf
->keytab_file
) + 1);
331 if (ktname
== NULL
) {
332 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
333 "malloc() failed: not enough memory");
334 ret
= HTTP_INTERNAL_SERVER_ERROR
;
338 * Put the keytab name in the environment so that Kerberos
339 * knows where to look later.
341 sprintf(ktname
, "KRB5_KTNAME=%s", conf
->keytab_file
);
344 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
, "Using keytab: %s", ktname
);
347 /* ap_getword() shifts parameter */
348 auth_param
= ap_getword_white(r
->pool
, &auth_line
);
349 if (auth_param
== NULL
) {
350 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
351 "No Authorization parameter in request from client");
352 ret
= HTTP_UNAUTHORIZED
;
356 input_token
.length
= apr_base64_decode_len(auth_param
) + 1;
357 input_token
.value
= apr_pcalloc(r
->connection
->pool
, input_token
.length
);
359 if (input_token
.value
== NULL
) {
360 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
361 "apr_pcalloc() failed (not enough memory)");
362 ret
= HTTP_INTERNAL_SERVER_ERROR
;
365 input_token
.length
= apr_base64_decode(input_token
.value
, auth_param
);
367 if (gss_connection
->server_creds
== GSS_C_NO_CREDENTIAL
) {
368 gss_OID_set_desc desiredMechs
;
369 gss_OID_desc client_mech_desc
;
370 gss_OID client_mechoid
= &client_mech_desc
;
371 char *mechstr
= NULL
;
373 if (!__gss_get_mech_type(client_mechoid
, &input_token
)) {
374 mechstr
= (char *)__gss_oid_to_mech(client_mechoid
);
376 if (mechstr
== NULL
) {
377 client_mechoid
= GSS_C_NULL_OID
;
378 mechstr
= "<unknown>";
382 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
383 "Client wants GSS mech: %s", mechstr
);
385 desiredMechs
.count
= 1;
386 desiredMechs
.elements
= client_mechoid
;
388 /* Get creds using the mechanism that the client requested */
389 ret
= acquire_server_creds(r
, conf
, &desiredMechs
,
390 &gss_connection
->server_creds
);
395 * Try to display the server creds information.
397 if (conf
->gss_debug
) {
399 gss_buffer_desc dname
;
401 major_status
= gss_inquire_cred(&minor_status
,
402 gss_connection
->server_creds
,
403 &sname
, NULL
, NULL
, NULL
);
404 if (major_status
== GSS_S_COMPLETE
) {
405 major_status
= gss_display_name(&minor_status
,
406 sname
, &dname
, NULL
);
408 if (major_status
== GSS_S_COMPLETE
) {
409 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
410 "got server creds for: %.*s",
412 (char *)dname
.value
);
413 (void) gss_release_name(&minor_status
, &sname
);
414 (void) gss_release_buffer(&minor_status
, &dname
);
418 major_status
= gss_accept_sec_context(&minor_status
,
419 &gss_connection
->context
,
420 gss_connection
->server_creds
,
422 GSS_C_NO_CHANNEL_BINDINGS
,
430 if (output_token
.length
) {
433 len
= apr_base64_encode_len(output_token
.length
) + 1;
434 token
= apr_pcalloc(r
->connection
->pool
, len
+ 1);
436 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
437 "apr_pcalloc() failed (not enough memory)");
438 ret
= HTTP_INTERNAL_SERVER_ERROR
;
439 gss_release_buffer(&minor_status2
, &output_token
);
442 apr_base64_encode(token
, output_token
.value
, output_token
.length
);
444 *negotiate_ret_value
= token
;
447 if (GSS_ERROR(major_status
)) {
448 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
449 "%s", gss_error_msg(r
->pool
, major_status
, minor_status
,
450 "gss_accept_sec_context() failed"));
451 /* Don't offer the Negotiate method again if call to GSS layer failed */
452 *negotiate_ret_value
= NULL
;
453 ret
= HTTP_UNAUTHORIZED
;
457 if (major_status
== GSS_S_CONTINUE_NEEDED
) {
459 * Some GSSAPI mechanisms may require multiple iterations to
460 * establish authentication. Most notably, when MUTUAL_AUTHENTICATION
461 * flag is used, multiple round trips are needed.
463 ret
= HTTP_UNAUTHORIZED
;
467 if (client_name
!= GSS_C_NO_NAME
) {
468 gss_buffer_desc name_token
= GSS_C_EMPTY_BUFFER
;
469 major_status
= gss_display_name(&minor_status
, client_name
,
472 if (GSS_ERROR(major_status
)) {
473 log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
474 "%s", gss_error_msg(r
->pool
, major_status
,
476 "gss_export_name() failed"));
477 ret
= HTTP_INTERNAL_SERVER_ERROR
;
480 if (name_token
.length
) {
481 r
->user
= apr_pstrdup(r
->pool
, name_token
.value
);
482 gss_release_buffer(&minor_status
, &name_token
);
486 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
487 "Authenticated user: %s",
488 r
->user
? r
->user
: "<unknown>");
490 r
->ap_auth_type
= "Negotiate";
494 gss_release_cred(&minor_status
, &delegated_cred
);
496 if (output_token
.length
)
497 gss_release_buffer(&minor_status
, &output_token
);
499 if (client_name
!= GSS_C_NO_NAME
)
500 gss_release_name(&minor_status
, &client_name
);
502 cleanup_gss_connection(gss_connection
);
508 already_succeeded(request_rec
*r
)
510 if (ap_is_initial_req(r
) || r
->ap_auth_type
== NULL
)
513 return (strcmp(r
->ap_auth_type
, "Negotiate") ||
514 (strcmp(r
->ap_auth_type
, "Basic") && strchr(r
->user
, '@')));
518 note_gss_auth_failure(request_rec
*r
, const gss_auth_config
*conf
,
519 char *negotiate_ret_value
)
521 const char *auth_name
= NULL
;
523 char *negoauth_param
;
525 /* get the user realm specified in .htaccess */
526 auth_name
= ap_auth_name(r
);
529 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
530 "note_gss_auth_failure: auth_name = %s",
531 auth_name
? auth_name
: "<undefined>");
533 if (negotiate_ret_value
!= NULL
) {
534 negoauth_param
= (*negotiate_ret_value
== '\0') ? "Negotiate" :
535 apr_pstrcat(r
->pool
, "Negotiate ", negotiate_ret_value
, NULL
);
536 apr_table_add(r
->err_headers_out
, "WWW-Authenticate", negoauth_param
);
541 gss_authenticate(request_rec
*r
)
544 gss_auth_config
*conf
=
545 (gss_auth_config
*) ap_get_module_config(r
->per_dir_config
,
547 const char *auth_type
= NULL
;
548 const char *auth_line
= NULL
;
549 const char *type
= NULL
;
550 char *negotiate_ret_value
;
551 static int last_return
= HTTP_UNAUTHORIZED
;
553 /* get the type specified in .htaccess */
554 type
= ap_auth_type(r
);
557 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
558 "gss_authenticate: type = %s", type
);
560 if (type
== NULL
|| (strcasecmp(type
, "GSSAPI") != 0)) {
564 /* get what the user sent us in the HTTP header */
565 auth_line
= apr_table_get(r
->headers_in
, "Authorization");
569 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
570 "No authentication data found");
571 note_gss_auth_failure(r
, conf
, "\0");
572 return HTTP_UNAUTHORIZED
;
574 auth_type
= ap_getword_white(r
->pool
, &auth_line
);
576 if (already_succeeded(r
))
579 if (strcasecmp(auth_type
, "Negotiate") == 0) {
580 ret
= authenticate_user_gss(r
, conf
, auth_line
, &negotiate_ret_value
);
582 ret
= HTTP_UNAUTHORIZED
;
585 if (ret
== HTTP_UNAUTHORIZED
) {
587 log_rerror(APLOG_MARK
, APLOG_DEBUG
, 0, r
,
588 "Authentication failed.");
589 note_gss_auth_failure(r
, conf
, negotiate_ret_value
);