2 * Copyright (c) 2004-2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 #include <http_config.h>
37 #include <http_request.h>
39 #include <apr_strings.h>
40 #include <apr_tables.h>
41 #include <apr_base64.h>
43 #ifdef HAVE_GSS_FRAMEWORK
44 #include <GSS/gssapi.h>
50 extern module AP_MODULE_DECLARE_DATA spnego_module
;
52 static const char *NEGOTIATE_NAME
= "Negotiate";
53 static const char *NTLM_NAME
= "NTLM";
54 static const char *WWW_AUTHENTICATE
= "WWW-Authenticate";
56 static gss_OID_desc ntlm_mechanism_oid
= { 10, (void *)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" };
58 #define SPNEGO_DEBUG(c, r, ...) \
60 if (c->spnego_debug) { \
61 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, \
67 unsigned int spnego_on
;
68 unsigned int spnego_debug
;
70 unsigned int spnego_save_cred
;
71 char *spnego_krb5_acceptor_identity
;
72 unsigned int spnego_use_display_name
;
73 int spnego_supports_ntlm
;
74 /* allowed mechs .... */
77 static const command_rec spnego_cmds
[] = {
78 AP_INIT_FLAG("SPNEGOAuth",
80 (void *) APR_OFFSETOF(spnego_config
, spnego_on
),
82 "set to 'on' to activate SPNEGO authentication"),
83 AP_INIT_FLAG("SPNEGODebug",
85 (void *) APR_OFFSETOF(spnego_config
, spnego_debug
),
87 "set to 'on' to activate SPNEGO debugging"),
88 AP_INIT_TAKE1("SPNEGOAuthAcceptorName",
90 (void *) APR_OFFSETOF(spnego_config
, spnego_name
),
92 "The acceptor name imported into the GSS-API library"),
93 AP_INIT_FLAG("SPNEGOAuthSaveDelegatedCred",
95 (void *) APR_OFFSETOF(spnego_config
, spnego_save_cred
),
97 "set to 'on' to save delegated GSS credential "
98 "(requires non standard API support from GSS-API)"),
99 AP_INIT_TAKE1("SPNEGOAuthKrb5AcceptorIdentity",
101 (void *) APR_OFFSETOF(spnego_config
,
102 spnego_krb5_acceptor_identity
),
104 "set to Kerberos 5 keytab filename "
105 "(valid if compiled with Kerberos 5 support)"),
106 AP_INIT_FLAG("SPNEGOUseDisplayName",
108 (void *) APR_OFFSETOF(spnego_config
, spnego_use_display_name
),
110 "set to 'on' to make SPNEGO use display name instead of "
111 "export name in REMOTE_USER"),
112 AP_INIT_FLAG("SPNEGOSupportsNTLM",
114 (void *)APR_OFFSETOF(spnego_config
, spnego_supports_ntlm
),
116 "set to 'off' to make SPNEGO not announce NTLM"),
121 spnego_dir_config(apr_pool_t
* p
, char *d
)
123 spnego_config
*conf
= (spnego_config
*) apr_pcalloc(p
, sizeof(spnego_config
));
124 OM_uint32 minor
, major
;
125 gss_OID_set mechs
= NULL
;
127 /* Set the defaults. */
130 conf
->spnego_name
= NULL
;
131 conf
->spnego_save_cred
= 0;
132 conf
->spnego_krb5_acceptor_identity
= NULL
;
133 conf
->spnego_use_display_name
= 1;
134 conf
->spnego_supports_ntlm
= 0;
136 major
= gss_indicate_mechs(&minor
, &mechs
);
138 (void)gss_test_oid_set_member(&minor
, &ntlm_mechanism_oid
, mechs
, &conf
->spnego_supports_ntlm
);
139 (void)gss_release_oid_set(&minor
, &mechs
);
147 k5_save(request_rec
* r
, gss_cred_id_t cred
)
150 krb5_context kcontext
;
151 krb5_error_code kret
;
152 OM_uint32 maj_stat
, min_stat
;
155 kret
= krb5_init_context(&kcontext
);
159 kret
= krb5_cc_new_unique(kcontext
, "FILE", NULL
, &id
);
161 krb5_free_context(kcontext
);
165 maj_stat
= gss_krb5_copy_ccache(&min_stat
, cred
, id
);
167 krb5_cc_destroy(kcontext
, id
);
171 fn
= apr_psprintf(r
->pool
, "FILE:%s", krb5_cc_get_name(kcontext
, id
));
173 krb5_cc_close(kcontext
, id
);
174 apr_table_set(r
->subprocess_env
, "KRB5CCNAME", fn
);
177 krb5_free_context(kcontext
);
181 struct mech_specific
{
184 void (*save_cred
) (request_rec
*, gss_cred_id_t
);
186 { "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02", 9, k5_save
},
190 static const struct mech_specific
*
191 find_mech(gss_OID oid
)
195 for (i
= 0; mechs
[i
].oid
!= NULL
; i
++) {
196 if (oid
->length
!= mechs
[i
].oid_len
)
198 if (memcmp(oid
->elements
, mechs
[i
].oid
, mechs
[i
].oid_len
) != 0)
206 static char *MOD_SPNEGO_KEY
= "mod-spnego-key";
217 ctx_cleanup(void *data
)
219 struct spnego_ctx
*ctx
= data
;
221 gss_release_cred(&junk
, &ctx
->cred
);
222 gss_delete_sec_context(&junk
, &ctx
->ctx
, GSS_C_NO_BUFFER
);
227 static struct spnego_ctx
*
228 get_gss_context(conn_rec
*c
, const char *mech
)
230 struct spnego_ctx
*ctx
= NULL
;
231 apr_pool_userdata_get((void **) &ctx
, MOD_SPNEGO_KEY
, c
->pool
);
233 ctx
= calloc(1, sizeof(*ctx
));
236 apr_pool_userdata_setn(ctx
, MOD_SPNEGO_KEY
, ctx_cleanup
, c
->pool
);
237 ctx
->mech
= apr_pstrdup(c
->pool
, mech
);
246 check_user_id(request_rec
*r
)
248 const struct mech_specific
*m
;
249 OM_uint32 maj_stat
, min_stat
;
250 gss_buffer_desc in
, out
;
251 gss_name_t src_name
= GSS_C_NO_NAME
;
252 struct spnego_ctx
*ctx
= NULL
;
258 const char *mech
= "unknown";
260 c
= ap_get_module_config(r
->per_dir_config
, &spnego_module
);
261 if (c
== NULL
|| !c
->spnego_on
)
264 p
= apr_table_get(r
->headers_in
, "Authorization");
266 SPNEGO_DEBUG(c
, r
, "mod_spnego: no Authorization header");
267 apr_table_addn(r
->err_headers_out
, WWW_AUTHENTICATE
, NEGOTIATE_NAME
);
268 if (c
->spnego_supports_ntlm
)
269 apr_table_addn(r
->err_headers_out
, WWW_AUTHENTICATE
, NTLM_NAME
);
270 return HTTP_UNAUTHORIZED
;
273 mech
= ap_getword_white(r
->pool
, &p
);
275 SPNEGO_DEBUG(c
, r
, "mod_spnego: Authorization header malformed");
276 apr_table_addn(r
->err_headers_out
, WWW_AUTHENTICATE
, NEGOTIATE_NAME
);
277 if (c
->spnego_supports_ntlm
)
278 apr_table_addn(r
->err_headers_out
, WWW_AUTHENTICATE
, NTLM_NAME
);
279 return HTTP_UNAUTHORIZED
;
282 int mechs_not_matched
;
284 mechs_not_matched
= strcmp(mech
, NEGOTIATE_NAME
) != 0;
286 if (mechs_not_matched
&& c
->spnego_supports_ntlm
)
287 mechs_not_matched
= strcmp(mech
, NTLM_NAME
) != 0;
289 if (mechs_not_matched
) {
290 SPNEGO_DEBUG(c
, r
, "mod_spnego: auth not supported: %s", mech
);
291 apr_table_addn(r
->err_headers_out
, WWW_AUTHENTICATE
, NEGOTIATE_NAME
);
292 if (c
->spnego_supports_ntlm
)
293 apr_table_addn(r
->err_headers_out
, WWW_AUTHENTICATE
, NTLM_NAME
);
295 return HTTP_UNAUTHORIZED
;
298 in
.value
= apr_palloc(r
->pool
, apr_base64_decode_len(p
));
299 in
.length
= apr_base64_decode_binary(in
.value
, p
);
305 if (c
->spnego_krb5_acceptor_identity
)
306 krb5_gss_register_acceptor_identity(c
->spnego_krb5_acceptor_identity
);
309 ctx
= get_gss_context(r
->connection
, mech
);
311 return HTTP_UNAUTHORIZED
;
313 if (!ctx
->auth_done
) {
315 SPNEGO_DEBUG(c
, r
, "mod_spnego: calling accept_sec_context");
317 maj_stat
= gss_accept_sec_context(&min_stat
,
321 GSS_C_NO_CHANNEL_BINDINGS
,
329 if ((maj_stat
& GSS_S_CONTINUE_NEEDED
)) {
330 SPNEGO_DEBUG(c
, r
, "mod_spnego: continue needed");
331 ret
= HTTP_UNAUTHORIZED
;
333 } else if (maj_stat
!= GSS_S_COMPLETE
) {
334 OM_uint32 message_context
= 0, junk
, ret2
;
335 gss_buffer_desc error
;
338 gss_delete_sec_context(&junk
, &ctx
->ctx
, GSS_C_NO_BUFFER
);
340 ap_log_rerror(APLOG_MARK
, APLOG_NOERRNO
| APLOG_ERR
, 0, r
,
341 "mod_spnego: accept_sec_context %d/%d",
344 ret2
= gss_display_status(&junk
,
350 if (ret2
== GSS_S_COMPLETE
) {
351 ap_log_rerror(APLOG_MARK
, APLOG_NOERRNO
| APLOG_ERR
, 0, r
,
352 "mod_spnego: major: %.*s",
353 (int) error
.length
, (char *) error
.value
);
354 gss_release_buffer(&junk
, &error
);
357 ret2
= gss_display_status(&junk
,
363 if (ret2
== GSS_S_COMPLETE
) {
364 ap_log_rerror(APLOG_MARK
, APLOG_NOERRNO
| APLOG_ERR
, 0, r
,
365 "mod_spnego: minor: %.*s",
366 (int) error
.length
, (char *) error
.value
);
367 gss_release_buffer(&junk
, &error
);
370 ret
= HTTP_UNAUTHORIZED
;
374 if (c
->spnego_use_display_name
) {
375 gss_buffer_desc name
;
377 maj_stat
= gss_display_name(&min_stat
, src_name
, &name
, NULL
);
378 if (maj_stat
!= GSS_S_COMPLETE
) {
379 SPNEGO_DEBUG(c
, r
, "mod_spnego: failed to display name");
380 ret
= HTTP_UNAUTHORIZED
;
384 ctx
->user
= apr_palloc(r
->connection
->pool
, name
.length
+ 1);
385 memcpy(ctx
->user
, name
.value
, name
.length
);
386 ctx
->user
[name
.length
] = '\0';
388 gss_release_buffer(&min_stat
, &name
);
390 gss_buffer_desc name
;
392 maj_stat
= gss_export_name(&min_stat
, src_name
, &name
);
393 if (maj_stat
!= GSS_S_COMPLETE
) {
394 SPNEGO_DEBUG(c
, r
, "mod_spnego: failed to export name");
395 ret
= HTTP_UNAUTHORIZED
;
399 ctx
->user
= apr_palloc(r
->connection
->pool
,
400 apr_base64_encode_len(name
.length
));
401 apr_base64_encode(ctx
->user
, name
.value
, name
.length
);
403 gss_release_buffer(&min_stat
, &name
);
410 r
->ap_auth_type
= ctx
->mech
;
412 apr_table_set(r
->subprocess_env
, "NEGOTIATE_INITIATOR_NAME", ctx
->user
);
414 /* push cred to disk */
415 if (ctx
->cred
&& c
->spnego_save_cred
) {
417 if (m
&& m
->save_cred
)
418 (*m
->save_cred
)(r
, ctx
->cred
);
426 len
= apr_base64_encode_len(out
.length
);
427 reply
= apr_palloc(r
->pool
, len
+ 1);
428 apr_base64_encode(reply
, out
.value
, out
.length
);
433 apr_table_setn(r
->err_headers_out
, WWW_AUTHENTICATE
,
434 apr_pstrcat(r
->pool
, ctx
->mech
, " ", reply
, NULL
));
437 SPNEGO_DEBUG(c
, r
, "mod_spnego: %s: done: %x/%x", mech
, maj_stat
, min_stat
);
438 if (src_name
!= GSS_C_NO_NAME
)
439 gss_release_name(&min_stat
, &src_name
);
441 gss_release_buffer(&min_stat
, &out
);
447 post_config(apr_pool_t
*p
, apr_pool_t
*plog
, apr_pool_t
*ptemp
, server_rec
*s
)
450 * turn off the reply cache, it just hurts us since browsers are
451 * too fast and does the wrong thing.
453 * XXX should force running over https.
455 putenv(strdup("KRB5RCACHETYPE=none"));
460 mod_spnego_register_hooks (apr_pool_t
*p
)
462 ap_hook_post_config(post_config
, NULL
, NULL
, APR_HOOK_MIDDLE
);
463 ap_hook_check_user_id(check_user_id
, NULL
, NULL
, APR_HOOK_MIDDLE
);
467 module AP_MODULE_DECLARE_DATA spnego_module
=
469 STANDARD20_MODULE_STUFF
,
475 mod_spnego_register_hooks