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,
367 gss_cred_id_t *delegated_cred,
370 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
371 const char *user_ccache = NULL;
372 const char *orig_ccache = NULL;
373 long long unsigned int rndname;
376 gss_name_t user = GSS_C_NO_NAME;
377 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
378 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
379 gss_name_t server = GSS_C_NO_NAME;
380 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
381 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
382 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
383 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
384 gss_OID_set allowed_mechs;
385 gss_OID_set filtered_mechs;
386 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
387 uint32_t init_flags = 0;
392 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
393 if (GSS_ERROR(maj)) {
394 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
396 mag_error(req, "gss_import_name() failed",
401 if (cfg->basic_mechs) {
402 allowed_mechs = cfg->basic_mechs;
403 } else if (cfg->allowed_mechs) {
404 allowed_mechs = cfg->allowed_mechs;
406 struct mag_server_config *scfg;
407 /* Try to fetch the default set if not explicitly configured,
408 * We need to do this because gss_acquire_cred_with_password()
409 * is currently limited to acquire creds for a single "default"
410 * mechanism if no desired mechanisms are passed in. This causes
411 * authentication to fail for secondary mechanisms as no user
412 * credentials are generated for those. */
413 scfg = ap_get_module_config(req->server->module_config,
414 &auth_gssapi_module);
415 /* In the worst case scenario default_mechs equals to GSS_C_NO_OID_SET.
416 * This generally causes only the krb5 mechanism to be tried due
417 * to implementation constraints, but may change in future. */
418 allowed_mechs = scfg->default_mechs;
421 /* Remove Spnego if present, or we'd repeat failed authentiations
422 * multiple times, one within Spnego and then again with an explicit
423 * mechanism. We would normally just force Spnego and use
424 * gss_set_neg_mechs, but due to the way we source the server name
425 * and the fact MIT up to 1.14 at least does no handle union names,
426 * we can't provide spnego with a server name that can be used by
427 * multiple mechanisms, causing any but the first mechanism to fail.
428 * Also remove unwanted krb mechs, or AS requests will be repeated
429 * multiple times uselessly.
431 filtered_mechs = mag_filter_unwanted_mechs(allowed_mechs);
432 if (filtered_mechs == allowed_mechs) {
433 /* in case filtered_mechs was not allocated here don't free it */
434 filtered_mechs = GSS_C_NO_OID_SET;
435 } else if (filtered_mechs == GSS_C_NO_OID_SET) {
436 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
437 "failure while filtering mechs, aborting");
440 /* use the filtered list */
441 allowed_mechs = filtered_mechs;
444 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
445 /* If we are using the krb5 mechanism make sure to set a per thread
446 * memory ccache so that there can't be interferences between threads.
447 * Also make sure we have new cache so no cached results end up being
448 * used. Some implementations of gss_acquire_cred_with_password() do
449 * not reacquire creds if cached ones are around, failing to check
450 * again for the password. */
451 maj = gss_test_oid_set_member(&min, discard_const(gss_mech_krb5),
452 allowed_mechs, &present);
453 if (GSS_ERROR(maj)) {
454 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
456 mag_error(req, "gss_test_oid_set_member() failed",
461 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
462 sizeof(long long unsigned int));
463 if (rs != APR_SUCCESS) {
464 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
465 "Failed to generate random ccache name");
468 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
469 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
470 if (GSS_ERROR(maj)) {
471 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
473 mag_error(req, "gss_krb5_ccache_name() "
474 "failed", maj, min));
480 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
484 &user_cred, &actual_mechs, NULL);
485 if (GSS_ERROR(maj)) {
486 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
488 mag_error(req, "gss_acquire_cred_with_password() "
489 "failed", maj, min));
493 /* must acquire creds based on the actual mechs we want to try */
494 if (!mag_acquire_creds(req, cfg, actual_mechs,
495 GSS_C_ACCEPT, &server_cred, NULL)) {
499 #ifdef HAVE_CRED_STORE
500 if (cfg->deleg_ccache_dir) {
501 /* delegate ourselves credentials so we store them as requested */
502 init_flags |= GSS_C_DELEG_FLAG;
506 for (int i = 0; i < actual_mechs->count; i++) {
508 /* free these if looping */
509 gss_release_buffer(&min, &output);
510 gss_release_buffer(&min, &input);
511 gss_release_name(&min, &server);
513 maj = gss_inquire_cred_by_mech(&min, server_cred,
514 &actual_mechs->elements[i],
515 &server, NULL, NULL, NULL);
516 if (GSS_ERROR(maj)) {
517 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
518 "%s", mag_error(req, "gss_inquired_cred_by_mech() "
519 "failed", maj, min));
524 /* output and input are inverted here, this is intentional */
525 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
526 &actual_mechs->elements[i], init_flags,
527 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
528 NULL, &input, NULL, NULL);
529 if (GSS_ERROR(maj)) {
530 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
531 "%s", mag_error(req, "gss_init_sec_context() "
532 "failed", maj, min));
535 gss_release_buffer(&min, &output);
536 maj = gss_accept_sec_context(&min, &server_ctx, server_cred,
537 &input, GSS_C_NO_CHANNEL_BINDINGS,
538 client, mech_type, &output, NULL,
539 vtime, delegated_cred);
540 if (GSS_ERROR(maj)) {
541 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
542 "%s", mag_error(req, "gss_accept_sec_context()"
543 " failed", maj, min));
546 gss_release_buffer(&min, &input);
547 } while (maj == GSS_S_CONTINUE_NEEDED);
549 if (maj == GSS_S_COMPLETE) {
556 gss_release_buffer(&min, &output);
557 gss_release_buffer(&min, &input);
558 gss_release_name(&min, &server);
559 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
560 gss_release_cred(&min, &server_cred);
561 gss_release_name(&min, &user);
562 gss_release_cred(&min, &user_cred);
563 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
564 gss_release_oid_set(&min, &actual_mechs);
565 gss_release_oid_set(&min, &filtered_mechs);
566 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
567 if (user_ccache != NULL) {
568 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
569 if (maj != GSS_S_COMPLETE) {
570 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
571 "Failed to restore per-thread ccache, %s",
572 mag_error(req, "gss_krb5_ccache_name() "
573 "failed", maj, min));
580 struct mag_req_cfg *mag_init_cfg(request_rec *req)
582 struct mag_server_config *scfg;
583 struct mag_req_cfg *req_cfg = apr_pcalloc(req->pool,
584 sizeof(struct mag_req_cfg));
586 req_cfg->cfg = ap_get_module_config(req->per_dir_config,
587 &auth_gssapi_module);
589 scfg = ap_get_module_config(req->server->module_config,
590 &auth_gssapi_module);
592 if (req_cfg->cfg->allowed_mechs) {
593 req_cfg->desired_mechs = req_cfg->cfg->allowed_mechs;
595 /* Use the default set if not explicitly configured */
596 req_cfg->desired_mechs = scfg->default_mechs;
599 if (req_cfg->cfg->mag_skey) {
600 req_cfg->mag_skey = req_cfg->cfg->mag_skey;
602 /* Use server random key if not explicitly configured */
603 req_cfg->mag_skey = scfg->mag_skey;
606 if (req->proxyreq == PROXYREQ_PROXY) {
607 req_cfg->req_proto = "Proxy-Authorization";
608 req_cfg->rep_proto = "Proxy-Authenticate";
610 req_cfg->req_proto = "Authorization";
611 req_cfg->rep_proto = "WWW-Authenticate";
612 req_cfg->use_sessions = req_cfg->cfg->use_sessions;
613 req_cfg->send_persist = req_cfg->cfg->send_persist;
619 #ifdef HAVE_CRED_STORE
620 static bool use_s4u2proxy(struct mag_req_cfg *req_cfg) {
621 if (req_cfg->cfg->use_s4u2proxy) {
622 if (req_cfg->cfg->deleg_ccache_dir != NULL) {
625 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req_cfg->req,
626 "S4U2 Proxy requested but GssapiDelegCcacheDir "
627 "is not set. Constrained delegation disabled!");
634 static int mag_auth(request_rec *req)
638 struct mag_req_cfg *req_cfg;
639 struct mag_config *cfg;
640 const char *auth_header;
641 char *auth_header_type;
642 int ret = HTTP_UNAUTHORIZED;
643 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
645 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
646 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
647 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
648 gss_buffer_desc ba_user;
649 gss_buffer_desc ba_pwd;
650 gss_name_t client = GSS_C_NO_NAME;
651 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
652 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
653 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
658 gss_OID mech_type = GSS_C_NO_OID;
659 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
660 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
661 struct mag_conn *mc = NULL;
663 bool send_auth_header = true;
665 type = ap_auth_type(req);
666 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
670 req_cfg = mag_init_cfg(req);
674 desired_mechs = req_cfg->desired_mechs;
676 /* implicit auth for subrequests if main auth already happened */
677 if (!ap_is_initial_req(req) && req->main != NULL) {
678 type = ap_auth_type(req->main);
679 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
680 /* warn if the subrequest location and the main request
681 * location have different configs */
682 if (cfg != ap_get_module_config(req->main->per_dir_config,
683 &auth_gssapi_module)) {
684 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
685 req, "Subrequest authentication bypass on "
686 "location with different configuration!");
688 if (req->main->user) {
689 req->user = apr_pstrdup(req->pool, req->main->user);
692 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
693 "The main request is tasked to establish the "
694 "security context, can't proceed!");
695 return HTTP_UNAUTHORIZED;
698 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
699 "Subrequest GSSAPI auth with no auth on the main "
700 "request. This operation may fail if other "
701 "subrequests already established a context or the "
702 "mechanism requires multiple roundtrips.");
707 if (!mag_conn_is_https(req->connection)) {
708 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
709 "Not a TLS connection, refusing to authenticate!");
714 if (cfg->gss_conn_ctx) {
715 mc = (struct mag_conn *)ap_get_module_config(
716 req->connection->conn_config,
717 &auth_gssapi_module);
719 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
720 "Failed to retrieve connection context!");
725 /* if available, session always supersedes connection bound data */
726 if (req_cfg->use_sessions) {
727 mag_check_session(req_cfg, &mc);
730 auth_header = apr_table_get(req->headers_in, req_cfg->req_proto);
733 if (mc->established &&
734 (auth_header == NULL) &&
735 (mc->auth_type != AUTH_TYPE_BASIC)) {
736 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
737 "Already established context found!");
738 mag_set_req_data(req, cfg, mc);
744 /* no preserved mc, create one just for this request */
745 mc = mag_new_conn_ctx(req->pool);
749 /* We can proceed only if we do have an auth header */
750 if (!auth_header) goto done;
752 auth_header_type = ap_getword_white(req->pool, &auth_header);
753 if (!auth_header_type) goto done;
755 /* We got auth header, sending auth header would mean re-auth */
756 send_auth_header = !cfg->negotiate_once;
758 for (i = 0; auth_types[i] != NULL; i++) {
759 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
766 case AUTH_TYPE_NEGOTIATE:
767 if (!parse_auth_header(req->pool, &auth_header, &input)) {
771 case AUTH_TYPE_BASIC:
772 if (!cfg->use_basic_auth) {
776 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
777 if (!ba_pwd.value) goto done;
778 ba_user.value = ap_getword_nulls_nc(req->pool,
779 (char **)&ba_pwd.value, ':');
780 if (!ba_user.value) goto done;
782 if (((char *)ba_user.value)[0] == '\0' ||
783 ((char *)ba_pwd.value)[0] == '\0') {
784 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
785 "Invalid empty user or password for Basic Auth");
788 ba_user.length = strlen(ba_user.value);
789 ba_pwd.length = strlen(ba_pwd.value);
791 if (mc->is_preserved && mc->established &&
792 mag_basic_check(req_cfg, mc, ba_user, ba_pwd)) {
793 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
794 "Already established BASIC AUTH context found!");
795 mag_set_req_data(req, cfg, mc);
802 case AUTH_TYPE_RAW_NTLM:
803 if (!is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
804 cfg->gss_conn_ctx)) {
805 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
806 "NTLM Authentication is not allowed!");
810 if (!parse_auth_header(req->pool, &auth_header, &input)) {
814 desired_mechs = discard_const(gss_mech_set_ntlmssp);
821 if (mc->established) {
822 /* if we are re-authenticating make sure the conn context
823 * is cleaned up so we do not accidentally reuse an existing
824 * established context */
828 mc->auth_type = auth_type;
830 #ifdef HAVE_CRED_STORE
831 if (use_s4u2proxy(req_cfg)) {
832 cred_usage = GSS_C_BOTH;
836 if (auth_type == AUTH_TYPE_BASIC) {
837 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
839 &delegated_cred, &vtime)) {
845 if (!mag_acquire_creds(req, cfg, desired_mechs,
846 cred_usage, &acquired_cred, NULL)) {
850 if (auth_type == AUTH_TYPE_NEGOTIATE &&
851 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
852 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
853 if (GSS_ERROR(maj)) {
854 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
855 mag_error(req, "gss_set_neg_mechs() failed",
861 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
862 &input, GSS_C_NO_CHANNEL_BINDINGS,
863 &client, &mech_type, &output, NULL, &vtime,
865 if (GSS_ERROR(maj)) {
866 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
867 mag_error(req, "gss_accept_sec_context() failed",
870 } else if (maj == GSS_S_CONTINUE_NEEDED) {
871 if (!mc->is_preserved) {
872 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
873 "Mechanism needs continuation but neither "
874 "GssapiConnectionBound nor "
875 "GssapiUseSessions are available");
876 gss_release_buffer(&min, &output);
879 /* auth not complete send token and wait next packet */
884 maj = gss_display_name(&min, client, &name, NULL);
885 if (GSS_ERROR(maj)) {
886 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
887 mag_error(req, "gss_display_name() failed",
892 mc->gss_name = apr_pstrndup(req->pool, name.value, name.length);
893 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
894 vtime = MIN_SESS_EXP_TIME;
896 mc->expiration = time(NULL) + vtime;
898 mag_get_name_attributes(req, cfg, client, mc);
900 #ifdef HAVE_CRED_STORE
901 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
902 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, mc->gss_name,
904 mc->delegated = true;
908 if (cfg->map_to_local) {
909 maj = gss_localname(&min, client, mech_type, &lname);
910 if (maj != GSS_S_COMPLETE) {
911 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
912 mag_error(req, "gss_localname() failed", maj, min));
915 mc->user_name = apr_pstrndup(req->pool, lname.value, lname.length);
917 mc->user_name = apr_pstrdup(mc->pool, mc->gss_name);
920 mc->established = true;
921 if (auth_type == AUTH_TYPE_BASIC) {
922 mag_basic_cache(req_cfg, mc, ba_user, ba_pwd);
924 if (req_cfg->use_sessions) {
925 mag_attempt_session(req_cfg, mc);
928 /* Now set request data and env vars */
929 mag_set_req_data(req, cfg, mc);
931 if (req_cfg->send_persist)
932 apr_table_set(req->headers_out, "Persistent-Auth",
933 cfg->gss_conn_ctx ? "true" : "false");
939 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
940 int prefixlen = strlen(mag_str_auth_type(auth_type)) + 1;
941 replen = apr_base64_encode_len(output.length) + 1;
942 reply = apr_pcalloc(req->pool, prefixlen + replen);
944 memcpy(reply, mag_str_auth_type(auth_type), prefixlen - 1);
945 reply[prefixlen - 1] = ' ';
946 apr_base64_encode(&reply[prefixlen], output.value, output.length);
947 apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply);
949 } else if (ret == HTTP_UNAUTHORIZED) {
950 if (send_auth_header) {
951 apr_table_add(req->err_headers_out,
952 req_cfg->rep_proto, "Negotiate");
953 if (is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
954 cfg->gss_conn_ctx)) {
955 apr_table_add(req->err_headers_out, req_cfg->rep_proto,
959 if (cfg->use_basic_auth) {
960 apr_table_add(req->err_headers_out, req_cfg->rep_proto,
961 apr_psprintf(req->pool, "Basic realm=\"%s\"",
966 if (ctx != GSS_C_NO_CONTEXT)
967 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
968 gss_release_cred(&min, &acquired_cred);
969 gss_release_cred(&min, &delegated_cred);
970 gss_release_buffer(&min, &output);
971 gss_release_name(&min, &client);
972 gss_release_buffer(&min, &name);
973 gss_release_buffer(&min, &lname);
978 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
980 struct mag_config *cfg;
982 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
988 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
990 struct mag_config *cfg = (struct mag_config *)mconfig;
991 cfg->ssl_only = on ? true : false;
995 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
997 struct mag_config *cfg = (struct mag_config *)mconfig;
998 cfg->map_to_local = on ? true : false;
1002 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
1004 struct mag_config *cfg = (struct mag_config *)mconfig;
1005 cfg->gss_conn_ctx = on ? true : false;
1009 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1011 struct mag_config *cfg = (struct mag_config *)mconfig;
1012 cfg->send_persist = on ? true : false;
1016 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1018 struct mag_config *cfg = (struct mag_config *)mconfig;
1019 cfg->use_sessions = on ? true : false;
1023 #ifdef HAVE_CRED_STORE
1024 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1026 struct mag_config *cfg = (struct mag_config *)mconfig;
1027 cfg->use_s4u2proxy = on ? true : false;
1033 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1035 struct mag_config *cfg = (struct mag_config *)mconfig;
1036 struct databuf keys;
1042 if (strncmp(w, "key:", 4) != 0) {
1043 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1044 "Invalid key format, expected prefix 'key:'");
1049 l = apr_base64_decode_len(k);
1050 val = apr_palloc(parms->temp_pool, l);
1052 keys.length = (int)apr_base64_decode_binary(val, k);
1053 keys.value = (unsigned char *)val;
1055 if (keys.length != 32) {
1056 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1057 "Invalid key length, expected 32 got %d", keys.length);
1061 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1063 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1064 "Failed to import sealing key!");
1069 #ifdef HAVE_CRED_STORE
1071 #define MAX_CRED_OPTIONS 10
1073 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1076 struct mag_config *cfg = (struct mag_config *)mconfig;
1077 gss_key_value_element_desc *elements;
1086 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1087 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1091 key = apr_pstrndup(parms->pool, w, (p-w));
1092 value = apr_pstrdup(parms->pool, p + 1);
1094 if (!cfg->cred_store) {
1095 cfg->cred_store = apr_pcalloc(parms->pool,
1096 sizeof(gss_key_value_set_desc));
1097 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1098 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1101 elements = cfg->cred_store->elements;
1102 count = cfg->cred_store->count;
1104 if (count >= MAX_CRED_OPTIONS) {
1105 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1106 "Too many GssapiCredStore options (MAX: %d)",
1110 cfg->cred_store->count++;
1112 elements[count].key = key;
1113 elements[count].value = value;
1118 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1121 struct mag_config *cfg = (struct mag_config *)mconfig;
1123 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1129 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1130 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1132 struct mag_config *cfg = (struct mag_config *)mconfig;
1134 cfg->use_basic_auth = on ? true : false;
1139 static apr_status_t mag_oid_set_destroy(void *ptr)
1142 gss_OID_set set = (gss_OID_set)ptr;
1143 (void)gss_release_oid_set(&min, &set);
1147 static bool mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1148 bool add_spnego, const char *w)
1150 gss_buffer_desc buf = { 0 };
1154 bool release_oid = false;
1156 if (NULL == *oidset) {
1157 maj = gss_create_empty_oid_set(&min, &set);
1158 if (maj != GSS_S_COMPLETE) {
1159 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1160 "gss_create_empty_oid_set() failed.");
1161 *oidset = GSS_C_NO_OID_SET;
1165 oid = discard_const(&gss_mech_spnego);
1166 maj = gss_add_oid_set_member(&min, oid, &set);
1167 if (maj != GSS_S_COMPLETE) {
1168 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1169 "gss_add_oid_set_member() failed.");
1170 (void)gss_release_oid_set(&min, &set);
1171 *oidset = GSS_C_NO_OID_SET;
1175 /* register in the pool so it can be released once the server
1177 apr_pool_cleanup_register(parms->pool, (void *)set,
1178 mag_oid_set_destroy,
1179 apr_pool_cleanup_null);
1185 if (strcmp(w, "krb5") == 0) {
1186 oid = discard_const(gss_mech_krb5);
1187 } else if (strcmp(w, "iakerb") == 0) {
1188 oid = discard_const(gss_mech_iakerb);
1189 } else if (strcmp(w, "ntlmssp") == 0) {
1190 oid = discard_const(gss_mech_ntlmssp);
1192 buf.value = discard_const(w);
1193 buf.length = strlen(w);
1194 maj = gss_str_to_oid(&min, &buf, &oid);
1195 if (maj != GSS_S_COMPLETE) {
1196 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1197 "Unrecognized GSSAPI Mechanism: [%s]", w);
1202 maj = gss_add_oid_set_member(&min, oid, &set);
1203 if (maj != GSS_S_COMPLETE) {
1204 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1205 "gss_add_oid_set_member() failed for [%s].", w);
1208 (void)gss_release_oid(&min, &oid);
1214 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1217 struct mag_config *cfg = (struct mag_config *)mconfig;
1219 if (!mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w))
1220 return "Failed to apply GssapiAllowedMech directive";
1225 static const char *mag_negotiate_once(cmd_parms *parms, void *mconfig, int on)
1227 struct mag_config *cfg = (struct mag_config *)mconfig;
1229 cfg->negotiate_once = on ? true : false;
1233 #define GSS_NAME_ATTR_USERDATA "GSS Name Attributes Userdata"
1235 static apr_status_t mag_name_attrs_cleanup(void *data)
1237 struct mag_config *cfg = (struct mag_config *)data;
1238 free(cfg->name_attributes);
1239 cfg->name_attributes = NULL;
1243 static const char *mag_name_attrs(cmd_parms *parms, void *mconfig,
1246 struct mag_config *cfg = (struct mag_config *)mconfig;
1252 if (!cfg->name_attributes) {
1253 size = sizeof(struct mag_name_attributes)
1254 + (sizeof(struct mag_na_map) * 16);
1255 } else if (cfg->name_attributes->map_count % 16 == 0) {
1256 size = sizeof(struct mag_name_attributes)
1257 + (sizeof(struct mag_na_map)
1258 * (cfg->name_attributes->map_count + 16));
1261 tmp_na = realloc(cfg->name_attributes, size);
1262 if (!tmp_na) apr_pool_abort_get(cfg->pool)(ENOMEM);
1264 if (cfg->name_attributes) {
1265 size_t empty = (sizeof(struct mag_na_map) * 16);
1266 memset(tmp_na + size - empty, 0, empty);
1268 memset(tmp_na, 0, size);
1270 cfg->name_attributes = (struct mag_name_attributes *)tmp_na;
1271 apr_pool_userdata_setn(cfg, GSS_NAME_ATTR_USERDATA,
1272 mag_name_attrs_cleanup, cfg->pool);
1277 if (strcmp(w, "json") == 0) {
1278 cfg->name_attributes->output_json = true;
1280 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1281 "Invalid Name Attributes value [%s].", w);
1286 c = cfg->name_attributes->map_count;
1287 cfg->name_attributes->map[c].env_name = apr_pstrndup(cfg->pool, w, p-w);
1289 cfg->name_attributes->map[c].attr_name = apr_pstrdup(cfg->pool, p);
1290 cfg->name_attributes->map_count += 1;
1295 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1296 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1299 struct mag_config *cfg = (struct mag_config *)mconfig;
1301 if (!mag_list_of_mechs(parms, &cfg->basic_mechs, false, w))
1302 return "Failed to apply GssapiBasicAuthMech directive";
1308 static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
1310 struct mag_server_config *scfg;
1314 scfg = apr_pcalloc(p, sizeof(struct mag_server_config));
1316 maj = gss_indicate_mechs(&min, &scfg->default_mechs);
1317 if (maj != GSS_S_COMPLETE) {
1318 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
1319 "gss_indicate_mechs() failed");
1321 /* Register the set in pool */
1322 apr_pool_cleanup_register(p, (void *)scfg->default_mechs,
1323 mag_oid_set_destroy, apr_pool_cleanup_null);
1326 rc = SEAL_KEY_CREATE(p, &scfg->mag_skey, NULL);
1328 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
1329 "Failed to generate random sealing key!");
1335 static const command_rec mag_commands[] = {
1336 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1337 "Work only if connection is SSL Secured"),
1338 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1339 "Translate principals to local names"),
1340 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1341 "Authentication is bound to the TCP connection"),
1342 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1343 "Send Persitent-Auth header according to connection bound"),
1344 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1345 "Authentication uses mod_sessions to hold status"),
1346 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1347 "Key Used to seal session data."),
1348 #ifdef HAVE_CRED_STORE
1349 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1350 "Initializes credentials for s4u2proxy usage"),
1351 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1352 "Credential Store"),
1353 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1354 OR_AUTHCFG, "Directory to store delegated credentials"),
1356 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1357 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1358 "Allows use of Basic Auth for authentication"),
1359 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1360 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1362 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1363 "Allowed Mechanisms"),
1364 AP_INIT_FLAG("GssapiNegotiateOnce", mag_negotiate_once, NULL, OR_AUTHCFG,
1365 "Don't resend negotiate header on negotiate failure"),
1366 AP_INIT_RAW_ARGS("GssapiNameAttributes", mag_name_attrs, NULL, OR_AUTHCFG,
1367 "Name Attributes to be exported as environ variables"),
1372 mag_register_hooks(apr_pool_t *p)
1374 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1375 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1376 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1379 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1381 STANDARD20_MODULE_STUFF,
1382 mag_create_dir_config,
1384 mag_create_server_config,