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 allowed_mechs;
411 gss_OID_set filtered_mechs;
412 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
413 uint32_t init_flags = 0;
418 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
419 if (GSS_ERROR(maj)) {
420 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
422 mag_error(req, "gss_import_name() failed",
427 if (cfg->basic_mechs) {
428 allowed_mechs = cfg->basic_mechs;
429 } else if (cfg->allowed_mechs) {
430 allowed_mechs = cfg->allowed_mechs;
432 struct mag_server_config *scfg;
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 scfg = ap_get_module_config(req->server->module_config,
440 &auth_gssapi_module);
441 /* In the worst case scenario default_mechs equals to GSS_C_NO_OID_SET.
442 * This generally causes only the krb5 mechanism to be tried due
443 * to implementation constraints, but may change in future. */
444 allowed_mechs = scfg->default_mechs;
447 /* Remove Spnego if present, or we'd repeat failed authentiations
448 * multiple times, one within Spnego and then again with an explicit
449 * mechanism. We would normally just force Spnego and use
450 * gss_set_neg_mechs, but due to the way we source the server name
451 * and the fact MIT up to 1.14 at least does no handle union names,
452 * we can't provide spnego with a server name that can be used by
453 * multiple mechanisms, causing any but the first mechanism to fail.
454 * Also remove unwanted krb mechs, or AS requests will be repeated
455 * multiple times uselessly.
457 filtered_mechs = mag_filter_unwanted_mechs(allowed_mechs);
458 if (filtered_mechs == allowed_mechs) {
459 /* in case filtered_mechs was not allocated here don't free it */
460 filtered_mechs = GSS_C_NO_OID_SET;
461 } else if (filtered_mechs == GSS_C_NO_OID_SET) {
462 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
463 "failure while filtering mechs, aborting");
466 /* use the filtered list */
467 allowed_mechs = filtered_mechs;
470 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
471 /* If we are using the krb5 mechanism make sure to set a per thread
472 * memory ccache so that there can't be interferences between threads.
473 * Also make sure we have new cache so no cached results end up being
474 * used. Some implementations of gss_acquire_cred_with_password() do
475 * not reacquire creds if cached ones are around, failing to check
476 * again for the password. */
477 maj = gss_test_oid_set_member(&min, discard_const(gss_mech_krb5),
478 allowed_mechs, &present);
479 if (GSS_ERROR(maj)) {
480 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
482 mag_error(req, "gss_test_oid_set_member() failed",
487 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
488 sizeof(long long unsigned int));
489 if (rs != APR_SUCCESS) {
490 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
491 "Failed to generate random ccache name");
494 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
495 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
496 if (GSS_ERROR(maj)) {
497 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
499 mag_error(req, "gss_krb5_ccache_name() "
500 "failed", maj, min));
506 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
510 &user_cred, &actual_mechs, NULL);
511 if (GSS_ERROR(maj)) {
512 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
514 mag_error(req, "gss_acquire_cred_with_password() "
515 "failed", maj, min));
519 /* must acquire creds based on the actual mechs we want to try */
520 if (!mag_acquire_creds(req, cfg, actual_mechs,
521 cred_usage, &acquired_cred, NULL)) {
525 if (cred_usage == GSS_C_BOTH) {
526 /* must acquire with GSS_C_ACCEPT to get the server name */
527 if (!mag_acquire_creds(req, cfg, actual_mechs,
528 GSS_C_ACCEPT, &server_cred, NULL)) {
532 server_cred = acquired_cred;
535 #ifdef HAVE_CRED_STORE
536 if (cfg->deleg_ccache_dir) {
537 /* delegate ourselves credentials so we store them as requested */
538 init_flags |= GSS_C_DELEG_FLAG;
542 for (int i = 0; i < actual_mechs->count; i++) {
544 /* free these if looping */
545 gss_release_buffer(&min, &output);
546 gss_release_buffer(&min, &input);
547 gss_release_name(&min, &server);
549 maj = gss_inquire_cred_by_mech(&min, server_cred,
550 &actual_mechs->elements[i],
551 &server, NULL, NULL, NULL);
552 if (GSS_ERROR(maj)) {
553 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
554 "%s", mag_error(req, "gss_inquired_cred_by_mech() "
555 "failed", maj, min));
560 /* output and input are inverted here, this is intentional */
561 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
562 &actual_mechs->elements[i], init_flags,
563 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
564 NULL, &input, NULL, NULL);
565 if (GSS_ERROR(maj)) {
566 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
567 "%s", mag_error(req, "gss_init_sec_context() "
568 "failed", maj, min));
571 gss_release_buffer(&min, &output);
572 maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
573 &input, GSS_C_NO_CHANNEL_BINDINGS,
574 client, mech_type, &output, NULL,
575 vtime, delegated_cred);
576 if (GSS_ERROR(maj)) {
577 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
578 "%s", mag_error(req, "gss_accept_sec_context()"
579 " failed", maj, min));
582 gss_release_buffer(&min, &input);
583 } while (maj == GSS_S_CONTINUE_NEEDED);
585 if (maj == GSS_S_COMPLETE) {
592 gss_release_buffer(&min, &output);
593 gss_release_buffer(&min, &input);
594 gss_release_name(&min, &server);
595 if (server_cred != acquired_cred)
596 gss_release_cred(&min, &server_cred);
597 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
598 gss_release_cred(&min, &acquired_cred);
599 gss_release_name(&min, &user);
600 gss_release_cred(&min, &user_cred);
601 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
602 gss_release_oid_set(&min, &actual_mechs);
603 gss_release_oid_set(&min, &filtered_mechs);
604 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
605 if (user_ccache != NULL) {
606 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
607 if (maj != GSS_S_COMPLETE) {
608 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
609 "Failed to restore per-thread ccache, %s",
610 mag_error(req, "gss_krb5_ccache_name() "
611 "failed", maj, min));
619 static int mag_auth(request_rec *req)
623 struct mag_config *cfg;
624 const char *auth_header;
625 char *auth_header_type;
626 int ret = HTTP_UNAUTHORIZED;
627 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
629 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
630 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
631 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
632 gss_buffer_desc ba_user;
633 gss_buffer_desc ba_pwd;
634 gss_name_t client = GSS_C_NO_NAME;
635 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
636 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
637 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
643 gss_OID mech_type = GSS_C_NO_OID;
644 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
645 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
646 struct mag_conn *mc = NULL;
650 type = ap_auth_type(req);
651 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
655 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
657 if (cfg->allowed_mechs) {
658 desired_mechs = cfg->allowed_mechs;
660 struct mag_server_config *scfg;
661 /* Try to fetch the default set if not explicitly configured */
662 scfg = ap_get_module_config(req->server->module_config,
663 &auth_gssapi_module);
664 desired_mechs = scfg->default_mechs;
667 /* implicit auth for subrequests if main auth already happened */
668 if (!ap_is_initial_req(req) && req->main != NULL) {
669 type = ap_auth_type(req->main);
670 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
671 /* warn if the subrequest location and the main request
672 * location have different configs */
673 if (cfg != ap_get_module_config(req->main->per_dir_config,
674 &auth_gssapi_module)) {
675 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
676 req, "Subrequest authentication bypass on "
677 "location with different configuration!");
679 if (req->main->user) {
680 req->user = apr_pstrdup(req->pool, req->main->user);
683 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
684 "The main request is tasked to establish the "
685 "security context, can't proceed!");
686 return HTTP_UNAUTHORIZED;
689 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
690 "Subrequest GSSAPI auth with no auth on the main "
691 "request. This operation may fail if other "
692 "subrequests already established a context or the "
693 "mechanism requires multiple roundtrips.");
698 if (!mag_conn_is_https(req->connection)) {
699 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
700 "Not a TLS connection, refusing to authenticate!");
705 if (cfg->gss_conn_ctx) {
706 mc = (struct mag_conn *)ap_get_module_config(
707 req->connection->conn_config,
708 &auth_gssapi_module);
710 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
711 "Failed to retrieve connection context!");
716 /* if available, session always supersedes connection bound data */
717 if (cfg->use_sessions) {
718 mag_check_session(req, cfg, &mc);
721 auth_header = apr_table_get(req->headers_in, "Authorization");
724 if (mc->established &&
725 (auth_header == NULL) &&
726 (mc->auth_type != AUTH_TYPE_BASIC)) {
727 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
728 "Already established context found!");
729 mag_set_req_data(req, cfg, mc);
738 /* We can proceed only if we do have an auth header */
739 if (!auth_header) goto done;
741 auth_header_type = ap_getword_white(req->pool, &auth_header);
742 if (!auth_header_type) goto done;
744 for (i = 0; auth_types[i] != NULL; i++) {
745 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
752 case AUTH_TYPE_NEGOTIATE:
753 if (!parse_auth_header(req->pool, &auth_header, &input)) {
757 case AUTH_TYPE_BASIC:
758 if (!cfg->use_basic_auth) {
762 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
763 if (!ba_pwd.value) goto done;
764 ba_user.value = ap_getword_nulls_nc(req->pool,
765 (char **)&ba_pwd.value, ':');
766 if (!ba_user.value) goto done;
767 if (((char *)ba_user.value)[0] == '\0' ||
768 ((char *)ba_pwd.value)[0] == '\0') {
769 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
770 "Invalid empty user or password for Basic Auth");
773 ba_user.length = strlen(ba_user.value);
774 ba_pwd.length = strlen(ba_pwd.value);
776 if (mc && mc->established &&
777 mag_basic_check(cfg, mc, ba_user, ba_pwd)) {
778 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
779 "Already established BASIC AUTH context found!");
780 mag_set_req_data(req, cfg, mc);
787 case AUTH_TYPE_RAW_NTLM:
788 if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
789 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
790 "NTLM Authentication is not allowed!");
794 if (!parse_auth_header(req->pool, &auth_header, &input)) {
798 desired_mechs = discard_const(&gss_mech_set_ntlmssp);
805 if (mc && mc->established) {
806 /* if we are re-authenticating make sure the conn context
807 * is cleaned up so we do not accidentally reuse an existing
808 * established context */
812 req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
814 #ifdef HAVE_CRED_STORE
815 if (cfg->use_s4u2proxy) {
816 cred_usage = GSS_C_BOTH;
820 if (auth_type == AUTH_TYPE_BASIC) {
821 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
822 cred_usage, &client, &mech_type,
823 &delegated_cred, &vtime)) {
829 if (!mag_acquire_creds(req, cfg, desired_mechs,
830 cred_usage, &acquired_cred, NULL)) {
834 if (auth_type == AUTH_TYPE_NEGOTIATE &&
835 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
836 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
837 if (GSS_ERROR(maj)) {
838 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
839 mag_error(req, "gss_set_neg_mechs() failed",
845 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
846 &input, GSS_C_NO_CHANNEL_BINDINGS,
847 &client, &mech_type, &output, NULL, &vtime,
849 if (GSS_ERROR(maj)) {
850 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
851 mag_error(req, "gss_accept_sec_context() failed",
854 } else if (maj == GSS_S_CONTINUE_NEEDED) {
856 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
857 "Mechanism needs continuation but neither "
858 "GssapiConnectionBound nor "
859 "GssapiUseSessions are available");
860 gss_release_buffer(&min, &output);
863 /* auth not complete send token and wait next packet */
868 /* Always set the GSS name in an env var */
869 maj = gss_display_name(&min, client, &name, NULL);
870 if (GSS_ERROR(maj)) {
871 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
872 mag_error(req, "gss_display_name() failed",
876 clientname = apr_pstrndup(req->pool, name.value, name.length);
877 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
878 expiration = time(NULL) + vtime;
879 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
880 apr_psprintf(req->pool, "%ld", (long)expiration));
882 #ifdef HAVE_CRED_STORE
883 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
884 char *ccachefile = NULL;
886 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
887 delegated_cred, &ccachefile);
890 mag_set_KRB5CCANME(req, ccachefile);
894 mc->delegated = true;
899 if (cfg->map_to_local) {
900 maj = gss_localname(&min, client, mech_type, &lname);
901 if (maj != GSS_S_COMPLETE) {
902 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
903 mag_error(req, "gss_localname() failed", maj, min));
906 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
908 req->user = clientname;
912 mc->user_name = apr_pstrdup(mc->pool, req->user);
913 mc->gss_name = apr_pstrdup(mc->pool, clientname);
914 mc->established = true;
915 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
916 vtime = MIN_SESS_EXP_TIME;
918 mc->expiration = expiration;
919 mc->auth_type = auth_type;
920 if (auth_type == AUTH_TYPE_BASIC) {
921 mag_basic_cache(cfg, mc, ba_user, ba_pwd);
923 if (cfg->use_sessions) {
924 mag_attempt_session(req, cfg, mc);
928 if (cfg->send_persist)
929 apr_table_set(req->headers_out, "Persistent-Auth",
930 cfg->gss_conn_ctx ? "true" : "false");
935 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
936 int prefixlen = strlen(auth_types[auth_type]) + 1;
937 replen = apr_base64_encode_len(output.length) + 1;
938 reply = apr_pcalloc(req->pool, prefixlen + replen);
940 memcpy(reply, auth_types[auth_type], prefixlen - 1);
941 reply[prefixlen - 1] = ' ';
942 apr_base64_encode(&reply[prefixlen], output.value, output.length);
943 apr_table_add(req->err_headers_out,
944 "WWW-Authenticate", reply);
946 } else if (ret == HTTP_UNAUTHORIZED) {
947 apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
948 if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
949 apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
951 if (cfg->use_basic_auth) {
952 apr_table_add(req->err_headers_out,
954 apr_psprintf(req->pool, "Basic realm=\"%s\"",
959 if (ctx != GSS_C_NO_CONTEXT)
960 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
961 gss_release_cred(&min, &acquired_cred);
962 gss_release_cred(&min, &delegated_cred);
963 gss_release_buffer(&min, &output);
964 gss_release_name(&min, &client);
965 gss_release_buffer(&min, &name);
966 gss_release_buffer(&min, &lname);
971 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
973 struct mag_config *cfg;
975 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
981 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
983 struct mag_config *cfg = (struct mag_config *)mconfig;
984 cfg->ssl_only = on ? true : false;
988 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
990 struct mag_config *cfg = (struct mag_config *)mconfig;
991 cfg->map_to_local = on ? true : false;
995 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
997 struct mag_config *cfg = (struct mag_config *)mconfig;
998 cfg->gss_conn_ctx = on ? true : false;
1002 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1004 struct mag_config *cfg = (struct mag_config *)mconfig;
1005 cfg->send_persist = on ? true : false;
1009 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1011 struct mag_config *cfg = (struct mag_config *)mconfig;
1012 cfg->use_sessions = on ? true : false;
1016 #ifdef HAVE_CRED_STORE
1017 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1019 struct mag_config *cfg = (struct mag_config *)mconfig;
1020 cfg->use_s4u2proxy = on ? true : false;
1022 if (cfg->deleg_ccache_dir == NULL) {
1023 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
1029 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1031 struct mag_config *cfg = (struct mag_config *)mconfig;
1032 struct databuf keys;
1038 if (strncmp(w, "key:", 4) != 0) {
1039 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1040 "Invalid key format, expected prefix 'key:'");
1045 l = apr_base64_decode_len(k);
1046 val = apr_palloc(parms->temp_pool, l);
1048 keys.length = (int)apr_base64_decode_binary(val, k);
1049 keys.value = (unsigned char *)val;
1051 if (keys.length != 32) {
1052 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1053 "Invalid key length, expected 32 got %d", keys.length);
1057 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1059 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1060 "Failed to import sealing key!");
1065 #ifdef HAVE_CRED_STORE
1067 #define MAX_CRED_OPTIONS 10
1069 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1072 struct mag_config *cfg = (struct mag_config *)mconfig;
1073 gss_key_value_element_desc *elements;
1082 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1083 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1087 key = apr_pstrndup(parms->pool, w, (p-w));
1088 value = apr_pstrdup(parms->pool, p + 1);
1090 if (!cfg->cred_store) {
1091 cfg->cred_store = apr_pcalloc(parms->pool,
1092 sizeof(gss_key_value_set_desc));
1093 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1094 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1097 elements = cfg->cred_store->elements;
1098 count = cfg->cred_store->count;
1100 if (count >= MAX_CRED_OPTIONS) {
1101 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1102 "Too many GssapiCredStore options (MAX: %d)",
1106 cfg->cred_store->count++;
1108 elements[count].key = key;
1109 elements[count].value = value;
1114 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1117 struct mag_config *cfg = (struct mag_config *)mconfig;
1119 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1125 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1126 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1128 struct mag_config *cfg = (struct mag_config *)mconfig;
1130 cfg->use_basic_auth = on ? true : false;
1135 static apr_status_t mag_oid_set_destroy(void *ptr)
1138 gss_OID_set set = (gss_OID_set)ptr;
1139 (void)gss_release_oid_set(&min, &set);
1143 static bool mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1144 bool add_spnego, const char *w)
1146 gss_buffer_desc buf = { 0 };
1150 bool release_oid = false;
1152 if (NULL == *oidset) {
1153 maj = gss_create_empty_oid_set(&min, &set);
1154 if (maj != GSS_S_COMPLETE) {
1155 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1156 "gss_create_empty_oid_set() failed.");
1157 *oidset = GSS_C_NO_OID_SET;
1161 oid = discard_const(&gss_mech_spnego);
1162 maj = gss_add_oid_set_member(&min, oid, &set);
1163 if (maj != GSS_S_COMPLETE) {
1164 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1165 "gss_add_oid_set_member() failed.");
1166 (void)gss_release_oid_set(&min, &set);
1167 *oidset = GSS_C_NO_OID_SET;
1171 /* register in the pool so it can be released once the server
1173 apr_pool_cleanup_register(parms->pool, (void *)set,
1174 mag_oid_set_destroy,
1175 apr_pool_cleanup_null);
1181 if (strcmp(w, "krb5") == 0) {
1182 oid = discard_const(gss_mech_krb5);
1183 } else if (strcmp(w, "iakerb") == 0) {
1184 oid = discard_const(gss_mech_iakerb);
1185 } else if (strcmp(w, "ntlmssp") == 0) {
1186 oid = discard_const(&gss_mech_ntlmssp);
1188 buf.value = discard_const(w);
1189 buf.length = strlen(w);
1190 maj = gss_str_to_oid(&min, &buf, &oid);
1191 if (maj != GSS_S_COMPLETE) {
1192 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1193 "Unrecognized GSSAPI Mechanism: [%s]", w);
1198 maj = gss_add_oid_set_member(&min, oid, &set);
1199 if (maj != GSS_S_COMPLETE) {
1200 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1201 "gss_add_oid_set_member() failed for [%s].", w);
1204 (void)gss_release_oid(&min, &oid);
1210 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1213 struct mag_config *cfg = (struct mag_config *)mconfig;
1215 if (!mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w))
1216 return "Failed to apply GssapiAllowedMech directive";
1221 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1222 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1225 struct mag_config *cfg = (struct mag_config *)mconfig;
1227 if (!mag_list_of_mechs(parms, &cfg->basic_mechs, false, w))
1228 return "Failed to apply GssapiBasicAuthMech directive";
1234 static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
1236 struct mag_server_config *scfg;
1239 scfg = apr_pcalloc(p, sizeof(struct mag_server_config));
1241 maj = gss_indicate_mechs(&min, &scfg->default_mechs);
1242 if (maj != GSS_S_COMPLETE) {
1243 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
1244 "gss_indicate_mechs() failed");
1246 /* Register the set in pool */
1247 apr_pool_cleanup_register(p, (void *)scfg->default_mechs,
1248 mag_oid_set_destroy, apr_pool_cleanup_null);
1254 static const command_rec mag_commands[] = {
1255 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1256 "Work only if connection is SSL Secured"),
1257 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1258 "Translate principals to local names"),
1259 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1260 "Authentication is bound to the TCP connection"),
1261 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1262 "Send Persitent-Auth header according to connection bound"),
1263 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1264 "Authentication uses mod_sessions to hold status"),
1265 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1266 "Key Used to seal session data."),
1267 #ifdef HAVE_CRED_STORE
1268 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1269 "Initializes credentials for s4u2proxy usage"),
1270 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1271 "Credential Store"),
1272 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1273 OR_AUTHCFG, "Directory to store delegated credentials"),
1275 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1276 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1277 "Allows use of Basic Auth for authentication"),
1278 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1279 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1281 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1282 "Allowed Mechanisms"),
1287 mag_register_hooks(apr_pool_t *p)
1289 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1290 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1291 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1294 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1296 STANDARD20_MODULE_STUFF,
1297 mag_create_dir_config,
1299 mag_create_server_config,