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(gss_OID_set allowed_mechs, gss_const_OID mech,
296 bool multi_step_supported)
298 if (!multi_step_supported && gss_oid_equal(&gss_mech_ntlmssp, mech))
301 if (allowed_mechs == GSS_C_NO_OID_SET) return true;
303 for (int i = 0; i < allowed_mechs->count; i++) {
304 if (gss_oid_equal(&allowed_mechs->elements[i], mech)) {
311 #define AUTH_TYPE_NEGOTIATE 0
312 #define AUTH_TYPE_BASIC 1
313 #define AUTH_TYPE_RAW_NTLM 2
314 const char *auth_types[] = {
321 static void mag_set_req_data(request_rec *req,
322 struct mag_config *cfg,
325 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
326 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
327 apr_psprintf(req->pool,
328 "%ld", (long)mc->expiration));
329 req->ap_auth_type = apr_pstrdup(req->pool,
330 auth_types[mc->auth_type]);
331 req->user = apr_pstrdup(req->pool, mc->user_name);
332 if (cfg->deleg_ccache_dir && mc->delegated) {
334 ccname = mag_gss_name_to_ccache_name(req,
335 cfg->deleg_ccache_dir,
338 mag_set_KRB5CCANME(req, ccname);
343 gss_OID_set mag_filter_unwanted_mechs(gss_OID_set src)
345 gss_const_OID unwanted_mechs[] = {
356 if (src == GSS_C_NO_OID_SET) return GSS_C_NO_OID_SET;
358 for (int i = 0; unwanted_mechs[i] != GSS_C_NO_OID; i++) {
359 maj = gss_test_oid_set_member(&min,
360 discard_const(unwanted_mechs[i]),
365 maj = gss_create_empty_oid_set(&min, &dst);
366 if (maj != GSS_S_COMPLETE) {
367 return GSS_C_NO_OID_SET;
369 for (int i = 0; i < src->count; i++) {
371 for (int j = 0; unwanted_mechs[j] != GSS_C_NO_OID; j++) {
372 if (gss_oid_equal(&src->elements[i], unwanted_mechs[j])) {
377 if (present) continue;
378 maj = gss_add_oid_set_member(&min, &src->elements[i], &dst);
379 if (maj != GSS_S_COMPLETE) {
380 gss_release_oid_set(&min, &dst);
381 return GSS_C_NO_OID_SET;
389 static bool mag_auth_basic(request_rec *req,
390 struct mag_config *cfg,
391 gss_buffer_desc ba_user,
392 gss_buffer_desc ba_pwd,
393 gss_cred_usage_t cred_usage,
396 gss_cred_id_t *delegated_cred,
399 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
400 const char *user_ccache = NULL;
401 const char *orig_ccache = NULL;
402 long long unsigned int rndname;
405 gss_name_t user = GSS_C_NO_NAME;
406 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
407 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
408 gss_name_t server = GSS_C_NO_NAME;
409 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
410 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
411 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
412 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
413 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
414 gss_OID_set allowed_mechs;
415 gss_OID_set filtered_mechs;
416 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
417 uint32_t init_flags = 0;
422 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
423 if (GSS_ERROR(maj)) {
424 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
426 mag_error(req, "gss_import_name() failed",
431 if (cfg->basic_mechs) {
432 allowed_mechs = cfg->basic_mechs;
433 } else if (cfg->allowed_mechs) {
434 allowed_mechs = cfg->allowed_mechs;
436 struct mag_server_config *scfg;
437 /* Try to fetch the default set if not explicitly configured,
438 * We need to do this because gss_acquire_cred_with_password()
439 * is currently limited to acquire creds for a single "default"
440 * mechanism if no desired mechanisms are passed in. This causes
441 * authentication to fail for secondary mechanisms as no user
442 * credentials are generated for those. */
443 scfg = ap_get_module_config(req->server->module_config,
444 &auth_gssapi_module);
445 /* In the worst case scenario default_mechs equals to GSS_C_NO_OID_SET.
446 * This generally causes only the krb5 mechanism to be tried due
447 * to implementation constraints, but may change in future. */
448 allowed_mechs = scfg->default_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 == allowed_mechs) {
463 /* in case filtered_mechs was not allocated here don't free it */
464 filtered_mechs = GSS_C_NO_OID_SET;
465 } else if (filtered_mechs == GSS_C_NO_OID_SET) {
466 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
467 "failure while filtering mechs, aborting");
470 /* use the filtered list */
471 allowed_mechs = filtered_mechs;
474 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
475 /* If we are using the krb5 mechanism make sure to set a per thread
476 * memory ccache so that there can't be interferences between threads.
477 * Also make sure we have new cache so no cached results end up being
478 * used. Some implementations of gss_acquire_cred_with_password() do
479 * not reacquire creds if cached ones are around, failing to check
480 * again for the password. */
481 maj = gss_test_oid_set_member(&min, discard_const(gss_mech_krb5),
482 allowed_mechs, &present);
483 if (GSS_ERROR(maj)) {
484 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
486 mag_error(req, "gss_test_oid_set_member() failed",
491 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
492 sizeof(long long unsigned int));
493 if (rs != APR_SUCCESS) {
494 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
495 "Failed to generate random ccache name");
498 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
499 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
500 if (GSS_ERROR(maj)) {
501 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
503 mag_error(req, "gss_krb5_ccache_name() "
504 "failed", maj, min));
510 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
514 &user_cred, &actual_mechs, NULL);
515 if (GSS_ERROR(maj)) {
516 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
518 mag_error(req, "gss_acquire_cred_with_password() "
519 "failed", maj, min));
523 /* must acquire creds based on the actual mechs we want to try */
524 if (!mag_acquire_creds(req, cfg, actual_mechs,
525 cred_usage, &acquired_cred, NULL)) {
529 if (cred_usage == GSS_C_BOTH) {
530 /* must acquire with GSS_C_ACCEPT to get the server name */
531 if (!mag_acquire_creds(req, cfg, actual_mechs,
532 GSS_C_ACCEPT, &server_cred, NULL)) {
536 server_cred = acquired_cred;
539 #ifdef HAVE_CRED_STORE
540 if (cfg->deleg_ccache_dir) {
541 /* delegate ourselves credentials so we store them as requested */
542 init_flags |= GSS_C_DELEG_FLAG;
546 for (int i = 0; i < actual_mechs->count; i++) {
548 /* free these if looping */
549 gss_release_buffer(&min, &output);
550 gss_release_buffer(&min, &input);
551 gss_release_name(&min, &server);
553 maj = gss_inquire_cred_by_mech(&min, server_cred,
554 &actual_mechs->elements[i],
555 &server, NULL, NULL, NULL);
556 if (GSS_ERROR(maj)) {
557 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
558 "%s", mag_error(req, "gss_inquired_cred_by_mech() "
559 "failed", maj, min));
564 /* output and input are inverted here, this is intentional */
565 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
566 &actual_mechs->elements[i], init_flags,
567 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
568 NULL, &input, NULL, NULL);
569 if (GSS_ERROR(maj)) {
570 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
571 "%s", mag_error(req, "gss_init_sec_context() "
572 "failed", maj, min));
575 gss_release_buffer(&min, &output);
576 maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
577 &input, GSS_C_NO_CHANNEL_BINDINGS,
578 client, mech_type, &output, NULL,
579 vtime, delegated_cred);
580 if (GSS_ERROR(maj)) {
581 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
582 "%s", mag_error(req, "gss_accept_sec_context()"
583 " failed", maj, min));
586 gss_release_buffer(&min, &input);
587 } while (maj == GSS_S_CONTINUE_NEEDED);
589 if (maj == GSS_S_COMPLETE) {
596 gss_release_buffer(&min, &output);
597 gss_release_buffer(&min, &input);
598 gss_release_name(&min, &server);
599 if (server_cred != acquired_cred)
600 gss_release_cred(&min, &server_cred);
601 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
602 gss_release_cred(&min, &acquired_cred);
603 gss_release_name(&min, &user);
604 gss_release_cred(&min, &user_cred);
605 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
606 gss_release_oid_set(&min, &actual_mechs);
607 gss_release_oid_set(&min, &filtered_mechs);
608 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
609 if (user_ccache != NULL) {
610 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
611 if (maj != GSS_S_COMPLETE) {
612 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
613 "Failed to restore per-thread ccache, %s",
614 mag_error(req, "gss_krb5_ccache_name() "
615 "failed", maj, min));
622 struct mag_req_cfg *mag_init_cfg(request_rec *req)
624 struct mag_server_config *scfg;
625 struct mag_req_cfg *req_cfg = apr_pcalloc(req->pool,
626 sizeof(struct mag_req_cfg));
628 req_cfg->cfg = ap_get_module_config(req->per_dir_config,
629 &auth_gssapi_module);
631 scfg = ap_get_module_config(req->server->module_config,
632 &auth_gssapi_module);
634 if (req_cfg->cfg->allowed_mechs) {
635 req_cfg->desired_mechs = req_cfg->cfg->allowed_mechs;
637 /* Use the default set if not explicitly configured */
638 req_cfg->desired_mechs = scfg->default_mechs;
641 if (!req_cfg->cfg->mag_skey) {
642 req_cfg->mag_skey = req_cfg->cfg->mag_skey;
644 /* Use server random key if not explicitly configured */
645 req_cfg->mag_skey = scfg->mag_skey;
648 if (req->proxyreq == PROXYREQ_PROXY) {
649 req_cfg->req_proto = "Proxy-Authorization";
650 req_cfg->rep_proto = "Proxy-Authenticate";
652 req_cfg->req_proto = "Authorization";
653 req_cfg->rep_proto = "WWW-Authenticate";
654 req_cfg->use_sessions = req_cfg->cfg->use_sessions;
655 req_cfg->send_persist = req_cfg->cfg->send_persist;
661 static bool use_s4u2proxy(struct mag_req_cfg *req_cfg) {
662 if (req_cfg->cfg->use_s4u2proxy) {
663 if (req_cfg->cfg->deleg_ccache_dir != NULL) {
666 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req_cfg->req,
667 "S4U2 Proxy requested but GssapiDelegCcacheDir "
668 "is not set. Constrained delegation disabled!");
674 static int mag_auth(request_rec *req)
678 struct mag_req_cfg *req_cfg;
679 struct mag_config *cfg;
680 const char *auth_header;
681 char *auth_header_type;
682 int ret = HTTP_UNAUTHORIZED;
683 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
685 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
686 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
687 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
688 gss_buffer_desc ba_user;
689 gss_buffer_desc ba_pwd;
690 gss_name_t client = GSS_C_NO_NAME;
691 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
692 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
693 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
699 gss_OID mech_type = GSS_C_NO_OID;
700 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
701 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
702 struct mag_conn *mc = NULL;
706 type = ap_auth_type(req);
707 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
711 req_cfg = mag_init_cfg(req);
715 desired_mechs = req_cfg->desired_mechs;
717 /* implicit auth for subrequests if main auth already happened */
718 if (!ap_is_initial_req(req) && req->main != NULL) {
719 type = ap_auth_type(req->main);
720 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
721 /* warn if the subrequest location and the main request
722 * location have different configs */
723 if (cfg != ap_get_module_config(req->main->per_dir_config,
724 &auth_gssapi_module)) {
725 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
726 req, "Subrequest authentication bypass on "
727 "location with different configuration!");
729 if (req->main->user) {
730 req->user = apr_pstrdup(req->pool, req->main->user);
733 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
734 "The main request is tasked to establish the "
735 "security context, can't proceed!");
736 return HTTP_UNAUTHORIZED;
739 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
740 "Subrequest GSSAPI auth with no auth on the main "
741 "request. This operation may fail if other "
742 "subrequests already established a context or the "
743 "mechanism requires multiple roundtrips.");
748 if (!mag_conn_is_https(req->connection)) {
749 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
750 "Not a TLS connection, refusing to authenticate!");
755 if (cfg->gss_conn_ctx) {
756 mc = (struct mag_conn *)ap_get_module_config(
757 req->connection->conn_config,
758 &auth_gssapi_module);
760 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
761 "Failed to retrieve connection context!");
766 /* if available, session always supersedes connection bound data */
767 if (req_cfg->use_sessions) {
768 mag_check_session(req_cfg, &mc);
771 auth_header = apr_table_get(req->headers_in, req_cfg->req_proto);
774 if (mc->established &&
775 (auth_header == NULL) &&
776 (mc->auth_type != AUTH_TYPE_BASIC)) {
777 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
778 "Already established context found!");
779 mag_set_req_data(req, cfg, mc);
788 /* We can proceed only if we do have an auth header */
789 if (!auth_header) goto done;
791 auth_header_type = ap_getword_white(req->pool, &auth_header);
792 if (!auth_header_type) goto done;
794 for (i = 0; auth_types[i] != NULL; i++) {
795 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
802 case AUTH_TYPE_NEGOTIATE:
803 if (!parse_auth_header(req->pool, &auth_header, &input)) {
807 case AUTH_TYPE_BASIC:
808 if (!cfg->use_basic_auth) {
812 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
813 if (!ba_pwd.value) goto done;
814 ba_user.value = ap_getword_nulls_nc(req->pool,
815 (char **)&ba_pwd.value, ':');
816 if (!ba_user.value) goto done;
817 if (((char *)ba_user.value)[0] == '\0' ||
818 ((char *)ba_pwd.value)[0] == '\0') {
819 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
820 "Invalid empty user or password for Basic Auth");
823 ba_user.length = strlen(ba_user.value);
824 ba_pwd.length = strlen(ba_pwd.value);
826 if (mc && mc->established &&
827 mag_basic_check(req_cfg, mc, ba_user, ba_pwd)) {
828 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
829 "Already established BASIC AUTH context found!");
830 mag_set_req_data(req, cfg, mc);
837 case AUTH_TYPE_RAW_NTLM:
838 if (!is_mech_allowed(desired_mechs, &gss_mech_ntlmssp,
839 cfg->gss_conn_ctx)) {
840 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
841 "NTLM Authentication is not allowed!");
845 if (!parse_auth_header(req->pool, &auth_header, &input)) {
849 desired_mechs = discard_const(&gss_mech_set_ntlmssp);
856 if (mc && mc->established) {
857 /* if we are re-authenticating make sure the conn context
858 * is cleaned up so we do not accidentally reuse an existing
859 * established context */
863 req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
865 #ifdef HAVE_CRED_STORE
866 if (use_s4u2proxy(req_cfg)) {
867 cred_usage = GSS_C_BOTH;
871 if (auth_type == AUTH_TYPE_BASIC) {
872 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
873 cred_usage, &client, &mech_type,
874 &delegated_cred, &vtime)) {
880 if (!mag_acquire_creds(req, cfg, desired_mechs,
881 cred_usage, &acquired_cred, NULL)) {
885 if (auth_type == AUTH_TYPE_NEGOTIATE &&
886 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
887 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
888 if (GSS_ERROR(maj)) {
889 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
890 mag_error(req, "gss_set_neg_mechs() failed",
896 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
897 &input, GSS_C_NO_CHANNEL_BINDINGS,
898 &client, &mech_type, &output, NULL, &vtime,
900 if (GSS_ERROR(maj)) {
901 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
902 mag_error(req, "gss_accept_sec_context() failed",
905 } else if (maj == GSS_S_CONTINUE_NEEDED) {
907 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
908 "Mechanism needs continuation but neither "
909 "GssapiConnectionBound nor "
910 "GssapiUseSessions are available");
911 gss_release_buffer(&min, &output);
914 /* auth not complete send token and wait next packet */
919 /* Always set the GSS name in an env var */
920 maj = gss_display_name(&min, client, &name, NULL);
921 if (GSS_ERROR(maj)) {
922 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
923 mag_error(req, "gss_display_name() failed",
927 clientname = apr_pstrndup(req->pool, name.value, name.length);
928 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
929 expiration = time(NULL) + vtime;
930 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
931 apr_psprintf(req->pool, "%ld", (long)expiration));
933 #ifdef HAVE_CRED_STORE
934 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
935 char *ccachefile = NULL;
937 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
938 delegated_cred, &ccachefile);
941 mag_set_KRB5CCANME(req, ccachefile);
945 mc->delegated = true;
950 if (cfg->map_to_local) {
951 maj = gss_localname(&min, client, mech_type, &lname);
952 if (maj != GSS_S_COMPLETE) {
953 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
954 mag_error(req, "gss_localname() failed", maj, min));
957 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
959 req->user = clientname;
963 mc->user_name = apr_pstrdup(mc->pool, req->user);
964 mc->gss_name = apr_pstrdup(mc->pool, clientname);
965 mc->established = true;
966 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
967 vtime = MIN_SESS_EXP_TIME;
969 mc->expiration = expiration;
970 mc->auth_type = auth_type;
971 if (auth_type == AUTH_TYPE_BASIC) {
972 mag_basic_cache(req_cfg, mc, ba_user, ba_pwd);
974 if (req_cfg->use_sessions) {
975 mag_attempt_session(req_cfg, mc);
979 if (req_cfg->send_persist)
980 apr_table_set(req->headers_out, "Persistent-Auth",
981 cfg->gss_conn_ctx ? "true" : "false");
987 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
988 int prefixlen = strlen(auth_types[auth_type]) + 1;
989 replen = apr_base64_encode_len(output.length) + 1;
990 reply = apr_pcalloc(req->pool, prefixlen + replen);
992 memcpy(reply, auth_types[auth_type], prefixlen - 1);
993 reply[prefixlen - 1] = ' ';
994 apr_base64_encode(&reply[prefixlen], output.value, output.length);
995 apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply);
997 } else if (ret == HTTP_UNAUTHORIZED) {
998 apr_table_add(req->err_headers_out, req_cfg->rep_proto, "Negotiate");
1000 if (is_mech_allowed(desired_mechs, &gss_mech_ntlmssp,
1001 cfg->gss_conn_ctx)) {
1002 apr_table_add(req->err_headers_out, req_cfg->rep_proto, "NTLM");
1004 if (cfg->use_basic_auth) {
1005 apr_table_add(req->err_headers_out, req_cfg->rep_proto,
1006 apr_psprintf(req->pool, "Basic realm=\"%s\"",
1007 ap_auth_name(req)));
1011 if (ctx != GSS_C_NO_CONTEXT)
1012 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
1013 gss_release_cred(&min, &acquired_cred);
1014 gss_release_cred(&min, &delegated_cred);
1015 gss_release_buffer(&min, &output);
1016 gss_release_name(&min, &client);
1017 gss_release_buffer(&min, &name);
1018 gss_release_buffer(&min, &lname);
1023 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
1025 struct mag_config *cfg;
1027 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
1033 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
1035 struct mag_config *cfg = (struct mag_config *)mconfig;
1036 cfg->ssl_only = on ? true : false;
1040 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
1042 struct mag_config *cfg = (struct mag_config *)mconfig;
1043 cfg->map_to_local = on ? true : false;
1047 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
1049 struct mag_config *cfg = (struct mag_config *)mconfig;
1050 cfg->gss_conn_ctx = on ? true : false;
1054 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1056 struct mag_config *cfg = (struct mag_config *)mconfig;
1057 cfg->send_persist = on ? true : false;
1061 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1063 struct mag_config *cfg = (struct mag_config *)mconfig;
1064 cfg->use_sessions = on ? true : false;
1068 #ifdef HAVE_CRED_STORE
1069 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1071 struct mag_config *cfg = (struct mag_config *)mconfig;
1072 cfg->use_s4u2proxy = on ? true : false;
1078 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1080 struct mag_config *cfg = (struct mag_config *)mconfig;
1081 struct databuf keys;
1087 if (strncmp(w, "key:", 4) != 0) {
1088 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1089 "Invalid key format, expected prefix 'key:'");
1094 l = apr_base64_decode_len(k);
1095 val = apr_palloc(parms->temp_pool, l);
1097 keys.length = (int)apr_base64_decode_binary(val, k);
1098 keys.value = (unsigned char *)val;
1100 if (keys.length != 32) {
1101 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1102 "Invalid key length, expected 32 got %d", keys.length);
1106 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1108 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1109 "Failed to import sealing key!");
1114 #ifdef HAVE_CRED_STORE
1116 #define MAX_CRED_OPTIONS 10
1118 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1121 struct mag_config *cfg = (struct mag_config *)mconfig;
1122 gss_key_value_element_desc *elements;
1131 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1132 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1136 key = apr_pstrndup(parms->pool, w, (p-w));
1137 value = apr_pstrdup(parms->pool, p + 1);
1139 if (!cfg->cred_store) {
1140 cfg->cred_store = apr_pcalloc(parms->pool,
1141 sizeof(gss_key_value_set_desc));
1142 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1143 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1146 elements = cfg->cred_store->elements;
1147 count = cfg->cred_store->count;
1149 if (count >= MAX_CRED_OPTIONS) {
1150 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1151 "Too many GssapiCredStore options (MAX: %d)",
1155 cfg->cred_store->count++;
1157 elements[count].key = key;
1158 elements[count].value = value;
1163 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1166 struct mag_config *cfg = (struct mag_config *)mconfig;
1168 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1174 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1175 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1177 struct mag_config *cfg = (struct mag_config *)mconfig;
1179 cfg->use_basic_auth = on ? true : false;
1184 static apr_status_t mag_oid_set_destroy(void *ptr)
1187 gss_OID_set set = (gss_OID_set)ptr;
1188 (void)gss_release_oid_set(&min, &set);
1192 static bool mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1193 bool add_spnego, const char *w)
1195 gss_buffer_desc buf = { 0 };
1199 bool release_oid = false;
1201 if (NULL == *oidset) {
1202 maj = gss_create_empty_oid_set(&min, &set);
1203 if (maj != GSS_S_COMPLETE) {
1204 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1205 "gss_create_empty_oid_set() failed.");
1206 *oidset = GSS_C_NO_OID_SET;
1210 oid = discard_const(&gss_mech_spnego);
1211 maj = gss_add_oid_set_member(&min, oid, &set);
1212 if (maj != GSS_S_COMPLETE) {
1213 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1214 "gss_add_oid_set_member() failed.");
1215 (void)gss_release_oid_set(&min, &set);
1216 *oidset = GSS_C_NO_OID_SET;
1220 /* register in the pool so it can be released once the server
1222 apr_pool_cleanup_register(parms->pool, (void *)set,
1223 mag_oid_set_destroy,
1224 apr_pool_cleanup_null);
1230 if (strcmp(w, "krb5") == 0) {
1231 oid = discard_const(gss_mech_krb5);
1232 } else if (strcmp(w, "iakerb") == 0) {
1233 oid = discard_const(gss_mech_iakerb);
1234 } else if (strcmp(w, "ntlmssp") == 0) {
1235 oid = discard_const(&gss_mech_ntlmssp);
1237 buf.value = discard_const(w);
1238 buf.length = strlen(w);
1239 maj = gss_str_to_oid(&min, &buf, &oid);
1240 if (maj != GSS_S_COMPLETE) {
1241 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1242 "Unrecognized GSSAPI Mechanism: [%s]", w);
1247 maj = gss_add_oid_set_member(&min, oid, &set);
1248 if (maj != GSS_S_COMPLETE) {
1249 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1250 "gss_add_oid_set_member() failed for [%s].", w);
1253 (void)gss_release_oid(&min, &oid);
1259 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1262 struct mag_config *cfg = (struct mag_config *)mconfig;
1264 if (!mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w))
1265 return "Failed to apply GssapiAllowedMech directive";
1270 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1271 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1274 struct mag_config *cfg = (struct mag_config *)mconfig;
1276 if (!mag_list_of_mechs(parms, &cfg->basic_mechs, false, w))
1277 return "Failed to apply GssapiBasicAuthMech directive";
1283 static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
1285 struct mag_server_config *scfg;
1289 scfg = apr_pcalloc(p, sizeof(struct mag_server_config));
1291 maj = gss_indicate_mechs(&min, &scfg->default_mechs);
1292 if (maj != GSS_S_COMPLETE) {
1293 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
1294 "gss_indicate_mechs() failed");
1296 /* Register the set in pool */
1297 apr_pool_cleanup_register(p, (void *)scfg->default_mechs,
1298 mag_oid_set_destroy, apr_pool_cleanup_null);
1301 rc = SEAL_KEY_CREATE(p, &scfg->mag_skey, NULL);
1303 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
1304 "Failed to generate random sealing key!");
1310 static const command_rec mag_commands[] = {
1311 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1312 "Work only if connection is SSL Secured"),
1313 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1314 "Translate principals to local names"),
1315 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1316 "Authentication is bound to the TCP connection"),
1317 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1318 "Send Persitent-Auth header according to connection bound"),
1319 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1320 "Authentication uses mod_sessions to hold status"),
1321 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1322 "Key Used to seal session data."),
1323 #ifdef HAVE_CRED_STORE
1324 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1325 "Initializes credentials for s4u2proxy usage"),
1326 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1327 "Credential Store"),
1328 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1329 OR_AUTHCFG, "Directory to store delegated credentials"),
1331 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1332 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1333 "Allows use of Basic Auth for authentication"),
1334 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1335 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1337 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1338 "Allowed Mechanisms"),
1343 mag_register_hooks(apr_pool_t *p)
1345 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1346 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1347 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1350 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1352 STANDARD20_MODULE_STUFF,
1353 mag_create_dir_config,
1355 mag_create_server_config,