2 * Copyright (c) 2010 CESNET
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of CESNET nor the names of its contributors may
16 * be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
32 #include "mod_auth_gssapi.h"
35 gss_log(const char *file,
37 #if AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER == 4
49 vsnprintf(errstr, sizeof(errstr), fmt, ap);
54 #if AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER == 4
57 level | APLOG_NOERRNO,
65 gss_cleanup_conn_ctx(void *data)
67 gss_conn_ctx ctx = (gss_conn_ctx) data;
68 OM_uint32 minor_status;
70 if (ctx && ctx->context != GSS_C_NO_CONTEXT)
71 gss_delete_sec_context(&minor_status, &ctx->context, GSS_C_NO_BUFFER);
73 if (ctx && ctx->server_creds != GSS_C_NO_CREDENTIAL)
74 gss_release_cred(&minor_status, &ctx->server_creds);
80 gss_create_conn_ctx(request_rec *r, gss_auth_config *conf)
83 gss_conn_ctx ctx = NULL;
85 snprintf(key, sizeof(key), "mod_auth_gssweb:conn_ctx");
87 if (NULL == (ctx = (gss_conn_ctx) apr_pcalloc(r->connection->pool, sizeof(*ctx)))) {
88 gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gss_create_conn_ctx: Can't allocate GSS context");
91 ctx->context = GSS_C_NO_CONTEXT;
92 ctx->state = GSS_CTX_EMPTY;
93 ctx->filter_stat = GSS_FILT_NEW;
95 ctx->name_attributes = NULL;
96 apr_pool_create(&ctx->pool, r->connection->pool);
97 /* register the context in the memory pool, so it can be freed
98 * when the connection/request is terminated */
99 apr_pool_cleanup_register(ctx->pool, (void *)ctx,
100 gss_cleanup_conn_ctx, apr_pool_cleanup_null);
102 /* Acquire and store server credentials */
103 if (0 == get_gss_creds(r, conf, &(ctx->server_creds))) {
104 gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gss_create_conn_ctx: Server credentials acquired");
106 gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gss_create_conn_ctx: Error: Server credentials NOT acquired");
110 apr_pool_userdata_set(ctx, key, gss_cleanup_conn_ctx, r->connection->pool);
116 gss_retrieve_conn_ctx(request_rec *r)
119 gss_conn_ctx ctx = NULL;
121 snprintf(key, sizeof(key), "mod_auth_gssweb:conn_ctx");
122 apr_pool_userdata_get((void **)&ctx, key, r->connection->pool);
125 gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gss_retrieve_conn_ctx: No GSS context found");
131 gss_config_dir_create(apr_pool_t *p, char *d)
133 gss_auth_config *conf;
135 conf = (gss_auth_config *) apr_pcalloc(p, sizeof(*conf));
143 get_gss_error(request_rec *r, OM_uint32 err_maj, OM_uint32 err_min, char *prefix)
145 OM_uint32 maj_stat, min_stat;
146 OM_uint32 msg_ctx = 0;
147 gss_buffer_desc status_string;
151 gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
152 "GSS-API major_status:%8.8x, minor_status:%8.8x",
155 err_msg = apr_pstrdup(r->pool, prefix);
157 maj_stat = gss_display_status (&min_stat,
163 if (!GSS_ERROR(maj_stat)) {
164 err_msg = apr_pstrcat(r->pool, err_msg,
165 ": ", (char*) status_string.value, NULL);
166 gss_release_buffer(&min_stat, &status_string);
169 } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
172 err_msg = apr_pstrcat(r->pool, err_msg, " (", NULL);
175 maj_stat = gss_display_status (&min_stat,
181 if (!GSS_ERROR(maj_stat)) {
182 err_msg = apr_pstrcat(r->pool, err_msg,
183 (first_pass) ? "" : ", ",
184 (char *) status_string.value,
186 gss_release_buffer(&min_stat, &status_string);
189 } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
190 err_msg = apr_pstrcat(r->pool, err_msg, ")", NULL);
196 get_gss_creds(request_rec *r,
197 gss_auth_config *conf,
198 gss_cred_id_t *server_creds)
200 gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
201 OM_uint32 major_status, minor_status, minor_status2;
202 gss_name_t server_name = GSS_C_NO_NAME;
204 int have_server_princ;
206 if (conf->service_name && strcmp(conf->service_name, "Any") == 0) {
207 *server_creds = GSS_C_NO_CREDENTIAL;
211 have_server_princ = conf->service_name && strchr(conf->service_name, '/') != NULL;
212 if (have_server_princ)
213 strncpy(buf, conf->service_name, sizeof(buf));
215 snprintf(buf, sizeof(buf), "%s@%s",
216 (conf->service_name) ? conf->service_name : SERVICE_NAME,
217 ap_get_server_name(r));
220 token.length = strlen(buf) + 1;
222 major_status = gss_import_name(&minor_status, &token,
223 (have_server_princ) ? (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME : (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
225 memset(&token, 0, sizeof(token));
226 if (GSS_ERROR(major_status)) {
227 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
228 "%s", get_gss_error(r, major_status, minor_status,
229 "gss_import_name() failed"));
230 return HTTP_INTERNAL_SERVER_ERROR;
233 major_status = gss_display_name(&minor_status, server_name, &token, NULL);
234 if (GSS_ERROR(major_status)) {
235 /* Perhaps we could just ignore this error but it's safer to give up now,
237 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
238 "%s", get_gss_error(r, major_status, minor_status,
239 "gss_display_name() failed"));
240 return HTTP_INTERNAL_SERVER_ERROR;
243 gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "Acquiring creds for %s", token.value);
244 gss_release_buffer(&minor_status, &token);
246 major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
247 GSS_C_NO_OID_SET, GSS_C_ACCEPT,
248 server_creds, NULL, NULL);
249 gss_release_name(&minor_status2, &server_name);
250 if (GSS_ERROR(major_status)) {
251 gss_log(APLOG_MARK, APLOG_ERR, 0, r,
252 "%s", get_gss_error(r, major_status, minor_status,
253 "Failed to load GSS-API credentials"));
254 return HTTP_INTERNAL_SERVER_ERROR;
261 cmp_gss_type(gss_buffer_t token, gss_OID oid)
266 if (token->length == 0)
267 return GSS_S_DEFECTIVE_TOKEN;
271 return GSS_S_DEFECTIVE_TOKEN;
274 if ((len & 0x7f) > 4)
275 return GSS_S_DEFECTIVE_TOKEN;
279 return GSS_S_DEFECTIVE_TOKEN;
281 if (((OM_uint32) *p++) != oid->length)
282 return GSS_S_DEFECTIVE_TOKEN;
284 return memcmp(p, oid->elements, oid->length);
288 * Name attributes to environment variables code
289 * This code is strongly based on the code from https://github.com/modauthgssapi/mod_auth_gssapi
292 static char *mag_status(request_rec *req, int type, uint32_t err)
294 uint32_t maj_ret, min_ret;
295 gss_buffer_desc text;
303 maj_ret = gss_display_status(&min_ret, err, type,
304 GSS_C_NO_OID, &msg_ctx, &text);
305 if (maj_ret != GSS_S_COMPLETE) {
311 msg_ret = apr_psprintf(req->pool, "%s, %*s",
312 msg_ret, len, (char *)text.value);
314 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
316 gss_release_buffer(&min_ret, &text);
317 } while (msg_ctx != 0);
322 char *mag_error(request_rec *req, const char *msg, uint32_t maj, uint32_t min)
327 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
328 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
329 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
333 static char mag_get_name_attr(request_rec *req,
334 gss_name_t name, name_attr *attr)
338 maj = gss_get_name_attribute(&min, name, &attr->name,
339 &attr->authenticated,
342 &attr->display_value,
344 if (GSS_ERROR(maj)) {
345 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
346 "gss_get_name_attribute() failed on %.*s%s",
347 (int)attr->name.length, (char *)attr->name.value,
348 mag_error(req, "", maj, min));
355 #define GSS_NAME_ATTR_USERDATA "GSS Name Attributes Userdata"
357 static apr_status_t mag_gss_name_attrs_cleanup(void *data)
359 gss_conn_ctx_t *gss_ctx = (gss_conn_ctx_t *) data;
360 free(gss_ctx->name_attributes);
361 gss_ctx->name_attributes = NULL;
365 static void mc_add_name_attribute(gss_conn_ctx_t *gss_ctx,
366 const char *name, const char *value)
370 if (gss_ctx->na_count % 16 == 0) {
371 size = sizeof(mag_attr) * (gss_ctx->na_count + 16);
372 gss_ctx->name_attributes = realloc(gss_ctx->name_attributes, size);
373 if (!gss_ctx->name_attributes) apr_pool_abort_get(gss_ctx->pool)(ENOMEM);
374 apr_pool_userdata_setn(gss_ctx, GSS_NAME_ATTR_USERDATA,
375 mag_gss_name_attrs_cleanup, gss_ctx->pool);
378 gss_ctx->name_attributes[gss_ctx->na_count].name = apr_pstrdup(gss_ctx->pool, name);
379 gss_ctx->name_attributes[gss_ctx->na_count].value = apr_pstrdup(gss_ctx->pool, value);
383 static void mag_set_env_name_attr(request_rec *req, gss_conn_ctx_t *gss_ctx,
389 /* Prefer a display_value, otherwise fallback to value */
390 if (attr->display_value.length != 0) {
391 len = attr->display_value.length;
392 value = (char *)attr->display_value.value;
393 } else if (attr->value.length != 0) {
394 len = apr_base64_encode_len(attr->value.length);
395 value = apr_pcalloc(req->pool, len);
396 len = apr_base64_encode(value,
397 (char *)attr->value.value,
401 if (attr->number == 1) {
402 mc_add_name_attribute(gss_ctx,
404 apr_psprintf(req->pool, "%.*s", len, value));
406 if (attr->more != 0 || attr->number > 1) {
407 mc_add_name_attribute(gss_ctx,
408 apr_psprintf(req->pool, "%s_%d",
409 attr->env_name, attr->number),
410 apr_psprintf(req->pool, "%.*s", len, value));
412 if (attr->more == 0 && attr->number > 1) {
413 mc_add_name_attribute(gss_ctx,
414 apr_psprintf(req->pool, "%s_N", attr->env_name),
415 apr_psprintf(req->pool, "%d", attr->number - 1));
419 static char *mag_escape_display_value(request_rec *req,
420 gss_buffer_desc disp_value)
422 /* This function returns a copy (in the pool) of the given gss_buffer_t
423 * where some characters are escaped as required by RFC4627. The string is
425 char *value = disp_value.value;
426 char *escaped_value = NULL;
430 /* gss_buffer_t are not \0 terminated, but our result will be. Hence,
431 * escaped length will be original length * 6 + 1 in the worst case */
432 p = escaped_value = apr_palloc(req->pool, disp_value.length * 6 + 1);
433 for (i = 0; i < disp_value.length; i++) {
436 memcpy(p, "\\\"", 2);
440 memcpy(p, "\\\\", 2);
464 if (value[i] <= 0x1F) {
465 apr_snprintf(p, 7, "\\u%04d", (int)value[i]);
474 /* make the string NULL terminated */
476 return escaped_value;
479 static void mag_add_json_name_attr(request_rec *req, char first,
480 name_attr *attr, char **json)
482 const char *value = "";
484 char *b64value = NULL;
486 const char *vstart = "";
487 const char *vend = "";
490 if (attr->value.length != 0) {
491 b64len = apr_base64_encode_len(attr->value.length);
492 b64value = apr_pcalloc(req->pool, b64len);
493 b64len = apr_base64_encode(b64value,
494 (char *)attr->value.value,
497 if (attr->display_value.length != 0) {
498 value = mag_escape_display_value(req, attr->display_value);
501 if (attr->number == 1) {
502 *json = apr_psprintf(req->pool,
503 "%s%s\"%.*s\":{\"authenticated\":%s,"
506 *json, (first ? "" : ","),
507 (int)attr->name.length, (char *)attr->name.value,
508 attr->authenticated ? "true" : "false",
509 attr->complete ? "true" : "false");
516 vformat = "%s%s{\"raw\":\"%s\",\"display\":\"%.*s\"}%s";
518 vformat = "%s%s{\"raw\":\"%s\",\"display\":%.*s}%s";
522 vformat = "%s%s{\"raw\":%s,\"display\":\"%.*s\"}%s";
524 vformat = "%s%s{\"raw\":%s,\"display\":%.*s}%s";
528 if (attr->more == 0) {
532 *json = apr_psprintf(req->pool, vformat, *json,
534 b64value ? b64value : "null",
535 len ? len : 4, len ? value : "null",
539 gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
541 void mag_get_name_attributes(request_rec *req, gss_auth_config *cfg,
542 gss_name_t name, gss_conn_ctx_t *gss_ctx)
545 gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET;
552 maj = gss_inquire_name(&min, name, NULL, NULL, &attrs);
553 if (GSS_ERROR(maj)) {
554 error = mag_error(req, "gss_inquire_name() failed", maj, min);
555 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s", error);
556 apr_table_set(req->subprocess_env, "GSS_NAME_ATTR_ERROR", error);
560 if (!attrs || attrs->count == 0) {
561 mc_add_name_attribute(gss_ctx, "GSS_NAME_ATTR_ERROR", "0 attributes found");
564 if (cfg->name_attributes->output_json) {
566 if (attrs) count = attrs->count;
568 json = apr_psprintf(req->pool,
569 "{\"name\":\"%s\",\"attributes\":{",
572 count = cfg->name_attributes->map_count;
575 for (i = 0; i < count; i++) {
576 memset(&attr, 0, sizeof(name_attr));
578 if (cfg->name_attributes->output_json) {
579 attr.name = attrs->elements[i];
580 for (j = 0; j < cfg->name_attributes->map_count; j++) {
581 if (strncmp(cfg->name_attributes->map[j].attr_name,
582 attrs->elements[i].value,
583 attrs->elements[i].length) == 0) {
584 attr.env_name = cfg->name_attributes->map[j].env_name;
589 attr.name.length = strlen(cfg->name_attributes->map[i].attr_name);
590 attr.name.value = cfg->name_attributes->map[i].attr_name;
591 attr.env_name = cfg->name_attributes->map[i].env_name;
598 attr.value = empty_buffer;
599 attr.display_value = empty_buffer;
600 if (!mag_get_name_attr(req, name, &attr)) break;
602 if (cfg->name_attributes->output_json) {
603 mag_add_json_name_attr(req, i == 0, &attr, &json);
606 mag_set_env_name_attr(req, gss_ctx, &attr);
609 gss_release_buffer(&min, &attr.value);
610 gss_release_buffer(&min, &attr.display_value);
611 } while (attr.more != 0);
614 if (cfg->name_attributes->output_json) {
615 json = apr_psprintf(req->pool, "%s}}", json);
616 mc_add_name_attribute(gss_ctx, "GSS_NAME_ATTRS_JSON", json);
620 static void mag_set_name_attributes(request_rec *req, gss_conn_ctx_t *gss_ctx)
623 for (i = 0; i < gss_ctx->na_count; i++) {
624 apr_table_set(req->subprocess_env,
625 gss_ctx->name_attributes[i].name,
626 gss_ctx->name_attributes[i].value);
630 void mag_set_req_data(request_rec *req,
631 gss_auth_config *cfg,
632 gss_conn_ctx_t *gss_ctx)
634 if (gss_ctx->name_attributes) {
635 mag_set_name_attributes(req, gss_ctx);
639 static apr_status_t mag_name_attrs_cleanup(void *data)
641 gss_auth_config *cfg = (gss_auth_config *)data;
642 free(cfg->name_attributes);
643 cfg->name_attributes = NULL;
647 const char *mag_name_attrs(cmd_parms *parms, void *mconfig,
650 gss_auth_config *cfg = (gss_auth_config *)mconfig;
656 if (!cfg->name_attributes) {
657 size = sizeof(mag_name_attributes)
658 + (sizeof(mag_na_map) * 16);
659 } else if (cfg->name_attributes->map_count % 16 == 0) {
660 size = sizeof(mag_name_attributes)
661 + (sizeof(mag_na_map)
662 * (cfg->name_attributes->map_count + 16));
665 tmp_na = realloc(cfg->name_attributes, size);
666 if (!tmp_na) apr_pool_abort_get(cfg->pool)(ENOMEM);
668 if (cfg->name_attributes) {
669 size_t empty = (sizeof(mag_na_map) * 16);
670 memset(tmp_na + size - empty, 0, empty);
672 memset(tmp_na, 0, size);
674 cfg->name_attributes = (mag_name_attributes *)tmp_na;
675 apr_pool_userdata_setn(cfg, GSS_NAME_ATTR_USERDATA,
676 mag_name_attrs_cleanup, cfg->pool);
682 if (strcmp(w, "json") == 0) {
683 cfg->name_attributes->output_json = 1;
685 ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
686 "Invalid Name Attributes value [%s].", w);
691 c = cfg->name_attributes->map_count;
692 cfg->name_attributes->map[c].env_name = apr_pstrndup(cfg->pool, w, p-w);
694 cfg->name_attributes->map[c].attr_name = apr_pstrdup(cfg->pool, p);
695 cfg->name_attributes->map_count += 1;