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 static char *mag_error(request_rec *req, const char *msg,
84 uint32_t maj, uint32_t min)
89 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
90 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
91 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
94 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
96 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
97 apr_pool_t *temp, server_rec *s)
99 /* FIXME: create mutex to deal with connections and contexts ? */
100 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
101 mag_post_config_session();
102 ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
107 static int mag_pre_connection(conn_rec *c, void *csd)
111 mc = mag_new_conn_ctx(c->pool);
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);
141 static void mag_conn_clear(struct mag_conn *mc)
143 (void)mag_conn_destroy(mc);
146 apr_pool_clear(mc->pool);
148 memset(mc, 0, sizeof(struct mag_conn));
152 static bool mag_conn_is_https(conn_rec *c)
155 if (mag_is_https(c)) return true;
161 static bool mag_acquire_creds(request_rec *req,
162 struct mag_config *cfg,
163 gss_OID_set desired_mechs,
164 gss_cred_usage_t cred_usage,
165 gss_cred_id_t *creds,
166 gss_OID_set *actual_mechs)
169 #ifdef HAVE_CRED_STORE
170 gss_const_key_value_set_t store = cfg->cred_store;
172 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
173 desired_mechs, cred_usage, store, creds,
176 maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
177 desired_mechs, cred_usage, creds,
181 if (GSS_ERROR(maj)) {
182 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
183 mag_error(req, "gss_acquire_cred[_from]() "
184 "failed to get server creds",
192 #ifdef HAVE_CRED_STORE
193 static char *escape(apr_pool_t *pool, const char *name,
194 char find, const char *replace)
196 char *escaped = NULL;
201 namecopy = apr_pstrdup(pool, name);
203 p = strchr(namecopy, find);
204 if (!p) return namecopy;
209 /* terminate previous segment */
212 escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
214 escaped = apr_pstrcat(pool, n, replace, NULL);
216 /* move to next segment */
220 /* append last segment if any */
222 escaped = apr_pstrcat(pool, escaped, n, NULL);
228 static char *mag_gss_name_to_ccache_name(request_rec *req,
229 char *dir, const char *gss_name)
233 /* We need to escape away '/', we can't have path separators in
234 * a ccache file name */
235 /* first double escape the esacping char (~) if any */
236 escaped = escape(req->pool, gss_name, '~', "~~");
237 /* then escape away the separator (/) if any */
238 escaped = escape(req->pool, escaped, '/', "~");
240 return apr_psprintf(req->pool, "%s/%s", dir, escaped);
243 static void mag_set_KRB5CCANME(request_rec *req, char *ccname)
249 status = apr_stat(&finfo, ccname, APR_FINFO_MIN, req->pool);
250 if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
251 /* set the file cache anyway, but warn */
252 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
253 "KRB5CCNAME file (%s) lookup failed!", ccname);
256 value = apr_psprintf(req->pool, "FILE:%s", ccname);
257 apr_table_set(req->subprocess_env, "KRB5CCNAME", value);
260 static void mag_store_deleg_creds(request_rec *req,
261 char *dir, char *clientname,
262 gss_cred_id_t delegated_cred,
265 gss_key_value_element_desc element;
266 gss_key_value_set_desc store;
269 element.key = "ccache";
270 store.elements = &element;
273 ccname = mag_gss_name_to_ccache_name(req, dir, clientname);
274 element.value = apr_psprintf(req->pool, "FILE:%s", ccname);
276 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
277 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
278 if (GSS_ERROR(maj)) {
279 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
280 mag_error(req, "failed to store delegated creds",
284 *ccachefile = ccname;
288 static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
291 char *auth_header_value;
293 auth_header_value = ap_getword_white(pool, auth_header);
294 if (!auth_header_value) return false;
295 value->length = apr_base64_decode_len(auth_header_value) + 1;
296 value->value = apr_pcalloc(pool, value->length);
297 if (!value->value) return false;
298 value->length = apr_base64_decode(value->value, auth_header_value);
303 static bool is_mech_allowed(gss_OID_set allowed_mechs, gss_const_OID mech,
304 bool multi_step_supported)
306 if (mech == GSS_C_NO_OID) return false;
308 if (!multi_step_supported && gss_oid_equal(gss_mech_ntlmssp, mech))
311 if (allowed_mechs == GSS_C_NO_OID_SET) return true;
313 for (int i = 0; i < allowed_mechs->count; i++) {
314 if (gss_oid_equal(&allowed_mechs->elements[i], mech)) {
321 #define AUTH_TYPE_NEGOTIATE 0
322 #define AUTH_TYPE_BASIC 1
323 #define AUTH_TYPE_RAW_NTLM 2
324 const char *auth_types[] = {
331 static void mag_set_req_data(request_rec *req,
332 struct mag_config *cfg,
335 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
336 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
337 apr_psprintf(req->pool,
338 "%ld", (long)mc->expiration));
339 req->ap_auth_type = apr_pstrdup(req->pool,
340 auth_types[mc->auth_type]);
341 req->user = apr_pstrdup(req->pool, mc->user_name);
342 if (cfg->deleg_ccache_dir && mc->delegated) {
344 ccname = mag_gss_name_to_ccache_name(req,
345 cfg->deleg_ccache_dir,
348 mag_set_KRB5CCANME(req, ccname);
353 gss_OID_set mag_filter_unwanted_mechs(gss_OID_set src)
355 gss_const_OID unwanted_mechs[] = {
366 if (src == GSS_C_NO_OID_SET) return GSS_C_NO_OID_SET;
368 for (int i = 0; unwanted_mechs[i] != GSS_C_NO_OID; i++) {
369 maj = gss_test_oid_set_member(&min,
370 discard_const(unwanted_mechs[i]),
375 maj = gss_create_empty_oid_set(&min, &dst);
376 if (maj != GSS_S_COMPLETE) {
377 return GSS_C_NO_OID_SET;
379 for (int i = 0; i < src->count; i++) {
381 for (int j = 0; unwanted_mechs[j] != GSS_C_NO_OID; j++) {
382 if (gss_oid_equal(&src->elements[i], unwanted_mechs[j])) {
387 if (present) continue;
388 maj = gss_add_oid_set_member(&min, &src->elements[i], &dst);
389 if (maj != GSS_S_COMPLETE) {
390 gss_release_oid_set(&min, &dst);
391 return GSS_C_NO_OID_SET;
399 static bool mag_auth_basic(request_rec *req,
400 struct mag_config *cfg,
401 gss_buffer_desc ba_user,
402 gss_buffer_desc ba_pwd,
403 gss_cred_usage_t cred_usage,
406 gss_cred_id_t *delegated_cred,
409 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
410 const char *user_ccache = NULL;
411 const char *orig_ccache = NULL;
412 long long unsigned int rndname;
415 gss_name_t user = GSS_C_NO_NAME;
416 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
417 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
418 gss_name_t server = GSS_C_NO_NAME;
419 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
420 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
421 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
422 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
423 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
424 gss_OID_set allowed_mechs;
425 gss_OID_set filtered_mechs;
426 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
427 uint32_t init_flags = 0;
432 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
433 if (GSS_ERROR(maj)) {
434 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
436 mag_error(req, "gss_import_name() failed",
441 if (cfg->basic_mechs) {
442 allowed_mechs = cfg->basic_mechs;
443 } else if (cfg->allowed_mechs) {
444 allowed_mechs = cfg->allowed_mechs;
446 struct mag_server_config *scfg;
447 /* Try to fetch the default set if not explicitly configured,
448 * We need to do this because gss_acquire_cred_with_password()
449 * is currently limited to acquire creds for a single "default"
450 * mechanism if no desired mechanisms are passed in. This causes
451 * authentication to fail for secondary mechanisms as no user
452 * credentials are generated for those. */
453 scfg = ap_get_module_config(req->server->module_config,
454 &auth_gssapi_module);
455 /* In the worst case scenario default_mechs equals to GSS_C_NO_OID_SET.
456 * This generally causes only the krb5 mechanism to be tried due
457 * to implementation constraints, but may change in future. */
458 allowed_mechs = scfg->default_mechs;
461 /* Remove Spnego if present, or we'd repeat failed authentiations
462 * multiple times, one within Spnego and then again with an explicit
463 * mechanism. We would normally just force Spnego and use
464 * gss_set_neg_mechs, but due to the way we source the server name
465 * and the fact MIT up to 1.14 at least does no handle union names,
466 * we can't provide spnego with a server name that can be used by
467 * multiple mechanisms, causing any but the first mechanism to fail.
468 * Also remove unwanted krb mechs, or AS requests will be repeated
469 * multiple times uselessly.
471 filtered_mechs = mag_filter_unwanted_mechs(allowed_mechs);
472 if (filtered_mechs == allowed_mechs) {
473 /* in case filtered_mechs was not allocated here don't free it */
474 filtered_mechs = GSS_C_NO_OID_SET;
475 } else if (filtered_mechs == GSS_C_NO_OID_SET) {
476 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
477 "failure while filtering mechs, aborting");
480 /* use the filtered list */
481 allowed_mechs = filtered_mechs;
484 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
485 /* If we are using the krb5 mechanism make sure to set a per thread
486 * memory ccache so that there can't be interferences between threads.
487 * Also make sure we have new cache so no cached results end up being
488 * used. Some implementations of gss_acquire_cred_with_password() do
489 * not reacquire creds if cached ones are around, failing to check
490 * again for the password. */
491 maj = gss_test_oid_set_member(&min, discard_const(gss_mech_krb5),
492 allowed_mechs, &present);
493 if (GSS_ERROR(maj)) {
494 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
496 mag_error(req, "gss_test_oid_set_member() failed",
501 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
502 sizeof(long long unsigned int));
503 if (rs != APR_SUCCESS) {
504 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
505 "Failed to generate random ccache name");
508 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
509 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
510 if (GSS_ERROR(maj)) {
511 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
513 mag_error(req, "gss_krb5_ccache_name() "
514 "failed", maj, min));
520 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
524 &user_cred, &actual_mechs, NULL);
525 if (GSS_ERROR(maj)) {
526 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
528 mag_error(req, "gss_acquire_cred_with_password() "
529 "failed", maj, min));
533 /* must acquire creds based on the actual mechs we want to try */
534 if (!mag_acquire_creds(req, cfg, actual_mechs,
535 cred_usage, &acquired_cred, NULL)) {
539 if (cred_usage == GSS_C_BOTH) {
540 /* must acquire with GSS_C_ACCEPT to get the server name */
541 if (!mag_acquire_creds(req, cfg, actual_mechs,
542 GSS_C_ACCEPT, &server_cred, NULL)) {
546 server_cred = acquired_cred;
549 #ifdef HAVE_CRED_STORE
550 if (cfg->deleg_ccache_dir) {
551 /* delegate ourselves credentials so we store them as requested */
552 init_flags |= GSS_C_DELEG_FLAG;
556 for (int i = 0; i < actual_mechs->count; i++) {
558 /* free these if looping */
559 gss_release_buffer(&min, &output);
560 gss_release_buffer(&min, &input);
561 gss_release_name(&min, &server);
563 maj = gss_inquire_cred_by_mech(&min, server_cred,
564 &actual_mechs->elements[i],
565 &server, NULL, NULL, NULL);
566 if (GSS_ERROR(maj)) {
567 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
568 "%s", mag_error(req, "gss_inquired_cred_by_mech() "
569 "failed", maj, min));
574 /* output and input are inverted here, this is intentional */
575 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
576 &actual_mechs->elements[i], init_flags,
577 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
578 NULL, &input, NULL, NULL);
579 if (GSS_ERROR(maj)) {
580 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
581 "%s", mag_error(req, "gss_init_sec_context() "
582 "failed", maj, min));
585 gss_release_buffer(&min, &output);
586 maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
587 &input, GSS_C_NO_CHANNEL_BINDINGS,
588 client, mech_type, &output, NULL,
589 vtime, delegated_cred);
590 if (GSS_ERROR(maj)) {
591 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
592 "%s", mag_error(req, "gss_accept_sec_context()"
593 " failed", maj, min));
596 gss_release_buffer(&min, &input);
597 } while (maj == GSS_S_CONTINUE_NEEDED);
599 if (maj == GSS_S_COMPLETE) {
606 gss_release_buffer(&min, &output);
607 gss_release_buffer(&min, &input);
608 gss_release_name(&min, &server);
609 if (server_cred != acquired_cred)
610 gss_release_cred(&min, &server_cred);
611 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
612 gss_release_cred(&min, &acquired_cred);
613 gss_release_name(&min, &user);
614 gss_release_cred(&min, &user_cred);
615 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
616 gss_release_oid_set(&min, &actual_mechs);
617 gss_release_oid_set(&min, &filtered_mechs);
618 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
619 if (user_ccache != NULL) {
620 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
621 if (maj != GSS_S_COMPLETE) {
622 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
623 "Failed to restore per-thread ccache, %s",
624 mag_error(req, "gss_krb5_ccache_name() "
625 "failed", maj, min));
632 struct mag_req_cfg *mag_init_cfg(request_rec *req)
634 struct mag_server_config *scfg;
635 struct mag_req_cfg *req_cfg = apr_pcalloc(req->pool,
636 sizeof(struct mag_req_cfg));
638 req_cfg->cfg = ap_get_module_config(req->per_dir_config,
639 &auth_gssapi_module);
641 scfg = ap_get_module_config(req->server->module_config,
642 &auth_gssapi_module);
644 if (req_cfg->cfg->allowed_mechs) {
645 req_cfg->desired_mechs = req_cfg->cfg->allowed_mechs;
647 /* Use the default set if not explicitly configured */
648 req_cfg->desired_mechs = scfg->default_mechs;
651 if (req_cfg->cfg->mag_skey) {
652 req_cfg->mag_skey = req_cfg->cfg->mag_skey;
654 /* Use server random key if not explicitly configured */
655 req_cfg->mag_skey = scfg->mag_skey;
658 if (req->proxyreq == PROXYREQ_PROXY) {
659 req_cfg->req_proto = "Proxy-Authorization";
660 req_cfg->rep_proto = "Proxy-Authenticate";
662 req_cfg->req_proto = "Authorization";
663 req_cfg->rep_proto = "WWW-Authenticate";
664 req_cfg->use_sessions = req_cfg->cfg->use_sessions;
665 req_cfg->send_persist = req_cfg->cfg->send_persist;
671 static bool use_s4u2proxy(struct mag_req_cfg *req_cfg) {
672 if (req_cfg->cfg->use_s4u2proxy) {
673 if (req_cfg->cfg->deleg_ccache_dir != NULL) {
676 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req_cfg->req,
677 "S4U2 Proxy requested but GssapiDelegCcacheDir "
678 "is not set. Constrained delegation disabled!");
684 static int mag_auth(request_rec *req)
688 struct mag_req_cfg *req_cfg;
689 struct mag_config *cfg;
690 const char *auth_header;
691 char *auth_header_type;
692 int ret = HTTP_UNAUTHORIZED;
693 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
695 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
696 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
697 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
698 gss_buffer_desc ba_user;
699 gss_buffer_desc ba_pwd;
700 gss_name_t client = GSS_C_NO_NAME;
701 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
702 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
703 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
709 gss_OID mech_type = GSS_C_NO_OID;
710 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
711 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
712 struct mag_conn *mc = NULL;
716 type = ap_auth_type(req);
717 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
721 req_cfg = mag_init_cfg(req);
725 desired_mechs = req_cfg->desired_mechs;
727 /* implicit auth for subrequests if main auth already happened */
728 if (!ap_is_initial_req(req) && req->main != NULL) {
729 type = ap_auth_type(req->main);
730 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
731 /* warn if the subrequest location and the main request
732 * location have different configs */
733 if (cfg != ap_get_module_config(req->main->per_dir_config,
734 &auth_gssapi_module)) {
735 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
736 req, "Subrequest authentication bypass on "
737 "location with different configuration!");
739 if (req->main->user) {
740 req->user = apr_pstrdup(req->pool, req->main->user);
743 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
744 "The main request is tasked to establish the "
745 "security context, can't proceed!");
746 return HTTP_UNAUTHORIZED;
749 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
750 "Subrequest GSSAPI auth with no auth on the main "
751 "request. This operation may fail if other "
752 "subrequests already established a context or the "
753 "mechanism requires multiple roundtrips.");
758 if (!mag_conn_is_https(req->connection)) {
759 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
760 "Not a TLS connection, refusing to authenticate!");
765 if (cfg->gss_conn_ctx) {
766 mc = (struct mag_conn *)ap_get_module_config(
767 req->connection->conn_config,
768 &auth_gssapi_module);
770 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
771 "Failed to retrieve connection context!");
776 /* if available, session always supersedes connection bound data */
777 if (req_cfg->use_sessions) {
778 mag_check_session(req_cfg, &mc);
781 auth_header = apr_table_get(req->headers_in, req_cfg->req_proto);
784 if (mc->established &&
785 (auth_header == NULL) &&
786 (mc->auth_type != AUTH_TYPE_BASIC)) {
787 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
788 "Already established context found!");
789 mag_set_req_data(req, cfg, mc);
798 /* We can proceed only if we do have an auth header */
799 if (!auth_header) goto done;
801 auth_header_type = ap_getword_white(req->pool, &auth_header);
802 if (!auth_header_type) goto done;
804 for (i = 0; auth_types[i] != NULL; i++) {
805 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
812 case AUTH_TYPE_NEGOTIATE:
813 if (!parse_auth_header(req->pool, &auth_header, &input)) {
817 case AUTH_TYPE_BASIC:
818 if (!cfg->use_basic_auth) {
822 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
823 if (!ba_pwd.value) goto done;
824 ba_user.value = ap_getword_nulls_nc(req->pool,
825 (char **)&ba_pwd.value, ':');
826 if (!ba_user.value) goto done;
828 if (((char *)ba_user.value)[0] == '\0' ||
829 ((char *)ba_pwd.value)[0] == '\0') {
830 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
831 "Invalid empty user or password for Basic Auth");
834 ba_user.length = strlen(ba_user.value);
835 ba_pwd.length = strlen(ba_pwd.value);
837 if (mc && mc->established &&
838 mag_basic_check(req_cfg, mc, ba_user, ba_pwd)) {
839 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
840 "Already established BASIC AUTH context found!");
841 mag_set_req_data(req, cfg, mc);
848 case AUTH_TYPE_RAW_NTLM:
849 if (!is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
850 cfg->gss_conn_ctx)) {
851 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
852 "NTLM Authentication is not allowed!");
856 if (!parse_auth_header(req->pool, &auth_header, &input)) {
860 desired_mechs = discard_const(gss_mech_set_ntlmssp);
867 if (mc && mc->established) {
868 /* if we are re-authenticating make sure the conn context
869 * is cleaned up so we do not accidentally reuse an existing
870 * established context */
874 req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
876 #ifdef HAVE_CRED_STORE
877 if (use_s4u2proxy(req_cfg)) {
878 cred_usage = GSS_C_BOTH;
882 if (auth_type == AUTH_TYPE_BASIC) {
883 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
884 cred_usage, &client, &mech_type,
885 &delegated_cred, &vtime)) {
891 if (!mag_acquire_creds(req, cfg, desired_mechs,
892 cred_usage, &acquired_cred, NULL)) {
896 if (auth_type == AUTH_TYPE_NEGOTIATE &&
897 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
898 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
899 if (GSS_ERROR(maj)) {
900 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
901 mag_error(req, "gss_set_neg_mechs() failed",
907 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
908 &input, GSS_C_NO_CHANNEL_BINDINGS,
909 &client, &mech_type, &output, NULL, &vtime,
911 if (GSS_ERROR(maj)) {
912 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
913 mag_error(req, "gss_accept_sec_context() failed",
916 } else if (maj == GSS_S_CONTINUE_NEEDED) {
918 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
919 "Mechanism needs continuation but neither "
920 "GssapiConnectionBound nor "
921 "GssapiUseSessions are available");
922 gss_release_buffer(&min, &output);
925 /* auth not complete send token and wait next packet */
930 /* Always set the GSS name in an env var */
931 maj = gss_display_name(&min, client, &name, NULL);
932 if (GSS_ERROR(maj)) {
933 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
934 mag_error(req, "gss_display_name() failed",
938 clientname = apr_pstrndup(req->pool, name.value, name.length);
939 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
940 expiration = time(NULL) + vtime;
941 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
942 apr_psprintf(req->pool, "%ld", (long)expiration));
944 #ifdef HAVE_CRED_STORE
945 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
946 char *ccachefile = NULL;
948 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
949 delegated_cred, &ccachefile);
952 mag_set_KRB5CCANME(req, ccachefile);
956 mc->delegated = true;
961 if (cfg->map_to_local) {
962 maj = gss_localname(&min, client, mech_type, &lname);
963 if (maj != GSS_S_COMPLETE) {
964 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
965 mag_error(req, "gss_localname() failed", maj, min));
968 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
970 req->user = clientname;
974 mc->user_name = apr_pstrdup(mc->pool, req->user);
975 mc->gss_name = apr_pstrdup(mc->pool, clientname);
976 mc->established = true;
977 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
978 vtime = MIN_SESS_EXP_TIME;
980 mc->expiration = expiration;
981 mc->auth_type = auth_type;
982 if (auth_type == AUTH_TYPE_BASIC) {
983 mag_basic_cache(req_cfg, mc, ba_user, ba_pwd);
985 if (req_cfg->use_sessions) {
986 mag_attempt_session(req_cfg, mc);
990 if (req_cfg->send_persist)
991 apr_table_set(req->headers_out, "Persistent-Auth",
992 cfg->gss_conn_ctx ? "true" : "false");
998 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
999 int prefixlen = strlen(auth_types[auth_type]) + 1;
1000 replen = apr_base64_encode_len(output.length) + 1;
1001 reply = apr_pcalloc(req->pool, prefixlen + replen);
1003 memcpy(reply, auth_types[auth_type], prefixlen - 1);
1004 reply[prefixlen - 1] = ' ';
1005 apr_base64_encode(&reply[prefixlen], output.value, output.length);
1006 apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply);
1008 } else if (ret == HTTP_UNAUTHORIZED) {
1009 apr_table_add(req->err_headers_out, req_cfg->rep_proto, "Negotiate");
1011 if (is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
1012 cfg->gss_conn_ctx)) {
1013 apr_table_add(req->err_headers_out, req_cfg->rep_proto, "NTLM");
1015 if (cfg->use_basic_auth) {
1016 apr_table_add(req->err_headers_out, req_cfg->rep_proto,
1017 apr_psprintf(req->pool, "Basic realm=\"%s\"",
1018 ap_auth_name(req)));
1022 if (ctx != GSS_C_NO_CONTEXT)
1023 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
1024 gss_release_cred(&min, &acquired_cred);
1025 gss_release_cred(&min, &delegated_cred);
1026 gss_release_buffer(&min, &output);
1027 gss_release_name(&min, &client);
1028 gss_release_buffer(&min, &name);
1029 gss_release_buffer(&min, &lname);
1034 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
1036 struct mag_config *cfg;
1038 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
1044 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
1046 struct mag_config *cfg = (struct mag_config *)mconfig;
1047 cfg->ssl_only = on ? true : false;
1051 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
1053 struct mag_config *cfg = (struct mag_config *)mconfig;
1054 cfg->map_to_local = on ? true : false;
1058 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
1060 struct mag_config *cfg = (struct mag_config *)mconfig;
1061 cfg->gss_conn_ctx = on ? true : false;
1065 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1067 struct mag_config *cfg = (struct mag_config *)mconfig;
1068 cfg->send_persist = on ? true : false;
1072 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1074 struct mag_config *cfg = (struct mag_config *)mconfig;
1075 cfg->use_sessions = on ? true : false;
1079 #ifdef HAVE_CRED_STORE
1080 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1082 struct mag_config *cfg = (struct mag_config *)mconfig;
1083 cfg->use_s4u2proxy = on ? true : false;
1089 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1091 struct mag_config *cfg = (struct mag_config *)mconfig;
1092 struct databuf keys;
1098 if (strncmp(w, "key:", 4) != 0) {
1099 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1100 "Invalid key format, expected prefix 'key:'");
1105 l = apr_base64_decode_len(k);
1106 val = apr_palloc(parms->temp_pool, l);
1108 keys.length = (int)apr_base64_decode_binary(val, k);
1109 keys.value = (unsigned char *)val;
1111 if (keys.length != 32) {
1112 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1113 "Invalid key length, expected 32 got %d", keys.length);
1117 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1119 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1120 "Failed to import sealing key!");
1125 #ifdef HAVE_CRED_STORE
1127 #define MAX_CRED_OPTIONS 10
1129 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1132 struct mag_config *cfg = (struct mag_config *)mconfig;
1133 gss_key_value_element_desc *elements;
1142 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1143 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1147 key = apr_pstrndup(parms->pool, w, (p-w));
1148 value = apr_pstrdup(parms->pool, p + 1);
1150 if (!cfg->cred_store) {
1151 cfg->cred_store = apr_pcalloc(parms->pool,
1152 sizeof(gss_key_value_set_desc));
1153 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1154 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1157 elements = cfg->cred_store->elements;
1158 count = cfg->cred_store->count;
1160 if (count >= MAX_CRED_OPTIONS) {
1161 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1162 "Too many GssapiCredStore options (MAX: %d)",
1166 cfg->cred_store->count++;
1168 elements[count].key = key;
1169 elements[count].value = value;
1174 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1177 struct mag_config *cfg = (struct mag_config *)mconfig;
1179 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1185 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1186 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1188 struct mag_config *cfg = (struct mag_config *)mconfig;
1190 cfg->use_basic_auth = on ? true : false;
1195 static apr_status_t mag_oid_set_destroy(void *ptr)
1198 gss_OID_set set = (gss_OID_set)ptr;
1199 (void)gss_release_oid_set(&min, &set);
1203 static bool mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1204 bool add_spnego, const char *w)
1206 gss_buffer_desc buf = { 0 };
1210 bool release_oid = false;
1212 if (NULL == *oidset) {
1213 maj = gss_create_empty_oid_set(&min, &set);
1214 if (maj != GSS_S_COMPLETE) {
1215 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1216 "gss_create_empty_oid_set() failed.");
1217 *oidset = GSS_C_NO_OID_SET;
1221 oid = discard_const(&gss_mech_spnego);
1222 maj = gss_add_oid_set_member(&min, oid, &set);
1223 if (maj != GSS_S_COMPLETE) {
1224 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1225 "gss_add_oid_set_member() failed.");
1226 (void)gss_release_oid_set(&min, &set);
1227 *oidset = GSS_C_NO_OID_SET;
1231 /* register in the pool so it can be released once the server
1233 apr_pool_cleanup_register(parms->pool, (void *)set,
1234 mag_oid_set_destroy,
1235 apr_pool_cleanup_null);
1241 if (strcmp(w, "krb5") == 0) {
1242 oid = discard_const(gss_mech_krb5);
1243 } else if (strcmp(w, "iakerb") == 0) {
1244 oid = discard_const(gss_mech_iakerb);
1245 } else if (strcmp(w, "ntlmssp") == 0) {
1246 oid = discard_const(gss_mech_ntlmssp);
1248 buf.value = discard_const(w);
1249 buf.length = strlen(w);
1250 maj = gss_str_to_oid(&min, &buf, &oid);
1251 if (maj != GSS_S_COMPLETE) {
1252 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1253 "Unrecognized GSSAPI Mechanism: [%s]", w);
1258 maj = gss_add_oid_set_member(&min, oid, &set);
1259 if (maj != GSS_S_COMPLETE) {
1260 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1261 "gss_add_oid_set_member() failed for [%s].", w);
1264 (void)gss_release_oid(&min, &oid);
1270 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1273 struct mag_config *cfg = (struct mag_config *)mconfig;
1275 if (!mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w))
1276 return "Failed to apply GssapiAllowedMech directive";
1281 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1282 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1285 struct mag_config *cfg = (struct mag_config *)mconfig;
1287 if (!mag_list_of_mechs(parms, &cfg->basic_mechs, false, w))
1288 return "Failed to apply GssapiBasicAuthMech directive";
1294 static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
1296 struct mag_server_config *scfg;
1300 scfg = apr_pcalloc(p, sizeof(struct mag_server_config));
1302 maj = gss_indicate_mechs(&min, &scfg->default_mechs);
1303 if (maj != GSS_S_COMPLETE) {
1304 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
1305 "gss_indicate_mechs() failed");
1307 /* Register the set in pool */
1308 apr_pool_cleanup_register(p, (void *)scfg->default_mechs,
1309 mag_oid_set_destroy, apr_pool_cleanup_null);
1312 rc = SEAL_KEY_CREATE(p, &scfg->mag_skey, NULL);
1314 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
1315 "Failed to generate random sealing key!");
1321 static const command_rec mag_commands[] = {
1322 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1323 "Work only if connection is SSL Secured"),
1324 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1325 "Translate principals to local names"),
1326 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1327 "Authentication is bound to the TCP connection"),
1328 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1329 "Send Persitent-Auth header according to connection bound"),
1330 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1331 "Authentication uses mod_sessions to hold status"),
1332 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1333 "Key Used to seal session data."),
1334 #ifdef HAVE_CRED_STORE
1335 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1336 "Initializes credentials for s4u2proxy usage"),
1337 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1338 "Credential Store"),
1339 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1340 OR_AUTHCFG, "Directory to store delegated credentials"),
1342 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1343 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1344 "Allows use of Basic Auth for authentication"),
1345 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1346 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1348 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1349 "Allowed Mechanisms"),
1354 mag_register_hooks(apr_pool_t *p)
1356 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1357 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1358 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1361 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1363 STANDARD20_MODULE_STUFF,
1364 mag_create_dir_config,
1366 mag_create_server_config,