Add dhcp_options: xlat to decode DHCP options packed into RADIUS attributes
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 1 Nov 2012 14:38:51 +0000 (14:38 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 1 Nov 2012 14:38:51 +0000 (14:38 +0000)
src/include/dhcp.h
src/lib/dhcp.c
src/main/xlat.c

index 6ccc7d9..5ee38eb 100644 (file)
@@ -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);
 
 /*
index 47efb7c..5799f96 100644 (file)
@@ -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;
index dddc64c..7e0b8c1 100644 (file)
@@ -28,6 +28,7 @@ RCSID("$Id$")
 #include       <freeradius-devel/radiusd.h>
 #include       <freeradius-devel/rad_assert.h>
 #include       <freeradius-devel/base64.h>
+#include       <freeradius-devel/dhcp.h>
 
 #include       <ctype.h>
 
@@ -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.