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_buffer_desc input = GSS_C_EMPTY_BUFFER;
406 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
407 gss_OID_set indicated_mechs = GSS_C_NO_OID_SET;
408 gss_OID_set allowed_mechs;
409 gss_OID_set filtered_mechs;
410 gss_OID_set_desc all_mechs_desc;
411 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
412 uint32_t init_flags = 0;
416 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
417 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
418 sizeof(long long unsigned int));
419 if (rs != APR_SUCCESS) {
420 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
421 "Failed to generate random ccache name");
424 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
425 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
426 if (GSS_ERROR(maj)) {
427 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
429 mag_error(req, "gss_krb5_ccache_name() "
430 "failed", maj, min));
435 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
436 if (GSS_ERROR(maj)) {
437 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
439 mag_error(req, "gss_import_name() failed",
444 if (cfg->basic_mechs) {
445 allowed_mechs = cfg->basic_mechs;
446 } else if (cfg->allowed_mechs) {
447 allowed_mechs = cfg->allowed_mechs;
449 /* Try to fetch the default set if not explicitly configured,
450 * We need to do this because gss_acquire_cred_with_password()
451 * is currently limited to acquire creds for a single "default"
452 * mechanism if no desired mechanisms are passed in. This causes
453 * authentication to fail for secondary mechanisms as no user
454 * credentials are generated for those. */
455 maj = gss_indicate_mechs(&min, &indicated_mechs);
456 if (maj != GSS_S_COMPLETE) {
457 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
458 mag_error(req, "gss_indicate_mechs() failed",
460 /* if indicated _mechs failed, set GSS_C_NO_OID_SET. This
461 * generally causes only the krb5 mechanism to be tried due
462 * to implementation constraints, but may change in future. */
463 allowed_mechs = GSS_C_NO_OID_SET;
465 allowed_mechs = indicated_mechs;
469 /* Remove Spnego if present, or we'd repeat failed authentiations
470 * multiple times, one within Spnego and then again with an explicit
471 * mechanism. We would normally just force Spnego and use
472 * gss_set_neg_mechs, but due to the way we source the server name
473 * and the fact MIT up to 1.14 at least does no handle union names,
474 * we can't provide spnego with a server name that can be used by
475 * multiple mechanisms, causing any but the first mechanism to fail.
476 * Also remove unwanted krb mechs, or AS requests will be repeated
477 * multiple times uselessly.
479 filtered_mechs = mag_filter_unwanted_mechs(allowed_mechs);
480 if (filtered_mechs == GSS_C_NO_OID_SET) {
481 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "Fatal "
482 "failure while filtering mechs, aborting");
484 } else if (filtered_mechs != allowed_mechs) {
485 /* if indicated_mechs where sourced then free them here before
486 * reusing the pointer */
487 gss_release_oid_set(&min, &indicated_mechs);
489 /* mark the list of mechs needs to be freed */
490 indicated_mechs = filtered_mechs;
492 /* use the filtered list */
493 allowed_mechs = filtered_mechs;
496 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
500 &user_cred, &actual_mechs, NULL);
501 if (GSS_ERROR(maj)) {
502 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
504 mag_error(req, "gss_acquire_cred_with_password() "
505 "failed", maj, min));
509 #ifdef HAVE_CRED_STORE
510 if (cfg->deleg_ccache_dir) {
511 /* delegate ourselves credentials so we store them as requested */
512 init_flags |= GSS_C_DELEG_FLAG;
516 for (int i = 0; i < actual_mechs->count; i++) {
518 /* skip spnego if present */
519 if (gss_oid_equal(&actual_mechs->elements[i],
524 /* free these if looping */
525 gss_release_buffer(&min, &output);
526 gss_release_buffer(&min, &input);
527 gss_release_name(&min, &server);
528 gss_release_cred(&min, &server_cred);
530 all_mechs_desc.count = 1;
531 all_mechs_desc.elements = &actual_mechs->elements[i];
532 allowed_mechs = &all_mechs_desc;
534 /* must acquire with GSS_C_ACCEPT to get the server name */
535 if (!mag_acquire_creds(req, cfg, allowed_mechs,
536 GSS_C_ACCEPT, &server_cred, NULL)) {
539 maj = gss_inquire_cred(&min, server_cred, &server,
541 if (GSS_ERROR(maj)) {
542 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
543 "%s", mag_error(req, "gss_inquired_cred_() "
544 "failed", maj, min));
548 if (cred_usage == GSS_C_BOTH) {
549 /* reacquire server creds in order to allow delegation */
550 gss_release_cred(&min, &server_cred);
551 if (!mag_acquire_creds(req, cfg, allowed_mechs,
552 GSS_C_BOTH, &server_cred, NULL)) {
558 /* output and input are inverted here, this is intentional */
559 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
560 &actual_mechs->elements[i], init_flags,
561 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
562 NULL, &input, NULL, NULL);
563 if (GSS_ERROR(maj)) {
564 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
565 "%s", mag_error(req, "gss_init_sec_context() "
566 "failed", maj, min));
569 gss_release_buffer(&min, &output);
570 maj = gss_accept_sec_context(&min, &server_ctx, server_cred,
571 &input, GSS_C_NO_CHANNEL_BINDINGS,
572 client, mech_type, &output, NULL,
573 vtime, delegated_cred);
574 if (GSS_ERROR(maj)) {
575 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
576 "%s", mag_error(req, "gss_accept_sec_context()"
577 " failed", maj, min));
580 gss_release_buffer(&min, &input);
581 } while (maj == GSS_S_CONTINUE_NEEDED);
583 if (maj == GSS_S_COMPLETE) {
590 gss_release_buffer(&min, &output);
591 gss_release_buffer(&min, &input);
592 gss_release_name(&min, &server);
593 gss_release_cred(&min, &server_cred);
594 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
595 gss_release_name(&min, &user);
596 gss_release_cred(&min, &user_cred);
597 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
598 gss_release_oid_set(&min, &actual_mechs);
599 gss_release_oid_set(&min, &indicated_mechs);
600 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
601 if (user_ccache != NULL) {
602 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
603 if (maj != GSS_S_COMPLETE) {
604 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
605 "Failed to restore per-thread ccache, %s",
606 mag_error(req, "gss_krb5_ccache_name() "
607 "failed", maj, min));
615 static int mag_auth(request_rec *req)
619 struct mag_config *cfg;
620 const char *auth_header;
621 char *auth_header_type;
622 int ret = HTTP_UNAUTHORIZED;
623 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
625 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
626 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
627 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
628 gss_buffer_desc ba_user;
629 gss_buffer_desc ba_pwd;
630 gss_name_t client = GSS_C_NO_NAME;
631 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
632 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
633 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
639 gss_OID mech_type = GSS_C_NO_OID;
640 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
641 gss_OID_set indicated_mechs = GSS_C_NO_OID_SET;
642 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
643 struct mag_conn *mc = NULL;
647 type = ap_auth_type(req);
648 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
652 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
654 if (cfg->allowed_mechs) {
655 desired_mechs = cfg->allowed_mechs;
657 /* Try to fetch the default set if not explicitly configured */
658 maj = gss_indicate_mechs(&min, &indicated_mechs);
659 if (maj != GSS_S_COMPLETE) {
660 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
661 mag_error(req, "gss_indicate_mechs() failed",
664 desired_mechs = indicated_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;
819 if (auth_type == AUTH_TYPE_BASIC) {
820 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
821 cred_usage, &client, &mech_type,
822 &delegated_cred, &vtime)) {
828 if (!mag_acquire_creds(req, cfg, desired_mechs,
829 cred_usage, &acquired_cred, NULL)) {
833 if (auth_type == AUTH_TYPE_NEGOTIATE &&
834 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
835 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
836 if (GSS_ERROR(maj)) {
837 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
838 mag_error(req, "gss_set_neg_mechs() failed",
844 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
845 &input, GSS_C_NO_CHANNEL_BINDINGS,
846 &client, &mech_type, &output, NULL, &vtime,
848 if (GSS_ERROR(maj)) {
849 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
850 mag_error(req, "gss_accept_sec_context() failed",
853 } else if (maj == GSS_S_CONTINUE_NEEDED) {
855 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
856 "Mechanism needs continuation but neither "
857 "GssapiConnectionBound nor "
858 "GssapiUseSessions are available");
859 gss_release_buffer(&min, &output);
862 /* auth not complete send token and wait next packet */
867 /* Always set the GSS name in an env var */
868 maj = gss_display_name(&min, client, &name, NULL);
869 if (GSS_ERROR(maj)) {
870 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
871 mag_error(req, "gss_display_name() failed",
875 clientname = apr_pstrndup(req->pool, name.value, name.length);
876 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
877 expiration = time(NULL) + vtime;
878 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
879 apr_psprintf(req->pool, "%ld", (long)expiration));
881 #ifdef HAVE_CRED_STORE
882 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
883 char *ccachefile = NULL;
885 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
886 delegated_cred, &ccachefile);
889 mag_set_KRB5CCANME(req, ccachefile);
893 mc->delegated = true;
898 if (cfg->map_to_local) {
899 maj = gss_localname(&min, client, mech_type, &lname);
900 if (maj != GSS_S_COMPLETE) {
901 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
902 mag_error(req, "gss_localname() failed", maj, min));
905 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
907 req->user = clientname;
911 mc->user_name = apr_pstrdup(mc->pool, req->user);
912 mc->gss_name = apr_pstrdup(mc->pool, clientname);
913 mc->established = true;
914 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
915 vtime = MIN_SESS_EXP_TIME;
917 mc->expiration = expiration;
918 mc->auth_type = auth_type;
919 if (auth_type == AUTH_TYPE_BASIC) {
920 mag_basic_cache(cfg, mc, ba_user, ba_pwd);
922 if (cfg->use_sessions) {
923 mag_attempt_session(req, cfg, mc);
927 if (cfg->send_persist)
928 apr_table_set(req->headers_out, "Persistent-Auth",
929 cfg->gss_conn_ctx ? "true" : "false");
934 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
935 int prefixlen = strlen(auth_types[auth_type]) + 1;
936 replen = apr_base64_encode_len(output.length) + 1;
937 reply = apr_pcalloc(req->pool, prefixlen + replen);
939 memcpy(reply, auth_types[auth_type], prefixlen - 1);
940 reply[prefixlen - 1] = ' ';
941 apr_base64_encode(&reply[prefixlen], output.value, output.length);
942 apr_table_add(req->err_headers_out,
943 "WWW-Authenticate", reply);
945 } else if (ret == HTTP_UNAUTHORIZED) {
946 apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
947 if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
948 apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
950 if (cfg->use_basic_auth) {
951 apr_table_add(req->err_headers_out,
953 apr_psprintf(req->pool, "Basic realm=\"%s\"",
957 gss_release_oid_set(&min, &indicated_mechs);
958 if (ctx != GSS_C_NO_CONTEXT)
959 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
960 gss_release_cred(&min, &acquired_cred);
961 gss_release_cred(&min, &delegated_cred);
962 gss_release_buffer(&min, &output);
963 gss_release_name(&min, &client);
964 gss_release_buffer(&min, &name);
965 gss_release_buffer(&min, &lname);
970 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
972 struct mag_config *cfg;
974 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
980 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
982 struct mag_config *cfg = (struct mag_config *)mconfig;
983 cfg->ssl_only = on ? true : false;
987 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
989 struct mag_config *cfg = (struct mag_config *)mconfig;
990 cfg->map_to_local = on ? true : false;
994 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
996 struct mag_config *cfg = (struct mag_config *)mconfig;
997 cfg->gss_conn_ctx = on ? true : false;
1001 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
1003 struct mag_config *cfg = (struct mag_config *)mconfig;
1004 cfg->send_persist = on ? true : false;
1008 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
1010 struct mag_config *cfg = (struct mag_config *)mconfig;
1011 cfg->use_sessions = on ? true : false;
1015 #ifdef HAVE_CRED_STORE
1016 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
1018 struct mag_config *cfg = (struct mag_config *)mconfig;
1019 cfg->use_s4u2proxy = on ? true : false;
1021 if (cfg->deleg_ccache_dir == NULL) {
1022 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
1028 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
1030 struct mag_config *cfg = (struct mag_config *)mconfig;
1031 struct databuf keys;
1037 if (strncmp(w, "key:", 4) != 0) {
1038 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1039 "Invalid key format, expected prefix 'key:'");
1044 l = apr_base64_decode_len(k);
1045 val = apr_palloc(parms->temp_pool, l);
1047 keys.length = (int)apr_base64_decode_binary(val, k);
1048 keys.value = (unsigned char *)val;
1050 if (keys.length != 32) {
1051 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1052 "Invalid key length, expected 32 got %d", keys.length);
1056 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
1058 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1059 "Failed to import sealing key!");
1064 #ifdef HAVE_CRED_STORE
1066 #define MAX_CRED_OPTIONS 10
1068 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
1071 struct mag_config *cfg = (struct mag_config *)mconfig;
1072 gss_key_value_element_desc *elements;
1081 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1082 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
1086 key = apr_pstrndup(parms->pool, w, (p-w));
1087 value = apr_pstrdup(parms->pool, p + 1);
1089 if (!cfg->cred_store) {
1090 cfg->cred_store = apr_pcalloc(parms->pool,
1091 sizeof(gss_key_value_set_desc));
1092 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1093 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1096 elements = cfg->cred_store->elements;
1097 count = cfg->cred_store->count;
1099 if (count >= MAX_CRED_OPTIONS) {
1100 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1101 "Too many GssapiCredStore options (MAX: %d)",
1105 cfg->cred_store->count++;
1107 elements[count].key = key;
1108 elements[count].value = value;
1113 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1116 struct mag_config *cfg = (struct mag_config *)mconfig;
1118 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1124 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1125 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1127 struct mag_config *cfg = (struct mag_config *)mconfig;
1129 cfg->use_basic_auth = on ? true : false;
1134 #define MAX_ALLOWED_MECHS 10
1136 static void mag_list_of_mechs(cmd_parms *parms, gss_OID_set *oidset,
1137 bool add_spnego, const char *w)
1143 if (NULL == *oidset) {
1144 set = apr_pcalloc(parms->pool, sizeof(gss_OID_set_desc));
1145 size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
1146 set->elements = apr_palloc(parms->pool, size);
1148 set->elements[0] = gss_mech_spnego;
1156 if (strcmp(w, "krb5") == 0) {
1157 oid = gss_mech_krb5;
1158 } else if (strcmp(w, "iakerb") == 0) {
1159 oid = gss_mech_iakerb;
1160 } else if (strcmp(w, "ntlmssp") == 0) {
1161 oid = &gss_mech_ntlmssp;
1163 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1164 "Unrecognized GSSAPI Mechanism: %s", w);
1168 if (set->count >= MAX_ALLOWED_MECHS) {
1169 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1170 "Too many GssapiAllowedMech options (MAX: %d)",
1174 set->elements[set->count] = *oid;
1178 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1181 struct mag_config *cfg = (struct mag_config *)mconfig;
1183 mag_list_of_mechs(parms, &cfg->allowed_mechs, true, w);
1188 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1189 static const char *mag_basic_auth_mechs(cmd_parms *parms, void *mconfig,
1192 struct mag_config *cfg = (struct mag_config *)mconfig;
1194 mag_list_of_mechs(parms, &cfg->basic_mechs, false, w);
1200 static const command_rec mag_commands[] = {
1201 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1202 "Work only if connection is SSL Secured"),
1203 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1204 "Translate principals to local names"),
1205 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1206 "Authentication is bound to the TCP connection"),
1207 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1208 "Send Persitent-Auth header according to connection bound"),
1209 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1210 "Authentication uses mod_sessions to hold status"),
1211 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1212 "Key Used to seal session data."),
1213 #ifdef HAVE_CRED_STORE
1214 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1215 "Initializes credentials for s4u2proxy usage"),
1216 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1217 "Credential Store"),
1218 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1219 OR_AUTHCFG, "Directory to store delegated credentials"),
1221 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1222 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1223 "Allows use of Basic Auth for authentication"),
1224 AP_INIT_ITERATE("GssapiBasicAuthMech", mag_basic_auth_mechs, NULL,
1225 OR_AUTHCFG, "Mechanisms to use for basic auth"),
1227 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1228 "Allowed Mechanisms"),
1233 mag_register_hooks(apr_pool_t *p)
1235 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1236 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1237 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1240 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1242 STANDARD20_MODULE_STUFF,
1243 mag_create_dir_config,