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 static bool mag_auth_basic(request_rec *req,
340 struct mag_config *cfg,
341 gss_buffer_desc ba_user,
342 gss_buffer_desc ba_pwd,
343 gss_cred_usage_t cred_usage,
346 gss_cred_id_t *delegated_cred,
349 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
350 const char *user_ccache = NULL;
351 const char *orig_ccache = NULL;
352 long long unsigned int rndname;
355 gss_name_t user = GSS_C_NO_NAME;
356 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
357 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
358 gss_name_t server = GSS_C_NO_NAME;
359 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
360 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
361 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
362 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
363 gss_OID_set allowed_mechs = GSS_C_NO_OID_SET;
364 gss_OID_set_desc all_mechs_desc;
365 gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
366 uint32_t init_flags = 0;
370 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
371 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
372 sizeof(long long unsigned int));
373 if (rs != APR_SUCCESS) {
374 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
375 "Failed to generate random ccache name");
378 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
379 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
380 if (GSS_ERROR(maj)) {
381 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
383 mag_error(req, "gss_krb5_ccache_name() "
384 "failed", maj, min));
389 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
390 if (GSS_ERROR(maj)) {
391 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
393 mag_error(req, "gss_import_name() failed",
398 if (cfg->allowed_mechs && cfg->allowed_mechs->count > 1) {
399 all_mechs_desc.count = cfg->allowed_mechs->count - 1;
400 all_mechs_desc.elements = &cfg->allowed_mechs->elements[1];
401 allowed_mechs = &all_mechs_desc;
404 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
408 &user_cred, &actual_mechs, NULL);
409 if (GSS_ERROR(maj)) {
410 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
412 mag_error(req, "gss_acquire_cred_with_password() "
413 "failed", maj, min));
417 #ifdef HAVE_CRED_STORE
418 if (cfg->deleg_ccache_dir) {
419 /* delegate ourselves credentials so we store them as requested */
420 init_flags |= GSS_C_DELEG_FLAG;
424 for (int i = 0; i < actual_mechs->count; i++) {
426 /* skip spnego if present (it is usually present when
427 * cfg->allowed_mechs is not set) */
428 if (gss_oid_equal(&actual_mechs->elements[i],
433 /* free these if looping */
434 gss_release_buffer(&min, &output);
435 gss_release_buffer(&min, &input);
436 gss_release_name(&min, &server);
437 gss_release_cred(&min, &server_cred);
439 all_mechs_desc.count = 1;
440 all_mechs_desc.elements = &actual_mechs->elements[i];
442 /* must acquire with GSS_C_ACCEPT to get the server name */
443 if (!mag_acquire_creds(req, cfg, allowed_mechs,
444 GSS_C_ACCEPT, &server_cred, NULL)) {
447 maj = gss_inquire_cred(&min, server_cred, &server,
449 if (GSS_ERROR(maj)) {
450 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
451 "%s", mag_error(req, "gss_inquired_cred_() "
452 "failed", maj, min));
456 if (cred_usage == GSS_C_BOTH) {
457 /* reacquire server creds in order to allow delegation */
458 gss_release_cred(&min, &server_cred);
459 if (!mag_acquire_creds(req, cfg, allowed_mechs,
460 GSS_C_BOTH, &server_cred, NULL)) {
466 /* output and input are inverted here, this is intentional */
467 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
468 &actual_mechs->elements[i], init_flags,
469 300, GSS_C_NO_CHANNEL_BINDINGS, &output,
470 NULL, &input, NULL, NULL);
471 if (GSS_ERROR(maj)) {
472 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
473 "%s", mag_error(req, "gss_init_sec_context() "
474 "failed", maj, min));
477 gss_release_buffer(&min, &output);
478 maj = gss_accept_sec_context(&min, &server_ctx, server_cred,
479 &input, GSS_C_NO_CHANNEL_BINDINGS,
480 client, mech_type, &output, NULL,
481 vtime, delegated_cred);
482 if (GSS_ERROR(maj)) {
483 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
484 "%s", mag_error(req, "gss_accept_sec_context()"
485 " failed", maj, min));
488 gss_release_buffer(&min, &input);
489 } while (maj == GSS_S_CONTINUE_NEEDED);
491 if (maj == GSS_S_COMPLETE) {
498 gss_release_buffer(&min, &output);
499 gss_release_buffer(&min, &input);
500 gss_release_name(&min, &server);
501 gss_release_cred(&min, &server_cred);
502 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
503 gss_release_name(&min, &user);
504 gss_release_cred(&min, &user_cred);
505 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
506 gss_release_oid_set(&min, &actual_mechs);
507 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
508 if (user_ccache != NULL) {
509 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
510 if (maj != GSS_S_COMPLETE) {
511 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
512 "Failed to restore per-thread ccache, %s",
513 mag_error(req, "gss_krb5_ccache_name() "
514 "failed", maj, min));
522 static int mag_auth(request_rec *req)
526 struct mag_config *cfg;
527 const char *auth_header;
528 char *auth_header_type;
529 int ret = HTTP_UNAUTHORIZED;
530 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
532 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
533 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
534 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
535 gss_buffer_desc ba_user;
536 gss_buffer_desc ba_pwd;
537 gss_name_t client = GSS_C_NO_NAME;
538 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
539 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
540 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
546 gss_OID mech_type = GSS_C_NO_OID;
547 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
548 gss_OID_set indicated_mechs = GSS_C_NO_OID_SET;
549 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
550 struct mag_conn *mc = NULL;
554 type = ap_auth_type(req);
555 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
559 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
561 if (cfg->allowed_mechs) {
562 desired_mechs = cfg->allowed_mechs;
564 /* Try to fetch the default set if not explicitly configured */
565 maj = gss_indicate_mechs(&min, &indicated_mechs);
566 if (maj != GSS_S_COMPLETE) {
567 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
568 mag_error(req, "gss_indicate_mechs() failed",
571 desired_mechs = indicated_mechs;
574 /* implicit auth for subrequests if main auth already happened */
575 if (!ap_is_initial_req(req) && req->main != NULL) {
576 type = ap_auth_type(req->main);
577 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
578 /* warn if the subrequest location and the main request
579 * location have different configs */
580 if (cfg != ap_get_module_config(req->main->per_dir_config,
581 &auth_gssapi_module)) {
582 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
583 req, "Subrequest authentication bypass on "
584 "location with different configuration!");
586 if (req->main->user) {
587 req->user = apr_pstrdup(req->pool, req->main->user);
590 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
591 "The main request is tasked to establish the "
592 "security context, can't proceed!");
593 return HTTP_UNAUTHORIZED;
596 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
597 "Subrequest GSSAPI auth with no auth on the main "
598 "request. This operation may fail if other "
599 "subrequests already established a context or the "
600 "mechanism requires multiple roundtrips.");
605 if (!mag_conn_is_https(req->connection)) {
606 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
607 "Not a TLS connection, refusing to authenticate!");
612 if (cfg->gss_conn_ctx) {
613 mc = (struct mag_conn *)ap_get_module_config(
614 req->connection->conn_config,
615 &auth_gssapi_module);
617 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
618 "Failed to retrieve connection context!");
623 /* if available, session always supersedes connection bound data */
624 if (cfg->use_sessions) {
625 mag_check_session(req, cfg, &mc);
628 auth_header = apr_table_get(req->headers_in, "Authorization");
631 if (mc->established &&
632 (auth_header == NULL) &&
633 (mc->auth_type != AUTH_TYPE_BASIC)) {
634 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
635 "Already established context found!");
636 mag_set_req_data(req, cfg, mc);
645 /* We can proceed only if we do have an auth header */
646 if (!auth_header) goto done;
648 auth_header_type = ap_getword_white(req->pool, &auth_header);
649 if (!auth_header_type) goto done;
651 for (i = 0; auth_types[i] != NULL; i++) {
652 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
659 case AUTH_TYPE_NEGOTIATE:
660 if (!parse_auth_header(req->pool, &auth_header, &input)) {
664 case AUTH_TYPE_BASIC:
665 if (!cfg->use_basic_auth) {
669 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
670 if (!ba_pwd.value) goto done;
671 ba_user.value = ap_getword_nulls_nc(req->pool,
672 (char **)&ba_pwd.value, ':');
673 if (!ba_user.value) goto done;
674 if (((char *)ba_user.value)[0] == '\0' ||
675 ((char *)ba_pwd.value)[0] == '\0') {
676 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
677 "Invalid empty user or password for Basic Auth");
680 ba_user.length = strlen(ba_user.value);
681 ba_pwd.length = strlen(ba_pwd.value);
683 if (mc && mc->established &&
684 mag_basic_check(cfg, mc, ba_user, ba_pwd)) {
685 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
686 "Already established BASIC AUTH context found!");
687 mag_set_req_data(req, cfg, mc);
694 case AUTH_TYPE_RAW_NTLM:
695 if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
696 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
697 "NTLM Authentication is not allowed!");
701 if (!parse_auth_header(req->pool, &auth_header, &input)) {
705 desired_mechs = discard_const(&gss_mech_set_ntlmssp);
712 if (mc && mc->established) {
713 /* if we are re-authenticating make sure the conn context
714 * is cleaned up so we do not accidentally reuse an existing
715 * established context */
719 req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
721 #ifdef HAVE_CRED_STORE
722 if (cfg->use_s4u2proxy) {
723 cred_usage = GSS_C_BOTH;
726 if (auth_type == AUTH_TYPE_BASIC) {
727 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
728 cred_usage, &client, &mech_type,
729 &delegated_cred, &vtime)) {
735 if (!mag_acquire_creds(req, cfg, desired_mechs,
736 cred_usage, &acquired_cred, NULL)) {
740 if (auth_type == AUTH_TYPE_NEGOTIATE &&
741 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
742 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
743 if (GSS_ERROR(maj)) {
744 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
745 mag_error(req, "gss_set_neg_mechs() failed",
751 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
752 &input, GSS_C_NO_CHANNEL_BINDINGS,
753 &client, &mech_type, &output, NULL, &vtime,
755 if (GSS_ERROR(maj)) {
756 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
757 mag_error(req, "gss_accept_sec_context() failed",
760 } else if (maj == GSS_S_CONTINUE_NEEDED) {
762 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
763 "Mechanism needs continuation but neither "
764 "GssapiConnectionBound nor "
765 "GssapiUseSessions are available");
766 gss_release_buffer(&min, &output);
769 /* auth not complete send token and wait next packet */
774 /* Always set the GSS name in an env var */
775 maj = gss_display_name(&min, client, &name, NULL);
776 if (GSS_ERROR(maj)) {
777 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
778 mag_error(req, "gss_display_name() failed",
782 clientname = apr_pstrndup(req->pool, name.value, name.length);
783 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
784 expiration = time(NULL) + vtime;
785 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
786 apr_psprintf(req->pool, "%ld", (long)expiration));
788 #ifdef HAVE_CRED_STORE
789 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
790 char *ccachefile = NULL;
792 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
793 delegated_cred, &ccachefile);
796 mag_set_KRB5CCANME(req, ccachefile);
800 mc->delegated = true;
805 if (cfg->map_to_local) {
806 maj = gss_localname(&min, client, mech_type, &lname);
807 if (maj != GSS_S_COMPLETE) {
808 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
809 mag_error(req, "gss_localname() failed", maj, min));
812 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
814 req->user = clientname;
818 mc->user_name = apr_pstrdup(mc->pool, req->user);
819 mc->gss_name = apr_pstrdup(mc->pool, clientname);
820 mc->established = true;
821 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
822 vtime = MIN_SESS_EXP_TIME;
824 mc->expiration = expiration;
825 mc->auth_type = auth_type;
826 if (auth_type == AUTH_TYPE_BASIC) {
827 mag_basic_cache(cfg, mc, ba_user, ba_pwd);
829 if (cfg->use_sessions) {
830 mag_attempt_session(req, cfg, mc);
834 if (cfg->send_persist)
835 apr_table_set(req->headers_out, "Persistent-Auth",
836 cfg->gss_conn_ctx ? "true" : "false");
841 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
842 int prefixlen = strlen(auth_types[auth_type]) + 1;
843 replen = apr_base64_encode_len(output.length) + 1;
844 reply = apr_pcalloc(req->pool, prefixlen + replen);
846 memcpy(reply, auth_types[auth_type], prefixlen - 1);
847 reply[prefixlen - 1] = ' ';
848 apr_base64_encode(&reply[prefixlen], output.value, output.length);
849 apr_table_add(req->err_headers_out,
850 "WWW-Authenticate", reply);
852 } else if (ret == HTTP_UNAUTHORIZED) {
853 apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
854 if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
855 apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
857 if (cfg->use_basic_auth) {
858 apr_table_add(req->err_headers_out,
860 apr_psprintf(req->pool, "Basic realm=\"%s\"",
864 gss_release_oid_set(&min, &indicated_mechs);
865 if (ctx != GSS_C_NO_CONTEXT)
866 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
867 gss_release_cred(&min, &acquired_cred);
868 gss_release_cred(&min, &delegated_cred);
869 gss_release_buffer(&min, &output);
870 gss_release_name(&min, &client);
871 gss_release_buffer(&min, &name);
872 gss_release_buffer(&min, &lname);
877 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
879 struct mag_config *cfg;
881 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
887 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
889 struct mag_config *cfg = (struct mag_config *)mconfig;
890 cfg->ssl_only = on ? true : false;
894 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
896 struct mag_config *cfg = (struct mag_config *)mconfig;
897 cfg->map_to_local = on ? true : false;
901 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
903 struct mag_config *cfg = (struct mag_config *)mconfig;
904 cfg->gss_conn_ctx = on ? true : false;
908 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
910 struct mag_config *cfg = (struct mag_config *)mconfig;
911 cfg->send_persist = on ? true : false;
915 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
917 struct mag_config *cfg = (struct mag_config *)mconfig;
918 cfg->use_sessions = on ? true : false;
922 #ifdef HAVE_CRED_STORE
923 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
925 struct mag_config *cfg = (struct mag_config *)mconfig;
926 cfg->use_s4u2proxy = on ? true : false;
928 if (cfg->deleg_ccache_dir == NULL) {
929 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
935 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
937 struct mag_config *cfg = (struct mag_config *)mconfig;
944 if (strncmp(w, "key:", 4) != 0) {
945 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
946 "Invalid key format, expected prefix 'key:'");
951 l = apr_base64_decode_len(k);
952 val = apr_palloc(parms->temp_pool, l);
954 keys.length = (int)apr_base64_decode_binary(val, k);
955 keys.value = (unsigned char *)val;
957 if (keys.length != 32) {
958 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
959 "Invalid key length, expected 32 got %d", keys.length);
963 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
965 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
966 "Failed to import sealing key!");
971 #ifdef HAVE_CRED_STORE
973 #define MAX_CRED_OPTIONS 10
975 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
978 struct mag_config *cfg = (struct mag_config *)mconfig;
979 gss_key_value_element_desc *elements;
988 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
989 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
993 key = apr_pstrndup(parms->pool, w, (p-w));
994 value = apr_pstrdup(parms->pool, p + 1);
996 if (!cfg->cred_store) {
997 cfg->cred_store = apr_pcalloc(parms->pool,
998 sizeof(gss_key_value_set_desc));
999 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
1000 cfg->cred_store->elements = apr_palloc(parms->pool, size);
1003 elements = cfg->cred_store->elements;
1004 count = cfg->cred_store->count;
1006 if (count >= MAX_CRED_OPTIONS) {
1007 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1008 "Too many GssapiCredStore options (MAX: %d)",
1012 cfg->cred_store->count++;
1014 elements[count].key = key;
1015 elements[count].value = value;
1020 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
1023 struct mag_config *cfg = (struct mag_config *)mconfig;
1025 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1031 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1033 struct mag_config *cfg = (struct mag_config *)mconfig;
1035 cfg->use_basic_auth = on ? true : false;
1039 #define MAX_ALLOWED_MECHS 10
1041 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1044 struct mag_config *cfg = (struct mag_config *)mconfig;
1048 if (!cfg->allowed_mechs) {
1049 cfg->allowed_mechs = apr_pcalloc(parms->pool,
1050 sizeof(gss_OID_set_desc));
1051 size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
1052 cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
1054 cfg->allowed_mechs->elements[0] = gss_mech_spnego;
1055 cfg->allowed_mechs->count++;
1058 if (strcmp(w, "krb5") == 0) {
1059 oid = gss_mech_krb5;
1060 } else if (strcmp(w, "iakerb") == 0) {
1061 oid = gss_mech_iakerb;
1062 } else if (strcmp(w, "ntlmssp") == 0) {
1063 oid = &gss_mech_ntlmssp;
1065 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1066 "Unrecognized GSSAPI Mechanism: %s", w);
1070 if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
1071 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1072 "Too many GssapiAllowedMech options (MAX: %d)",
1076 cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
1077 cfg->allowed_mechs->count++;
1082 static const command_rec mag_commands[] = {
1083 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1084 "Work only if connection is SSL Secured"),
1085 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1086 "Translate principals to local names"),
1087 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1088 "Authentication is bound to the TCP connection"),
1089 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1090 "Send Persitent-Auth header according to connection bound"),
1091 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1092 "Authentication uses mod_sessions to hold status"),
1093 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1094 "Key Used to seal session data."),
1095 #ifdef HAVE_CRED_STORE
1096 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1097 "Initializes credentials for s4u2proxy usage"),
1098 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1099 "Credential Store"),
1100 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1101 OR_AUTHCFG, "Directory to store delegated credentials"),
1103 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1104 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1105 "Allows use of Basic Auth for authentication"),
1107 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1108 "Allowed Mechanisms"),
1113 mag_register_hooks(apr_pool_t *p)
1115 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1116 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1117 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1120 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1122 STANDARD20_MODULE_STUFF,
1123 mag_create_dir_config,