/* Copyright (C) 2015, 2016 mod_auth_gssapi contributors - See COPYING for (C) terms */ #include "mod_auth_gssapi.h" struct name_attr { gss_buffer_desc name; int authenticated; int complete; gss_buffer_desc value; gss_buffer_desc display_value; const char *env_name; int number; int more; }; static bool mag_get_name_attr(request_rec *req, gss_name_t name, struct name_attr *attr) { uint32_t maj, min; maj = gss_get_name_attribute(&min, name, &attr->name, &attr->authenticated, &attr->complete, &attr->value, &attr->display_value, &attr->more); if (GSS_ERROR(maj)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "gss_get_name_attribute() failed on %.*s%s", (int)attr->name.length, (char *)attr->name.value, mag_error(req->pool, "", maj, min)); return false; } return true; } static apr_status_t mag_mc_name_attrs_cleanup(void *data) { struct mag_conn *mc = (struct mag_conn *)data; free(mc->name_attributes); mc->name_attributes = NULL; return 0; } static apr_status_t mag_mc_req_name_attrs_cleanup(void *data) { struct mag_conn *mc = (struct mag_conn *)data; free(mc->required_name_attrs); free(mc->required_name_vals); mc->required_name_attrs = NULL; mc->required_name_vals = NULL; return 0; } static void mag_set_required_name_attr(request_rec *req, struct mag_conn *mc, struct name_attr *attr) { size_t count, len; char *val = NULL, *attrval = NULL; /* Count the existing name attribute and value pairs. Both * required_name_attrs and required_name_vals are allocated together and * hold the same number of strings, with pairs corresponding by index. */ for (count = 0; mc->required_name_attrs != NULL && mc->required_name_attrs[count] != NULL && mc->required_name_vals != NULL && mc->required_name_vals[count] != NULL; count++); /* Prefer a display value string. */ if (attr->display_value.length != 0) { len = attr->display_value.length; attrval = attr->display_value.value; } else if (attr->value.length != 0) { len = attr->value.length; attrval = attr->value.value; } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req, "no name attribute value available"); return; } /* Prepend the value length. */ val = apr_pcalloc(mc->pool, sizeof(len) + len + 1); memcpy(val, &len, sizeof(len)); memcpy(val + sizeof(len), attrval, len); /* Allocate/realloc a new bunch. */ if (count % 16 == 0) { size_t size = sizeof(*mc->required_name_attrs) * (count + 16 + 2); mc->required_name_attrs = realloc(mc->required_name_attrs, size); mc->required_name_vals = realloc(mc->required_name_vals, size); if (!mc->required_name_attrs || !mc->required_name_vals) { apr_pool_abort_get(mc->pool)(ENOMEM); } apr_pool_userdata_setn(mc, GSS_NAME_ATTR_USERDATA, mag_mc_req_name_attrs_cleanup, mc->pool); } mc->required_name_attrs[count] = apr_pstrndup(mc->pool, attr->name.value, attr->name.length); mc->required_name_attrs[count + 1] = NULL; mc->required_name_vals[count] = val; mc->required_name_vals[count + 1] = NULL; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req, "found name attribute '%s' with length %lu", mc->required_name_attrs[count], len); ap_log_rdata(APLOG_MARK, APLOG_DEBUG, req, mc->required_name_attrs[count], mc->required_name_vals[count] + sizeof(len), len, 0); } static void mc_add_name_attribute(struct mag_conn *mc, const char *name, const char *value) { size_t size; if (mc->na_count % 16 == 0) { size = sizeof(struct mag_attr) * (mc->na_count + 16); mc->name_attributes = realloc(mc->name_attributes, size); if (!mc->name_attributes) apr_pool_abort_get(mc->pool)(ENOMEM); apr_pool_userdata_setn(mc, GSS_NAME_ATTR_USERDATA, mag_mc_name_attrs_cleanup, mc->pool); } mc->name_attributes[mc->na_count].name = apr_pstrdup(mc->pool, name); mc->name_attributes[mc->na_count].value = apr_pstrdup(mc->pool, value); mc->na_count++; } static void mag_set_env_name_attr(request_rec *req, struct mag_conn *mc, struct name_attr *attr) { char *value = ""; int len = 0; /* Prefer a display_value, otherwise fallback to value */ if (attr->display_value.length != 0) { len = attr->display_value.length; value = (char *)attr->display_value.value; } else if (attr->value.length != 0) { len = apr_base64_encode_len(attr->value.length); value = apr_pcalloc(req->pool, len); len = apr_base64_encode(value, (char *)attr->value.value, attr->value.length); } if (attr->number == 1) { mc_add_name_attribute(mc, attr->env_name, apr_psprintf(req->pool, "%.*s", len, value)); } if (attr->more != 0 || attr->number > 1) { mc_add_name_attribute(mc, apr_psprintf(req->pool, "%s_%d", attr->env_name, attr->number), apr_psprintf(req->pool, "%.*s", len, value)); } if (attr->more == 0 && attr->number > 1) { mc_add_name_attribute(mc, apr_psprintf(req->pool, "%s_N", attr->env_name), apr_psprintf(req->pool, "%d", attr->number - 1)); } } static char *mag_escape_display_value(request_rec *req, gss_buffer_desc disp_value) { /* This function returns a copy (in the pool) of the given gss_buffer_t * where some characters are escaped as required by RFC4627. The string is * NULL terminated */ char *value = disp_value.value; char *escaped_value = NULL; char *p = NULL; /* gss_buffer_t are not \0 terminated, but our result will be. Hence, * escaped length will be original length * 6 + 1 in the worst case */ p = escaped_value = apr_palloc(req->pool, disp_value.length * 6 + 1); for (size_t i = 0; i < disp_value.length; i++) { switch (value[i]) { case '"': memcpy(p, "\\\"", 2); p += 2; break; case '\\': memcpy(p, "\\\\", 2); p += 2; break; case '\b': memcpy(p, "\\b", 2); p += 2; break; case '\t': memcpy(p, "\\t", 2); p += 2; break; case '\r': memcpy(p, "\\r", 2); p += 2; break; case '\f': memcpy(p, "\\f", 2); p += 2; break; case '\n': memcpy(p, "\\n", 2); p += 2; break; default: if (value[i] <= 0x1F) { apr_snprintf(p, 7, "\\u%04d", (int)value[i]); p += 6; } else { *p = value[i]; p += 1; } break; } } /* make the string NULL terminated */ *p = '\0'; return escaped_value; } static void mag_add_json_name_attr(request_rec *req, bool first, struct name_attr *attr, char **json) { const char *value = ""; int len = 0; char *b64value = NULL; int b64len = 0; const char *vstart = ""; const char *vend = ""; const char *vformat; if (attr->value.length != 0) { b64len = apr_base64_encode_len(attr->value.length); b64value = apr_pcalloc(req->pool, b64len); b64len = apr_base64_encode(b64value, (char *)attr->value.value, attr->value.length); } if (attr->display_value.length != 0) { value = mag_escape_display_value(req, attr->display_value); len = strlen(value); } if (attr->number == 1) { *json = apr_psprintf(req->pool, "%s%s\"%.*s\":{\"authenticated\":%s," "\"complete\":%s," "\"values\":[", *json, (first ? "" : ","), (int)attr->name.length, (char *)attr->name.value, attr->authenticated ? "true" : "false", attr->complete ? "true" : "false"); } else { vstart = ","; } if (b64value) { if (len) { vformat = "%s%s{\"raw\":\"%s\",\"display\":\"%.*s\"}%s"; } else { vformat = "%s%s{\"raw\":\"%s\",\"display\":%.*s}%s"; } } else { if (len) { vformat = "%s%s{\"raw\":%s,\"display\":\"%.*s\"}%s"; } else { vformat = "%s%s{\"raw\":%s,\"display\":%.*s}%s"; } } if (attr->more == 0) { vend = "]}"; } *json = apr_psprintf(req->pool, vformat, *json, vstart, b64value ? b64value : "null", len ? len : 4, len ? value : "null", vend); } gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER; void mag_get_name_attributes(request_rec *req, struct mag_config *cfg, gss_name_t name, struct mag_conn *mc) { uint32_t maj, min; gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET; struct name_attr attr; char *json = NULL; char *error; int count = 0, map_count = 0; int i; if (!cfg->name_attributes && !cfg->required_na_expr) { return; } maj = gss_inquire_name(&min, name, NULL, NULL, &attrs); if (GSS_ERROR(maj)) { error = mag_error(req->pool, "gss_inquire_name() failed", maj, min); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s", error); apr_table_set(mc->env, "GSS_NAME_ATTR_ERROR", error); return; } if (!attrs || attrs->count == 0) { if (cfg->name_attributes) { mc_add_name_attribute(mc, "GSS_NAME_ATTR_ERROR", "0 attributes found"); } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req, "no name attributes found"); } if (attrs) { count = attrs->count; } if (cfg->name_attributes) { map_count = cfg->name_attributes->map_count; } if (cfg->name_attributes && cfg->name_attributes->output_json) { json = apr_psprintf(req->pool, "{\"name\":\"%s\",\"attributes\":{", mc->gss_name); } /* Collect all name attributes */ for (i = 0; i < count; i++) { memset(&attr, 0, sizeof(struct name_attr)); attr.name = attrs->elements[i]; if (map_count) { /* Use the environment variable name matching the attribute name * from the map. */ for (int j = 0; j < map_count; j++) { if (mag_strbuf_equal(cfg->name_attributes->map[j].attr_name, &attr.name)) { attr.env_name = cfg->name_attributes->map[j].env_name; break; } } } attr.number = 0; attr.more = -1; do { attr.number++; attr.value = empty_buffer; attr.display_value = empty_buffer; /* Fetch the next attribute value. */ if (!mag_get_name_attr(req, name, &attr)) { break; } /* Add as a JSON value if needed. */ if (cfg->name_attributes && cfg->name_attributes->output_json) { mag_add_json_name_attr(req, i == 0, &attr, &json); } /* Add as an environment variable if needed. */ if (attr.env_name) { mag_set_env_name_attr(req, mc, &attr); } /* Add to the list of name attributes lined up for the requirement * check if needed. */ if (cfg->required_na_expr) { mag_set_required_name_attr(req, mc, &attr); } gss_release_buffer(&min, &attr.value); gss_release_buffer(&min, &attr.display_value); } while (attr.more != 0); } if (cfg->name_attributes && cfg->name_attributes->output_json) { json = apr_psprintf(req->pool, "%s}}", json); mc_add_name_attribute(mc, "GSS_NAME_ATTRS_JSON", json); } } static void mag_set_name_attributes(request_rec *req, struct mag_conn *mc) { for (int i = 0; i < mc->na_count; i++) { apr_table_set(mc->env, mc->name_attributes[i].name, mc->name_attributes[i].value); } } static void mag_set_ccname_envvar(request_rec *req, struct mag_config *cfg, struct mag_conn *mc) { #ifdef HAVE_CRED_STORE apr_status_t status; apr_int32_t wanted = APR_FINFO_MIN | APR_FINFO_OWNER | APR_FINFO_PROT; apr_finfo_t finfo = { 0 }; char *path; char *value; if (!cfg->deleg_ccache_dir || !mc->delegated || !mc->ccname) return; path = apr_psprintf(req->pool, "%s/%s", cfg->deleg_ccache_dir, mc->ccname); status = apr_stat(&finfo, path, wanted, req->pool); if (status == APR_SUCCESS) { if ((cfg->deleg_ccache_mode != 0) && (finfo.protection != cfg->deleg_ccache_mode)) { status = apr_file_perms_set(path, cfg->deleg_ccache_mode); if (status != APR_SUCCESS) ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, req, "failed to set perms (%o) on file (%s)!", cfg->deleg_ccache_mode, path); } if ((cfg->deleg_ccache_uid != 0) && (finfo.user != cfg->deleg_ccache_uid)) { status = lchown(path, cfg->deleg_ccache_uid, -1); if (status != 0) ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, req, "failed to set user (%u) on file (%s)!", cfg->deleg_ccache_uid, path); } if ((cfg->deleg_ccache_gid != 0) && (finfo.group != cfg->deleg_ccache_gid)) { status = lchown(path, -1, cfg->deleg_ccache_gid); if (status != 0) ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, req, "failed to set group (%u) on file (%s)!", cfg->deleg_ccache_gid, path); } } else { /* set the file cache anyway, but warn */ ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, req, "KRB5CCNAME file (%s) lookup failed!", path); } value = apr_psprintf(req->pool, "FILE:%s", path); apr_table_set(mc->env, cfg->ccname_envvar, value); #endif } void mag_export_req_env(request_rec *req, apr_table_t *env) { const apr_array_header_t *arr = apr_table_elts(env); const apr_table_entry_t *elts = (const apr_table_entry_t*)arr->elts; for (int i = 0; i < arr->nelts; ++i) apr_table_set(req->subprocess_env, elts[i].key, elts[i].val); } void mag_set_req_data(request_rec *req, struct mag_config *cfg, struct mag_conn *mc) { apr_table_set(mc->env, "GSS_NAME", mc->gss_name); apr_table_set(mc->env, "GSS_SESSION_EXPIRATION", apr_psprintf(req->pool, "%ld", (long)mc->expiration)); req->ap_auth_type = (char *) mag_str_auth_type(mc->auth_type); req->user = apr_pstrdup(req->pool, mc->user_name); if (mc->name_attributes) { mag_set_name_attributes(req, mc); } mag_set_ccname_envvar(req, cfg, mc); ap_set_module_config(req->request_config, &auth_gssapi_module, mc->env); mag_export_req_env(req, mc->env); } void mag_set_req_attr_fail(request_rec *req, struct mag_config *cfg, struct mag_conn *mc) { apr_table_set(mc->env, "GSS_NAME_ATTR_ERROR", "required name attributes check unsatisfied"); mag_set_req_data(req, cfg, mc); } void mag_publish_error(request_rec *req, uint32_t maj, uint32_t min, const char *gss_err, const char *mag_err) { if (gss_err) { apr_table_set(req->subprocess_env, "GSS_ERROR_MAJ", apr_psprintf(req->pool, "%u", (unsigned)maj)); apr_table_set(req->subprocess_env, "GSS_ERROR_MIN", apr_psprintf(req->pool, "%u", (unsigned)min)); apr_table_set(req->subprocess_env, "MAG_ERROR_TEXT", gss_err); } if (mag_err) apr_table_set(req->subprocess_env, "MAG_ERROR", mag_err); }