X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=util_radius.cpp;h=4e2f6e079724311dd6304f9ca620f8050cac262e;hb=e063ba4e45d12dbc1a397653f9e77228835e4a2b;hp=7fe84a4f91ca2cdaabbc123369d1b922fcd9b4c3;hpb=c52cc1a5f4ddc9d308508ef8ac9f64ab9304adcc;p=mech_eap.git diff --git a/util_radius.cpp b/util_radius.cpp index 7fe84a4..4e2f6e0 100644 --- a/util_radius.cpp +++ b/util_radius.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, JANET(UK) + * Copyright (c) 2011, JANET(UK) * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,49 +30,291 @@ * SUCH DAMAGE. */ +/* + * RADIUS attribute provider implementation. + */ + #include "gssapiP_eap.h" +/* stuff that should be provided by libradsec/libfreeradius-radius */ +#define VENDORATTR(vendor, attr) (((vendor) << 16) | (attr)) + +#ifndef ATTRID +#define ATTRID(attr) ((attr) & 0xFFFF) +#endif + +static gss_buffer_desc radiusUrnPrefix = { + sizeof("urn:x-radius:") - 1, + (void *)"urn:x-radius:" +}; + +static VALUE_PAIR *copyAvps(const VALUE_PAIR *src); + +gss_eap_radius_attr_provider::gss_eap_radius_attr_provider(void) +{ + m_vps = NULL; + m_authenticated = false; +} + +gss_eap_radius_attr_provider::~gss_eap_radius_attr_provider(void) +{ + if (m_vps != NULL) + pairfree(&m_vps); +} + bool -gss_eap_radius_attr_provider::initFromExistingContext(const gss_eap_attr_ctx *source, +gss_eap_radius_attr_provider::initFromExistingContext(const gss_eap_attr_ctx *manager, const gss_eap_attr_provider *ctx) { - if (!gss_eap_attr_provider::initFromExistingContext(source, ctx)) + const gss_eap_radius_attr_provider *radius; + + if (!gss_eap_attr_provider::initFromExistingContext(manager, ctx)) return false; + radius = static_cast(ctx); + + if (radius->m_vps != NULL) + m_vps = copyAvps(const_cast(radius->getAvps())); + + m_authenticated = radius->m_authenticated; + return true; } bool -gss_eap_radius_attr_provider::initFromGssContext(const gss_eap_attr_ctx *source, +gss_eap_radius_attr_provider::initFromGssContext(const gss_eap_attr_ctx *manager, const gss_cred_id_t gssCred, const gss_ctx_id_t gssCtx) { - if (!gss_eap_attr_provider::initFromGssContext(source, gssCred, gssCtx)) + if (!gss_eap_attr_provider::initFromGssContext(manager, gssCred, gssCtx)) return false; + if (gssCtx != GSS_C_NO_CONTEXT) { + if (gssCtx->acceptorCtx.vps != NULL) { + m_vps = copyAvps(gssCtx->acceptorCtx.vps); + if (m_vps == NULL) + return false; + + /* We assume libradsec validated this for us */ + assert(pairfind(m_vps, PW_MESSAGE_AUTHENTICATOR) != NULL); + m_authenticated = true; + } + } + return true; } -gss_eap_radius_attr_provider::~gss_eap_radius_attr_provider(void) +static bool +alreadyAddedAttributeP(std::vector &attrs, VALUE_PAIR *vp) +{ + for (std::vector::const_iterator a = attrs.begin(); + a != attrs.end(); + ++a) { + if (strcmp(vp->name, (*a).c_str()) == 0) + return true; + } + + return false; +} + +static bool +isSecretAttributeP(uint16_t attrid, uint16_t vendor) +{ + bool bSecretAttribute = false; + + switch (vendor) { + case VENDORPEC_MS: + switch (attrid) { + case PW_MS_MPPE_SEND_KEY: + case PW_MS_MPPE_RECV_KEY: + bSecretAttribute = true; + break; + default: + break; + } + default: + break; + } + + return bSecretAttribute; +} + +static bool +isSecretAttributeP(uint32_t attribute) { + return isSecretAttributeP(ATTRID(attribute), VENDOR(attribute)); +} + +static bool +isInternalAttributeP(uint16_t attrid, uint16_t vendor) +{ + bool bInternalAttribute = false; + + /* should have been filtered */ + assert(!isSecretAttributeP(attrid, vendor)); + + switch (vendor) { + case VENDORPEC_UKERNA: + bInternalAttribute = true; + break; + default: + break; + } + + return bInternalAttribute; +} + +static bool +isInternalAttributeP(uint32_t attribute) +{ + return isInternalAttributeP(ATTRID(attribute), VENDOR(attribute)); +} + +/* + * Copy AVP list, same as paircopy except it filters out attributes + * containing keys. + */ +static VALUE_PAIR * +copyAvps(const VALUE_PAIR *src) +{ + const VALUE_PAIR *vp; + VALUE_PAIR *dst = NULL, **pDst = &dst; + + for (vp = src; vp != NULL; vp = vp->next) { + VALUE_PAIR *vpcopy; + + if (isSecretAttributeP(vp->attribute)) + continue; + + vpcopy = paircopyvp(vp); + if (vpcopy == NULL) { + pairfree(&dst); + throw new std::bad_alloc; + return NULL; + } + *pDst = vpcopy; + pDst = &vpcopy->next; + } + + return dst; } bool -gss_eap_radius_attr_provider::getAttributeTypes(gss_eap_attr_enumeration_cb addAttribute, void *data) const +gss_eap_radius_attr_provider::getAttributeTypes(gss_eap_attr_enumeration_cb addAttribute, + void *data) const { + VALUE_PAIR *vp; + std::vector seen; + + for (vp = m_vps; vp != NULL; vp = vp->next) { + gss_buffer_desc attribute; + char attrid[64]; + + /* Don't advertise attributes that are internal to the GSS-EAP mechanism */ + if (isInternalAttributeP(vp->attribute)) + continue; + + if (alreadyAddedAttributeP(seen, vp)) + continue; + + snprintf(attrid, sizeof(attrid), "%s%d", + (char *)radiusUrnPrefix.value, vp->attribute); + + attribute.value = attrid; + attribute.length = strlen(attrid); + + if (!addAttribute(m_manager, this, &attribute, data)) + return false; + + seen.push_back(std::string(vp->name)); + } + return true; } -void +uint32_t +getAttributeId(const gss_buffer_t attr) +{ + OM_uint32 tmpMinor; + gss_buffer_desc strAttr = GSS_C_EMPTY_BUFFER; + DICT_ATTR *da; + char *s; + uint32_t attrid = 0; + + if (attr->length < radiusUrnPrefix.length || + memcmp(attr->value, radiusUrnPrefix.value, radiusUrnPrefix.length) != 0) + return 0; + + /* need to duplicate because attr may not be NUL terminated */ + duplicateBuffer(*attr, &strAttr); + s = (char *)strAttr.value + radiusUrnPrefix.length; + + if (isdigit(*s)) { + attrid = strtoul(s, NULL, 10); + } else { + da = dict_attrbyname(s); + if (da != NULL) + attrid = da->attr; + } + + gss_release_buffer(&tmpMinor, &strAttr); + + return attrid; +} + +bool +gss_eap_radius_attr_provider::setAttribute(int complete GSSEAP_UNUSED, + uint32_t attrid, + const gss_buffer_t value) +{ + OM_uint32 major = GSS_S_UNAVAILABLE, minor; + + if (!isSecretAttributeP(attrid) && + !isInternalAttributeP(attrid)) { + deleteAttribute(attrid); + + major = gssEapRadiusAddAvp(&minor, &m_vps, + ATTRID(attrid), VENDOR(attrid), + value); + } + + return !GSS_ERROR(major); +} + +bool gss_eap_radius_attr_provider::setAttribute(int complete, const gss_buffer_t attr, const gss_buffer_t value) { + uint32_t attrid = getAttributeId(attr); + + if (!attrid) + return false; + + return setAttribute(complete, attrid, value); } -void -gss_eap_radius_attr_provider::deleteAttribute(const gss_buffer_t value) +bool +gss_eap_radius_attr_provider::deleteAttribute(uint32_t attrid) { + if (isSecretAttributeP(attrid) || isInternalAttributeP(attrid) || + pairfind(m_vps, attrid) == NULL) + return false; + + pairdelete(&m_vps, attrid); + + return true; +} + +bool +gss_eap_radius_attr_provider::deleteAttribute(const gss_buffer_t attr) +{ + uint32_t attrid = getAttributeId(attr); + + if (!attrid) + return false; + + return deleteAttribute(attrid); } bool @@ -83,54 +325,161 @@ gss_eap_radius_attr_provider::getAttribute(const gss_buffer_t attr, gss_buffer_t display_value, int *more) const { - return false; + uint32_t attrid; + + attrid = getAttributeId(attr); + if (!attrid) + return false; + + return getAttribute(attrid, authenticated, complete, + value, display_value, more); } bool -gss_eap_radius_attr_provider::getAttribute(unsigned int attr, +gss_eap_radius_attr_provider::getAttribute(uint32_t attrid, int *authenticated, int *complete, gss_buffer_t value, gss_buffer_t display_value, int *more) const { - return false; + VALUE_PAIR *vp; + int i = *more, count = 0; + + *more = 0; + + if (i == -1) + i = 0; + + for (vp = pairfind(m_vps, attrid); + vp != NULL; + vp = pairfind(vp->next, attrid)) { + if (count++ == i) { + if (pairfind(vp->next, attrid) != NULL) + *more = count; + break; + } + } + + if (vp == NULL && *more == 0) + return false; + + if (value != GSS_C_NO_BUFFER) { + gss_buffer_desc valueBuf; + + valueBuf.value = (void *)vp->vp_octets; + valueBuf.length = vp->length; + + duplicateBuffer(valueBuf, value); + } + + if (display_value != GSS_C_NO_BUFFER) { + char displayString[MAX_STRING_LEN]; + gss_buffer_desc displayBuf; + + displayBuf.length = vp_prints_value(displayString, + sizeof(displayString), vp, 0); + displayBuf.value = (void *)displayString; + + duplicateBuffer(displayBuf, display_value); + } + + if (authenticated != NULL) + *authenticated = m_authenticated; + if (complete != NULL) + *complete = true; + + return true; } -gss_any_t -gss_eap_radius_attr_provider::mapToAny(int authenticated, - gss_buffer_t type_id) const +bool +gss_eap_radius_attr_provider::getFragmentedAttribute(uint16_t attribute, + uint16_t vendor, + int *authenticated, + int *complete, + gss_buffer_t value) const { - return (gss_any_t)NULL; + OM_uint32 major, minor; + + major = gssEapRadiusGetAvp(&minor, m_vps, attribute, vendor, value, TRUE); + + if (authenticated != NULL) + *authenticated = m_authenticated; + if (complete != NULL) + *complete = true; + + return !GSS_ERROR(major); } -void -gss_eap_radius_attr_provider::releaseAnyNameMapping(gss_buffer_t type_id, - gss_any_t input) const +bool +gss_eap_radius_attr_provider::getAttribute(uint16_t attribute, + uint16_t vendor, + int *authenticated, + int *complete, + gss_buffer_t value, + gss_buffer_t display_value, + int *more) const { + + return getAttribute(VENDORATTR(attribute, vendor), + authenticated, complete, + value, display_value, more); } -void -gss_eap_radius_attr_provider::marshall(gss_buffer_t buffer) const +gss_any_t +gss_eap_radius_attr_provider::mapToAny(int authenticated, + gss_buffer_t type_id GSSEAP_UNUSED) const { + if (authenticated && !m_authenticated) + return (gss_any_t)NULL; + + return (gss_any_t)copyAvps(m_vps); } -bool -gss_eap_radius_attr_provider::unmarshall(const gss_eap_attr_ctx *ctx, - const gss_buffer_t buffer) +void +gss_eap_radius_attr_provider::releaseAnyNameMapping(gss_buffer_t type_id GSSEAP_UNUSED, + gss_any_t input) const { - return false; + VALUE_PAIR *vp = (VALUE_PAIR *)input; + pairfree(&vp); } bool gss_eap_radius_attr_provider::init(void) { + struct rs_context *radContext; + + gss_eap_attr_ctx::registerProvider(ATTR_TYPE_RADIUS, createAttrContext); + +#if 1 + /* + * This hack is necessary in order to force the loading of the global + * dictionary, otherwise accepting reauthentication tokens fails unless + * the acceptor has already accepted a normal authentication token. + */ + if (rs_context_create(&radContext) != 0) + return false; + + if (rs_context_read_config(radContext, RS_CONFIG_FILE) != 0) { + rs_context_destroy(radContext); + return false; + } + + if (rs_context_init_freeradius_dict(radContext, NULL)) { + rs_context_destroy(radContext); + return false; + } + + rs_context_destroy(radContext); +#endif + return true; } void gss_eap_radius_attr_provider::finalize(void) { + gss_eap_attr_ctx::unregisterProvider(ATTR_TYPE_RADIUS); } gss_eap_attr_provider * @@ -138,3 +487,348 @@ gss_eap_radius_attr_provider::createAttrContext(void) { return new gss_eap_radius_attr_provider; } + +OM_uint32 +gssEapRadiusAddAvp(OM_uint32 *minor, + VALUE_PAIR **vps, + uint16_t attribute, + uint16_t vendor, + const gss_buffer_t buffer) +{ + uint32_t attrid = VENDORATTR(vendor, attribute); + unsigned char *p = (unsigned char *)buffer->value; + size_t remain = buffer->length; + + do { + VALUE_PAIR *vp; + size_t n = remain; + + /* + * There's an extra byte of padding; RADIUS AVPs can only + * be 253 octets. + */ + if (n >= MAX_STRING_LEN) + n = MAX_STRING_LEN - 1; + + vp = paircreate(attrid, PW_TYPE_OCTETS); + if (vp == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + memcpy(vp->vp_octets, p, n); + vp->length = n; + + pairadd(vps, vp); + + p += n; + remain -= n; + } while (remain != 0); + + return GSS_S_COMPLETE; +} + +OM_uint32 +gssEapRadiusGetRawAvp(OM_uint32 *minor, + VALUE_PAIR *vps, + uint16_t attribute, + uint16_t vendor, + VALUE_PAIR **vp) +{ + uint32_t attr = VENDORATTR(vendor, attribute); + + *vp = pairfind(vps, attr); + if (*vp == NULL) { + *minor = GSSEAP_NO_SUCH_ATTR; + return GSS_S_UNAVAILABLE; + } + + return GSS_S_COMPLETE; +} + +OM_uint32 +gssEapRadiusGetAvp(OM_uint32 *minor, + VALUE_PAIR *vps, + uint16_t attribute, + uint16_t vendor, + gss_buffer_t buffer, + int concat) +{ + VALUE_PAIR *vp; + unsigned char *p; + uint32_t attr = VENDORATTR(vendor, attribute); + + buffer->length = 0; + buffer->value = NULL; + + vp = pairfind(vps, attr); + if (vp == NULL) { + *minor = GSSEAP_NO_SUCH_ATTR; + return GSS_S_UNAVAILABLE; + } + + do { + buffer->length += vp->length; + } while (concat && (vp = pairfind(vp->next, attr)) != NULL); + + buffer->value = GSSEAP_MALLOC(buffer->length); + if (buffer->value == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + p = (unsigned char *)buffer->value; + + for (vp = pairfind(vps, attr); + concat && vp != NULL; + vp = pairfind(vp->next, attr)) { + memcpy(p, vp->vp_octets, vp->length); + p += vp->length; + } + + *minor = 0; + return GSS_S_COMPLETE; +} + +OM_uint32 +gssEapRadiusFreeAvps(OM_uint32 *minor, + VALUE_PAIR **vps) +{ + pairfree(vps); + *minor = 0; + return GSS_S_COMPLETE; +} + +OM_uint32 +gssEapRadiusAttrProviderInit(OM_uint32 *minor) +{ + if (!gss_eap_radius_attr_provider::init()) { + *minor = GSSEAP_RADSEC_INIT_FAILURE; + return GSS_S_FAILURE; + } + + return GSS_S_COMPLETE; +} + +OM_uint32 +gssEapRadiusAttrProviderFinalize(OM_uint32 *minor) +{ + gss_eap_radius_attr_provider::finalize(); + + *minor = 0; + return GSS_S_COMPLETE; +} + +static json_t * +avpToJson(const VALUE_PAIR *vp) +{ + json_t *obj = json_object(); + + if (obj == NULL) { + throw new std::bad_alloc; + return NULL; + } + + /* FIXME check json_object_set_new return value */ + json_object_set_new(obj, "type", json_integer(vp->attribute)); + + assert(vp->length <= MAX_STRING_LEN); + + switch (vp->type) { + case PW_TYPE_INTEGER: + case PW_TYPE_IPADDR: + case PW_TYPE_DATE: + json_object_set_new(obj, "value", json_integer(vp->lvalue)); + break; + case PW_TYPE_STRING: + json_object_set_new(obj, "value", json_string(vp->vp_strvalue)); + break; + default: { + char *b64; + + if (base64Encode(vp->vp_octets, vp->length, &b64) < 0) { + json_decref(obj); + throw new std::bad_alloc; + } + + json_object_set_new(obj, "value", json_string(b64)); + GSSEAP_FREE(b64); + break; + } + } + + return obj; +} + +static bool +jsonToAvp(VALUE_PAIR **pVp, json_t *obj) +{ + VALUE_PAIR *vp = NULL; + DICT_ATTR *da; + uint32_t attrid; + json_t *type, *value; + + type = json_object_get(obj, "type"); + value = json_object_get(obj, "value"); + if (type == NULL || value == NULL) + goto fail; + + attrid = json_integer_value(type); + da = dict_attrbyvalue(attrid); + if (da != NULL) { + vp = pairalloc(da); + } else { + vp = paircreate(attrid, PW_TYPE_STRING); + } + if (vp == NULL) { + throw new std::bad_alloc; + goto fail; + } + + switch (vp->type) { + case PW_TYPE_INTEGER: + case PW_TYPE_IPADDR: + case PW_TYPE_DATE: + vp->length = 4; + vp->lvalue = json_integer_value(value); + break; + case PW_TYPE_STRING: { + const char *str = json_string_value(value); + size_t len; + + if (str == NULL || (len = strlen(str)) >= MAX_STRING_LEN) + goto fail; + + vp->length = len; + memcpy(vp->vp_strvalue, str, len + 1); + break; + } + case PW_TYPE_OCTETS: + default: { + const char *str = json_string_value(value); + int len; + + /* this optimization requires base64Decode only understand packed encoding */ + if (str == NULL || + strlen(str) >= BASE64_EXPAND(MAX_STRING_LEN)) + goto fail; + + len = base64Decode(str, vp->vp_octets); + if (len < 0) + goto fail; + + vp->length = len; + vp->vp_octets[len] = '\0'; + break; + } + } + + *pVp = vp; + + return true; + +fail: + if (vp != NULL) + pairbasicfree(vp); + *pVp = NULL; + return false; +} + +const char * +gss_eap_radius_attr_provider::name(void) const +{ + return "radius"; +} + +bool +gss_eap_radius_attr_provider::initWithJsonObject(const gss_eap_attr_ctx *ctx, + json_t *obj) +{ + VALUE_PAIR **pNext = &m_vps; + json_t *attrs; + size_t i; + + if (!gss_eap_attr_provider::initWithJsonObject(ctx, obj)) + return false; + + attrs = json_object_get(obj, "attributes"); + + for (i = 0; i < json_array_size(attrs); i++) { + json_t *attr = json_array_get(attrs, i); + VALUE_PAIR *vp; + + if (!jsonToAvp(&vp, attr)) + return false; + + *pNext = vp; + pNext = &vp->next; + } + + return true; +} + +const char * +gss_eap_radius_attr_provider::prefix(void) const +{ + return "urn:ietf:params:gss-eap:radius-avp"; +} + +json_t * +gss_eap_radius_attr_provider::jsonRepresentation(void) const +{ + json_t *obj, *attrs; + + attrs = json_array(); + if (attrs == NULL) + throw new std::bad_alloc; + + for (VALUE_PAIR *vp = m_vps; vp != NULL; vp = vp->next) { + json_t *attr = avpToJson(vp); + json_array_append_new(attrs, attr); + } + + obj = json_object(); + if (obj == NULL) { + json_decref(attrs); + throw new std::bad_alloc; + } + + json_object_set_new(obj, "attributes", attrs); + + return obj; +} + +time_t +gss_eap_radius_attr_provider::getExpiryTime(void) const +{ + VALUE_PAIR *vp; + + vp = pairfind(m_vps, PW_SESSION_TIMEOUT); + if (vp == NULL || vp->lvalue == 0) + return 0; + + return time(NULL) + vp->lvalue; +} + +OM_uint32 +gssEapRadiusMapError(OM_uint32 *minor, + struct rs_error *err) +{ + int code; + + assert(err != NULL); + + code = rs_err_code(err, 0); + + if (code == RSE_OK) { + *minor = 0; + return GSS_S_COMPLETE; + } + + *minor = ERROR_TABLE_BASE_rse + code; + + gssEapSaveStatusInfo(*minor, "%s", rs_err_msg(err)); + rs_err_free(err); + + return GSS_S_FAILURE; +}