4 Copyright (C) 2014 Simo Sorce <simo@redhat.com>
6 Permission is hereby granted, free of charge, to any person obtaining a
7 copy of this software and associated documentation files (the "Software"),
8 to deal in the Software without restriction, including without limitation
9 the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 and/or sell copies of the Software, and to permit persons to whom the
11 Software is furnished to do so, subject to the following conditions:
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 DEALINGS IN THE SOFTWARE.
25 #include "mod_auth_gssapi.h"
27 const gss_OID_desc gss_mech_spnego = {
28 6, "\x2b\x06\x01\x05\x05\x02"
31 #ifdef HAVE_GSSAPI_GSSAPI_NTLMSSP_H
32 const gss_OID_desc gss_mech_ntlmssp_desc = {
33 GSS_NTLMSSP_OID_LENGTH, GSS_NTLMSSP_OID_STRING
35 gss_const_OID gss_mech_ntlmssp = &gss_mech_ntlmssp_desc;
37 const gss_OID_set_desc gss_mech_set_ntlmssp_desc = {
38 1, discard_const(&gss_mech_ntlmssp_desc)
40 gss_const_OID_set gss_mech_set_ntlmssp = &gss_mech_set_ntlmssp_desc;
43 gss_OID gss_mech_ntlmssp = GSS_C_NO_OID;
44 gss_OID_set gss_mech_set_ntlmssp = GSS_C_NO_OID_SET;
47 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
49 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
51 APLOG_USE_MODULE(auth_gssapi);
53 static char *mag_status(request_rec *req, int type, uint32_t err)
55 uint32_t maj_ret, min_ret;
64 maj_ret = gss_display_status(&min_ret, err, type,
65 GSS_C_NO_OID, &msg_ctx, &text);
66 if (maj_ret != GSS_S_COMPLETE) {
72 msg_ret = apr_psprintf(req->pool, "%s, %*s",
73 msg_ret, len, (char *)text.value);
75 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
77 gss_release_buffer(&min_ret, &text);
78 } while (msg_ctx != 0);
83 char *mag_error(request_rec *req, const char *msg, uint32_t maj, uint32_t min)
88 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
89 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
90 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
93 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
95 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
96 apr_pool_t *temp, server_rec *s)
98 /* FIXME: create mutex to deal with connections and contexts ? */
99 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
100 mag_post_config_session();
101 ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
106 static int mag_pre_connection(conn_rec *c, void *csd)
110 mc = mag_new_conn_ctx(c->pool);
111 mc->is_preserved = true;
112 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
116 static apr_status_t mag_conn_destroy(void *ptr)
118 struct mag_conn *mc = (struct mag_conn *)ptr;
122 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
127 struct mag_conn *mag_new_conn_ctx(apr_pool_t *pool)
131 mc = apr_pcalloc(pool, sizeof(struct mag_conn));
132 apr_pool_create(&mc->pool, pool);
133 /* register the context in the memory pool, so it can be freed
134 * when the connection/request is terminated */
135 apr_pool_cleanup_register(mc->pool, (void *)mc,
136 mag_conn_destroy, apr_pool_cleanup_null);
140 static void mag_conn_clear(struct mag_conn *mc)
142 (void)mag_conn_destroy(mc);
145 apr_pool_clear(mc->pool);
147 memset(mc, 0, sizeof(struct mag_conn));
151 static bool mag_conn_is_https(conn_rec *c)
154 if (mag_is_https(c)) return true;
160 static bool mag_acquire_creds(request_rec *req,
161 struct mag_config *cfg,
162 gss_OID_set desired_mechs,
163 gss_cred_usage_t cred_usage,
164 gss_cred_id_t *creds,
165 gss_OID_set *actual_mechs)
168 #ifdef HAVE_CRED_STORE
169 gss_const_key_value_set_t store = cfg->cred_store;
171 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
172 desired_mechs, cred_usage, store, creds,
175 maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
176 desired_mechs, cred_usage, creds,
180 if (GSS_ERROR(maj)) {
181 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
182 mag_error(req, "gss_acquire_cred[_from]() "
183 "failed to get server creds",
191 #ifdef HAVE_CRED_STORE
192 static char *escape(apr_pool_t *pool, const char *name,
193 char find, const char *replace)
195 char *escaped = NULL;
200 namecopy = apr_pstrdup(pool, name);
202 p = strchr(namecopy, find);
203 if (!p) return namecopy;
208 /* terminate previous segment */
211 escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
213 escaped = apr_pstrcat(pool, n, replace, NULL);
215 /* move to next segment */
219 /* append last segment if any */
221 escaped = apr_pstrcat(pool, escaped, n, NULL);
227 char *mag_gss_name_to_ccache_name(request_rec *req,
228 char *dir, const char *gss_name)
232 /* We need to escape away '/', we can't have path separators in
233 * a ccache file name */
234 /* first double escape the esacping char (~) if any */
235 escaped = escape(req->pool, gss_name, '~', "~~");
236 /* then escape away the separator (/) if any */
237 escaped = escape(req->pool, escaped, '/', "~");
239 return apr_psprintf(req->pool, "%s/%s", dir, escaped);
242 static void mag_store_deleg_creds(request_rec *req,
243 char *dir, const char *gss_name,
244 gss_cred_id_t delegated_cred)
246 gss_key_value_element_desc element;
247 gss_key_value_set_desc store;
250 element.key = "ccache";
251 store.elements = &element;
254 ccname = mag_gss_name_to_ccache_name(req, dir, gss_name);
255 element.value = apr_psprintf(req->pool, "FILE:%s", ccname);
257 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
258 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
259 if (GSS_ERROR(maj)) {
260 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
261 mag_error(req, "failed to store delegated creds",
267 static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
270 char *auth_header_value;
272 auth_header_value = ap_getword_white(pool, auth_header);
273 if (!auth_header_value) return false;
274 value->length = apr_base64_decode_len(auth_header_value) + 1;
275 value->value = apr_pcalloc(pool, value->length);
276 if (!value->value) return false;
277 value->length = apr_base64_decode(value->value, auth_header_value);
282 static bool is_mech_allowed(gss_OID_set allowed_mechs, gss_const_OID mech,
283 bool multi_step_supported)
285 if (mech == GSS_C_NO_OID) return false;
287 if (!multi_step_supported && gss_oid_equal(gss_mech_ntlmssp, mech))
290 if (allowed_mechs == GSS_C_NO_OID_SET) return true;
292 for (int i = 0; i < allowed_mechs->count; i++) {
293 if (gss_oid_equal(&allowed_mechs->elements[i], mech)) {
300 #define AUTH_TYPE_NEGOTIATE 0
301 #define AUTH_TYPE_BASIC 1
302 #define AUTH_TYPE_RAW_NTLM 2
303 const char *auth_types[] = {
310 const char *mag_str_auth_type(int auth_type)
312 return auth_types[auth_type];
315 gss_OID_set mag_filter_unwanted_mechs(gss_OID_set src)
317 gss_const_OID unwanted_mechs[] = {
328 if (src == GSS_C_NO_OID_SET) return GSS_C_NO_OID_SET;
330 for (int i = 0; unwanted_mechs[i] != GSS_C_NO_OID; i++) {
331 maj = gss_test_oid_set_member(&min,
332 discard_const(unwanted_mechs[i]),
337 maj = gss_create_empty_oid_set(&min, &dst);
338 if (maj != GSS_S_COMPLETE) {
339 return GSS_C_NO_OID_SET;
341 for (int i = 0; i < src->count; i++) {
343 for (int j = 0; unwanted_mechs[j] != GSS_C_NO_OID; j++) {
344 if (gss_oid_equal(&src->elements[i], unwanted_mechs[j])) {
349 if (present) continue;
350 maj = gss_add_oid_set_member(&min, &src->elements[i], &dst);
351 if (maj != GSS_S_COMPLETE) {
352 gss_release_oid_set(&min, &dst);
353 return GSS_C_NO_OID_SET;
361 static bool mag_auth_basic(request_rec *req,
362 struct mag_config *cfg,
363 gss_buffer_desc ba_user,
364 gss_buffer_desc ba_pwd,
365 gss_cred_usage_t cred_usage,
368 gss_cred_id_t *delegated_cred,
371 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
372 const char *user_ccache = NULL;
373 const char *orig_ccache = NULL;
374 long long unsigned int rndname;
377 gss_name_t user = GSS_C_NO_NAME;
378 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
379 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
380 gss_name_t server = GSS_C_NO_NAME;
381 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
382 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
383 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
384 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
385 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
386 gss_OID_set allowed_mechs;
387 gss_OID_set filtered_mechs;
388 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
389 uint32_t init_flags = 0;
394 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
395 if (GSS_ERROR(maj)) {
396 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
398 mag_error(req, "gss_import_name() failed",
403 if (cfg->basic_mechs) {
404 allowed_mechs = cfg->basic_mechs;
405 } else if (cfg->allowed_mechs) {
406 allowed_mechs = cfg->allowed_mechs;
408 struct mag_server_config *scfg;
409 /* Try to fetch the default set if not explicitly configured,
410 * We need to do this because gss_acquire_cred_with_password()
411 * is currently limited to acquire creds for a single "default"
412 * mechanism if no desired mechanisms are passed in. This causes
413 * authentication to fail for secondary mechanisms as no user
414 * credentials are generated for those. */
415 scfg = ap_get_module_config(req->server->module_config,
416 &auth_gssapi_module);
417 /* In the worst case scenario default_mechs equals to GSS_C_NO_OID_SET.
418 * This generally causes only the krb5 mechanism to be tried due
419 * to implementation constraints, but may change in future. */
420 allowed_mechs = scfg->default_mechs;
423 /* Remove Spnego if present, or we'd repeat failed authentiations
424 * multiple times, one within Spnego and then again with an explicit
425 * mechanism. We would normally just force Spnego and use
426 * gss_set_neg_mechs, but due to the way we source the server name
427 * and the fact MIT up to 1.14 at least does no handle union names,
428 * we can't provide spnego with a server name that can be used by
429 * multiple mechanisms, causing any but the first mechanism to fail.
430 * Also remove unwanted krb mechs, or AS requests will be repeated
431 * multiple times uselessly.
433 filtered_mechs = mag_filter_unwanted_mechs(allowed_mechs);
434 if (filtered_mechs == allowed_mechs) {
435 /* in case filtered_mechs was not allocated here don't free it */
436 filtered_mechs = GSS_C_NO_OID_SET;
437 } else if (filtered_mechs == GSS_C_NO_OID_SET) {
438 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
439 "failure while filtering mechs, aborting");
442 /* use the filtered list */
443 allowed_mechs = filtered_mechs;
446 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
447 /* If we are using the krb5 mechanism make sure to set a per thread
448 * memory ccache so that there can't be interferences between threads.
449 * Also make sure we have new cache so no cached results end up being
450 * used. Some implementations of gss_acquire_cred_with_password() do
451 * not reacquire creds if cached ones are around, failing to check
452 * again for the password. */
453 maj = gss_test_oid_set_member(&min, discard_const(gss_mech_krb5),
454 allowed_mechs, &present);
455 if (GSS_ERROR(maj)) {
456 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
458 mag_error(req, "gss_test_oid_set_member() failed",
463 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
464 sizeof(long long unsigned int));
465 if (rs != APR_SUCCESS) {
466 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
467 "Failed to generate random ccache name");
470 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
471 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
472 if (GSS_ERROR(maj)) {
473 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
475 mag_error(req, "gss_krb5_ccache_name() "
476 "failed", maj, min));
482 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
486 &user_cred, &actual_mechs, NULL);
487 if (GSS_ERROR(maj)) {
488 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
490 mag_error(req, "gss_acquire_cred_with_password() "
491 "failed", maj, min));
495 /* must acquire creds based on the actual mechs we want to try */
496 if (!mag_acquire_creds(req, cfg, actual_mechs,
497 cred_usage, &acquired_cred, NULL)) {
501 if (cred_usage == GSS_C_BOTH) {
502 /* must acquire with GSS_C_ACCEPT to get the server name */
503 if (!mag_acquire_creds(req, cfg, actual_mechs,
504 GSS_C_ACCEPT, &server_cred, NULL)) {
508 server_cred = acquired_cred;
511 #ifdef HAVE_CRED_STORE
512 if (cfg->deleg_ccache_dir) {
513 /* delegate ourselves credentials so we store them as requested */
514 init_flags |= GSS_C_DELEG_FLAG;
518 for (int i = 0; i < actual_mechs->count; i++) {
520 /* free these if looping */
521 gss_release_buffer(&min, &output);
522 gss_release_buffer(&min, &input);
523 gss_release_name(&min, &server);
525 maj = gss_inquire_cred_by_mech(&min, server_cred,
526 &actual_mechs->elements[i],
527 &server, NULL, NULL, NULL);
528 if (GSS_ERROR(maj)) {
529 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
530 "%s", mag_error(req, "gss_inquired_cred_by_mech() "
531 "failed", maj, min));
536 /* output and input are inverted here, this is intentional */
537 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
538 &actual_mechs->elements[i], init_flags,
539 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
540 NULL, &input, NULL, NULL);
541 if (GSS_ERROR(maj)) {
542 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
543 "%s", mag_error(req, "gss_init_sec_context() "
544 "failed", maj, min));
547 gss_release_buffer(&min, &output);
548 maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
549 &input, GSS_C_NO_CHANNEL_BINDINGS,
550 client, mech_type, &output, NULL,
551 vtime, delegated_cred);
552 if (GSS_ERROR(maj)) {
553 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
554 "%s", mag_error(req, "gss_accept_sec_context()"
555 " failed", maj, min));
558 gss_release_buffer(&min, &input);
559 } while (maj == GSS_S_CONTINUE_NEEDED);
561 if (maj == GSS_S_COMPLETE) {
568 gss_release_buffer(&min, &output);
569 gss_release_buffer(&min, &input);
570 gss_release_name(&min, &server);
571 if (server_cred != acquired_cred)
572 gss_release_cred(&min, &server_cred);
573 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
574 gss_release_cred(&min, &acquired_cred);
575 gss_release_name(&min, &user);
576 gss_release_cred(&min, &user_cred);
577 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
578 gss_release_oid_set(&min, &actual_mechs);
579 gss_release_oid_set(&min, &filtered_mechs);
580 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
581 if (user_ccache != NULL) {
582 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
583 if (maj != GSS_S_COMPLETE) {
584 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
585 "Failed to restore per-thread ccache, %s",
586 mag_error(req, "gss_krb5_ccache_name() "
587 "failed", maj, min));
594 struct mag_req_cfg *mag_init_cfg(request_rec *req)
596 struct mag_server_config *scfg;
597 struct mag_req_cfg *req_cfg = apr_pcalloc(req->pool,
598 sizeof(struct mag_req_cfg));
600 req_cfg->cfg = ap_get_module_config(req->per_dir_config,
601 &auth_gssapi_module);
603 scfg = ap_get_module_config(req->server->module_config,
604 &auth_gssapi_module);
606 if (req_cfg->cfg->allowed_mechs) {
607 req_cfg->desired_mechs = req_cfg->cfg->allowed_mechs;
609 /* Use the default set if not explicitly configured */
610 req_cfg->desired_mechs = scfg->default_mechs;
613 if (req_cfg->cfg->mag_skey) {
614 req_cfg->mag_skey = req_cfg->cfg->mag_skey;
616 /* Use server random key if not explicitly configured */
617 req_cfg->mag_skey = scfg->mag_skey;
620 if (req->proxyreq == PROXYREQ_PROXY) {
621 req_cfg->req_proto = "Proxy-Authorization";
622 req_cfg->rep_proto = "Proxy-Authenticate";
624 req_cfg->req_proto = "Authorization";
625 req_cfg->rep_proto = "WWW-Authenticate";
626 req_cfg->use_sessions = req_cfg->cfg->use_sessions;
627 req_cfg->send_persist = req_cfg->cfg->send_persist;
633 #ifdef HAVE_CRED_STORE
634 static bool use_s4u2proxy(struct mag_req_cfg *req_cfg) {
635 if (req_cfg->cfg->use_s4u2proxy) {
636 if (req_cfg->cfg->deleg_ccache_dir != NULL) {
639 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req_cfg->req,
640 "S4U2 Proxy requested but GssapiDelegCcacheDir "
641 "is not set. Constrained delegation disabled!");
648 static int mag_auth(request_rec *req)
652 struct mag_req_cfg *req_cfg;
653 struct mag_config *cfg;
654 const char *auth_header;
655 char *auth_header_type;
656 int ret = HTTP_UNAUTHORIZED;
657 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
659 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
660 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
661 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
662 gss_buffer_desc ba_user;
663 gss_buffer_desc ba_pwd;
664 gss_name_t client = GSS_C_NO_NAME;
665 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
666 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
667 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
672 gss_OID mech_type = GSS_C_NO_OID;
673 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
674 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
675 struct mag_conn *mc = NULL;
677 bool send_auth_header = true;
679 type = ap_auth_type(req);
680 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
684 req_cfg = mag_init_cfg(req);
688 desired_mechs = req_cfg->desired_mechs;
690 /* implicit auth for subrequests if main auth already happened */
691 if (!ap_is_initial_req(req) && req->main != NULL) {
692 type = ap_auth_type(req->main);
693 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
694 /* warn if the subrequest location and the main request
695 * location have different configs */
696 if (cfg != ap_get_module_config(req->main->per_dir_config,
697 &auth_gssapi_module)) {
698 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
699 req, "Subrequest authentication bypass on "
700 "location with different configuration!");
702 if (req->main->user) {
703 req->user = apr_pstrdup(req->pool, req->main->user);
706 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
707 "The main request is tasked to establish the "
708 "security context, can't proceed!");
709 return HTTP_UNAUTHORIZED;
712 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
713 "Subrequest GSSAPI auth with no auth on the main "
714 "request. This operation may fail if other "
715 "subrequests already established a context or the "
716 "mechanism requires multiple roundtrips.");
721 if (!mag_conn_is_https(req->connection)) {
722 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
723 "Not a TLS connection, refusing to authenticate!");
728 if (cfg->gss_conn_ctx) {
729 mc = (struct mag_conn *)ap_get_module_config(
730 req->connection->conn_config,
731 &auth_gssapi_module);
733 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
734 "Failed to retrieve connection context!");
739 /* if available, session always supersedes connection bound data */
740 if (req_cfg->use_sessions) {
741 mag_check_session(req_cfg, &mc);
744 auth_header = apr_table_get(req->headers_in, req_cfg->req_proto);
747 if (mc->established &&
748 (auth_header == NULL) &&
749 (mc->auth_type != AUTH_TYPE_BASIC)) {
750 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
751 "Already established context found!");
752 mag_set_req_data(req, cfg, mc);
758 /* no preserved mc, create one just for this request */
759 mc = mag_new_conn_ctx(req->pool);
763 /* We can proceed only if we do have an auth header */
764 if (!auth_header) goto done;
766 auth_header_type = ap_getword_white(req->pool, &auth_header);
767 if (!auth_header_type) goto done;
769 /* We got auth header, sending auth header would mean re-auth */
770 send_auth_header = !cfg->negotiate_once;
772 for (i = 0; auth_types[i] != NULL; i++) {
773 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
780 case AUTH_TYPE_NEGOTIATE:
781 if (!parse_auth_header(req->pool, &auth_header, &input)) {
785 case AUTH_TYPE_BASIC:
786 if (!cfg->use_basic_auth) {
790 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
791 if (!ba_pwd.value) goto done;
792 ba_user.value = ap_getword_nulls_nc(req->pool,
793 (char **)&ba_pwd.value, ':');
794 if (!ba_user.value) goto done;
796 if (((char *)ba_user.value)[0] == '\0' ||
797 ((char *)ba_pwd.value)[0] == '\0') {
798 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
799 "Invalid empty user or password for Basic Auth");
802 ba_user.length = strlen(ba_user.value);
803 ba_pwd.length = strlen(ba_pwd.value);
805 if (mc->is_preserved && mc->established &&
806 mag_basic_check(req_cfg, mc, ba_user, ba_pwd)) {
807 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
808 "Already established BASIC AUTH context found!");
809 mag_set_req_data(req, cfg, mc);
816 case AUTH_TYPE_RAW_NTLM:
817 if (!is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
818 cfg->gss_conn_ctx)) {
819 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
820 "NTLM Authentication is not allowed!");
824 if (!parse_auth_header(req->pool, &auth_header, &input)) {
828 desired_mechs = discard_const(gss_mech_set_ntlmssp);
835 if (mc->established) {
836 /* if we are re-authenticating make sure the conn context
837 * is cleaned up so we do not accidentally reuse an existing
838 * established context */
842 mc->auth_type = auth_type;
844 #ifdef HAVE_CRED_STORE
845 if (use_s4u2proxy(req_cfg)) {
846 cred_usage = GSS_C_BOTH;
850 if (auth_type == AUTH_TYPE_BASIC) {
851 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
852 cred_usage, &client, &mech_type,
853 &delegated_cred, &vtime)) {
859 if (!mag_acquire_creds(req, cfg, desired_mechs,
860 cred_usage, &acquired_cred, NULL)) {
864 if (auth_type == AUTH_TYPE_NEGOTIATE &&
865 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
866 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
867 if (GSS_ERROR(maj)) {
868 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
869 mag_error(req, "gss_set_neg_mechs() failed",
875 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
876 &input, GSS_C_NO_CHANNEL_BINDINGS,
877 &client, &mech_type, &output, NULL, &vtime,
879 if (GSS_ERROR(maj)) {
880 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
881 mag_error(req, "gss_accept_sec_context() failed",
884 } else if (maj == GSS_S_CONTINUE_NEEDED) {
885 if (!mc->is_preserved) {
886 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
887 "Mechanism needs continuation but neither "
888 "GssapiConnectionBound nor "
889 "GssapiUseSessions are available");
890 gss_release_buffer(&min, &output);
893 /* auth not complete send token and wait next packet */
898 maj = gss_display_name(&min, client, &name, NULL);
899 if (GSS_ERROR(maj)) {
900 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
901 mag_error(req, "gss_display_name() failed",
906 mc->gss_name = apr_pstrndup(req->pool, name.value, name.length);
907 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
908 vtime = MIN_SESS_EXP_TIME;
910 mc->expiration = time(NULL) + vtime;
912 mag_get_name_attributes(req, cfg, client, mc);
914 #ifdef HAVE_CRED_STORE
915 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
916 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, mc->gss_name,
918 mc->delegated = true;
922 if (cfg->map_to_local) {
923 maj = gss_localname(&min, client, mech_type, &lname);
924 if (maj != GSS_S_COMPLETE) {
925 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
926 mag_error(req, "gss_localname() failed", maj, min));
929 mc->user_name = apr_pstrndup(req->pool, lname.value, lname.length);
931 mc->user_name = apr_pstrdup(mc->pool, mc->gss_name);
934 mc->established = true;
935 if (auth_type == AUTH_TYPE_BASIC) {
936 mag_basic_cache(req_cfg, mc, ba_user, ba_pwd);
938 if (req_cfg->use_sessions) {
939 mag_attempt_session(req_cfg, mc);
942 /* Now set request data and env vars */
943 mag_set_req_data(req, cfg, mc);
945 if (req_cfg->send_persist)
946 apr_table_set(req->headers_out, "Persistent-Auth",
947 cfg->gss_conn_ctx ? "true" : "false");
953 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
954 int prefixlen = strlen(mag_str_auth_type(auth_type)) + 1;
955 replen = apr_base64_encode_len(output.length) + 1;
956 reply = apr_pcalloc(req->pool, prefixlen + replen);
958 memcpy(reply, mag_str_auth_type(auth_type), prefixlen - 1);
959 reply[prefixlen - 1] = ' ';
960 apr_base64_encode(&reply[prefixlen], output.value, output.length);
961 apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply);
963 } else if (ret == HTTP_UNAUTHORIZED) {
964 if (send_auth_header) {
965 apr_table_add(req->err_headers_out,
966 req_cfg->rep_proto, "Negotiate");
967 if (is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
968 cfg->gss_conn_ctx)) {
969 apr_table_add(req->err_headers_out, req_cfg->rep_proto,
973 if (cfg->use_basic_auth) {
974 apr_table_add(req->err_headers_out, req_cfg->rep_proto,
975 apr_psprintf(req->pool, "Basic realm=\"%s\"",
980 if (ctx != GSS_C_NO_CONTEXT)
981 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
982 gss_release_cred(&min, &acquired_cred);
983 gss_release_cred(&min, &delegated_cred);
984 gss_release_buffer(&min, &output);
985 gss_release_name(&min, &client);
986 gss_release_buffer(&min, &name);
987 gss_release_buffer(&min, &lname);
992 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
994 struct mag_config *cfg;
996 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
1002 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
1004 struct mag_config *cfg = (struct mag_config *)mconfig;
1005 cfg->ssl_only = on ? true : false;
1009 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
1011 struct mag_config *cfg = (struct mag_config *)mconfig;
1012 cfg->map_to_local = on ? true : false;
1016 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
1018 struct mag_config *cfg = (struct mag_config *)mconfig;
1019 cfg->gss_conn_ctx = on ? true : false;
1023 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1025 struct mag_config *cfg = (struct mag_config *)mconfig;
1026 cfg->send_persist = on ? true : false;
1030 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1032 struct mag_config *cfg = (struct mag_config *)mconfig;
1033 cfg->use_sessions = on ? true : false;
1037 #ifdef HAVE_CRED_STORE
1038 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1040 struct mag_config *cfg = (struct mag_config *)mconfig;
1041 cfg->use_s4u2proxy = on ? true : false;
1047 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1049 struct mag_config *cfg = (struct mag_config *)mconfig;
1050 struct databuf keys;
1056 if (strncmp(w, "key:", 4) != 0) {
1057 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1058 "Invalid key format, expected prefix 'key:'");
1063 l = apr_base64_decode_len(k);
1064 val = apr_palloc(parms->temp_pool, l);
1066 keys.length = (int)apr_base64_decode_binary(val, k);
1067 keys.value = (unsigned char *)val;
1069 if (keys.length != 32) {
1070 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1071 "Invalid key length, expected 32 got %d", keys.length);
1075 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1077 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1078 "Failed to import sealing key!");
1083 #ifdef HAVE_CRED_STORE
1085 #define MAX_CRED_OPTIONS 10
1087 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1090 struct mag_config *cfg = (struct mag_config *)mconfig;
1091 gss_key_value_element_desc *elements;
1100 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1101 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1105 key = apr_pstrndup(parms->pool, w, (p-w));
1106 value = apr_pstrdup(parms->pool, p + 1);
1108 if (!cfg->cred_store) {
1109 cfg->cred_store = apr_pcalloc(parms->pool,
1110 sizeof(gss_key_value_set_desc));
1111 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1112 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1115 elements = cfg->cred_store->elements;
1116 count = cfg->cred_store->count;
1118 if (count >= MAX_CRED_OPTIONS) {
1119 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1120 "Too many GssapiCredStore options (MAX: %d)",
1124 cfg->cred_store->count++;
1126 elements[count].key = key;
1127 elements[count].value = value;
1132 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1135 struct mag_config *cfg = (struct mag_config *)mconfig;
1137 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1143 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1144 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1146 struct mag_config *cfg = (struct mag_config *)mconfig;
1148 cfg->use_basic_auth = on ? true : false;
1153 static apr_status_t mag_oid_set_destroy(void *ptr)
1156 gss_OID_set set = (gss_OID_set)ptr;
1157 (void)gss_release_oid_set(&min, &set);
1161 static bool mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1162 bool add_spnego, const char *w)
1164 gss_buffer_desc buf = { 0 };
1168 bool release_oid = false;
1170 if (NULL == *oidset) {
1171 maj = gss_create_empty_oid_set(&min, &set);
1172 if (maj != GSS_S_COMPLETE) {
1173 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1174 "gss_create_empty_oid_set() failed.");
1175 *oidset = GSS_C_NO_OID_SET;
1179 oid = discard_const(&gss_mech_spnego);
1180 maj = gss_add_oid_set_member(&min, oid, &set);
1181 if (maj != GSS_S_COMPLETE) {
1182 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1183 "gss_add_oid_set_member() failed.");
1184 (void)gss_release_oid_set(&min, &set);
1185 *oidset = GSS_C_NO_OID_SET;
1189 /* register in the pool so it can be released once the server
1191 apr_pool_cleanup_register(parms->pool, (void *)set,
1192 mag_oid_set_destroy,
1193 apr_pool_cleanup_null);
1199 if (strcmp(w, "krb5") == 0) {
1200 oid = discard_const(gss_mech_krb5);
1201 } else if (strcmp(w, "iakerb") == 0) {
1202 oid = discard_const(gss_mech_iakerb);
1203 } else if (strcmp(w, "ntlmssp") == 0) {
1204 oid = discard_const(gss_mech_ntlmssp);
1206 buf.value = discard_const(w);
1207 buf.length = strlen(w);
1208 maj = gss_str_to_oid(&min, &buf, &oid);
1209 if (maj != GSS_S_COMPLETE) {
1210 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1211 "Unrecognized GSSAPI Mechanism: [%s]", w);
1216 maj = gss_add_oid_set_member(&min, oid, &set);
1217 if (maj != GSS_S_COMPLETE) {
1218 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1219 "gss_add_oid_set_member() failed for [%s].", w);
1222 (void)gss_release_oid(&min, &oid);
1228 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1231 struct mag_config *cfg = (struct mag_config *)mconfig;
1233 if (!mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w))
1234 return "Failed to apply GssapiAllowedMech directive";
1239 static const char *mag_negotiate_once(cmd_parms *parms, void *mconfig, int on)
1241 struct mag_config *cfg = (struct mag_config *)mconfig;
1243 cfg->negotiate_once = on ? true : false;
1247 #define GSS_NAME_ATTR_USERDATA "GSS Name Attributes Userdata"
1249 static apr_status_t mag_name_attrs_cleanup(void *data)
1251 struct mag_config *cfg = (struct mag_config *)data;
1252 free(cfg->name_attributes);
1253 cfg->name_attributes = NULL;
1257 static const char *mag_name_attrs(cmd_parms *parms, void *mconfig,
1260 struct mag_config *cfg = (struct mag_config *)mconfig;
1266 if (!cfg->name_attributes) {
1267 size = sizeof(struct mag_name_attributes)
1268 + (sizeof(struct mag_na_map) * 16);
1269 } else if (cfg->name_attributes->map_count % 16 == 0) {
1270 size = sizeof(struct mag_name_attributes)
1271 + (sizeof(struct mag_na_map)
1272 * (cfg->name_attributes->map_count + 16));
1275 tmp_na = realloc(cfg->name_attributes, size);
1276 if (!tmp_na) apr_pool_abort_get(cfg->pool)(ENOMEM);
1278 if (cfg->name_attributes) {
1279 size_t empty = (sizeof(struct mag_na_map) * 16);
1280 memset(tmp_na + size - empty, 0, empty);
1282 memset(tmp_na, 0, size);
1284 cfg->name_attributes = (struct mag_name_attributes *)tmp_na;
1285 apr_pool_userdata_setn(cfg, GSS_NAME_ATTR_USERDATA,
1286 mag_name_attrs_cleanup, cfg->pool);
1291 if (strcmp(w, "json") == 0) {
1292 cfg->name_attributes->output_json = true;
1294 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1295 "Invalid Name Attributes value [%s].", w);
1300 c = cfg->name_attributes->map_count;
1301 cfg->name_attributes->map[c].env_name = apr_pstrndup(cfg->pool, w, p-w);
1303 cfg->name_attributes->map[c].attr_name = apr_pstrdup(cfg->pool, p);
1304 cfg->name_attributes->map_count += 1;
1309 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1310 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1313 struct mag_config *cfg = (struct mag_config *)mconfig;
1315 if (!mag_list_of_mechs(parms, &cfg->basic_mechs, false, w))
1316 return "Failed to apply GssapiBasicAuthMech directive";
1322 static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
1324 struct mag_server_config *scfg;
1328 scfg = apr_pcalloc(p, sizeof(struct mag_server_config));
1330 maj = gss_indicate_mechs(&min, &scfg->default_mechs);
1331 if (maj != GSS_S_COMPLETE) {
1332 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
1333 "gss_indicate_mechs() failed");
1335 /* Register the set in pool */
1336 apr_pool_cleanup_register(p, (void *)scfg->default_mechs,
1337 mag_oid_set_destroy, apr_pool_cleanup_null);
1340 rc = SEAL_KEY_CREATE(p, &scfg->mag_skey, NULL);
1342 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
1343 "Failed to generate random sealing key!");
1349 static const command_rec mag_commands[] = {
1350 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1351 "Work only if connection is SSL Secured"),
1352 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1353 "Translate principals to local names"),
1354 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1355 "Authentication is bound to the TCP connection"),
1356 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1357 "Send Persitent-Auth header according to connection bound"),
1358 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1359 "Authentication uses mod_sessions to hold status"),
1360 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1361 "Key Used to seal session data."),
1362 #ifdef HAVE_CRED_STORE
1363 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1364 "Initializes credentials for s4u2proxy usage"),
1365 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1366 "Credential Store"),
1367 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1368 OR_AUTHCFG, "Directory to store delegated credentials"),
1370 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1371 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1372 "Allows use of Basic Auth for authentication"),
1373 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1374 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1376 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1377 "Allowed Mechanisms"),
1378 AP_INIT_FLAG("GssapiNegotiateOnce", mag_negotiate_once, NULL, OR_AUTHCFG,
1379 "Don't resend negotiate header on negotiate failure"),
1380 AP_INIT_RAW_ARGS("GssapiNameAttributes", mag_name_attrs, NULL, OR_AUTHCFG,
1381 "Name Attributes to be exported as environ variables"),
1386 mag_register_hooks(apr_pool_t *p)
1388 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1389 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1390 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1393 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1395 STANDARD20_MODULE_STUFF,
1396 mag_create_dir_config,
1398 mag_create_server_config,