detect NTLM runtime
[mod_spnego.git] / mod_spnego.c
blob172b3ed72eb44c2d488201626b78937382eb28a1
1 /*
2 * Copyright (c) 2004-2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
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
31 * SUCH DAMAGE.
34 #include <httpd.h>
35 #include <http_config.h>
36 #include <http_log.h>
37 #include <http_request.h>
38 #include <mod_auth.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>
45 #else
46 #include <gssapi.h>
47 #include <krb5.h>
48 #endif
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, ...) \
59 do { \
60 if (c->spnego_debug) { \
61 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, \
62 0, r, __VA_ARGS__); \
63 } \
64 } while (0)
66 typedef struct {
67 unsigned int spnego_on;
68 unsigned int spnego_debug;
69 char *spnego_name;
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 .... */
75 } spnego_config;
77 static const command_rec spnego_cmds[] = {
78 AP_INIT_FLAG("SPNEGOAuth",
79 ap_set_flag_slot,
80 (void *) APR_OFFSETOF(spnego_config, spnego_on),
81 OR_AUTHCFG,
82 "set to 'on' to activate SPNEGO authentication"),
83 AP_INIT_FLAG("SPNEGODebug",
84 ap_set_flag_slot,
85 (void *) APR_OFFSETOF(spnego_config, spnego_debug),
86 OR_AUTHCFG,
87 "set to 'on' to activate SPNEGO debugging"),
88 AP_INIT_TAKE1("SPNEGOAuthAcceptorName",
89 ap_set_string_slot,
90 (void *) APR_OFFSETOF(spnego_config, spnego_name),
91 OR_AUTHCFG,
92 "The acceptor name imported into the GSS-API library"),
93 AP_INIT_FLAG("SPNEGOAuthSaveDelegatedCred",
94 ap_set_flag_slot,
95 (void *) APR_OFFSETOF(spnego_config, spnego_save_cred),
96 OR_AUTHCFG,
97 "set to 'on' to save delegated GSS credential "
98 "(requires non standard API support from GSS-API)"),
99 AP_INIT_TAKE1("SPNEGOAuthKrb5AcceptorIdentity",
100 ap_set_string_slot,
101 (void *) APR_OFFSETOF(spnego_config,
102 spnego_krb5_acceptor_identity),
103 OR_AUTHCFG,
104 "set to Kerberos 5 keytab filename "
105 "(valid if compiled with Kerberos 5 support)"),
106 AP_INIT_FLAG("SPNEGOUseDisplayName",
107 ap_set_flag_slot,
108 (void *) APR_OFFSETOF(spnego_config, spnego_use_display_name),
109 OR_AUTHCFG,
110 "set to 'on' to make SPNEGO use display name instead of "
111 "export name in REMOTE_USER"),
112 AP_INIT_FLAG("SPNEGOSupportsNTLM",
113 ap_set_flag_slot,
114 (void *)APR_OFFSETOF(spnego_config, spnego_supports_ntlm),
115 OR_AUTHCFG,
116 "set to 'off' to make SPNEGO not announce NTLM"),
117 { NULL }
120 static void *
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. */
129 conf->spnego_on = 0;
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);
137 if (major == 0) {
138 (void)gss_test_oid_set_member(&minor, &ntlm_mechanism_oid, mechs, &conf->spnego_supports_ntlm);
139 (void)gss_release_oid_set(&minor, &mechs);
142 return conf;
146 static void
147 k5_save(request_rec * r, gss_cred_id_t cred)
149 #ifdef HAVE_KRB5
150 krb5_context kcontext;
151 krb5_error_code kret;
152 OM_uint32 maj_stat, min_stat;
153 krb5_ccache id;
155 kret = krb5_init_context(&kcontext);
156 if (kret)
157 return;
159 kret = krb5_cc_new_unique(kcontext, "FILE", NULL, &id);
160 if (kret) {
161 krb5_free_context(kcontext);
162 return;
165 maj_stat = gss_krb5_copy_ccache(&min_stat, cred, id);
166 if (maj_stat)
167 krb5_cc_destroy(kcontext, id);
168 else {
169 const char *fn;
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);
178 #endif
181 struct mech_specific {
182 char *oid;
183 size_t oid_len;
184 void (*save_cred) (request_rec *, gss_cred_id_t);
185 } mechs[] = {
186 { "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02", 9, k5_save },
187 { NULL }
190 static const struct mech_specific *
191 find_mech(gss_OID oid)
193 int i;
195 for (i = 0; mechs[i].oid != NULL; i++) {
196 if (oid->length != mechs[i].oid_len)
197 continue;
198 if (memcmp(oid->elements, mechs[i].oid, mechs[i].oid_len) != 0)
199 continue;
200 return &mechs[i];
203 return NULL;
206 static char *MOD_SPNEGO_KEY = "mod-spnego-key";
208 struct spnego_ctx {
209 gss_ctx_id_t ctx;
210 char *user;
211 char *mech;
212 gss_cred_id_t cred;
213 int auth_done;
216 static apr_status_t
217 ctx_cleanup(void *data)
219 struct spnego_ctx *ctx = data;
220 OM_uint32 junk;
221 gss_release_cred(&junk, &ctx->cred);
222 gss_delete_sec_context(&junk, &ctx->ctx, GSS_C_NO_BUFFER);
223 free(ctx);
224 return 0;
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);
232 if (ctx == NULL) {
233 ctx = calloc(1, sizeof(*ctx));
234 if (ctx == NULL)
235 return NULL;
236 apr_pool_userdata_setn(ctx, MOD_SPNEGO_KEY, ctx_cleanup, c->pool);
237 ctx->mech = apr_pstrdup(c->pool, mech);
240 return ctx;
245 static int
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;
253 spnego_config *c;
254 const char *p;
255 char *reply;
256 gss_OID oid;
257 int ret;
258 const char *mech = "unknown";
260 c = ap_get_module_config(r->per_dir_config, &spnego_module);
261 if (c == NULL || !c->spnego_on)
262 return DECLINED;
264 p = apr_table_get(r->headers_in, "Authorization");
265 if (p == NULL) {
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);
274 if (mech == NULL) {
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);
301 out.length = 0;
302 out.value = NULL;
304 #ifdef HAVE_KRB5
305 if (c->spnego_krb5_acceptor_identity)
306 krb5_gss_register_acceptor_identity(c->spnego_krb5_acceptor_identity);
307 #endif
309 ctx = get_gss_context(r->connection, mech);
310 if (ctx == NULL)
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,
318 &ctx->ctx,
319 GSS_C_NO_CREDENTIAL,
320 &in,
321 GSS_C_NO_CHANNEL_BINDINGS,
322 &src_name,
323 &oid,
324 &out,
325 NULL,
326 NULL,
327 &ctx->cred);
329 if ((maj_stat & GSS_S_CONTINUE_NEEDED)) {
330 SPNEGO_DEBUG(c, r, "mod_spnego: continue needed");
331 ret = HTTP_UNAUTHORIZED;
332 goto reply;
333 } else if (maj_stat != GSS_S_COMPLETE) {
334 OM_uint32 message_context = 0, junk, ret2;
335 gss_buffer_desc error;
337 if (ctx->ctx)
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",
342 maj_stat, min_stat);
344 ret2 = gss_display_status(&junk,
345 maj_stat,
346 GSS_C_GSS_CODE,
347 GSS_C_NO_OID,
348 &message_context,
349 &error);
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,
358 min_stat,
359 GSS_C_MECH_CODE,
360 GSS_C_NO_OID,
361 &message_context,
362 &error);
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;
371 goto out;
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;
381 goto out;
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);
389 } else {
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;
396 goto out;
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);
406 ctx->auth_done = 1;
409 r->user = ctx->user;
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) {
416 m = find_mech(oid);
417 if (m && m->save_cred)
418 (*m->save_cred)(r, ctx->cred);
421 ret = OK;
423 reply:
424 if (out.length) {
425 size_t len;
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);
429 reply[len] = '\0';
430 } else
431 reply = NULL;
433 apr_table_setn(r->err_headers_out, WWW_AUTHENTICATE,
434 apr_pstrcat(r->pool, ctx->mech, " ", reply, NULL));
436 out:
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);
440 if (out.value)
441 gss_release_buffer(&min_stat, &out);
443 return ret;
446 static int
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"));
456 return OK;
459 static void
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,
470 spnego_dir_config,
471 NULL,
472 NULL,
473 NULL,
474 spnego_cmds,
475 mod_spnego_register_hooks