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 const gss_OID_desc gss_mech_ntlmssp = {
32 GSS_NTLMSSP_OID_LENGTH, GSS_NTLMSSP_OID_STRING
35 const gss_OID_set_desc gss_mech_set_ntlmssp = {
36 1, discard_const(&gss_mech_ntlmssp)
39 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
41 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
43 APLOG_USE_MODULE(auth_gssapi);
45 static char *mag_status(request_rec *req, int type, uint32_t err)
47 uint32_t maj_ret, min_ret;
56 maj_ret = gss_display_status(&min_ret, err, type,
57 GSS_C_NO_OID, &msg_ctx, &text);
58 if (maj_ret != GSS_S_COMPLETE) {
64 msg_ret = apr_psprintf(req->pool, "%s, %*s",
65 msg_ret, len, (char *)text.value);
67 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
69 gss_release_buffer(&min_ret, &text);
70 } while (msg_ctx != 0);
75 static char *mag_error(request_rec *req, const char *msg,
76 uint32_t maj, uint32_t min)
81 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
82 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
83 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
86 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
88 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
89 apr_pool_t *temp, server_rec *s)
91 /* FIXME: create mutex to deal with connections and contexts ? */
92 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
93 mag_post_config_session();
94 ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
99 static int mag_pre_connection(conn_rec *c, void *csd)
103 mc = mag_new_conn_ctx(c->pool);
104 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
108 static apr_status_t mag_conn_destroy(void *ptr)
110 struct mag_conn *mc = (struct mag_conn *)ptr;
114 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
119 struct mag_conn *mag_new_conn_ctx(apr_pool_t *pool)
123 mc = apr_pcalloc(pool, sizeof(struct mag_conn));
124 apr_pool_create(&mc->pool, pool);
125 /* register the context in the memory pool, so it can be freed
126 * when the connection/request is terminated */
127 apr_pool_cleanup_register(mc->pool, (void *)mc,
128 mag_conn_destroy, apr_pool_cleanup_null);
133 static void mag_conn_clear(struct mag_conn *mc)
135 (void)mag_conn_destroy(mc);
138 apr_pool_clear(mc->pool);
140 memset(mc, 0, sizeof(struct mag_conn));
144 static bool mag_conn_is_https(conn_rec *c)
147 if (mag_is_https(c)) return true;
153 static bool mag_acquire_creds(request_rec *req,
154 struct mag_config *cfg,
155 gss_OID_set desired_mechs,
156 gss_cred_usage_t cred_usage,
157 gss_cred_id_t *creds,
158 gss_OID_set *actual_mechs)
161 #ifdef HAVE_CRED_STORE
162 gss_const_key_value_set_t store = cfg->cred_store;
164 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
165 desired_mechs, cred_usage, store, creds,
168 maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
169 desired_mechs, cred_usage, creds,
173 if (GSS_ERROR(maj)) {
174 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
175 mag_error(req, "gss_acquire_cred[_from]() "
176 "failed to get server creds",
184 #ifdef HAVE_CRED_STORE
185 static char *escape(apr_pool_t *pool, const char *name,
186 char find, const char *replace)
188 char *escaped = NULL;
193 namecopy = apr_pstrdup(pool, name);
195 p = strchr(namecopy, find);
196 if (!p) return namecopy;
201 /* terminate previous segment */
204 escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
206 escaped = apr_pstrcat(pool, n, replace, NULL);
208 /* move to next segment */
212 /* append last segment if any */
214 escaped = apr_pstrcat(pool, escaped, n, NULL);
220 static char *mag_gss_name_to_ccache_name(request_rec *req,
221 char *dir, const char *gss_name)
225 /* We need to escape away '/', we can't have path separators in
226 * a ccache file name */
227 /* first double escape the esacping char (~) if any */
228 escaped = escape(req->pool, gss_name, '~', "~~");
229 /* then escape away the separator (/) if any */
230 escaped = escape(req->pool, escaped, '/', "~");
232 return apr_psprintf(req->pool, "%s/%s", dir, escaped);
235 static void mag_set_KRB5CCANME(request_rec *req, char *ccname)
241 status = apr_stat(&finfo, ccname, APR_FINFO_MIN, req->pool);
242 if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
243 /* set the file cache anyway, but warn */
244 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
245 "KRB5CCNAME file (%s) lookup failed!", ccname);
248 value = apr_psprintf(req->pool, "FILE:%s", ccname);
249 apr_table_set(req->subprocess_env, "KRB5CCNAME", value);
252 static void mag_store_deleg_creds(request_rec *req,
253 char *dir, char *clientname,
254 gss_cred_id_t delegated_cred,
257 gss_key_value_element_desc element;
258 gss_key_value_set_desc store;
261 element.key = "ccache";
262 store.elements = &element;
265 ccname = mag_gss_name_to_ccache_name(req, dir, clientname);
266 element.value = apr_psprintf(req->pool, "FILE:%s", ccname);
268 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
269 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
270 if (GSS_ERROR(maj)) {
271 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
272 mag_error(req, "failed to store delegated creds",
276 *ccachefile = ccname;
280 static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
283 char *auth_header_value;
285 auth_header_value = ap_getword_white(pool, auth_header);
286 if (!auth_header_value) return false;
287 value->length = apr_base64_decode_len(auth_header_value) + 1;
288 value->value = apr_pcalloc(pool, value->length);
289 if (!value->value) return false;
290 value->length = apr_base64_decode(value->value, auth_header_value);
295 static bool is_mech_allowed(struct mag_config *cfg, gss_const_OID mech)
297 if (cfg->allowed_mechs == GSS_C_NO_OID_SET) return true;
299 for (int i = 0; i < cfg->allowed_mechs->count; i++) {
300 if (gss_oid_equal(&cfg->allowed_mechs->elements[i], mech)) {
307 #define AUTH_TYPE_NEGOTIATE 0
308 #define AUTH_TYPE_BASIC 1
309 #define AUTH_TYPE_RAW_NTLM 2
310 const char *auth_types[] = {
317 static void mag_set_req_data(request_rec *req,
318 struct mag_config *cfg,
321 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
322 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
323 apr_psprintf(req->pool,
324 "%ld", (long)mc->expiration));
325 req->ap_auth_type = apr_pstrdup(req->pool,
326 auth_types[mc->auth_type]);
327 req->user = apr_pstrdup(req->pool, mc->user_name);
328 if (cfg->deleg_ccache_dir && mc->delegated) {
330 ccname = mag_gss_name_to_ccache_name(req,
331 cfg->deleg_ccache_dir,
334 mag_set_KRB5CCANME(req, ccname);
339 gss_OID_set mag_filter_unwanted_mechs(gss_OID_set src)
341 gss_const_OID unwanted_mechs[] = {
352 if (src == GSS_C_NO_OID_SET) return GSS_C_NO_OID_SET;
354 for (int i = 0; unwanted_mechs[i] != GSS_C_NO_OID; i++) {
355 maj = gss_test_oid_set_member(&min,
356 discard_const(unwanted_mechs[i]),
361 maj = gss_create_empty_oid_set(&min, &dst);
362 if (maj != GSS_S_COMPLETE) {
363 return GSS_C_NO_OID_SET;
365 for (int i = 0; i < src->count; i++) {
367 for (int j = 0; unwanted_mechs[j] != GSS_C_NO_OID; j++) {
368 if (gss_oid_equal(&src->elements[i], unwanted_mechs[j])) {
373 if (present) continue;
374 maj = gss_add_oid_set_member(&min, &src->elements[i], &dst);
375 if (maj != GSS_S_COMPLETE) {
376 gss_release_oid_set(&min, &dst);
377 return GSS_C_NO_OID_SET;
385 static bool mag_auth_basic(request_rec *req,
386 struct mag_config *cfg,
387 gss_buffer_desc ba_user,
388 gss_buffer_desc ba_pwd,
389 gss_cred_usage_t cred_usage,
392 gss_cred_id_t *delegated_cred,
395 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
396 const char *user_ccache = NULL;
397 const char *orig_ccache = NULL;
398 long long unsigned int rndname;
401 gss_name_t user = GSS_C_NO_NAME;
402 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
403 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
404 gss_name_t server = GSS_C_NO_NAME;
405 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
406 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
407 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
408 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
409 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
410 gss_OID_set indicated_mechs = GSS_C_NO_OID_SET;
411 gss_OID_set allowed_mechs;
412 gss_OID_set filtered_mechs;
413 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
414 uint32_t init_flags = 0;
419 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
420 if (GSS_ERROR(maj)) {
421 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
423 mag_error(req, "gss_import_name() failed",
428 if (cfg->basic_mechs) {
429 allowed_mechs = cfg->basic_mechs;
430 } else if (cfg->allowed_mechs) {
431 allowed_mechs = cfg->allowed_mechs;
433 /* Try to fetch the default set if not explicitly configured,
434 * We need to do this because gss_acquire_cred_with_password()
435 * is currently limited to acquire creds for a single "default"
436 * mechanism if no desired mechanisms are passed in. This causes
437 * authentication to fail for secondary mechanisms as no user
438 * credentials are generated for those. */
439 maj = gss_indicate_mechs(&min, &indicated_mechs);
440 if (maj != GSS_S_COMPLETE) {
441 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
442 mag_error(req, "gss_indicate_mechs() failed",
444 /* if indicated _mechs failed, set GSS_C_NO_OID_SET. This
445 * generally causes only the krb5 mechanism to be tried due
446 * to implementation constraints, but may change in future. */
447 allowed_mechs = GSS_C_NO_OID_SET;
449 allowed_mechs = indicated_mechs;
453 /* Remove Spnego if present, or we'd repeat failed authentiations
454 * multiple times, one within Spnego and then again with an explicit
455 * mechanism. We would normally just force Spnego and use
456 * gss_set_neg_mechs, but due to the way we source the server name
457 * and the fact MIT up to 1.14 at least does no handle union names,
458 * we can't provide spnego with a server name that can be used by
459 * multiple mechanisms, causing any but the first mechanism to fail.
460 * Also remove unwanted krb mechs, or AS requests will be repeated
461 * multiple times uselessly.
463 filtered_mechs = mag_filter_unwanted_mechs(allowed_mechs);
464 if ((allowed_mechs != GSS_C_NO_OID_SET) &&
465 (filtered_mechs == GSS_C_NO_OID_SET)) {
466 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
467 "failure while filtering mechs, aborting");
469 } else if (filtered_mechs != allowed_mechs) {
470 /* if indicated_mechs where sourced then free them here before
471 * reusing the pointer */
472 gss_release_oid_set(&min, &indicated_mechs);
474 /* mark the list of mechs needs to be freed */
475 indicated_mechs = filtered_mechs;
477 /* use the filtered list */
478 allowed_mechs = filtered_mechs;
481 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
482 /* If we are using the krb5 mechanism make sure to set a per thread
483 * memory ccache so that there can't be interferences between threads.
484 * Also make sure we have new cache so no cached results end up being
485 * used. Some implementations of gss_acquire_cred_with_password() do
486 * not reacquire creds if cached ones are around, failing to check
487 * again for the password. */
488 maj = gss_test_oid_set_member(&min, discard_const(gss_mech_krb5),
489 allowed_mechs, &present);
490 if (GSS_ERROR(maj)) {
491 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
493 mag_error(req, "gss_test_oid_set_member() failed",
498 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
499 sizeof(long long unsigned int));
500 if (rs != APR_SUCCESS) {
501 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
502 "Failed to generate random ccache name");
505 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
506 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
507 if (GSS_ERROR(maj)) {
508 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
510 mag_error(req, "gss_krb5_ccache_name() "
511 "failed", maj, min));
517 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
521 &user_cred, &actual_mechs, NULL);
522 if (GSS_ERROR(maj)) {
523 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
525 mag_error(req, "gss_acquire_cred_with_password() "
526 "failed", maj, min));
530 /* must acquire creds based on the actual mechs we want to try */
531 if (!mag_acquire_creds(req, cfg, actual_mechs,
532 cred_usage, &acquired_cred, NULL)) {
536 if (cred_usage == GSS_C_BOTH) {
537 /* must acquire with GSS_C_ACCEPT to get the server name */
538 if (!mag_acquire_creds(req, cfg, actual_mechs,
539 GSS_C_ACCEPT, &server_cred, NULL)) {
543 server_cred = acquired_cred;
546 #ifdef HAVE_CRED_STORE
547 if (cfg->deleg_ccache_dir) {
548 /* delegate ourselves credentials so we store them as requested */
549 init_flags |= GSS_C_DELEG_FLAG;
553 for (int i = 0; i < actual_mechs->count; i++) {
555 /* free these if looping */
556 gss_release_buffer(&min, &output);
557 gss_release_buffer(&min, &input);
558 gss_release_name(&min, &server);
560 maj = gss_inquire_cred_by_mech(&min, server_cred,
561 &actual_mechs->elements[i],
562 &server, NULL, NULL, NULL);
563 if (GSS_ERROR(maj)) {
564 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
565 "%s", mag_error(req, "gss_inquired_cred_by_mech() "
566 "failed", maj, min));
571 /* output and input are inverted here, this is intentional */
572 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
573 &actual_mechs->elements[i], init_flags,
574 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
575 NULL, &input, NULL, NULL);
576 if (GSS_ERROR(maj)) {
577 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
578 "%s", mag_error(req, "gss_init_sec_context() "
579 "failed", maj, min));
582 gss_release_buffer(&min, &output);
583 maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
584 &input, GSS_C_NO_CHANNEL_BINDINGS,
585 client, mech_type, &output, NULL,
586 vtime, delegated_cred);
587 if (GSS_ERROR(maj)) {
588 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
589 "%s", mag_error(req, "gss_accept_sec_context()"
590 " failed", maj, min));
593 gss_release_buffer(&min, &input);
594 } while (maj == GSS_S_CONTINUE_NEEDED);
596 if (maj == GSS_S_COMPLETE) {
603 gss_release_buffer(&min, &output);
604 gss_release_buffer(&min, &input);
605 gss_release_name(&min, &server);
606 if (server_cred != acquired_cred)
607 gss_release_cred(&min, &server_cred);
608 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
609 gss_release_cred(&min, &acquired_cred);
610 gss_release_name(&min, &user);
611 gss_release_cred(&min, &user_cred);
612 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
613 gss_release_oid_set(&min, &actual_mechs);
614 gss_release_oid_set(&min, &indicated_mechs);
615 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
616 if (user_ccache != NULL) {
617 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
618 if (maj != GSS_S_COMPLETE) {
619 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
620 "Failed to restore per-thread ccache, %s",
621 mag_error(req, "gss_krb5_ccache_name() "
622 "failed", maj, min));
630 static int mag_auth(request_rec *req)
634 struct mag_config *cfg;
635 const char *auth_header;
636 char *auth_header_type;
637 int ret = HTTP_UNAUTHORIZED;
638 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
640 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
641 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
642 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
643 gss_buffer_desc ba_user;
644 gss_buffer_desc ba_pwd;
645 gss_name_t client = GSS_C_NO_NAME;
646 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
647 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
648 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
654 gss_OID mech_type = GSS_C_NO_OID;
655 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
656 gss_OID_set indicated_mechs = GSS_C_NO_OID_SET;
657 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
658 struct mag_conn *mc = NULL;
662 type = ap_auth_type(req);
663 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
667 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
669 if (cfg->allowed_mechs) {
670 desired_mechs = cfg->allowed_mechs;
672 /* Try to fetch the default set if not explicitly configured */
673 maj = gss_indicate_mechs(&min, &indicated_mechs);
674 if (maj != GSS_S_COMPLETE) {
675 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
676 mag_error(req, "gss_indicate_mechs() failed",
679 desired_mechs = indicated_mechs;
682 /* implicit auth for subrequests if main auth already happened */
683 if (!ap_is_initial_req(req) && req->main != NULL) {
684 type = ap_auth_type(req->main);
685 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
686 /* warn if the subrequest location and the main request
687 * location have different configs */
688 if (cfg != ap_get_module_config(req->main->per_dir_config,
689 &auth_gssapi_module)) {
690 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
691 req, "Subrequest authentication bypass on "
692 "location with different configuration!");
694 if (req->main->user) {
695 req->user = apr_pstrdup(req->pool, req->main->user);
698 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
699 "The main request is tasked to establish the "
700 "security context, can't proceed!");
701 return HTTP_UNAUTHORIZED;
704 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
705 "Subrequest GSSAPI auth with no auth on the main "
706 "request. This operation may fail if other "
707 "subrequests already established a context or the "
708 "mechanism requires multiple roundtrips.");
713 if (!mag_conn_is_https(req->connection)) {
714 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
715 "Not a TLS connection, refusing to authenticate!");
720 if (cfg->gss_conn_ctx) {
721 mc = (struct mag_conn *)ap_get_module_config(
722 req->connection->conn_config,
723 &auth_gssapi_module);
725 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
726 "Failed to retrieve connection context!");
731 /* if available, session always supersedes connection bound data */
732 if (cfg->use_sessions) {
733 mag_check_session(req, cfg, &mc);
736 auth_header = apr_table_get(req->headers_in, "Authorization");
739 if (mc->established &&
740 (auth_header == NULL) &&
741 (mc->auth_type != AUTH_TYPE_BASIC)) {
742 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
743 "Already established context found!");
744 mag_set_req_data(req, cfg, mc);
753 /* We can proceed only if we do have an auth header */
754 if (!auth_header) goto done;
756 auth_header_type = ap_getword_white(req->pool, &auth_header);
757 if (!auth_header_type) goto done;
759 for (i = 0; auth_types[i] != NULL; i++) {
760 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
767 case AUTH_TYPE_NEGOTIATE:
768 if (!parse_auth_header(req->pool, &auth_header, &input)) {
772 case AUTH_TYPE_BASIC:
773 if (!cfg->use_basic_auth) {
777 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
778 if (!ba_pwd.value) goto done;
779 ba_user.value = ap_getword_nulls_nc(req->pool,
780 (char **)&ba_pwd.value, ':');
781 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 && mc->established &&
792 mag_basic_check(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(cfg, &gss_mech_ntlmssp)) {
804 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
805 "NTLM Authentication is not allowed!");
809 if (!parse_auth_header(req->pool, &auth_header, &input)) {
813 desired_mechs = discard_const(&gss_mech_set_ntlmssp);
820 if (mc && mc->established) {
821 /* if we are re-authenticating make sure the conn context
822 * is cleaned up so we do not accidentally reuse an existing
823 * established context */
827 req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
829 #ifdef HAVE_CRED_STORE
830 if (cfg->use_s4u2proxy) {
831 cred_usage = GSS_C_BOTH;
835 if (auth_type == AUTH_TYPE_BASIC) {
836 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
837 cred_usage, &client, &mech_type,
838 &delegated_cred, &vtime)) {
844 if (!mag_acquire_creds(req, cfg, desired_mechs,
845 cred_usage, &acquired_cred, NULL)) {
849 if (auth_type == AUTH_TYPE_NEGOTIATE &&
850 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
851 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
852 if (GSS_ERROR(maj)) {
853 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
854 mag_error(req, "gss_set_neg_mechs() failed",
860 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
861 &input, GSS_C_NO_CHANNEL_BINDINGS,
862 &client, &mech_type, &output, NULL, &vtime,
864 if (GSS_ERROR(maj)) {
865 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
866 mag_error(req, "gss_accept_sec_context() failed",
869 } else if (maj == GSS_S_CONTINUE_NEEDED) {
871 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
872 "Mechanism needs continuation but neither "
873 "GssapiConnectionBound nor "
874 "GssapiUseSessions are available");
875 gss_release_buffer(&min, &output);
878 /* auth not complete send token and wait next packet */
883 /* Always set the GSS name in an env var */
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",
891 clientname = apr_pstrndup(req->pool, name.value, name.length);
892 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
893 expiration = time(NULL) + vtime;
894 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
895 apr_psprintf(req->pool, "%ld", (long)expiration));
897 #ifdef HAVE_CRED_STORE
898 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
899 char *ccachefile = NULL;
901 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
902 delegated_cred, &ccachefile);
905 mag_set_KRB5CCANME(req, ccachefile);
909 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 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
923 req->user = clientname;
927 mc->user_name = apr_pstrdup(mc->pool, req->user);
928 mc->gss_name = apr_pstrdup(mc->pool, clientname);
929 mc->established = true;
930 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
931 vtime = MIN_SESS_EXP_TIME;
933 mc->expiration = expiration;
934 mc->auth_type = auth_type;
935 if (auth_type == AUTH_TYPE_BASIC) {
936 mag_basic_cache(cfg, mc, ba_user, ba_pwd);
938 if (cfg->use_sessions) {
939 mag_attempt_session(req, cfg, mc);
943 if (cfg->send_persist)
944 apr_table_set(req->headers_out, "Persistent-Auth",
945 cfg->gss_conn_ctx ? "true" : "false");
950 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
951 int prefixlen = strlen(auth_types[auth_type]) + 1;
952 replen = apr_base64_encode_len(output.length) + 1;
953 reply = apr_pcalloc(req->pool, prefixlen + replen);
955 memcpy(reply, auth_types[auth_type], prefixlen - 1);
956 reply[prefixlen - 1] = ' ';
957 apr_base64_encode(&reply[prefixlen], output.value, output.length);
958 apr_table_add(req->err_headers_out,
959 "WWW-Authenticate", reply);
961 } else if (ret == HTTP_UNAUTHORIZED) {
962 apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
963 if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
964 apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
966 if (cfg->use_basic_auth) {
967 apr_table_add(req->err_headers_out,
969 apr_psprintf(req->pool, "Basic realm=\"%s\"",
973 gss_release_oid_set(&min, &indicated_mechs);
974 if (ctx != GSS_C_NO_CONTEXT)
975 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
976 gss_release_cred(&min, &acquired_cred);
977 gss_release_cred(&min, &delegated_cred);
978 gss_release_buffer(&min, &output);
979 gss_release_name(&min, &client);
980 gss_release_buffer(&min, &name);
981 gss_release_buffer(&min, &lname);
986 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
988 struct mag_config *cfg;
990 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
996 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
998 struct mag_config *cfg = (struct mag_config *)mconfig;
999 cfg->ssl_only = on ? true : false;
1003 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
1005 struct mag_config *cfg = (struct mag_config *)mconfig;
1006 cfg->map_to_local = on ? true : false;
1010 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
1012 struct mag_config *cfg = (struct mag_config *)mconfig;
1013 cfg->gss_conn_ctx = on ? true : false;
1017 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1019 struct mag_config *cfg = (struct mag_config *)mconfig;
1020 cfg->send_persist = on ? true : false;
1024 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1026 struct mag_config *cfg = (struct mag_config *)mconfig;
1027 cfg->use_sessions = on ? true : false;
1031 #ifdef HAVE_CRED_STORE
1032 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1034 struct mag_config *cfg = (struct mag_config *)mconfig;
1035 cfg->use_s4u2proxy = on ? true : false;
1037 if (cfg->deleg_ccache_dir == NULL) {
1038 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
1044 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1046 struct mag_config *cfg = (struct mag_config *)mconfig;
1047 struct databuf keys;
1053 if (strncmp(w, "key:", 4) != 0) {
1054 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1055 "Invalid key format, expected prefix 'key:'");
1060 l = apr_base64_decode_len(k);
1061 val = apr_palloc(parms->temp_pool, l);
1063 keys.length = (int)apr_base64_decode_binary(val, k);
1064 keys.value = (unsigned char *)val;
1066 if (keys.length != 32) {
1067 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1068 "Invalid key length, expected 32 got %d", keys.length);
1072 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1074 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1075 "Failed to import sealing key!");
1080 #ifdef HAVE_CRED_STORE
1082 #define MAX_CRED_OPTIONS 10
1084 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1087 struct mag_config *cfg = (struct mag_config *)mconfig;
1088 gss_key_value_element_desc *elements;
1097 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1098 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1102 key = apr_pstrndup(parms->pool, w, (p-w));
1103 value = apr_pstrdup(parms->pool, p + 1);
1105 if (!cfg->cred_store) {
1106 cfg->cred_store = apr_pcalloc(parms->pool,
1107 sizeof(gss_key_value_set_desc));
1108 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1109 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1112 elements = cfg->cred_store->elements;
1113 count = cfg->cred_store->count;
1115 if (count >= MAX_CRED_OPTIONS) {
1116 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1117 "Too many GssapiCredStore options (MAX: %d)",
1121 cfg->cred_store->count++;
1123 elements[count].key = key;
1124 elements[count].value = value;
1129 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1132 struct mag_config *cfg = (struct mag_config *)mconfig;
1134 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1140 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1141 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1143 struct mag_config *cfg = (struct mag_config *)mconfig;
1145 cfg->use_basic_auth = on ? true : false;
1150 static apr_status_t mag_oid_set_destroy(void *ptr)
1153 gss_OID_set set = (gss_OID_set)ptr;
1154 (void)gss_release_oid_set(&min, &set);
1158 static bool mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1159 bool add_spnego, const char *w)
1161 gss_buffer_desc buf = { 0 };
1165 bool release_oid = false;
1167 if (NULL == *oidset) {
1168 maj = gss_create_empty_oid_set(&min, &set);
1169 if (maj != GSS_S_COMPLETE) {
1170 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1171 "gss_create_empty_oid_set() failed.");
1172 *oidset = GSS_C_NO_OID_SET;
1176 oid = discard_const(&gss_mech_spnego);
1177 maj = gss_add_oid_set_member(&min, oid, &set);
1178 if (maj != GSS_S_COMPLETE) {
1179 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1180 "gss_add_oid_set_member() failed.");
1181 (void)gss_release_oid_set(&min, &set);
1182 *oidset = GSS_C_NO_OID_SET;
1186 /* register in the pool so it can be released once the server
1188 apr_pool_cleanup_register(parms->pool, (void *)set,
1189 mag_oid_set_destroy,
1190 apr_pool_cleanup_null);
1196 if (strcmp(w, "krb5") == 0) {
1197 oid = discard_const(gss_mech_krb5);
1198 } else if (strcmp(w, "iakerb") == 0) {
1199 oid = discard_const(gss_mech_iakerb);
1200 } else if (strcmp(w, "ntlmssp") == 0) {
1201 oid = discard_const(&gss_mech_ntlmssp);
1203 buf.value = discard_const(w);
1204 buf.length = strlen(w);
1205 maj = gss_str_to_oid(&min, &buf, &oid);
1206 if (maj != GSS_S_COMPLETE) {
1207 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1208 "Unrecognized GSSAPI Mechanism: [%s]", w);
1213 maj = gss_add_oid_set_member(&min, oid, &set);
1214 if (maj != GSS_S_COMPLETE) {
1215 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1216 "gss_add_oid_set_member() failed for [%s].", w);
1219 (void)gss_release_oid(&min, &oid);
1225 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1228 struct mag_config *cfg = (struct mag_config *)mconfig;
1230 if (!mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w))
1231 return "Failed to apply GssapiAllowedMech directive";
1236 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1237 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1240 struct mag_config *cfg = (struct mag_config *)mconfig;
1242 if (!mag_list_of_mechs(parms, &cfg->basic_mechs, false, w))
1243 return "Failed to apply GssapiBasicAuthMech directive";
1249 static const command_rec mag_commands[] = {
1250 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1251 "Work only if connection is SSL Secured"),
1252 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1253 "Translate principals to local names"),
1254 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1255 "Authentication is bound to the TCP connection"),
1256 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1257 "Send Persitent-Auth header according to connection bound"),
1258 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1259 "Authentication uses mod_sessions to hold status"),
1260 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1261 "Key Used to seal session data."),
1262 #ifdef HAVE_CRED_STORE
1263 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1264 "Initializes credentials for s4u2proxy usage"),
1265 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1266 "Credential Store"),
1267 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1268 OR_AUTHCFG, "Directory to store delegated credentials"),
1270 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1271 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1272 "Allows use of Basic Auth for authentication"),
1273 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1274 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1276 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1277 "Allowed Mechanisms"),
1282 mag_register_hooks(apr_pool_t *p)
1284 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1285 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1286 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1289 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1291 STANDARD20_MODULE_STUFF,
1292 mag_create_dir_config,