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_ntlmssp = {
28 GSS_NTLMSSP_OID_LENGTH, GSS_NTLMSSP_OID_STRING
31 const gss_OID_set_desc gss_mech_set_ntlmssp = {
32 1, discard_const(&gss_mech_ntlmssp)
35 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
37 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
39 APLOG_USE_MODULE(auth_gssapi);
41 static char *mag_status(request_rec *req, int type, uint32_t err)
43 uint32_t maj_ret, min_ret;
52 maj_ret = gss_display_status(&min_ret, err, type,
53 GSS_C_NO_OID, &msg_ctx, &text);
54 if (maj_ret != GSS_S_COMPLETE) {
60 msg_ret = apr_psprintf(req->pool, "%s, %*s",
61 msg_ret, len, (char *)text.value);
63 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
65 gss_release_buffer(&min_ret, &text);
66 } while (msg_ctx != 0);
71 static char *mag_error(request_rec *req, const char *msg,
72 uint32_t maj, uint32_t min)
77 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
78 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
79 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
82 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
84 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
85 apr_pool_t *temp, server_rec *s)
87 /* FIXME: create mutex to deal with connections and contexts ? */
88 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
89 mag_post_config_session();
90 ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
95 static int mag_pre_connection(conn_rec *c, void *csd)
99 mc = mag_new_conn_ctx(c->pool);
100 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
104 static apr_status_t mag_conn_destroy(void *ptr)
106 struct mag_conn *mc = (struct mag_conn *)ptr;
110 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
115 struct mag_conn *mag_new_conn_ctx(apr_pool_t *pool)
119 mc = apr_pcalloc(pool, sizeof(struct mag_conn));
120 apr_pool_create(&mc->pool, pool);
121 /* register the context in the memory pool, so it can be freed
122 * when the connection/request is terminated */
123 apr_pool_cleanup_register(mc->pool, (void *)mc,
124 mag_conn_destroy, apr_pool_cleanup_null);
129 static void mag_conn_clear(struct mag_conn *mc)
131 (void)mag_conn_destroy(mc);
134 apr_pool_clear(mc->pool);
136 memset(mc, 0, sizeof(struct mag_conn));
140 static bool mag_conn_is_https(conn_rec *c)
143 if (mag_is_https(c)) return true;
149 static bool mag_acquire_creds(request_rec *req,
150 struct mag_config *cfg,
151 gss_OID_set desired_mechs,
152 gss_cred_usage_t cred_usage,
153 gss_cred_id_t *creds,
154 gss_OID_set *actual_mechs)
157 #ifdef HAVE_CRED_STORE
158 gss_const_key_value_set_t store = cfg->cred_store;
160 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
161 desired_mechs, cred_usage, store, creds,
164 maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
165 desired_mechs, cred_usage, creds,
169 if (GSS_ERROR(maj)) {
170 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
171 mag_error(req, "gss_acquire_cred[_from]() "
172 "failed to get server creds",
180 #ifdef HAVE_CRED_STORE
181 static char *escape(apr_pool_t *pool, const char *name,
182 char find, const char *replace)
184 char *escaped = NULL;
189 namecopy = apr_pstrdup(pool, name);
191 p = strchr(namecopy, find);
192 if (!p) return namecopy;
197 /* terminate previous segment */
200 escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
202 escaped = apr_pstrcat(pool, n, replace, NULL);
204 /* move to next segment */
208 /* append last segment if any */
210 escaped = apr_pstrcat(pool, escaped, n, NULL);
216 static char *mag_gss_name_to_ccache_name(request_rec *req,
217 char *dir, const char *gss_name)
221 /* We need to escape away '/', we can't have path separators in
222 * a ccache file name */
223 /* first double escape the esacping char (~) if any */
224 escaped = escape(req->pool, gss_name, '~', "~~");
225 /* then escape away the separator (/) if any */
226 escaped = escape(req->pool, escaped, '/', "~");
228 return apr_psprintf(req->pool, "%s/%s", dir, escaped);
231 static void mag_set_KRB5CCANME(request_rec *req, char *ccname)
237 status = apr_stat(&finfo, ccname, APR_FINFO_MIN, req->pool);
238 if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
239 /* set the file cache anyway, but warn */
240 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
241 "KRB5CCNAME file (%s) lookup failed!", ccname);
244 value = apr_psprintf(req->pool, "FILE:%s", ccname);
245 apr_table_set(req->subprocess_env, "KRB5CCNAME", value);
248 static void mag_store_deleg_creds(request_rec *req,
249 char *dir, char *clientname,
250 gss_cred_id_t delegated_cred,
253 gss_key_value_element_desc element;
254 gss_key_value_set_desc store;
257 element.key = "ccache";
258 store.elements = &element;
261 ccname = mag_gss_name_to_ccache_name(req, dir, clientname);
262 element.value = apr_psprintf(req->pool, "FILE:%s", ccname);
264 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
265 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
266 if (GSS_ERROR(maj)) {
267 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
268 mag_error(req, "failed to store delegated creds",
272 *ccachefile = ccname;
276 static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
279 char *auth_header_value;
281 auth_header_value = ap_getword_white(pool, auth_header);
282 if (!auth_header_value) return false;
283 value->length = apr_base64_decode_len(auth_header_value) + 1;
284 value->value = apr_pcalloc(pool, value->length);
285 if (!value->value) return false;
286 value->length = apr_base64_decode(value->value, auth_header_value);
291 static bool is_mech_allowed(struct mag_config *cfg, gss_const_OID mech)
293 if (cfg->allowed_mechs == GSS_C_NO_OID_SET) return true;
295 for (int i = 0; i < cfg->allowed_mechs->count; i++) {
296 if (gss_oid_equal(&cfg->allowed_mechs->elements[i], mech)) {
303 #define AUTH_TYPE_NEGOTIATE 0
304 #define AUTH_TYPE_BASIC 1
305 #define AUTH_TYPE_RAW_NTLM 2
306 const char *auth_types[] = {
313 static void mag_set_req_data(request_rec *req,
314 struct mag_config *cfg,
317 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
318 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
319 apr_psprintf(req->pool,
320 "%ld", (long)mc->expiration));
321 req->ap_auth_type = apr_pstrdup(req->pool,
322 auth_types[mc->auth_type]);
323 req->user = apr_pstrdup(req->pool, mc->user_name);
324 if (cfg->deleg_ccache_dir && mc->delegated) {
326 ccname = mag_gss_name_to_ccache_name(req,
327 cfg->deleg_ccache_dir,
330 mag_set_KRB5CCANME(req, ccname);
335 static int mag_auth(request_rec *req)
339 struct mag_config *cfg;
340 const char *auth_header;
341 char *auth_header_type;
342 int ret = HTTP_UNAUTHORIZED;
343 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
345 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
346 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
347 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
348 gss_buffer_desc ba_user;
349 gss_buffer_desc ba_pwd;
350 gss_name_t client = GSS_C_NO_NAME;
351 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
352 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
353 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
354 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
355 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
362 gss_OID mech_type = GSS_C_NO_OID;
363 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
364 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
365 struct mag_conn *mc = NULL;
366 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
367 gss_name_t server = GSS_C_NO_NAME;
368 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
369 const char *user_ccache = NULL;
370 const char *orig_ccache = NULL;
372 uint32_t init_flags = 0;
376 type = ap_auth_type(req);
377 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
381 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
383 if (!cfg->allowed_mechs) {
384 /* Try to fetch the default set if not explicitly configured */
385 (void)mag_acquire_creds(req, cfg, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
386 &server_cred, &cfg->allowed_mechs);
387 (void)gss_release_cred(&min, &server_cred);
390 /* implicit auth for subrequests if main auth already happened */
391 if (!ap_is_initial_req(req) && req->main != NULL) {
392 type = ap_auth_type(req->main);
393 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
394 /* warn if the subrequest location and the main request
395 * location have different configs */
396 if (cfg != ap_get_module_config(req->main->per_dir_config,
397 &auth_gssapi_module)) {
398 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
399 req, "Subrequest authentication bypass on "
400 "location with different configuration!");
402 if (req->main->user) {
403 req->user = apr_pstrdup(req->pool, req->main->user);
406 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
407 "The main request is tasked to establish the "
408 "security context, can't proceed!");
409 return HTTP_UNAUTHORIZED;
412 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
413 "Subrequest GSSAPI auth with no auth on the main "
414 "request. This operation may fail if other "
415 "subrequests already established a context or the "
416 "mechanism requires multiple roundtrips.");
421 if (!mag_conn_is_https(req->connection)) {
422 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
423 "Not a TLS connection, refusing to authenticate!");
428 if (cfg->gss_conn_ctx) {
429 mc = (struct mag_conn *)ap_get_module_config(
430 req->connection->conn_config,
431 &auth_gssapi_module);
433 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
434 "Failed to retrieve connection context!");
439 /* if available, session always supersedes connection bound data */
440 if (cfg->use_sessions) {
441 mag_check_session(req, cfg, &mc);
444 auth_header = apr_table_get(req->headers_in, "Authorization");
447 if (mc->established && !auth_header) {
448 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
449 "Already established context found!");
450 mag_set_req_data(req, cfg, mc);
459 /* We can proceed only if we do have an auth header */
460 if (!auth_header) goto done;
462 auth_header_type = ap_getword_white(req->pool, &auth_header);
463 if (!auth_header_type) goto done;
465 for (i = 0; auth_types[i] != NULL; i++) {
466 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
472 if (mc && mc->established && auth_type != AUTH_TYPE_BASIC) {
473 /* if we are re-authenticating make sure the conn context
474 * is cleaned up so we do not accidentally reuse an existing
475 * established context */
480 case AUTH_TYPE_NEGOTIATE:
481 if (!parse_auth_header(req->pool, &auth_header, &input)) {
485 case AUTH_TYPE_BASIC:
486 if (!cfg->use_basic_auth) {
490 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
491 if (!ba_pwd.value) goto done;
492 ba_user.value = ap_getword_nulls_nc(req->pool,
493 (char **)&ba_pwd.value, ':');
494 if (!ba_user.value) goto done;
495 if (((char *)ba_user.value)[0] == '\0' ||
496 ((char *)ba_pwd.value)[0] == '\0') {
497 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
498 "Invalid empty user or password for Basic Auth");
501 ba_user.length = strlen(ba_user.value);
502 ba_pwd.length = strlen(ba_pwd.value);
504 if (mc && mc->established) {
505 if (mag_basic_check(cfg, mc, ba_user, ba_pwd)) {
506 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
507 "Already established BASIC AUTH context found!");
508 mag_set_req_data(req, cfg, mc);
516 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
517 if (GSS_ERROR(maj)) {
518 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
520 mag_error(req, "gss_import_name() failed",
524 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
525 /* Set a per-thread ccache in case we are using kerberos,
526 * it is not elegant but avoids interference between threads */
527 long long unsigned int rndname;
529 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
530 sizeof(long long unsigned int));
531 if (rs != APR_SUCCESS) {
532 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
533 "Failed to generate random ccache name");
536 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
537 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
538 if (GSS_ERROR(maj)) {
539 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
541 mag_error(req, "gss_krb5_ccache_name() "
542 "failed", maj, min));
546 maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
550 &user_cred, NULL, NULL);
551 if (GSS_ERROR(maj)) {
552 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
554 mag_error(req, "gss_acquire_cred_with_password() "
555 "failed", maj, min));
558 gss_release_name(&min, &client);
561 case AUTH_TYPE_RAW_NTLM:
562 if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
563 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
564 "NTLM Authentication is not allowed!");
568 if (!parse_auth_header(req->pool, &auth_header, &input)) {
572 desired_mechs = discard_const(&gss_mech_set_ntlmssp);
579 req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
581 #ifdef HAVE_CRED_STORE
582 if (cfg->use_s4u2proxy) {
583 cred_usage = GSS_C_BOTH;
586 if (!mag_acquire_creds(req, cfg, desired_mechs,
587 cred_usage, &acquired_cred, NULL)) {
591 if (auth_type == AUTH_TYPE_BASIC) {
592 if (cred_usage == GSS_C_BOTH) {
593 /* If GSS_C_BOTH is used then inquire_cred will return the client
594 * name instead of the SPN of the server credentials. Therefore we
595 * need to acquire a different set of credential setting
596 * GSS_C_ACCEPT explicitly */
597 if (!mag_acquire_creds(req, cfg, cfg->allowed_mechs,
598 GSS_C_ACCEPT, &server_cred, NULL)) {
602 server_cred = acquired_cred;
604 maj = gss_inquire_cred(&min, server_cred, &server,
606 if (GSS_ERROR(maj)) {
607 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
608 "%s", mag_error(req, "gss_inquired_cred_() "
609 "failed", maj, min));
612 if (server_cred != acquired_cred) {
613 gss_release_cred(&min, &server_cred);
616 #ifdef HAVE_CRED_STORE
617 if (cfg->deleg_ccache_dir) {
618 /* delegate ourselves credentials so we store them as requested */
619 init_flags |= GSS_C_DELEG_FLAG;
623 /* output and input are inverted here, this is intentional */
624 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
625 GSS_C_NO_OID, init_flags, 300,
626 GSS_C_NO_CHANNEL_BINDINGS, &output,
627 NULL, &input, NULL, NULL);
628 if (GSS_ERROR(maj)) {
629 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
630 "%s", mag_error(req, "gss_init_sec_context() "
631 "failed", maj, min));
636 if (auth_type == AUTH_TYPE_NEGOTIATE &&
637 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
638 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
639 if (GSS_ERROR(maj)) {
640 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
641 mag_error(req, "gss_set_neg_mechs() failed",
647 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
648 &input, GSS_C_NO_CHANNEL_BINDINGS,
649 &client, &mech_type, &output, &flags, &vtime,
651 if (GSS_ERROR(maj)) {
652 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
653 mag_error(req, "gss_accept_sec_context() failed",
657 if (auth_type == AUTH_TYPE_BASIC) {
658 while (maj == GSS_S_CONTINUE_NEEDED) {
659 gss_release_buffer(&min, &input);
660 /* output and input are inverted here, this is intentional */
661 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
662 GSS_C_NO_OID, init_flags, 300,
663 GSS_C_NO_CHANNEL_BINDINGS, &output,
664 NULL, &input, NULL, NULL);
665 if (GSS_ERROR(maj)) {
666 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
667 "%s", mag_error(req, "gss_init_sec_context() "
668 "failed", maj, min));
671 gss_release_buffer(&min, &output);
672 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
673 &input, GSS_C_NO_CHANNEL_BINDINGS,
674 &client, &mech_type, &output, &flags,
675 &vtime, &delegated_cred);
676 if (GSS_ERROR(maj)) {
677 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
678 "%s", mag_error(req, "gss_accept_sec_context()"
679 " failed", maj, min));
683 } else if (maj == GSS_S_CONTINUE_NEEDED) {
685 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
686 "Mechanism needs continuation but neither "
687 "GssapiConnectionBound nor "
688 "GssapiUseSessions are available");
689 gss_release_buffer(&min, &output);
692 /* auth not complete send token and wait next packet */
696 /* Always set the GSS name in an env var */
697 maj = gss_display_name(&min, client, &name, NULL);
698 if (GSS_ERROR(maj)) {
699 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
700 mag_error(req, "gss_display_name() failed",
704 clientname = apr_pstrndup(req->pool, name.value, name.length);
705 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
706 expiration = time(NULL) + vtime;
707 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
708 apr_psprintf(req->pool, "%ld", (long)expiration));
710 #ifdef HAVE_CRED_STORE
711 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
712 char *ccachefile = NULL;
714 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
715 delegated_cred, &ccachefile);
718 mag_set_KRB5CCANME(req, ccachefile);
722 mc->delegated = true;
727 if (cfg->map_to_local) {
728 maj = gss_localname(&min, client, mech_type, &lname);
729 if (maj != GSS_S_COMPLETE) {
730 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
731 mag_error(req, "gss_localname() failed", maj, min));
734 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
736 req->user = clientname;
740 mc->user_name = apr_pstrdup(mc->pool, req->user);
741 mc->gss_name = apr_pstrdup(mc->pool, clientname);
742 mc->established = true;
743 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
744 vtime = MIN_SESS_EXP_TIME;
746 mc->expiration = expiration;
747 mc->auth_type = auth_type;
748 if (auth_type == AUTH_TYPE_BASIC) {
749 mag_basic_cache(cfg, mc, ba_user, ba_pwd);
751 if (cfg->use_sessions) {
752 mag_attempt_session(req, cfg, mc);
756 if (cfg->send_persist)
757 apr_table_set(req->headers_out, "Persistent-Auth",
758 cfg->gss_conn_ctx ? "true" : "false");
763 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
764 int prefixlen = strlen(auth_types[auth_type]) + 1;
765 replen = apr_base64_encode_len(output.length) + 1;
766 reply = apr_pcalloc(req->pool, prefixlen + replen);
768 memcpy(reply, auth_types[auth_type], prefixlen - 1);
769 reply[prefixlen - 1] = ' ';
770 apr_base64_encode(&reply[prefixlen], output.value, output.length);
771 apr_table_add(req->err_headers_out,
772 "WWW-Authenticate", reply);
774 } else if (ret == HTTP_UNAUTHORIZED) {
775 apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
776 if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
777 apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
779 if (cfg->use_basic_auth) {
780 apr_table_add(req->err_headers_out,
782 apr_psprintf(req->pool, "Basic realm=\"%s\"",
786 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
787 if (user_ccache != NULL) {
788 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
789 if (maj != GSS_S_COMPLETE) {
790 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
791 "Failed to restore per-thread ccache, %s",
792 mag_error(req, "gss_krb5_ccache_name() "
793 "failed", maj, min));
797 if (ctx != GSS_C_NO_CONTEXT)
798 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
799 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
800 gss_release_cred(&min, &user_cred);
801 gss_release_cred(&min, &acquired_cred);
802 gss_release_cred(&min, &delegated_cred);
803 gss_release_buffer(&min, &output);
804 gss_release_name(&min, &client);
805 gss_release_name(&min, &server);
806 gss_release_buffer(&min, &name);
807 gss_release_buffer(&min, &lname);
812 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
814 struct mag_config *cfg;
816 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
822 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
824 struct mag_config *cfg = (struct mag_config *)mconfig;
825 cfg->ssl_only = on ? true : false;
829 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
831 struct mag_config *cfg = (struct mag_config *)mconfig;
832 cfg->map_to_local = on ? true : false;
836 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
838 struct mag_config *cfg = (struct mag_config *)mconfig;
839 cfg->gss_conn_ctx = on ? true : false;
843 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
845 struct mag_config *cfg = (struct mag_config *)mconfig;
846 cfg->send_persist = on ? true : false;
850 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
852 struct mag_config *cfg = (struct mag_config *)mconfig;
853 cfg->use_sessions = on ? true : false;
857 #ifdef HAVE_CRED_STORE
858 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
860 struct mag_config *cfg = (struct mag_config *)mconfig;
861 cfg->use_s4u2proxy = on ? true : false;
863 if (cfg->deleg_ccache_dir == NULL) {
864 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
870 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
872 struct mag_config *cfg = (struct mag_config *)mconfig;
879 if (strncmp(w, "key:", 4) != 0) {
880 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
881 "Invalid key format, expected prefix 'key:'");
886 l = apr_base64_decode_len(k);
887 val = apr_palloc(parms->temp_pool, l);
889 keys.length = (int)apr_base64_decode_binary(val, k);
890 keys.value = (unsigned char *)val;
892 if (keys.length != 32) {
893 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
894 "Invalid key length, expected 32 got %d", keys.length);
898 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
900 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
901 "Failed to import sealing key!");
906 #ifdef HAVE_CRED_STORE
908 #define MAX_CRED_OPTIONS 10
910 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
913 struct mag_config *cfg = (struct mag_config *)mconfig;
914 gss_key_value_element_desc *elements;
923 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
924 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
928 key = apr_pstrndup(parms->pool, w, (p-w));
929 value = apr_pstrdup(parms->pool, p + 1);
931 if (!cfg->cred_store) {
932 cfg->cred_store = apr_pcalloc(parms->pool,
933 sizeof(gss_key_value_set_desc));
934 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
935 cfg->cred_store->elements = apr_palloc(parms->pool, size);
938 elements = cfg->cred_store->elements;
939 count = cfg->cred_store->count;
941 if (count >= MAX_CRED_OPTIONS) {
942 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
943 "Too many GssapiCredStore options (MAX: %d)",
947 cfg->cred_store->count++;
949 elements[count].key = key;
950 elements[count].value = value;
955 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
958 struct mag_config *cfg = (struct mag_config *)mconfig;
960 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
966 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
968 struct mag_config *cfg = (struct mag_config *)mconfig;
970 cfg->use_basic_auth = on ? true : false;
974 #define MAX_ALLOWED_MECHS 10
976 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
979 struct mag_config *cfg = (struct mag_config *)mconfig;
983 if (!cfg->allowed_mechs) {
984 cfg->allowed_mechs = apr_pcalloc(parms->pool,
985 sizeof(gss_OID_set_desc));
986 size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
987 cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
990 if (strcmp(w, "krb5") == 0) {
992 } else if (strcmp(w, "iakerb") == 0) {
993 oid = gss_mech_iakerb;
994 } else if (strcmp(w, "ntlmssp") == 0) {
995 oid = &gss_mech_ntlmssp;
997 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
998 "Unrecognized GSSAPI Mechanism: %s", w);
1002 if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
1003 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1004 "Too many GssapiAllowedMech options (MAX: %d)",
1008 cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
1009 cfg->allowed_mechs->count++;
1014 static const command_rec mag_commands[] = {
1015 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1016 "Work only if connection is SSL Secured"),
1017 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1018 "Translate principals to local names"),
1019 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1020 "Authentication is bound to the TCP connection"),
1021 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1022 "Send Persitent-Auth header according to connection bound"),
1023 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1024 "Authentication uses mod_sessions to hold status"),
1025 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1026 "Key Used to seal session data."),
1027 #ifdef HAVE_CRED_STORE
1028 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1029 "Initializes credentials for s4u2proxy usage"),
1030 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1031 "Credential Store"),
1032 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1033 OR_AUTHCFG, "Directory to store delegated credentials"),
1035 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1036 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1037 "Allows use of Basic Auth for authentication"),
1039 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1040 "Allowed Mechanisms"),
1045 mag_register_hooks(apr_pool_t *p)
1047 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1048 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1049 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1052 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1054 STANDARD20_MODULE_STUFF,
1055 mag_create_dir_config,