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_id_t acquired_cred,
344 gss_cred_usage_t cred_usage,
347 gss_cred_id_t *delegated_cred,
350 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
351 const char *user_ccache = NULL;
352 const char *orig_ccache = NULL;
353 long long unsigned int rndname;
356 gss_name_t user = GSS_C_NO_NAME;
357 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
358 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
359 gss_name_t server = GSS_C_NO_NAME;
360 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
361 gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
362 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
363 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
364 uint32_t init_flags = 0;
368 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
369 rs = apr_generate_random_bytes((unsigned char *)(&rndname),
370 sizeof(long long unsigned int));
371 if (rs != APR_SUCCESS) {
372 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
373 "Failed to generate random ccache name");
376 user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
377 maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
378 if (GSS_ERROR(maj)) {
379 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
381 mag_error(req, "gss_krb5_ccache_name() "
382 "failed", maj, min));
387 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
388 if (GSS_ERROR(maj)) {
389 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
391 mag_error(req, "gss_import_name() failed",
396 maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
400 &user_cred, NULL, NULL);
401 if (GSS_ERROR(maj)) {
402 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
404 mag_error(req, "gss_acquire_cred_with_password() "
405 "failed", maj, min));
409 if (cred_usage == GSS_C_BOTH) {
410 /* If GSS_C_BOTH is used then inquire_cred will return the client
411 * name instead of the SPN of the server credentials. Therefore we
412 * need to acquire a different set of credential setting
413 * GSS_C_ACCEPT explicitly */
414 if (!mag_acquire_creds(req, cfg, cfg->allowed_mechs,
415 GSS_C_ACCEPT, &server_cred, NULL)) {
419 server_cred = acquired_cred;
421 maj = gss_inquire_cred(&min, server_cred, &server,
423 if (GSS_ERROR(maj)) {
424 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
425 "%s", mag_error(req, "gss_inquired_cred_() "
426 "failed", maj, min));
430 #ifdef HAVE_CRED_STORE
431 if (cfg->deleg_ccache_dir) {
432 /* delegate ourselves credentials so we store them as requested */
433 init_flags |= GSS_C_DELEG_FLAG;
438 /* output and input are inverted here, this is intentional */
439 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
440 GSS_C_NO_OID, init_flags, 300,
441 GSS_C_NO_CHANNEL_BINDINGS, &output,
442 NULL, &input, NULL, NULL);
443 if (GSS_ERROR(maj)) {
444 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
445 "%s", mag_error(req, "gss_init_sec_context() "
446 "failed", maj, min));
449 gss_release_buffer(&min, &output);
450 maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
451 &input, GSS_C_NO_CHANNEL_BINDINGS,
452 client, mech_type, &output, NULL,
453 vtime, delegated_cred);
454 if (GSS_ERROR(maj)) {
455 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
456 "%s", mag_error(req, "gss_accept_sec_context()"
457 " failed", maj, min));
460 gss_release_buffer(&min, &input);
461 } while (maj == GSS_S_CONTINUE_NEEDED);
466 gss_release_buffer(&min, &output);
467 gss_release_buffer(&min, &input);
468 gss_release_name(&min, &server);
469 if (server_cred != acquired_cred) {
470 gss_release_cred(&min, &server_cred);
472 gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
473 gss_release_name(&min, &user);
474 gss_release_cred(&min, &user_cred);
475 gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
476 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
477 if (user_ccache != NULL) {
478 maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
479 if (maj != GSS_S_COMPLETE) {
480 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
481 "Failed to restore per-thread ccache, %s",
482 mag_error(req, "gss_krb5_ccache_name() "
483 "failed", maj, min));
491 static int mag_auth(request_rec *req)
495 struct mag_config *cfg;
496 const char *auth_header;
497 char *auth_header_type;
498 int ret = HTTP_UNAUTHORIZED;
499 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
501 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
502 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
503 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
504 gss_buffer_desc ba_user;
505 gss_buffer_desc ba_pwd;
506 gss_name_t client = GSS_C_NO_NAME;
507 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
508 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
509 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
515 gss_OID mech_type = GSS_C_NO_OID;
516 gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
517 gss_OID_set indicated_mechs = GSS_C_NO_OID_SET;
518 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
519 struct mag_conn *mc = NULL;
523 type = ap_auth_type(req);
524 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
528 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
530 if (cfg->allowed_mechs) {
531 desired_mechs = cfg->allowed_mechs;
533 /* Try to fetch the default set if not explicitly configured */
534 maj = gss_indicate_mechs(&min, &indicated_mechs);
535 if (maj != GSS_S_COMPLETE) {
536 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
537 mag_error(req, "gss_indicate_mechs() failed",
540 desired_mechs = indicated_mechs;
543 /* implicit auth for subrequests if main auth already happened */
544 if (!ap_is_initial_req(req) && req->main != NULL) {
545 type = ap_auth_type(req->main);
546 if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
547 /* warn if the subrequest location and the main request
548 * location have different configs */
549 if (cfg != ap_get_module_config(req->main->per_dir_config,
550 &auth_gssapi_module)) {
551 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
552 req, "Subrequest authentication bypass on "
553 "location with different configuration!");
555 if (req->main->user) {
556 req->user = apr_pstrdup(req->pool, req->main->user);
559 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
560 "The main request is tasked to establish the "
561 "security context, can't proceed!");
562 return HTTP_UNAUTHORIZED;
565 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
566 "Subrequest GSSAPI auth with no auth on the main "
567 "request. This operation may fail if other "
568 "subrequests already established a context or the "
569 "mechanism requires multiple roundtrips.");
574 if (!mag_conn_is_https(req->connection)) {
575 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
576 "Not a TLS connection, refusing to authenticate!");
581 if (cfg->gss_conn_ctx) {
582 mc = (struct mag_conn *)ap_get_module_config(
583 req->connection->conn_config,
584 &auth_gssapi_module);
586 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
587 "Failed to retrieve connection context!");
592 /* if available, session always supersedes connection bound data */
593 if (cfg->use_sessions) {
594 mag_check_session(req, cfg, &mc);
597 auth_header = apr_table_get(req->headers_in, "Authorization");
600 if (mc->established &&
601 (auth_header == NULL) &&
602 (mc->auth_type != AUTH_TYPE_BASIC)) {
603 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
604 "Already established context found!");
605 mag_set_req_data(req, cfg, mc);
614 /* We can proceed only if we do have an auth header */
615 if (!auth_header) goto done;
617 auth_header_type = ap_getword_white(req->pool, &auth_header);
618 if (!auth_header_type) goto done;
620 for (i = 0; auth_types[i] != NULL; i++) {
621 if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
628 case AUTH_TYPE_NEGOTIATE:
629 if (!parse_auth_header(req->pool, &auth_header, &input)) {
633 case AUTH_TYPE_BASIC:
634 if (!cfg->use_basic_auth) {
638 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
639 if (!ba_pwd.value) goto done;
640 ba_user.value = ap_getword_nulls_nc(req->pool,
641 (char **)&ba_pwd.value, ':');
642 if (!ba_user.value) goto done;
643 if (((char *)ba_user.value)[0] == '\0' ||
644 ((char *)ba_pwd.value)[0] == '\0') {
645 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
646 "Invalid empty user or password for Basic Auth");
649 ba_user.length = strlen(ba_user.value);
650 ba_pwd.length = strlen(ba_pwd.value);
652 if (mc && mc->established &&
653 mag_basic_check(cfg, mc, ba_user, ba_pwd)) {
654 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
655 "Already established BASIC AUTH context found!");
656 mag_set_req_data(req, cfg, mc);
663 case AUTH_TYPE_RAW_NTLM:
664 if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
665 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
666 "NTLM Authentication is not allowed!");
670 if (!parse_auth_header(req->pool, &auth_header, &input)) {
674 desired_mechs = discard_const(&gss_mech_set_ntlmssp);
681 if (mc && mc->established) {
682 /* if we are re-authenticating make sure the conn context
683 * is cleaned up so we do not accidentally reuse an existing
684 * established context */
688 req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
690 #ifdef HAVE_CRED_STORE
691 if (cfg->use_s4u2proxy) {
692 cred_usage = GSS_C_BOTH;
695 if (!mag_acquire_creds(req, cfg, desired_mechs,
696 cred_usage, &acquired_cred, NULL)) {
700 if (auth_type == AUTH_TYPE_BASIC) {
701 if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
702 acquired_cred, cred_usage,
704 &delegated_cred, &vtime)) {
710 if (auth_type == AUTH_TYPE_NEGOTIATE &&
711 cfg->allowed_mechs != GSS_C_NO_OID_SET) {
712 maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
713 if (GSS_ERROR(maj)) {
714 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
715 mag_error(req, "gss_set_neg_mechs() failed",
721 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
722 &input, GSS_C_NO_CHANNEL_BINDINGS,
723 &client, &mech_type, &output, NULL, &vtime,
725 if (GSS_ERROR(maj)) {
726 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
727 mag_error(req, "gss_accept_sec_context() failed",
730 } else if (maj == GSS_S_CONTINUE_NEEDED) {
732 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
733 "Mechanism needs continuation but neither "
734 "GssapiConnectionBound nor "
735 "GssapiUseSessions are available");
736 gss_release_buffer(&min, &output);
739 /* auth not complete send token and wait next packet */
744 /* Always set the GSS name in an env var */
745 maj = gss_display_name(&min, client, &name, NULL);
746 if (GSS_ERROR(maj)) {
747 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
748 mag_error(req, "gss_display_name() failed",
752 clientname = apr_pstrndup(req->pool, name.value, name.length);
753 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
754 expiration = time(NULL) + vtime;
755 apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
756 apr_psprintf(req->pool, "%ld", (long)expiration));
758 #ifdef HAVE_CRED_STORE
759 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
760 char *ccachefile = NULL;
762 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
763 delegated_cred, &ccachefile);
766 mag_set_KRB5CCANME(req, ccachefile);
770 mc->delegated = true;
775 if (cfg->map_to_local) {
776 maj = gss_localname(&min, client, mech_type, &lname);
777 if (maj != GSS_S_COMPLETE) {
778 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
779 mag_error(req, "gss_localname() failed", maj, min));
782 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
784 req->user = clientname;
788 mc->user_name = apr_pstrdup(mc->pool, req->user);
789 mc->gss_name = apr_pstrdup(mc->pool, clientname);
790 mc->established = true;
791 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
792 vtime = MIN_SESS_EXP_TIME;
794 mc->expiration = expiration;
795 mc->auth_type = auth_type;
796 if (auth_type == AUTH_TYPE_BASIC) {
797 mag_basic_cache(cfg, mc, ba_user, ba_pwd);
799 if (cfg->use_sessions) {
800 mag_attempt_session(req, cfg, mc);
804 if (cfg->send_persist)
805 apr_table_set(req->headers_out, "Persistent-Auth",
806 cfg->gss_conn_ctx ? "true" : "false");
811 if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
812 int prefixlen = strlen(auth_types[auth_type]) + 1;
813 replen = apr_base64_encode_len(output.length) + 1;
814 reply = apr_pcalloc(req->pool, prefixlen + replen);
816 memcpy(reply, auth_types[auth_type], prefixlen - 1);
817 reply[prefixlen - 1] = ' ';
818 apr_base64_encode(&reply[prefixlen], output.value, output.length);
819 apr_table_add(req->err_headers_out,
820 "WWW-Authenticate", reply);
822 } else if (ret == HTTP_UNAUTHORIZED) {
823 apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
824 if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
825 apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
827 if (cfg->use_basic_auth) {
828 apr_table_add(req->err_headers_out,
830 apr_psprintf(req->pool, "Basic realm=\"%s\"",
834 gss_release_oid_set(&min, &indicated_mechs);
835 if (ctx != GSS_C_NO_CONTEXT)
836 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
837 gss_release_cred(&min, &acquired_cred);
838 gss_release_cred(&min, &delegated_cred);
839 gss_release_buffer(&min, &output);
840 gss_release_name(&min, &client);
841 gss_release_buffer(&min, &name);
842 gss_release_buffer(&min, &lname);
847 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
849 struct mag_config *cfg;
851 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
857 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
859 struct mag_config *cfg = (struct mag_config *)mconfig;
860 cfg->ssl_only = on ? true : false;
864 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
866 struct mag_config *cfg = (struct mag_config *)mconfig;
867 cfg->map_to_local = on ? true : false;
871 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
873 struct mag_config *cfg = (struct mag_config *)mconfig;
874 cfg->gss_conn_ctx = on ? true : false;
878 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
880 struct mag_config *cfg = (struct mag_config *)mconfig;
881 cfg->send_persist = on ? true : false;
885 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
887 struct mag_config *cfg = (struct mag_config *)mconfig;
888 cfg->use_sessions = on ? true : false;
892 #ifdef HAVE_CRED_STORE
893 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
895 struct mag_config *cfg = (struct mag_config *)mconfig;
896 cfg->use_s4u2proxy = on ? true : false;
898 if (cfg->deleg_ccache_dir == NULL) {
899 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
905 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
907 struct mag_config *cfg = (struct mag_config *)mconfig;
914 if (strncmp(w, "key:", 4) != 0) {
915 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
916 "Invalid key format, expected prefix 'key:'");
921 l = apr_base64_decode_len(k);
922 val = apr_palloc(parms->temp_pool, l);
924 keys.length = (int)apr_base64_decode_binary(val, k);
925 keys.value = (unsigned char *)val;
927 if (keys.length != 32) {
928 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
929 "Invalid key length, expected 32 got %d", keys.length);
933 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
935 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
936 "Failed to import sealing key!");
941 #ifdef HAVE_CRED_STORE
943 #define MAX_CRED_OPTIONS 10
945 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
948 struct mag_config *cfg = (struct mag_config *)mconfig;
949 gss_key_value_element_desc *elements;
958 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
959 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
963 key = apr_pstrndup(parms->pool, w, (p-w));
964 value = apr_pstrdup(parms->pool, p + 1);
966 if (!cfg->cred_store) {
967 cfg->cred_store = apr_pcalloc(parms->pool,
968 sizeof(gss_key_value_set_desc));
969 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
970 cfg->cred_store->elements = apr_palloc(parms->pool, size);
973 elements = cfg->cred_store->elements;
974 count = cfg->cred_store->count;
976 if (count >= MAX_CRED_OPTIONS) {
977 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
978 "Too many GssapiCredStore options (MAX: %d)",
982 cfg->cred_store->count++;
984 elements[count].key = key;
985 elements[count].value = value;
990 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
993 struct mag_config *cfg = (struct mag_config *)mconfig;
995 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
1001 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1003 struct mag_config *cfg = (struct mag_config *)mconfig;
1005 cfg->use_basic_auth = on ? true : false;
1009 #define MAX_ALLOWED_MECHS 10
1011 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1014 struct mag_config *cfg = (struct mag_config *)mconfig;
1018 if (!cfg->allowed_mechs) {
1019 cfg->allowed_mechs = apr_pcalloc(parms->pool,
1020 sizeof(gss_OID_set_desc));
1021 size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
1022 cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
1024 cfg->allowed_mechs->elements[0] = gss_mech_spnego;
1025 cfg->allowed_mechs->count++;
1028 if (strcmp(w, "krb5") == 0) {
1029 oid = gss_mech_krb5;
1030 } else if (strcmp(w, "iakerb") == 0) {
1031 oid = gss_mech_iakerb;
1032 } else if (strcmp(w, "ntlmssp") == 0) {
1033 oid = &gss_mech_ntlmssp;
1035 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1036 "Unrecognized GSSAPI Mechanism: %s", w);
1040 if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
1041 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1042 "Too many GssapiAllowedMech options (MAX: %d)",
1046 cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
1047 cfg->allowed_mechs->count++;
1052 static const command_rec mag_commands[] = {
1053 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1054 "Work only if connection is SSL Secured"),
1055 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1056 "Translate principals to local names"),
1057 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1058 "Authentication is bound to the TCP connection"),
1059 AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1060 "Send Persitent-Auth header according to connection bound"),
1061 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1062 "Authentication uses mod_sessions to hold status"),
1063 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1064 "Key Used to seal session data."),
1065 #ifdef HAVE_CRED_STORE
1066 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1067 "Initializes credentials for s4u2proxy usage"),
1068 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1069 "Credential Store"),
1070 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1071 OR_AUTHCFG, "Directory to store delegated credentials"),
1073 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1074 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1075 "Allows use of Basic Auth for authentication"),
1077 AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1078 "Allowed Mechanisms"),
1083 mag_register_hooks(apr_pool_t *p)
1085 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1086 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1087 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1090 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1092 STANDARD20_MODULE_STUFF,
1093 mag_create_dir_config,