From: Arran Cudbard-Bell Date: Thu, 1 Nov 2012 14:38:51 +0000 (+0000) Subject: Add dhcp_options: xlat to decode DHCP options packed into RADIUS attributes X-Git-Tag: release_2_2_1~230 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=024d1ee7da8eb5c6bd39bef82c9619fa532ce9d4;p=thirdparty%2Ffreeradius-server.git Add dhcp_options: xlat to decode DHCP options packed into RADIUS attributes --- diff --git a/src/include/dhcp.h b/src/include/dhcp.h index 6ccc7d91798..5ee38eb9300 100644 --- a/src/include/dhcp.h +++ b/src/include/dhcp.h @@ -41,6 +41,7 @@ int fr_dhcp_send(RADIUS_PACKET *packet); int fr_dhcp_add_arp_entry(int fd, const char *interface, VALUE_PAIR *hwvp, VALUE_PAIR *clvp); int fr_dhcp_encode(RADIUS_PACKET *packet); +ssize_t fr_dhcp_decode_options(uint8_t *data, size_t len, VALUE_PAIR **head); int fr_dhcp_decode(RADIUS_PACKET *packet); /* diff --git a/src/lib/dhcp.c b/src/lib/dhcp.c index 47efb7c0f78..5799f969b0e 100644 --- a/src/lib/dhcp.c +++ b/src/lib/dhcp.c @@ -561,10 +561,117 @@ static int fr_dhcp_attr2vp(VALUE_PAIR *vp, const uint8_t *p, size_t alen) return 0; } -int fr_dhcp_decode(RADIUS_PACKET *packet) +ssize_t fr_dhcp_decode_options(uint8_t *data, size_t len, VALUE_PAIR **head) { int i; + VALUE_PAIR *vp, **tail; uint8_t *p, *next; + next = data; + + *head = NULL; + tail = head; + while (next < (data + len)) { + int num_entries, alen; + DICT_ATTR *da; + + p = next; + + if (*p == 0) break; + if (*p == 255) break; /* end of options signifier */ + if ((p + 2) > (data + len)) break; + + next = p + 2 + p[1]; + + if (p[1] >= 253) { + fr_strerror_printf("Attribute too long %u %u", + p[0], p[1]); + continue; + } + + da = dict_attrbyvalue(DHCP2ATTR(p[0])); + if (!da) { + fr_strerror_printf("Attribute not in our dictionary: %u", + p[0]); + continue; + } + + vp = NULL; + num_entries = 1; + alen = p[1]; + p += 2; + + /* + * Could be an array of bytes, integers, etc. + */ + if (da->flags.array) { + switch (da->type) { + case PW_TYPE_BYTE: + num_entries = alen; + alen = 1; + break; + + case PW_TYPE_SHORT: /* ignore any trailing data */ + num_entries = alen >> 1; + alen = 2; + break; + + case PW_TYPE_IPADDR: + case PW_TYPE_INTEGER: + case PW_TYPE_DATE: /* ignore any trailing data */ + num_entries = alen >> 2; + alen = 4; + break; + + default: + + break; /* really an internal sanity failure */ + } + } + + /* + * Loop over all of the entries, building VPs + */ + for (i = 0; i < num_entries; i++) { + vp = pairmake(da->name, NULL, T_OP_ADD); + if (!vp) { + fr_strerror_printf("Cannot build attribute %s", + fr_strerror()); + pairfree(head); + return -1; + } + + /* + * Hack for ease of use. + */ + if ((da->attr == DHCP2ATTR(0x3d)) && + !da->flags.array && + (alen == 7) && (*p == 1) && (num_entries == 1)) { + vp->type = PW_TYPE_ETHERNET; + memcpy(vp->vp_octets, p + 1, 6); + vp->length = alen; + + } else if (fr_dhcp_attr2vp(vp, p, alen) < 0) { + pairfree(&vp); + pairfree(head); + return -1; + } + + *tail = vp; + while (*tail) { + debug_pair(*tail); + tail = &(*tail)->next; + } + p += alen; + } /* loop over array entries */ + } /* loop over the entire packet */ + + return next - data; +} + +int fr_dhcp_decode(RADIUS_PACKET *packet) +{ + unsigned int i; + uint8_t *p; uint32_t giaddr; VALUE_PAIR *head, *vp, **tail; VALUE_PAIR *maxms, *mtu; @@ -663,106 +770,17 @@ int fr_dhcp_decode(RADIUS_PACKET *packet) /* * Loop over the options. */ - next = packet->data + 240; - + /* + * Nothing uses tail after this call, if it does in the future + * it'll need to find the new tail... * FIXME: This should also check sname && file fields. * See the dhcp_get_option() function above. */ - while (next < (packet->data + packet->data_len)) { - int num_entries, alen; - DICT_ATTR *da; - - p = next; - - if (*p == 0) break; - if (*p == 255) break; /* end of options signifier */ - if ((p + 2) > (packet->data + packet->data_len)) break; - - next = p + 2 + p[1]; - - if (p[1] >= 253) { - fr_strerror_printf("Attribute too long %u %u", - p[0], p[1]); - continue; - } - - da = dict_attrbyvalue(DHCP2ATTR(p[0])); - if (!da) { - fr_strerror_printf("Attribute not in our dictionary: %u", - p[0]); - continue; - } - - vp = NULL; - num_entries = 1; - alen = p[1]; - p += 2; - - /* - * Could be an array of bytes, integers, etc. - */ - if (da->flags.array) { - switch (da->type) { - case PW_TYPE_BYTE: - num_entries = alen; - alen = 1; - break; - - case PW_TYPE_SHORT: /* ignore any trailing data */ - num_entries = alen >> 1; - alen = 2; - break; - - case PW_TYPE_IPADDR: - case PW_TYPE_INTEGER: - case PW_TYPE_DATE: /* ignore any trailing data */ - num_entries = alen >> 2; - alen = 4; - break; - - default: - - break; /* really an internal sanity failure */ - } - } - - /* - * Loop over all of the entries, building VPs - */ - for (i = 0; i < num_entries; i++) { - vp = pairmake(da->name, NULL, T_OP_EQ); - if (!vp) { - fr_strerror_printf("Cannot build attribute %s", - fr_strerror()); - pairfree(&head); - return -1; - } - - /* - * Hack for ease of use. - */ - if ((da->attr == DHCP2ATTR(0x3d)) && - !da->flags.array && - (alen == 7) && (*p == 1) && (num_entries == 1)) { - vp->type = PW_TYPE_ETHERNET; - memcpy(vp->vp_octets, p + 1, 6); - vp->length = alen; - - } else if (fr_dhcp_attr2vp(vp, p, alen) < 0) { - pairfree(&vp); - pairfree(&head); - return -1; - } - - *tail = vp; - while (*tail) { - debug_pair(*tail); - tail = &(*tail)->next; - } - p += alen; - } /* loop over array entries */ - } /* loop over the entire packet */ + if (fr_dhcp_decode_options(packet->data + 240, packet->data_len - 240, + tail) < 0) { + return -1; + } /* * If DHCP request, set ciaddr to zero. @@ -988,7 +1006,7 @@ static VALUE_PAIR *fr_dhcp_vp2suboption(VALUE_PAIR *vps) int fr_dhcp_encode(RADIUS_PACKET *packet) { - int i, num_vps; + unsigned int i, num_vps; uint8_t *p; VALUE_PAIR *vp; uint32_t lvalue, mms; diff --git a/src/main/xlat.c b/src/main/xlat.c index dddc64cea81..7e0b8c10eef 100644 --- a/src/main/xlat.c +++ b/src/main/xlat.c @@ -28,6 +28,7 @@ RCSID("$Id$") #include #include #include +#include #include @@ -614,6 +615,45 @@ static size_t xlat_base64(UNUSED void *instance, REQUEST *request, return enc; } +#ifdef WITH_DHCP +static size_t xlat_dhcp_options(UNUSED void *instance, REQUEST *request, + char *fmt, char *out, size_t outlen, + UNUSED RADIUS_ESCAPE_STRING func) +{ + VALUE_PAIR *vp, *head = NULL, *next; + int decoded = 0; + + while (isspace((int) *fmt)) fmt++; + + if (!radius_get_vp(request, fmt, &vp) || !vp) { + *out = '\0'; + + return 0; + } + + if ((fr_dhcp_decode_options(vp->vp_octets, vp->length, &head) < 0) || + (head == NULL)) { + RDEBUG("WARNING: DHCP option decoding failed"); + goto fail; + } + + next = head; + + do { + next = next->next; + decoded++; + } while (next); + + pairmove(&(request->packet->vps), &head); + + fail: + + snprintf(out, outlen, "%i", decoded); + + return strlen(out); +} +#endif + #ifdef HAVE_REGEX_H /* * Pull %{0} to %{8} out of the packet. @@ -780,6 +820,13 @@ int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance) rad_assert(c != NULL); c->internal = TRUE; +#ifdef WITH_DHCP + xlat_register("dhcp_options", xlat_dhcp_options, ""); + c = xlat_find("dhcp_options"); + rad_assert(c != NULL); + c->internal = TRUE; +#endif + #ifdef HAVE_REGEX_H /* * Register xlat's for regexes.