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 mc->is_preserved = true;
113 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
117 static apr_status_t mag_conn_destroy(void *ptr)
119 struct mag_conn *mc = (struct mag_conn *)ptr;
123 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
128 struct mag_conn *mag_new_conn_ctx(apr_pool_t *pool)
132 mc = apr_pcalloc(pool, sizeof(struct mag_conn));
133 apr_pool_create(&mc->pool, pool);
134 /* register the context in the memory pool, so it can be freed
135 * when the connection/request is terminated */
136 apr_pool_cleanup_register(mc->pool, (void *)mc,
137 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 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_store_deleg_creds(request_rec *req,
244 char *dir, const char *gss_name,
245 gss_cred_id_t delegated_cred)
247 gss_key_value_element_desc element;
248 gss_key_value_set_desc store;
251 element.key = "ccache";
252 store.elements = &element;
255 ccname = mag_gss_name_to_ccache_name(req, dir, gss_name);
256 element.value = apr_psprintf(req->pool, "FILE:%s", ccname);
258 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
259 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
260 if (GSS_ERROR(maj)) {
261 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
262 mag_error(req, "failed to store delegated creds",
268 static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
271 char *auth_header_value;
273 auth_header_value = ap_getword_white(pool, auth_header);
274 if (!auth_header_value) return false;
275 value->length = apr_base64_decode_len(auth_header_value) + 1;
276 value->value = apr_pcalloc(pool, value->length);
277 if (!value->value) return false;
278 value->length = apr_base64_decode(value->value, auth_header_value);
283 static bool is_mech_allowed(gss_OID_set allowed_mechs, gss_const_OID mech,
284 bool multi_step_supported)
286 if (mech == GSS_C_NO_OID) return false;
288 if (!multi_step_supported && gss_oid_equal(gss_mech_ntlmssp, mech))
291 if (allowed_mechs == GSS_C_NO_OID_SET) return true;
293 for (int i = 0; i < allowed_mechs->count; i++) {
294 if (gss_oid_equal(&allowed_mechs->elements[i], mech)) {
301 #define AUTH_TYPE_NEGOTIATE 0
302 #define AUTH_TYPE_BASIC 1
303 #define AUTH_TYPE_RAW_NTLM 2
304 const char *auth_types[] = {
311 const char *mag_str_auth_type(int auth_type)
313 return auth_types[auth_type];
316 gss_OID_set mag_filter_unwanted_mechs(gss_OID_set src)
318 gss_const_OID unwanted_mechs[] = {
329 if (src == GSS_C_NO_OID_SET) return GSS_C_NO_OID_SET;
331 for (int i = 0; unwanted_mechs[i] != GSS_C_NO_OID; i++) {
332 maj = gss_test_oid_set_member(&min,
333 discard_const(unwanted_mechs[i]),
338 maj = gss_create_empty_oid_set(&min, &dst);
339 if (maj != GSS_S_COMPLETE) {
340 return GSS_C_NO_OID_SET;
342 for (int i = 0; i < src->count; i++) {
344 for (int j = 0; unwanted_mechs[j] != GSS_C_NO_OID; j++) {
345 if (gss_oid_equal(&src->elements[i], unwanted_mechs[j])) {
350 if (present) continue;
351 maj = gss_add_oid_set_member(&min, &src->elements[i], &dst);
352 if (maj != GSS_S_COMPLETE) {
353 gss_release_oid_set(&min, &dst);
354 return GSS_C_NO_OID_SET;
362 static bool mag_auth_basic(request_rec *req,
363 struct mag_config *cfg,
364 gss_buffer_desc ba_user,
365 gss_buffer_desc ba_pwd,
366 gss_cred_usage_t cred_usage,
369 gss_cred_id_t *delegated_cred,
372 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
373 const char *user_ccache = NULL;
374 const char *orig_ccache = NULL;
375 long long unsigned int rndname;
378 gss_name_t user = GSS_C_NO_NAME;
379 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
380 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
381 gss_name_t server = GSS_C_NO_NAME;
382 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
383 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
384 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
385 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
386 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
387 gss_OID_set allowed_mechs;
388 gss_OID_set filtered_mechs;
389 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
390 uint32_t init_flags = 0;
395 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
396 if (GSS_ERROR(maj)) {
397 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
399 mag_error(req, "gss_import_name() failed",
404 if (cfg->basic_mechs) {
405 allowed_mechs = cfg->basic_mechs;
406 } else if (cfg->allowed_mechs) {
407 allowed_mechs = cfg->allowed_mechs;
409 struct mag_server_config *scfg;
410 /* Try to fetch the default set if not explicitly configured,
411 * We need to do this because gss_acquire_cred_with_password()
412 * is currently limited to acquire creds for a single "default"
413 * mechanism if no desired mechanisms are passed in. This causes
414 * authentication to fail for secondary mechanisms as no user
415 * credentials are generated for those. */
416 scfg = ap_get_module_config(req->server->module_config,
417 &auth_gssapi_module);
418 /* In the worst case scenario default_mechs equals to GSS_C_NO_OID_SET.
419 * This generally causes only the krb5 mechanism to be tried due
420 * to implementation constraints, but may change in future. */
421 allowed_mechs = scfg->default_mechs;
424 /* Remove Spnego if present, or we'd repeat failed authentiations
425 * multiple times, one within Spnego and then again with an explicit
426 * mechanism. We would normally just force Spnego and use
427 * gss_set_neg_mechs, but due to the way we source the server name
428 * and the fact MIT up to 1.14 at least does no handle union names,
429 * we can't provide spnego with a server name that can be used by
430 * multiple mechanisms, causing any but the first mechanism to fail.
431 * Also remove unwanted krb mechs, or AS requests will be repeated
432 * multiple times uselessly.
434 filtered_mechs = mag_filter_unwanted_mechs(allowed_mechs);
435 if (filtered_mechs == allowed_mechs) {
436 /* in case filtered_mechs was not allocated here don't free it */
437 filtered_mechs = GSS_C_NO_OID_SET;
438 } else if (filtered_mechs == GSS_C_NO_OID_SET) {
439 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
440 "failure while filtering mechs, aborting");
443 /* use the filtered list */
444 allowed_mechs = filtered_mechs;
447 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
448 /* If we are using the krb5 mechanism make sure to set a per thread
449 * memory ccache so that there can't be interferences between threads.
450 * Also make sure we have new cache so no cached results end up being
451 * used. Some implementations of gss_acquire_cred_with_password() do
452 * not reacquire creds if cached ones are around, failing to check
453 * again for the password. */
454 maj = gss_test_oid_set_member(&min, discard_const(gss_mech_krb5),
455 allowed_mechs, &present);
456 if (GSS_ERROR(maj)) {
457 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
459 mag_error(req, "gss_test_oid_set_member() failed",
464 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
465 sizeof(long long unsigned int));
466 if (rs != APR_SUCCESS) {
467 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
468 "Failed to generate random ccache name");
471 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
472 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
473 if (GSS_ERROR(maj)) {
474 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
476 mag_error(req, "gss_krb5_ccache_name() "
477 "failed", maj, min));
483 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
487 &user_cred, &actual_mechs, NULL);
488 if (GSS_ERROR(maj)) {
489 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
491 mag_error(req, "gss_acquire_cred_with_password() "
492 "failed", maj, min));
496 /* must acquire creds based on the actual mechs we want to try */
497 if (!mag_acquire_creds(req, cfg, actual_mechs,
498 cred_usage, &acquired_cred, NULL)) {
502 if (cred_usage == GSS_C_BOTH) {
503 /* must acquire with GSS_C_ACCEPT to get the server name */
504 if (!mag_acquire_creds(req, cfg, actual_mechs,
505 GSS_C_ACCEPT, &server_cred, NULL)) {
509 server_cred = acquired_cred;
512 #ifdef HAVE_CRED_STORE
513 if (cfg->deleg_ccache_dir) {
514 /* delegate ourselves credentials so we store them as requested */
515 init_flags |= GSS_C_DELEG_FLAG;
519 for (int i = 0; i < actual_mechs->count; i++) {
521 /* free these if looping */
522 gss_release_buffer(&min, &output);
523 gss_release_buffer(&min, &input);
524 gss_release_name(&min, &server);
526 maj = gss_inquire_cred_by_mech(&min, server_cred,
527 &actual_mechs->elements[i],
528 &server, NULL, NULL, NULL);
529 if (GSS_ERROR(maj)) {
530 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
531 "%s", mag_error(req, "gss_inquired_cred_by_mech() "
532 "failed", maj, min));
537 /* output and input are inverted here, this is intentional */
538 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
539 &actual_mechs->elements[i], init_flags,
540 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
541 NULL, &input, NULL, NULL);
542 if (GSS_ERROR(maj)) {
543 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
544 "%s", mag_error(req, "gss_init_sec_context() "
545 "failed", maj, min));
548 gss_release_buffer(&min, &output);
549 maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
550 &input, GSS_C_NO_CHANNEL_BINDINGS,
551 client, mech_type, &output, NULL,
552 vtime, delegated_cred);
553 if (GSS_ERROR(maj)) {
554 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
555 "%s", mag_error(req, "gss_accept_sec_context()"
556 " failed", maj, min));
559 gss_release_buffer(&min, &input);
560 } while (maj == GSS_S_CONTINUE_NEEDED);
562 if (maj == GSS_S_COMPLETE) {
569 gss_release_buffer(&min, &output);
570 gss_release_buffer(&min, &input);
571 gss_release_name(&min, &server);
572 if (server_cred != acquired_cred)
573 gss_release_cred(&min, &server_cred);
574 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
575 gss_release_cred(&min, &acquired_cred);
576 gss_release_name(&min, &user);
577 gss_release_cred(&min, &user_cred);
578 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
579 gss_release_oid_set(&min, &actual_mechs);
580 gss_release_oid_set(&min, &filtered_mechs);
581 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
582 if (user_ccache != NULL) {
583 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
584 if (maj != GSS_S_COMPLETE) {
585 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
586 "Failed to restore per-thread ccache, %s",
587 mag_error(req, "gss_krb5_ccache_name() "
588 "failed", maj, min));
595 struct mag_req_cfg *mag_init_cfg(request_rec *req)
597 struct mag_server_config *scfg;
598 struct mag_req_cfg *req_cfg = apr_pcalloc(req->pool,
599 sizeof(struct mag_req_cfg));
601 req_cfg->cfg = ap_get_module_config(req->per_dir_config,
602 &auth_gssapi_module);
604 scfg = ap_get_module_config(req->server->module_config,
605 &auth_gssapi_module);
607 if (req_cfg->cfg->allowed_mechs) {
608 req_cfg->desired_mechs = req_cfg->cfg->allowed_mechs;
610 /* Use the default set if not explicitly configured */
611 req_cfg->desired_mechs = scfg->default_mechs;
614 if (req_cfg->cfg->mag_skey) {
615 req_cfg->mag_skey = req_cfg->cfg->mag_skey;
617 /* Use server random key if not explicitly configured */
618 req_cfg->mag_skey = scfg->mag_skey;
621 if (req->proxyreq == PROXYREQ_PROXY) {
622 req_cfg->req_proto = "Proxy-Authorization";
623 req_cfg->rep_proto = "Proxy-Authenticate";
625 req_cfg->req_proto = "Authorization";
626 req_cfg->rep_proto = "WWW-Authenticate";
627 req_cfg->use_sessions = req_cfg->cfg->use_sessions;
628 req_cfg->send_persist = req_cfg->cfg->send_persist;
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!");
647 static int mag_auth(request_rec *req)
651 struct mag_req_cfg *req_cfg;
652 struct mag_config *cfg;
653 const char *auth_header;
654 char *auth_header_type;
655 int ret = HTTP_UNAUTHORIZED;
656 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
658 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
659 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
660 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
661 gss_buffer_desc ba_user;
662 gss_buffer_desc ba_pwd;
663 gss_name_t client = GSS_C_NO_NAME;
664 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
665 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
666 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
671 gss_OID mech_type = GSS_C_NO_OID;
672 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
673 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
674 struct mag_conn *mc = NULL;
677 type = ap_auth_type(req);
678 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
682 req_cfg = mag_init_cfg(req);
686 desired_mechs = req_cfg->desired_mechs;
688 /* implicit auth for subrequests if main auth already happened */
689 if (!ap_is_initial_req(req) && req->main != NULL) {
690 type = ap_auth_type(req->main);
691 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
692 /* warn if the subrequest location and the main request
693 * location have different configs */
694 if (cfg != ap_get_module_config(req->main->per_dir_config,
695 &auth_gssapi_module)) {
696 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
697 req, "Subrequest authentication bypass on "
698 "location with different configuration!");
700 if (req->main->user) {
701 req->user = apr_pstrdup(req->pool, req->main->user);
704 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
705 "The main request is tasked to establish the "
706 "security context, can't proceed!");
707 return HTTP_UNAUTHORIZED;
710 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
711 "Subrequest GSSAPI auth with no auth on the main "
712 "request. This operation may fail if other "
713 "subrequests already established a context or the "
714 "mechanism requires multiple roundtrips.");
719 if (!mag_conn_is_https(req->connection)) {
720 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
721 "Not a TLS connection, refusing to authenticate!");
726 if (cfg->gss_conn_ctx) {
727 mc = (struct mag_conn *)ap_get_module_config(
728 req->connection->conn_config,
729 &auth_gssapi_module);
731 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
732 "Failed to retrieve connection context!");
737 /* if available, session always supersedes connection bound data */
738 if (req_cfg->use_sessions) {
739 mag_check_session(req_cfg, &mc);
742 auth_header = apr_table_get(req->headers_in, req_cfg->req_proto);
745 if (mc->established &&
746 (auth_header == NULL) &&
747 (mc->auth_type != AUTH_TYPE_BASIC)) {
748 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
749 "Already established context found!");
750 mag_set_req_data(req, cfg, mc);
756 /* no preserved mc, create one just for this request */
757 mc = mag_new_conn_ctx(req->pool);
761 /* We can proceed only if we do have an auth header */
762 if (!auth_header) goto done;
764 auth_header_type = ap_getword_white(req->pool, &auth_header);
765 if (!auth_header_type) goto done;
767 for (i = 0; auth_types[i] != NULL; i++) {
768 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
775 case AUTH_TYPE_NEGOTIATE:
776 if (!parse_auth_header(req->pool, &auth_header, &input)) {
780 case AUTH_TYPE_BASIC:
781 if (!cfg->use_basic_auth) {
785 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
786 if (!ba_pwd.value) goto done;
787 ba_user.value = ap_getword_nulls_nc(req->pool,
788 (char **)&ba_pwd.value, ':');
789 if (!ba_user.value) goto done;
791 if (((char *)ba_user.value)[0] == '\0' ||
792 ((char *)ba_pwd.value)[0] == '\0') {
793 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
794 "Invalid empty user or password for Basic Auth");
797 ba_user.length = strlen(ba_user.value);
798 ba_pwd.length = strlen(ba_pwd.value);
800 if (mc->is_preserved && mc->established &&
801 mag_basic_check(req_cfg, mc, ba_user, ba_pwd)) {
802 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
803 "Already established BASIC AUTH context found!");
804 mag_set_req_data(req, cfg, mc);
811 case AUTH_TYPE_RAW_NTLM:
812 if (!is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
813 cfg->gss_conn_ctx)) {
814 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
815 "NTLM Authentication is not allowed!");
819 if (!parse_auth_header(req->pool, &auth_header, &input)) {
823 desired_mechs = discard_const(gss_mech_set_ntlmssp);
830 if (mc->established) {
831 /* if we are re-authenticating make sure the conn context
832 * is cleaned up so we do not accidentally reuse an existing
833 * established context */
837 mc->auth_type = auth_type;
839 #ifdef HAVE_CRED_STORE
840 if (use_s4u2proxy(req_cfg)) {
841 cred_usage = GSS_C_BOTH;
845 if (auth_type == AUTH_TYPE_BASIC) {
846 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
847 cred_usage, &client, &mech_type,
848 &delegated_cred, &vtime)) {
854 if (!mag_acquire_creds(req, cfg, desired_mechs,
855 cred_usage, &acquired_cred, NULL)) {
859 if (auth_type == AUTH_TYPE_NEGOTIATE &&
860 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
861 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
862 if (GSS_ERROR(maj)) {
863 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
864 mag_error(req, "gss_set_neg_mechs() failed",
870 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
871 &input, GSS_C_NO_CHANNEL_BINDINGS,
872 &client, &mech_type, &output, NULL, &vtime,
874 if (GSS_ERROR(maj)) {
875 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
876 mag_error(req, "gss_accept_sec_context() failed",
879 } else if (maj == GSS_S_CONTINUE_NEEDED) {
880 if (!mc->is_preserved) {
881 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
882 "Mechanism needs continuation but neither "
883 "GssapiConnectionBound nor "
884 "GssapiUseSessions are available");
885 gss_release_buffer(&min, &output);
888 /* auth not complete send token and wait next packet */
893 maj = gss_display_name(&min, client, &name, NULL);
894 if (GSS_ERROR(maj)) {
895 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
896 mag_error(req, "gss_display_name() failed",
900 mc->gss_name = apr_pstrndup(req->pool, name.value, name.length);
901 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
902 vtime = MIN_SESS_EXP_TIME;
904 mc->expiration = time(NULL) + vtime;
906 #ifdef HAVE_CRED_STORE
907 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
908 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, mc->gss_name,
910 mc->delegated = true;
914 if (cfg->map_to_local) {
915 maj = gss_localname(&min, client, mech_type, &lname);
916 if (maj != GSS_S_COMPLETE) {
917 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
918 mag_error(req, "gss_localname() failed", maj, min));
921 mc->user_name = apr_pstrndup(req->pool, lname.value, lname.length);
923 mc->user_name = apr_pstrdup(mc->pool, mc->gss_name);
926 mc->established = true;
927 if (auth_type == AUTH_TYPE_BASIC) {
928 mag_basic_cache(req_cfg, mc, ba_user, ba_pwd);
930 if (req_cfg->use_sessions) {
931 mag_attempt_session(req_cfg, mc);
934 /* Now set request data and env vars */
935 mag_set_req_data(req, cfg, mc);
937 if (req_cfg->send_persist)
938 apr_table_set(req->headers_out, "Persistent-Auth",
939 cfg->gss_conn_ctx ? "true" : "false");
945 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
946 int prefixlen = strlen(mag_str_auth_type(auth_type)) + 1;
947 replen = apr_base64_encode_len(output.length) + 1;
948 reply = apr_pcalloc(req->pool, prefixlen + replen);
950 memcpy(reply, mag_str_auth_type(auth_type), prefixlen - 1);
951 reply[prefixlen - 1] = ' ';
952 apr_base64_encode(&reply[prefixlen], output.value, output.length);
953 apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply);
955 } else if (ret == HTTP_UNAUTHORIZED) {
956 apr_table_add(req->err_headers_out, req_cfg->rep_proto, "Negotiate");
958 if (is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
959 cfg->gss_conn_ctx)) {
960 apr_table_add(req->err_headers_out, req_cfg->rep_proto, "NTLM");
962 if (cfg->use_basic_auth) {
963 apr_table_add(req->err_headers_out, req_cfg->rep_proto,
964 apr_psprintf(req->pool, "Basic realm=\"%s\"",
969 if (ctx != GSS_C_NO_CONTEXT)
970 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
971 gss_release_cred(&min, &acquired_cred);
972 gss_release_cred(&min, &delegated_cred);
973 gss_release_buffer(&min, &output);
974 gss_release_name(&min, &client);
975 gss_release_buffer(&min, &name);
976 gss_release_buffer(&min, &lname);
981 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
983 struct mag_config *cfg;
985 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
991 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
993 struct mag_config *cfg = (struct mag_config *)mconfig;
994 cfg->ssl_only = on ? true : false;
998 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
1000 struct mag_config *cfg = (struct mag_config *)mconfig;
1001 cfg->map_to_local = on ? true : false;
1005 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
1007 struct mag_config *cfg = (struct mag_config *)mconfig;
1008 cfg->gss_conn_ctx = on ? true : false;
1012 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1014 struct mag_config *cfg = (struct mag_config *)mconfig;
1015 cfg->send_persist = on ? true : false;
1019 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1021 struct mag_config *cfg = (struct mag_config *)mconfig;
1022 cfg->use_sessions = on ? true : false;
1026 #ifdef HAVE_CRED_STORE
1027 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1029 struct mag_config *cfg = (struct mag_config *)mconfig;
1030 cfg->use_s4u2proxy = on ? true : false;
1036 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1038 struct mag_config *cfg = (struct mag_config *)mconfig;
1039 struct databuf keys;
1045 if (strncmp(w, "key:", 4) != 0) {
1046 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1047 "Invalid key format, expected prefix 'key:'");
1052 l = apr_base64_decode_len(k);
1053 val = apr_palloc(parms->temp_pool, l);
1055 keys.length = (int)apr_base64_decode_binary(val, k);
1056 keys.value = (unsigned char *)val;
1058 if (keys.length != 32) {
1059 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1060 "Invalid key length, expected 32 got %d", keys.length);
1064 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1066 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1067 "Failed to import sealing key!");
1072 #ifdef HAVE_CRED_STORE
1074 #define MAX_CRED_OPTIONS 10
1076 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1079 struct mag_config *cfg = (struct mag_config *)mconfig;
1080 gss_key_value_element_desc *elements;
1089 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1090 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1094 key = apr_pstrndup(parms->pool, w, (p-w));
1095 value = apr_pstrdup(parms->pool, p + 1);
1097 if (!cfg->cred_store) {
1098 cfg->cred_store = apr_pcalloc(parms->pool,
1099 sizeof(gss_key_value_set_desc));
1100 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1101 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1104 elements = cfg->cred_store->elements;
1105 count = cfg->cred_store->count;
1107 if (count >= MAX_CRED_OPTIONS) {
1108 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1109 "Too many GssapiCredStore options (MAX: %d)",
1113 cfg->cred_store->count++;
1115 elements[count].key = key;
1116 elements[count].value = value;
1121 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1124 struct mag_config *cfg = (struct mag_config *)mconfig;
1126 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1132 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1133 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1135 struct mag_config *cfg = (struct mag_config *)mconfig;
1137 cfg->use_basic_auth = on ? true : false;
1142 static apr_status_t mag_oid_set_destroy(void *ptr)
1145 gss_OID_set set = (gss_OID_set)ptr;
1146 (void)gss_release_oid_set(&min, &set);
1150 static bool mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1151 bool add_spnego, const char *w)
1153 gss_buffer_desc buf = { 0 };
1157 bool release_oid = false;
1159 if (NULL == *oidset) {
1160 maj = gss_create_empty_oid_set(&min, &set);
1161 if (maj != GSS_S_COMPLETE) {
1162 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1163 "gss_create_empty_oid_set() failed.");
1164 *oidset = GSS_C_NO_OID_SET;
1168 oid = discard_const(&gss_mech_spnego);
1169 maj = gss_add_oid_set_member(&min, oid, &set);
1170 if (maj != GSS_S_COMPLETE) {
1171 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1172 "gss_add_oid_set_member() failed.");
1173 (void)gss_release_oid_set(&min, &set);
1174 *oidset = GSS_C_NO_OID_SET;
1178 /* register in the pool so it can be released once the server
1180 apr_pool_cleanup_register(parms->pool, (void *)set,
1181 mag_oid_set_destroy,
1182 apr_pool_cleanup_null);
1188 if (strcmp(w, "krb5") == 0) {
1189 oid = discard_const(gss_mech_krb5);
1190 } else if (strcmp(w, "iakerb") == 0) {
1191 oid = discard_const(gss_mech_iakerb);
1192 } else if (strcmp(w, "ntlmssp") == 0) {
1193 oid = discard_const(gss_mech_ntlmssp);
1195 buf.value = discard_const(w);
1196 buf.length = strlen(w);
1197 maj = gss_str_to_oid(&min, &buf, &oid);
1198 if (maj != GSS_S_COMPLETE) {
1199 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1200 "Unrecognized GSSAPI Mechanism: [%s]", w);
1205 maj = gss_add_oid_set_member(&min, oid, &set);
1206 if (maj != GSS_S_COMPLETE) {
1207 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1208 "gss_add_oid_set_member() failed for [%s].", w);
1211 (void)gss_release_oid(&min, &oid);
1217 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1220 struct mag_config *cfg = (struct mag_config *)mconfig;
1222 if (!mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w))
1223 return "Failed to apply GssapiAllowedMech directive";
1228 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1229 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1232 struct mag_config *cfg = (struct mag_config *)mconfig;
1234 if (!mag_list_of_mechs(parms, &cfg->basic_mechs, false, w))
1235 return "Failed to apply GssapiBasicAuthMech directive";
1241 static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
1243 struct mag_server_config *scfg;
1247 scfg = apr_pcalloc(p, sizeof(struct mag_server_config));
1249 maj = gss_indicate_mechs(&min, &scfg->default_mechs);
1250 if (maj != GSS_S_COMPLETE) {
1251 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
1252 "gss_indicate_mechs() failed");
1254 /* Register the set in pool */
1255 apr_pool_cleanup_register(p, (void *)scfg->default_mechs,
1256 mag_oid_set_destroy, apr_pool_cleanup_null);
1259 rc = SEAL_KEY_CREATE(p, &scfg->mag_skey, NULL);
1261 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
1262 "Failed to generate random sealing key!");
1268 static const command_rec mag_commands[] = {
1269 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1270 "Work only if connection is SSL Secured"),
1271 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1272 "Translate principals to local names"),
1273 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1274 "Authentication is bound to the TCP connection"),
1275 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1276 "Send Persitent-Auth header according to connection bound"),
1277 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1278 "Authentication uses mod_sessions to hold status"),
1279 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1280 "Key Used to seal session data."),
1281 #ifdef HAVE_CRED_STORE
1282 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1283 "Initializes credentials for s4u2proxy usage"),
1284 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1285 "Credential Store"),
1286 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1287 OR_AUTHCFG, "Directory to store delegated credentials"),
1289 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1290 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1291 "Allows use of Basic Auth for authentication"),
1292 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1293 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1295 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1296 "Allowed Mechanisms"),
1301 mag_register_hooks(apr_pool_t *p)
1303 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1304 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1305 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1308 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1310 STANDARD20_MODULE_STUFF,
1311 mag_create_dir_config,
1313 mag_create_server_config,