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 for (int i = 0; unwanted_mechs[i] != GSS_C_NO_OID; i++) {
353 maj = gss_test_oid_set_member(&min,
354 discard_const(unwanted_mechs[i]),
359 maj = gss_create_empty_oid_set(&min, &dst);
360 if (maj != GSS_S_COMPLETE) {
361 return GSS_C_NO_OID_SET;
363 for (int i = 0; i < src->count; i++) {
365 for (int j = 0; unwanted_mechs[j] != GSS_C_NO_OID; j++) {
366 if (gss_oid_equal(&src->elements[i], unwanted_mechs[j])) {
371 if (present) continue;
372 maj = gss_add_oid_set_member(&min, &src->elements[i], &dst);
373 if (maj != GSS_S_COMPLETE) {
374 gss_release_oid_set(&min, &dst);
375 return GSS_C_NO_OID_SET;
383 static bool mag_auth_basic(request_rec *req,
384 struct mag_config *cfg,
385 gss_buffer_desc ba_user,
386 gss_buffer_desc ba_pwd,
387 gss_cred_usage_t cred_usage,
390 gss_cred_id_t *delegated_cred,
393 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
394 const char *user_ccache = NULL;
395 const char *orig_ccache = NULL;
396 long long unsigned int rndname;
399 gss_name_t user = GSS_C_NO_NAME;
400 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
401 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
402 gss_name_t server = GSS_C_NO_NAME;
403 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
404 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
405 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
406 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
407 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
408 gss_OID_set indicated_mechs = GSS_C_NO_OID_SET;
409 gss_OID_set allowed_mechs;
410 gss_OID_set filtered_mechs;
411 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
412 uint32_t init_flags = 0;
417 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
418 if (GSS_ERROR(maj)) {
419 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
421 mag_error(req, "gss_import_name() failed",
426 if (cfg->basic_mechs) {
427 allowed_mechs = cfg->basic_mechs;
428 } else if (cfg->allowed_mechs) {
429 allowed_mechs = cfg->allowed_mechs;
431 /* Try to fetch the default set if not explicitly configured,
432 * We need to do this because gss_acquire_cred_with_password()
433 * is currently limited to acquire creds for a single "default"
434 * mechanism if no desired mechanisms are passed in. This causes
435 * authentication to fail for secondary mechanisms as no user
436 * credentials are generated for those. */
437 maj = gss_indicate_mechs(&min, &indicated_mechs);
438 if (maj != GSS_S_COMPLETE) {
439 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
440 mag_error(req, "gss_indicate_mechs() failed",
442 /* if indicated _mechs failed, set GSS_C_NO_OID_SET. This
443 * generally causes only the krb5 mechanism to be tried due
444 * to implementation constraints, but may change in future. */
445 allowed_mechs = GSS_C_NO_OID_SET;
447 allowed_mechs = indicated_mechs;
451 /* Remove Spnego if present, or we'd repeat failed authentiations
452 * multiple times, one within Spnego and then again with an explicit
453 * mechanism. We would normally just force Spnego and use
454 * gss_set_neg_mechs, but due to the way we source the server name
455 * and the fact MIT up to 1.14 at least does no handle union names,
456 * we can't provide spnego with a server name that can be used by
457 * multiple mechanisms, causing any but the first mechanism to fail.
458 * Also remove unwanted krb mechs, or AS requests will be repeated
459 * multiple times uselessly.
461 filtered_mechs = mag_filter_unwanted_mechs(allowed_mechs);
462 if (filtered_mechs == GSS_C_NO_OID_SET) {
463 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
464 "failure while filtering mechs, aborting");
466 } else if (filtered_mechs != allowed_mechs) {
467 /* if indicated_mechs where sourced then free them here before
468 * reusing the pointer */
469 gss_release_oid_set(&min, &indicated_mechs);
471 /* mark the list of mechs needs to be freed */
472 indicated_mechs = filtered_mechs;
474 /* use the filtered list */
475 allowed_mechs = filtered_mechs;
478 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
479 /* If we are using the krb5 mechanism make sure to set a per thread
480 * memory ccache so that there can't be interferences between threads.
481 * Also make sure we have new cache so no cached results end up being
482 * used. Some implementations of gss_acquire_cred_with_password() do
483 * not reacquire creds if cached ones are around, failing to check
484 * again for the password. */
485 maj = gss_test_oid_set_member(&min, discard_const(gss_mech_krb5),
486 allowed_mechs, &present);
487 if (GSS_ERROR(maj)) {
488 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
490 mag_error(req, "gss_test_oid_set_member() failed",
495 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
496 sizeof(long long unsigned int));
497 if (rs != APR_SUCCESS) {
498 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
499 "Failed to generate random ccache name");
502 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
503 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
504 if (GSS_ERROR(maj)) {
505 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
507 mag_error(req, "gss_krb5_ccache_name() "
508 "failed", maj, min));
514 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
518 &user_cred, &actual_mechs, NULL);
519 if (GSS_ERROR(maj)) {
520 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
522 mag_error(req, "gss_acquire_cred_with_password() "
523 "failed", maj, min));
527 /* must acquire creds based on the actual mechs we want to try */
528 if (!mag_acquire_creds(req, cfg, actual_mechs,
529 cred_usage, &acquired_cred, NULL)) {
533 if (cred_usage == GSS_C_BOTH) {
534 /* must acquire with GSS_C_ACCEPT to get the server name */
535 if (!mag_acquire_creds(req, cfg, actual_mechs,
536 GSS_C_ACCEPT, &server_cred, NULL)) {
540 server_cred = acquired_cred;
543 #ifdef HAVE_CRED_STORE
544 if (cfg->deleg_ccache_dir) {
545 /* delegate ourselves credentials so we store them as requested */
546 init_flags |= GSS_C_DELEG_FLAG;
550 for (int i = 0; i < actual_mechs->count; i++) {
552 /* free these if looping */
553 gss_release_buffer(&min, &output);
554 gss_release_buffer(&min, &input);
555 gss_release_name(&min, &server);
557 maj = gss_inquire_cred_by_mech(&min, server_cred,
558 &actual_mechs->elements[i],
559 &server, NULL, NULL, NULL);
560 if (GSS_ERROR(maj)) {
561 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
562 "%s", mag_error(req, "gss_inquired_cred_by_mech() "
563 "failed", maj, min));
568 /* output and input are inverted here, this is intentional */
569 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
570 &actual_mechs->elements[i], init_flags,
571 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
572 NULL, &input, NULL, NULL);
573 if (GSS_ERROR(maj)) {
574 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
575 "%s", mag_error(req, "gss_init_sec_context() "
576 "failed", maj, min));
579 gss_release_buffer(&min, &output);
580 maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
581 &input, GSS_C_NO_CHANNEL_BINDINGS,
582 client, mech_type, &output, NULL,
583 vtime, delegated_cred);
584 if (GSS_ERROR(maj)) {
585 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
586 "%s", mag_error(req, "gss_accept_sec_context()"
587 " failed", maj, min));
590 gss_release_buffer(&min, &input);
591 } while (maj == GSS_S_CONTINUE_NEEDED);
593 if (maj == GSS_S_COMPLETE) {
600 gss_release_buffer(&min, &output);
601 gss_release_buffer(&min, &input);
602 gss_release_name(&min, &server);
603 if (server_cred != acquired_cred)
604 gss_release_cred(&min, &server_cred);
605 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
606 gss_release_cred(&min, &acquired_cred);
607 gss_release_name(&min, &user);
608 gss_release_cred(&min, &user_cred);
609 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
610 gss_release_oid_set(&min, &actual_mechs);
611 gss_release_oid_set(&min, &indicated_mechs);
612 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
613 if (user_ccache != NULL) {
614 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
615 if (maj != GSS_S_COMPLETE) {
616 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
617 "Failed to restore per-thread ccache, %s",
618 mag_error(req, "gss_krb5_ccache_name() "
619 "failed", maj, min));
627 static int mag_auth(request_rec *req)
631 struct mag_config *cfg;
632 const char *auth_header;
633 char *auth_header_type;
634 int ret = HTTP_UNAUTHORIZED;
635 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
637 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
638 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
639 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
640 gss_buffer_desc ba_user;
641 gss_buffer_desc ba_pwd;
642 gss_name_t client = GSS_C_NO_NAME;
643 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
644 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
645 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
651 gss_OID mech_type = GSS_C_NO_OID;
652 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
653 gss_OID_set indicated_mechs = GSS_C_NO_OID_SET;
654 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
655 struct mag_conn *mc = NULL;
659 type = ap_auth_type(req);
660 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
664 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
666 if (cfg->allowed_mechs) {
667 desired_mechs = cfg->allowed_mechs;
669 /* Try to fetch the default set if not explicitly configured */
670 maj = gss_indicate_mechs(&min, &indicated_mechs);
671 if (maj != GSS_S_COMPLETE) {
672 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
673 mag_error(req, "gss_indicate_mechs() failed",
676 desired_mechs = indicated_mechs;
679 /* implicit auth for subrequests if main auth already happened */
680 if (!ap_is_initial_req(req) && req->main != NULL) {
681 type = ap_auth_type(req->main);
682 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
683 /* warn if the subrequest location and the main request
684 * location have different configs */
685 if (cfg != ap_get_module_config(req->main->per_dir_config,
686 &auth_gssapi_module)) {
687 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
688 req, "Subrequest authentication bypass on "
689 "location with different configuration!");
691 if (req->main->user) {
692 req->user = apr_pstrdup(req->pool, req->main->user);
695 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
696 "The main request is tasked to establish the "
697 "security context, can't proceed!");
698 return HTTP_UNAUTHORIZED;
701 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
702 "Subrequest GSSAPI auth with no auth on the main "
703 "request. This operation may fail if other "
704 "subrequests already established a context or the "
705 "mechanism requires multiple roundtrips.");
710 if (!mag_conn_is_https(req->connection)) {
711 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
712 "Not a TLS connection, refusing to authenticate!");
717 if (cfg->gss_conn_ctx) {
718 mc = (struct mag_conn *)ap_get_module_config(
719 req->connection->conn_config,
720 &auth_gssapi_module);
722 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
723 "Failed to retrieve connection context!");
728 /* if available, session always supersedes connection bound data */
729 if (cfg->use_sessions) {
730 mag_check_session(req, cfg, &mc);
733 auth_header = apr_table_get(req->headers_in, "Authorization");
736 if (mc->established &&
737 (auth_header == NULL) &&
738 (mc->auth_type != AUTH_TYPE_BASIC)) {
739 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
740 "Already established context found!");
741 mag_set_req_data(req, cfg, mc);
750 /* We can proceed only if we do have an auth header */
751 if (!auth_header) goto done;
753 auth_header_type = ap_getword_white(req->pool, &auth_header);
754 if (!auth_header_type) goto done;
756 for (i = 0; auth_types[i] != NULL; i++) {
757 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
764 case AUTH_TYPE_NEGOTIATE:
765 if (!parse_auth_header(req->pool, &auth_header, &input)) {
769 case AUTH_TYPE_BASIC:
770 if (!cfg->use_basic_auth) {
774 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
775 if (!ba_pwd.value) goto done;
776 ba_user.value = ap_getword_nulls_nc(req->pool,
777 (char **)&ba_pwd.value, ':');
778 if (!ba_user.value) goto done;
779 if (((char *)ba_user.value)[0] == '\0' ||
780 ((char *)ba_pwd.value)[0] == '\0') {
781 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
782 "Invalid empty user or password for Basic Auth");
785 ba_user.length = strlen(ba_user.value);
786 ba_pwd.length = strlen(ba_pwd.value);
788 if (mc && mc->established &&
789 mag_basic_check(cfg, mc, ba_user, ba_pwd)) {
790 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
791 "Already established BASIC AUTH context found!");
792 mag_set_req_data(req, cfg, mc);
799 case AUTH_TYPE_RAW_NTLM:
800 if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
801 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
802 "NTLM Authentication is not allowed!");
806 if (!parse_auth_header(req->pool, &auth_header, &input)) {
810 desired_mechs = discard_const(&gss_mech_set_ntlmssp);
817 if (mc && mc->established) {
818 /* if we are re-authenticating make sure the conn context
819 * is cleaned up so we do not accidentally reuse an existing
820 * established context */
824 req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
826 #ifdef HAVE_CRED_STORE
827 if (cfg->use_s4u2proxy) {
828 cred_usage = GSS_C_BOTH;
832 if (auth_type == AUTH_TYPE_BASIC) {
833 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
834 cred_usage, &client, &mech_type,
835 &delegated_cred, &vtime)) {
841 if (!mag_acquire_creds(req, cfg, desired_mechs,
842 cred_usage, &acquired_cred, NULL)) {
846 if (auth_type == AUTH_TYPE_NEGOTIATE &&
847 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
848 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
849 if (GSS_ERROR(maj)) {
850 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
851 mag_error(req, "gss_set_neg_mechs() failed",
857 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
858 &input, GSS_C_NO_CHANNEL_BINDINGS,
859 &client, &mech_type, &output, NULL, &vtime,
861 if (GSS_ERROR(maj)) {
862 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
863 mag_error(req, "gss_accept_sec_context() failed",
866 } else if (maj == GSS_S_CONTINUE_NEEDED) {
868 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
869 "Mechanism needs continuation but neither "
870 "GssapiConnectionBound nor "
871 "GssapiUseSessions are available");
872 gss_release_buffer(&min, &output);
875 /* auth not complete send token and wait next packet */
880 /* Always set the GSS name in an env var */
881 maj = gss_display_name(&min, client, &name, NULL);
882 if (GSS_ERROR(maj)) {
883 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
884 mag_error(req, "gss_display_name() failed",
888 clientname = apr_pstrndup(req->pool, name.value, name.length);
889 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
890 expiration = time(NULL) + vtime;
891 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
892 apr_psprintf(req->pool, "%ld", (long)expiration));
894 #ifdef HAVE_CRED_STORE
895 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
896 char *ccachefile = NULL;
898 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
899 delegated_cred, &ccachefile);
902 mag_set_KRB5CCANME(req, ccachefile);
906 mc->delegated = true;
911 if (cfg->map_to_local) {
912 maj = gss_localname(&min, client, mech_type, &lname);
913 if (maj != GSS_S_COMPLETE) {
914 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
915 mag_error(req, "gss_localname() failed", maj, min));
918 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
920 req->user = clientname;
924 mc->user_name = apr_pstrdup(mc->pool, req->user);
925 mc->gss_name = apr_pstrdup(mc->pool, clientname);
926 mc->established = true;
927 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
928 vtime = MIN_SESS_EXP_TIME;
930 mc->expiration = expiration;
931 mc->auth_type = auth_type;
932 if (auth_type == AUTH_TYPE_BASIC) {
933 mag_basic_cache(cfg, mc, ba_user, ba_pwd);
935 if (cfg->use_sessions) {
936 mag_attempt_session(req, cfg, mc);
940 if (cfg->send_persist)
941 apr_table_set(req->headers_out, "Persistent-Auth",
942 cfg->gss_conn_ctx ? "true" : "false");
947 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
948 int prefixlen = strlen(auth_types[auth_type]) + 1;
949 replen = apr_base64_encode_len(output.length) + 1;
950 reply = apr_pcalloc(req->pool, prefixlen + replen);
952 memcpy(reply, auth_types[auth_type], prefixlen - 1);
953 reply[prefixlen - 1] = ' ';
954 apr_base64_encode(&reply[prefixlen], output.value, output.length);
955 apr_table_add(req->err_headers_out,
956 "WWW-Authenticate", reply);
958 } else if (ret == HTTP_UNAUTHORIZED) {
959 apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
960 if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
961 apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
963 if (cfg->use_basic_auth) {
964 apr_table_add(req->err_headers_out,
966 apr_psprintf(req->pool, "Basic realm=\"%s\"",
970 gss_release_oid_set(&min, &indicated_mechs);
971 if (ctx != GSS_C_NO_CONTEXT)
972 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
973 gss_release_cred(&min, &acquired_cred);
974 gss_release_cred(&min, &delegated_cred);
975 gss_release_buffer(&min, &output);
976 gss_release_name(&min, &client);
977 gss_release_buffer(&min, &name);
978 gss_release_buffer(&min, &lname);
983 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
985 struct mag_config *cfg;
987 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
993 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
995 struct mag_config *cfg = (struct mag_config *)mconfig;
996 cfg->ssl_only = on ? true : false;
1000 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
1002 struct mag_config *cfg = (struct mag_config *)mconfig;
1003 cfg->map_to_local = on ? true : false;
1007 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
1009 struct mag_config *cfg = (struct mag_config *)mconfig;
1010 cfg->gss_conn_ctx = on ? true : false;
1014 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1016 struct mag_config *cfg = (struct mag_config *)mconfig;
1017 cfg->send_persist = on ? true : false;
1021 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1023 struct mag_config *cfg = (struct mag_config *)mconfig;
1024 cfg->use_sessions = on ? true : false;
1028 #ifdef HAVE_CRED_STORE
1029 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1031 struct mag_config *cfg = (struct mag_config *)mconfig;
1032 cfg->use_s4u2proxy = on ? true : false;
1034 if (cfg->deleg_ccache_dir == NULL) {
1035 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
1041 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1043 struct mag_config *cfg = (struct mag_config *)mconfig;
1044 struct databuf keys;
1050 if (strncmp(w, "key:", 4) != 0) {
1051 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1052 "Invalid key format, expected prefix 'key:'");
1057 l = apr_base64_decode_len(k);
1058 val = apr_palloc(parms->temp_pool, l);
1060 keys.length = (int)apr_base64_decode_binary(val, k);
1061 keys.value = (unsigned char *)val;
1063 if (keys.length != 32) {
1064 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1065 "Invalid key length, expected 32 got %d", keys.length);
1069 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1071 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1072 "Failed to import sealing key!");
1077 #ifdef HAVE_CRED_STORE
1079 #define MAX_CRED_OPTIONS 10
1081 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1084 struct mag_config *cfg = (struct mag_config *)mconfig;
1085 gss_key_value_element_desc *elements;
1094 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1095 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1099 key = apr_pstrndup(parms->pool, w, (p-w));
1100 value = apr_pstrdup(parms->pool, p + 1);
1102 if (!cfg->cred_store) {
1103 cfg->cred_store = apr_pcalloc(parms->pool,
1104 sizeof(gss_key_value_set_desc));
1105 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1106 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1109 elements = cfg->cred_store->elements;
1110 count = cfg->cred_store->count;
1112 if (count >= MAX_CRED_OPTIONS) {
1113 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1114 "Too many GssapiCredStore options (MAX: %d)",
1118 cfg->cred_store->count++;
1120 elements[count].key = key;
1121 elements[count].value = value;
1126 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1129 struct mag_config *cfg = (struct mag_config *)mconfig;
1131 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1137 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1138 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1140 struct mag_config *cfg = (struct mag_config *)mconfig;
1142 cfg->use_basic_auth = on ? true : false;
1147 static apr_status_t mag_oid_set_destroy(void *ptr)
1150 gss_OID_set set = (gss_OID_set)ptr;
1151 (void)gss_release_oid_set(&min, &set);
1155 static bool mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1156 bool add_spnego, const char *w)
1158 gss_buffer_desc buf = { 0 };
1162 bool release_oid = false;
1164 if (NULL == *oidset) {
1165 maj = gss_create_empty_oid_set(&min, &set);
1166 if (maj != GSS_S_COMPLETE) {
1167 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1168 "gss_create_empty_oid_set() failed.");
1169 *oidset = GSS_C_NO_OID_SET;
1173 oid = discard_const(&gss_mech_spnego);
1174 maj = gss_add_oid_set_member(&min, oid, &set);
1175 if (maj != GSS_S_COMPLETE) {
1176 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1177 "gss_add_oid_set_member() failed.");
1178 (void)gss_release_oid_set(&min, &set);
1179 *oidset = GSS_C_NO_OID_SET;
1183 /* register in the pool so it can be released once the server
1185 apr_pool_cleanup_register(parms->pool, (void *)set,
1186 mag_oid_set_destroy,
1187 apr_pool_cleanup_null);
1193 if (strcmp(w, "krb5") == 0) {
1194 oid = discard_const(gss_mech_krb5);
1195 } else if (strcmp(w, "iakerb") == 0) {
1196 oid = discard_const(gss_mech_iakerb);
1197 } else if (strcmp(w, "ntlmssp") == 0) {
1198 oid = discard_const(&gss_mech_ntlmssp);
1200 buf.value = discard_const(w);
1201 buf.length = strlen(w);
1202 maj = gss_str_to_oid(&min, &buf, &oid);
1203 if (maj != GSS_S_COMPLETE) {
1204 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1205 "Unrecognized GSSAPI Mechanism: [%s]", w);
1210 maj = gss_add_oid_set_member(&min, oid, &set);
1211 if (maj != GSS_S_COMPLETE) {
1212 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1213 "gss_add_oid_set_member() failed for [%s].", w);
1216 (void)gss_release_oid(&min, &oid);
1222 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1225 struct mag_config *cfg = (struct mag_config *)mconfig;
1227 if (!mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w))
1228 return "Failed to apply GssapiAllowedMech directive";
1233 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1234 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1237 struct mag_config *cfg = (struct mag_config *)mconfig;
1239 if (!mag_list_of_mechs(parms, &cfg->basic_mechs, false, w))
1240 return "Failed to apply GssapiBasicAuthMech directive";
1246 static const command_rec mag_commands[] = {
1247 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1248 "Work only if connection is SSL Secured"),
1249 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1250 "Translate principals to local names"),
1251 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1252 "Authentication is bound to the TCP connection"),
1253 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1254 "Send Persitent-Auth header according to connection bound"),
1255 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1256 "Authentication uses mod_sessions to hold status"),
1257 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1258 "Key Used to seal session data."),
1259 #ifdef HAVE_CRED_STORE
1260 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1261 "Initializes credentials for s4u2proxy usage"),
1262 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1263 "Credential Store"),
1264 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1265 OR_AUTHCFG, "Directory to store delegated credentials"),
1267 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1268 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1269 "Allows use of Basic Auth for authentication"),
1270 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1271 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1273 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1274 "Allowed Mechanisms"),
1279 mag_register_hooks(apr_pool_t *p)
1281 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1282 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1283 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1286 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1288 STANDARD20_MODULE_STUFF,
1289 mag_create_dir_config,