Add support for Vendor Specific Suboptions (RFC 4243)
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 13 Jun 2014 10:12:04 +0000 (11:12 +0100)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 24 Jun 2014 20:44:22 +0000 (21:44 +0100)
Add support for array type suboptions

Allow terminating suboption and padding suboption

Add dictionary attributes for a many more DHCP RFCs

share/dictionary.dhcp
share/dictionary.rfc4679
src/include/dhcp.h
src/lib/dict.c
src/modules/proto_dhcp/dhcp.c
src/modules/proto_dhcp/rlm_dhcp.c

index 7012311..749e094 100644 (file)
@@ -33,6 +33,10 @@ VENDOR               DHCP                            54      format=2,1
 BEGIN-VENDOR   DHCP
 
 ATTRIBUTE      DHCP-Opcode                             256     byte
+
+VALUE  DHCP-Opcode                     Client-Message          1
+VALUE  DHCP-Opcode                     Server-Message          2
+
 ATTRIBUTE      DHCP-Hardware-Type                      257     byte
 ATTRIBUTE      DHCP-Hardware-Address-Length            258     byte
 ATTRIBUTE      DHCP-Hop-Count                          259     byte
@@ -126,7 +130,7 @@ ATTRIBUTE   DHCP-IP-Forward-Enable                  19      byte
 ATTRIBUTE      DHCP-Source-Route-Enable                20      byte
 # Routing Policy Filters
 ATTRIBUTE      DHCP-Policy-Filter                      21      octets
-ATTRIBUTE      DHCP-Max-Datagram-Reassembly-Sz         22      short
+ATTRIBUTE      DHCP-Max-Datagram-Reassembly-Size       22      short
 ATTRIBUTE      DHCP-Default-IP-TTL                     23      octets
 ATTRIBUTE      DHCP-Path-MTU-Aging-Timeout             24      integer
 ATTRIBUTE      DHCP-Path-MTU-Plateau-Table             25      short array
@@ -162,6 +166,24 @@ ATTRIBUTE  DHCP-IP-Address-Lease-Time              51      integer
 # Overload "sname" or "file"
 ATTRIBUTE      DHCP-Overload                           52      byte
 ATTRIBUTE      DHCP-Message-Type                       53      byte
+
+VALUE  DHCP-Message-Type               DHCP-Do-Not-Respond     0
+VALUE  DHCP-Message-Type               DHCP-Discover           1
+VALUE  DHCP-Message-Type               DHCP-Offer              2
+VALUE  DHCP-Message-Type               DHCP-Request            3
+VALUE  DHCP-Message-Type               DHCP-Decline            4
+VALUE  DHCP-Message-Type               DHCP-Ack                5
+VALUE  DHCP-Message-Type               DHCP-NAK                6
+VALUE  DHCP-Message-Type               DHCP-Release            7
+VALUE  DHCP-Message-Type               DHCP-Inform             8
+VALUE  DHCP-Message-Type               DHCP-Force-Renew        9
+VALUE  DHCP-Message-Type               DHCP-Lease-Query        10
+VALUE  DHCP-Message-Type               DHCP-Lease-Unassigned   11
+VALUE  DHCP-Message-Type               DHCP-Lease-Unknown      12
+VALUE  DHCP-Message-Type               DHCP-Lease-Active       13
+VALUE  DHCP-Message-Type               DHCP-Bulk-Lease-Query   14
+VALUE  DHCP-Message-Type               DHCP-Lease-Query-Done   15
+
 ATTRIBUTE      DHCP-DHCP-Server-Identifier             54      ipaddr
 
 # Array of 1-byte numbers indicating which options the client
@@ -175,7 +197,7 @@ ATTRIBUTE   DHCP-Vendor-Class-Identifier            60      octets
 
 # Client Identifier
 # First octet MAY be DHCP-Hardware-Type, rest are type-specific data,
-# e.g. MAC address. It's up to the administrator to make sense of 
+# e.g. MAC address. It's up to the administrator to make sense of
 # the value. We can't do anything more in the parser.
 ATTRIBUTE      DHCP-Client-Identifier                  61      octets
 ATTRIBUTE      DHCP-Netware-Domain-Name                62      octets
@@ -223,7 +245,7 @@ ATTRIBUTE   DHCP-RADIUS-Attributes                  82.7    octets
 
 # Horribly complicated
 ATTRIBUTE      DHCP-Authentication-Information         82.8    octets
-ATTRIBUTE      DHCP-Vendor-Specific-Information        82.9    octets
+ATTRIBUTE      DHCP-Vendor-Specific-Information        82.9    vsa
 ATTRIBUTE      DHCP-Relay-Agent-Flags                  82.10   byte
 ATTRIBUTE      DHCP-Server-Identifier-Override         82.11   ipaddr
 
@@ -235,6 +257,11 @@ ATTRIBUTE  DHCP-NDS-Servers                        85      octets
 ATTRIBUTE      DHCP-NDS-Tree-Name                      86      octets
 # Novell Directory Services
 ATTRIBUTE      DHCP-NDS-Context                        87      octets
+
+# RFC 4280 - Broadcast and Multicast Control Servers
+ATTRIBUTE      DHCP-BCMS-Server-IPv4-FQDN              88      string array
+ATTRIBUTE      DHCP-BCMS-Server-IPv4-Address           89      ipaddr array
+
 # Authentication
 ATTRIBUTE      DHCP-Authentication                     90      octets
 
@@ -251,6 +278,14 @@ ATTRIBUTE  DHCP-LDAP                               95      octets
 ATTRIBUTE      DHCP-UUID/GUID                          97      octets
 # Open Group's User Authentication
 ATTRIBUTE      DHCP-User-Auth                          98      octets
+
+# RFC 4776 - Option for Civic Addresses Configuration Information
+ATTRIBUTE      DHCP-GeoConf-Civic                      99      octets
+
+# RFC 4833 - Timezone Options for DHCP
+ATTRIBUTE      DHCP-Timezone-Posix                     100     string
+ATTRIBUTE      DHCP-Timezone-Database                  101     string
+
 # NetInfo Parent-Server Address
 ATTRIBUTE      DHCP-Netinfo-Address                    112     octets
 # NetInfo Parent-Server Tag
@@ -282,44 +317,130 @@ ATTRIBUTE        DHCP-GeoConf-Option                     123     octets
 ATTRIBUTE      DHCP-V-I-Vendor-Class                   124     octets
 # Vendor-Specific
 ATTRIBUTE      DHCP-V-I-Vendor-Specific                125     octets # tlv
+
 ATTRIBUTE      DHCP-Etherboot                          128     ether
 # (for IP Phone software load)
-ATTRIBUTE      DHCP-TFTP-Server-IP-Address             128     octets
 
+# RFC 4578 - Options for the Intel Preboot eXecution Environment
+ATTRIBUTE      DHCP-TFTP-Server-IP-Address             128     octets
 ATTRIBUTE      DHCP-Call-Server-IP-address             129     octets
-
 ATTRIBUTE      DHCP-Ethernet-Interface                 130     octets
-
 ATTRIBUTE      DHCP-Vendor-Discrimination-Str          130     octets
-
 ATTRIBUTE      DHCP-Remote-Stats-Svr-IP-Address        131     octets
-
-ATTRIBUTE      DHCP-IEEE-802.1Q-L2-Priority            132     octets
-
-ATTRIBUTE      DHCP-IEEE-802.1P-VLAN-ID                133     octets
-
+ATTRIBUTE      DHCP-IEEE-802.1P-VLAN-ID                132     octets
+ATTRIBUTE      DHCP-IEEE-802.1Q-L2-Priority            133     octets
 ATTRIBUTE      DHCP-Diffserv-Code-Point                134     octets
-
 ATTRIBUTE      DHCP-HTTP-Proxy                         135     octets
 
-ATTRIBUTE      DHCP-Cisco-TFTP-Server-IP-Addresses     150     ipaddr array
+# RFC 5192 - PANA Authentication Agent
+ATTRIBUTE      DHCP-PANA-Agent                         136     ipaddr array
+
+# RFC 5223 - Discovering Location-to-Service Translation (LoST)
+ATTRIBUTE      DHCP-LoST-Server                        137     octets
+
+# RFC 5417 - CAPWAP Access Controller DHCP Option
+ATTRIBUTE      DHCP-CAPWAP-AC-IPv4-Address             138     ipaddr array
+
+# RFC 5678 - Options for IEEE 802.21 Mobility Services (MoS)
+ATTRIBUTE      DHCP-MoS-IPv4-Address                   139     tlv
+ATTRIBUTE      DHCP-MoS-IPv4-Address-IS                139.1   ipaddr array
+ATTRIBUTE      DHCP-MoS-IPv4-Address-CS                139.2   ipaddr array
+ATTRIBUTE      DHCP-MoS-IPv4-Address-ES                139.3   ipaddr array
+
+ATTRIBUTE      DHCP-MoS-IPv4-FQDN                      140     tlv
+ATTRIBUTE      DHCP-MoS-IPv4-FQDN-IS                   140.1   string array
+ATTRIBUTE      DHCP-MoS-IPv4-FQDN-CS                   140.2   string array
+ATTRIBUTE      DHCP-MoS-IPv4-FQDN-ES                   140.3   string array
+
+# RFC 6011 - SIP UA Configuration Service Domains
+ATTRIBUTE      DHCP-SIP-UA-Configuration-Service-Domains 141   string
+
+# RFC 6153 - Access Network Discovery and Selection Function (ANDSF)
+ATTRIBUTE      DHCP-ANDSF-IPv4-Address                 142     ipaddr array
+ATTRIBUTE      DHCP-ANDSF-IPv6-Address                 143     ipv6addr array
+
+# 144 - 149 unused
+
+ATTRIBUTE      DHCP-TFTP-Server-IPv4-Address           150     ipaddr array
+
+# RFC 6926 - Bulk Lease Query
+ATTRIBUTE      DHCP-Query-Status-Code                  151     octets
+ATTRIBUTE      DHCP-Query-Server-Base-Time             152     date
+ATTRIBUTE      DHCP-Query-Start-Time-Of-State          153     integer
+ATTRIBUTE      DHCP-Query-Start-Time                   154     date
+ATTRIBUTE      DHCP-Query-End-Time                     155     date
+ATTRIBUTE      DHCP-State                              156     byte
+
+VALUE  DHCP-State                      Available               1
+VALUE  DHCP-State                      Active                  2
+VALUE  DHCP-State                      Expired                 3
+VALUE  DHCP-State                      Released                4
+VALUE  DHCP-State                      Abandoned               5
+VALUE  DHCP-State                      Reset                   6
+VALUE  DHCP-State                      Remote                  7
+VALUE  DHCP-State                      Transitioning           8
+
+ATTRIBUTE      DHCP-Data-Source                        157     byte
+
+# RFC draft-ietf-pcp-dhcp-13
+ATTRIBUTE      DHCP-PCP-IPv4-Server-Address            158     octets  # Complex format (not just ipaddr array)
+
+# RFC 3942 - 159-174 - Unassigned
+# RFC 3942 - 178-207 - Unassigned
+
+# RFC 5071 - PXELINUX
+ATTRIBUTE      DHCP-PXELINUX-Magic                     208     octets
+ATTRIBUTE      DHCP-Packet-Format                      209     string
+ATTRIBUTE      DHCP-Path-Prefix                        210     string
+ATTRIBUTE      DHCP-Reboot-Time                        211     date
+
+# RFC 5969 - IPv6 Rapid Deployment on IPv4 Infrastructures (6rd)
+ATTRIBUTE      DHCP-6RD                                212     octets
+
+# RFC 5986 - Discovering the Local Location Information Server (LIS)
+ATTRIBUTE      DHCP-Access-Network-Domain-Name         213     string array
+
+# RFC 3942 - 214-219 - Unassigned
+
+# RFC 6656 - Subnet Allocation Option
+ATTRIBUTE      DHCP-Virtual-Subnet-Allocation          220     octets  # Complex format not just tlv
+ATTRIBUTE      DHCP-Virtual-Subnet-Selection           221     octets  # Complex format not just tlv
+
+# RFC 3942 - 224-253 - Site Specific
+ATTRIBUTE      DHCP-Site-specific-0                    224     octets
+ATTRIBUTE      DHCP-Site-specific-1                    225     octets
+ATTRIBUTE      DHCP-Site-specific-2                    226     octets
+ATTRIBUTE      DHCP-Site-specific-3                    227     octets
+ATTRIBUTE      DHCP-Site-specific-4                    228     octets
+ATTRIBUTE      DHCP-Site-specific-5                    229     octets
+ATTRIBUTE      DHCP-Site-specific-6                    230     octets
+ATTRIBUTE      DHCP-Site-specific-7                    231     octets
+ATTRIBUTE      DHCP-Site-specific-8                    232     octets
+ATTRIBUTE      DHCP-Site-specific-9                    233     octets
+ATTRIBUTE      DHCP-Site-specific-10                   234     octets
+ATTRIBUTE      DHCP-Site-specific-11                   235     octets
+ATTRIBUTE      DHCP-Site-specific-12                   236     octets
+ATTRIBUTE      DHCP-Site-specific-13                   237     octets
+ATTRIBUTE      DHCP-Site-specific-14                   238     octets
+ATTRIBUTE      DHCP-Site-specific-15                   239     octets
+ATTRIBUTE      DHCP-Site-specific-16                   240     octets
+ATTRIBUTE      DHCP-Site-specific-17                   241     octets
+ATTRIBUTE      DHCP-Site-specific-18                   242     octets
+ATTRIBUTE      DHCP-Site-specific-19                   243     octets
+ATTRIBUTE      DHCP-Site-specific-20                   244     octets
+ATTRIBUTE      DHCP-Site-specific-21                   245     octets
+ATTRIBUTE      DHCP-Site-specific-22                   246     octets
+ATTRIBUTE      DHCP-Site-specific-23                   247     octets
+ATTRIBUTE      DHCP-Site-specific-24                   248     octets
+ATTRIBUTE      DHCP-Site-specific-25                   249     octets
+ATTRIBUTE      DHCP-Site-specific-26                   250     octets
+ATTRIBUTE      DHCP-Site-specific-27                   251     octets
+ATTRIBUTE      DHCP-Site-specific-28                   252     octets
+ATTRIBUTE      DHCP-Site-specific-29                   253     octets
+ATTRIBUTE      DHCP-Site-specific-30                   253     octets
 
 ATTRIBUTE      DHCP-End-Of-Options                     255     byte
 
-VALUE  DHCP-Opcode                     Client-Message          1
-VALUE  DHCP-Opcode                     Server-Message          2
-
-VALUE  DHCP-Message-Type               DHCP-Do-Not-Respond     0
-VALUE  DHCP-Message-Type               DHCP-Discover           1
-VALUE  DHCP-Message-Type               DHCP-Offer              2
-VALUE  DHCP-Message-Type               DHCP-Request            3
-VALUE  DHCP-Message-Type               DHCP-Decline            4
-VALUE  DHCP-Message-Type               DHCP-Ack                5
-VALUE  DHCP-Message-Type               DHCP-NAK                6
-VALUE  DHCP-Message-Type               DHCP-Release            7
-VALUE  DHCP-Message-Type               DHCP-Inform             8
-VALUE  DHCP-Message-Type               DHCP-Force-Renew        9
-
 VALUE  DHCP-Parameter-Request-List     DHCP-Subnet-Mask        1
 VALUE  DHCP-Parameter-Request-List     DHCP-Time-Offset        2
 VALUE  DHCP-Parameter-Request-List     DHCP-Router-Address     3
index bb71af8..d5661dc 100644 (file)
@@ -12,6 +12,12 @@ VENDOR               ADSL-Forum                      3561
 BEGIN-VENDOR   ADSL-Forum
 
 #
+#  Glue attribute to allow decoding of ADSL-Form vendor specific
+#  DHCP options.
+#
+ATTRIBUTE      ADSL-Forum-DHCP-Vendor-Specific         0       tlv
+
+#
 #  The first two attributes are prefixed with "ADSL-" because of
 #  conflicting names in dictionary.redback.
 #
index 654f135..8176e77 100644 (file)
@@ -42,8 +42,7 @@ int fr_dhcp_add_arp_entry(int fd, char const *interface, VALUE_PAIR *hwvp, VALUE
 int8_t fr_dhcp_attr_cmp(void const *a, void const *b);
 ssize_t fr_dhcp_encode_option(uint8_t *out, size_t outlen, TALLOC_CTX *ctx, vp_cursor_t *cursor);
 int fr_dhcp_encode(RADIUS_PACKET *packet);
-ssize_t fr_dhcp_decode_options(RADIUS_PACKET *packet,
-                              uint8_t const *data, size_t len, VALUE_PAIR **head);
+ssize_t fr_dhcp_decode_options(VALUE_PAIR **out, TALLOC_CTX *ctx, uint8_t const *data, size_t len);
 int fr_dhcp_decode(RADIUS_PACKET *packet);
 
 /*
index 0d87b47..1738237 100644 (file)
@@ -1532,15 +1532,19 @@ static int process_attribute(char const* fn, int const line,
 
                                switch (type) {
                                        case PW_TYPE_IPV4_ADDR:
+                                       case PW_TYPE_IPV6_ADDR:
                                        case PW_TYPE_BYTE:
                                        case PW_TYPE_SHORT:
                                        case PW_TYPE_INTEGER:
                                        case PW_TYPE_DATE:
+                                       case PW_TYPE_STRING:
                                                break;
 
                                        default:
-                                               fr_strerror_printf( "dict_init: %s[%d] Only IP addresses can have the \"array\" flag set.",
-                                                           fn, line);
+                                               fr_strerror_printf( "dict_init: %s[%d] \"%s\" type cannot have the "
+                                                                  "\"array\" flag set",
+                                                                  fn, line,
+                                                                  fr_int2str(dict_attr_types, type, "<UNKNOWN>"));
                                                return -1;
                                }
 
index eec47f1..9ad51fb 100644 (file)
@@ -459,25 +459,213 @@ int fr_dhcp_send(RADIUS_PACKET *packet)
 #endif
 }
 
-static int fr_dhcp_attr2vp(RADIUS_PACKET *packet, VALUE_PAIR *vp, uint8_t const *p, size_t alen);
+static int fr_dhcp_attr2vp(VALUE_PAIR **vp_p, TALLOC_CTX *ctx, uint8_t const *p, size_t alen);
 
-static int fr_dhcp_decode_suboption(RADIUS_PACKET *packet, VALUE_PAIR *tlv, uint8_t const *data, size_t data_len)
+/** Returns the number of array members for arrays with fixed element sizes
+ *
+ */
+static int fr_dhcp_array_members(size_t *len, DICT_ATTR const *da)
+{
+       int num_entries = 1;
+
+       /*
+        *      Could be an array of bytes, integers, etc.
+        */
+       if (da->flags.array) switch (da->type) {
+       case PW_TYPE_BYTE:
+               num_entries = *len;
+               *len = 1;
+               break;
+
+       case PW_TYPE_SHORT: /* ignore any trailing data */
+               num_entries = *len >> 1;
+               *len = 2;
+               break;
+
+       case PW_TYPE_IPV4_ADDR:
+       case PW_TYPE_INTEGER:
+       case PW_TYPE_DATE: /* ignore any trailing data */
+               num_entries = *len >> 2;
+               *len = 4;
+               break;
+
+       case PW_TYPE_IPV6_ADDR:
+               num_entries = *len >> 4;
+               *len = 16;
+               break;
+
+       default:
+               break;
+       }
+
+       return num_entries;
+}
+
+/** RFC 4243 Vendor Specific Suboptions
+ *
+ * Vendor specific suboptions are in the format.
+ @verbatim
+      0                   1                   2                   3
+      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     |                     Enterprise Number 0                       |
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     |    Len 0      |                                               /
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     /                      Suboption Data 0                         /
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     |                     Enterprise Number n                       |
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     |    Len n      |                                               /
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     /                      Suboption Data n                         /
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ @endverbatim
+ *
+ * So although the vendor is identified, the format of the data isn't specified
+ * so we can't actually resolve the suboption to an attribute.
+ *
+ * To get around that, we create an attribute with a vendor matching the
+ * enterprise number, and attr 0.
+ *
+ * How the suboption data is then processed, is dependent on what type
+ * \<iana\>.0 is defined as in the dictionary.
+ *
+ * @param[in,out] tlv to decode. *tlv will be set to the head of the list of suboptions and original will be freed.
+ * @param[in] ctx context to alloc new attributes in.
+ * @param[in] data to parse.
+ * @param[in] len length of data to parse.
+ */
+static int fr_dhcp_decode_vsa(VALUE_PAIR **tlv, TALLOC_CTX *ctx, uint8_t const *data, size_t len)
+{
+       uint8_t const *p, *q;
+       vp_cursor_t cursor;
+
+       VALUE_PAIR *head;
+
+       if (len < 4) goto malformed;
+
+       p = data;
+       q = p + len;
+       while (p < q) {
+               if (p + 5 >= q) goto malformed;
+               p += sizeof(uint32_t);
+               p += p[0];
+
+               /*
+                *      Check if length > the length of the buffer we have left
+                */
+               if (p >= q) goto malformed;
+               p++;
+       }
+
+       head = NULL;
+       fr_cursor_init(&cursor, &head);
+
+       /*
+        *      Now we know its sane, start decoding!
+        */
+       p = data;
+       while (p < q) {
+               uint32_t vendor;
+               DICT_ATTR const *da;
+               VALUE_PAIR *vp;
+
+               vendor = ntohl(*((uint32_t const *) p));
+               /*
+                *      This is pretty much all we can do.  RFC 4243 doesn't specify
+                *      an attribute field, so it's up to vendors to figure out how
+                *      they want to encode their attributes.
+                */
+               da = dict_attrbyvalue(0, vendor);
+               if (!da) {
+                       da = dict_attrunknown(0, vendor, true);
+                       if (!da) {
+                               pairfree(&head);
+                               goto malformed;
+                       }
+               }
+               vp = pairalloc(ctx, da);
+               if (!vp) {
+                       pairfree(&head);
+                       return -1;
+               }
+               vp->op = T_OP_ADD;
+
+               if (fr_dhcp_attr2vp(&vp, ctx, p + 5, p[4]) < 0) {
+                       pairfree(&head);
+                       return -1;
+               }
+
+               fr_cursor_insert(&cursor, vp);
+
+               p += 4 + 1 + p[4];      /* vendor id (4) + len (1) + vsa len (n) */
+       }
+
+       /*
+        *      The caller allocated TLV, if decoding it generated additional
+        *      attributes, we now need to free it, and write the HEAD of our
+        *      new list of attributes in its place.
+        */
+       if (head) {
+               vp_cursor_t tlv_cursor;
+
+               /*
+                *      Free the old TLV attribute
+                */
+               TALLOC_FREE(*tlv);
+
+               /*
+                *      Cursor not necessary but means we don't have to set
+                *      ->next directly.
+                */
+               fr_cursor_init(&tlv_cursor, tlv);
+               fr_cursor_insert(&tlv_cursor, head);
+       }
+
+       return 0;
+
+malformed:
+       (*tlv)->vp_tlv = talloc_array(*tlv, uint8_t, len);
+       if (!(*tlv)->vp_tlv) {
+               fr_strerror_printf("No memory");
+               return -1;
+       }
+       memcpy((*tlv)->vp_tlv, data, len);
+       (*tlv)->length = len;
+
+       return 0;
+}
+
+/** Decode DHCP suboptions
+ *
+ * @param[in,out] tlv to decode. *tlv will be set to the head of the list of suboptions and original will be freed.
+ * @param[in] ctx context to alloc new attributes in.
+ * @param[in] data to parse.
+ * @param[in] len length of data to parse.
+ */
+static int fr_dhcp_decode_suboption(VALUE_PAIR **tlv, TALLOC_CTX *ctx, uint8_t const *data, size_t len)
 {
        uint8_t const *p, *q;
        VALUE_PAIR *head, *vp;
        vp_cursor_t cursor;
 
        /*
+        *      TLV must already point to a VALUE_PAIR.
+        */
+       VERIFY_VP(*tlv);
+
+       /*
         *      Take a pass at parsing it.
         */
        p = data;
-       q = data + data_len;
+       q = data + len;
        while (p < q) {
                /*
-                *      The RFC 3046 is very specific about not allowing termination
-                *      with a 255 sub-option. But vendors are stupid, so allow it
-                *      and the 0 padding sub-option.
-                *      This requirement really should be a SHOULD anyway...
+                *      RFC 3046 is very specific about not allowing termination
+                *      with a 255 sub-option. But it's required for decoding
+                *      option 43, and vendors will probably screw it up
+                *      anyway.
                 */
                if (*p == 0) {
                        p++;
@@ -509,108 +697,181 @@ static int fr_dhcp_decode_suboption(RADIUS_PACKET *packet, VALUE_PAIR *tlv, uint
 
        p = data;
        while (p < q) {
-               vp = paircreate(packet, tlv->da->attr | (p[0] << 8), DHCP_MAGIC_VENDOR);
-               if (!vp) {
-                       pairfree(&head);
-                       goto malformed;
-               }
+               uint8_t const   *a_p;
+               size_t          a_len;
+               int             num_entries, i;
 
-               if (fr_dhcp_attr2vp(packet, vp, p + 2, p[1]) < 0) {
-                       pairfree(&head);
-                       goto malformed;
+               DICT_ATTR const *da;
+               uint32_t        attr;
+
+               /*
+                *      The initial OID string looks like:
+                *      <iana>.0
+                *
+                *      If <iana>.0 is type TLV then we attempt to decode its contents as more
+                *      DHCP suboptions, which gives us:
+                *      <iana>.<attr>
+                *
+                *      If <iana>.0 is not defined in the dictionary or is type octets, we leave
+                *      the attribute as is.
+                */
+               attr = (*tlv)->da->attr ? ((*tlv)->da->attr | (p[0] << 8)) : p[0];
+
+               /*
+                *      Use the vendor of the parent TLV which is not necessarily
+                *      DHCP_MAGIC_VENDOR.
+                *
+                *      Note: This does not deal with dictionary numbering clashes. If
+                *      the vendor uses different numbers for DHCP suboptions and RADIUS
+                *      attributes then it's time to break out %{hex:} and regular
+                *      expressions.
+                */
+               da = dict_attrbyvalue(attr, (*tlv)->da->vendor);
+               if (!da) {
+                       da = dict_attrunknown(attr, (*tlv)->da->vendor, true);
+                       if (!da) {
+                               pairfree(&head);
+                               return -1;
+                       }
                }
 
-               fr_cursor_insert(&cursor, vp);
-               p += 2 + p[1];
+               a_len = p[1];
+               a_p = p + 2;
+               num_entries = fr_dhcp_array_members(&a_len, da);
+               for (i = 0; i < num_entries; i++) {
+                       vp = pairalloc(ctx, da);
+                       if (!vp) {
+                               pairfree(&head);
+                               return -1;
+                       }
+                       vp->op = T_OP_ADD;
+
+                       if (fr_dhcp_attr2vp(&vp, ctx, a_p, a_len) < 0) {
+                               pairfree(&head);
+                               goto malformed;
+                       }
+                       fr_cursor_insert(&cursor, vp);
+
+                       a_p += a_len;
+               }
+               p += 2 + p[1];  /* code (1) + len (1) + suboption len (n)*/
        }
 
        /*
-        *      The caller allocated TLV, so we need to copy the FIRST
-        *      attribute over top of that.
-        *
-        *      This is a pretty awful hack, but we should be able to
-        *      clean it up when we get nested VPs so lets leave it for
-        *      now.
+        *      The caller allocated a TLV, if decoding it generated
+        *      additional attributes, we now need to free it, and write
+        *      the HEAD of our new list of attributes in its place.
         */
        if (head) {
-               /* Cleanup any old TLV data */
-               talloc_free(tlv->vp_tlv);
-
-               /* @fixme fragile */
-               memcpy(tlv, head, sizeof(*tlv));
+               vp_cursor_t tlv_cursor;
 
-               /* If the VP has a talloced value we need to reparent it to the original TLV attribute */
-               switch (head->da->type) {
-                       case PW_TYPE_STRING:
-                       case PW_TYPE_OCTETS:
-                       case PW_TYPE_TLV:
-                               (void) talloc_steal(tlv, head->data.ptr);
+               /*
+                *      Free the old TLV attribute
+                */
+               TALLOC_FREE(*tlv);
 
-                       default:
-                               break;
-               }
-               tlv->next = head->next;
-               talloc_free(head);
+               /*
+                *      Cursor not necessary but means we don't have to set
+                *      ->next directly.
+                */
+               fr_cursor_init(&tlv_cursor, tlv);
+               fr_cursor_insert(&tlv_cursor, head);
        }
 
        return 0;
 
 malformed:
-       tlv->vp_tlv = talloc_array(tlv, uint8_t, data_len);
-       if (!tlv->vp_tlv) {
+       (*tlv)->vp_tlv = talloc_array(*tlv, uint8_t, len);
+       if (!(*tlv)->vp_tlv) {
                fr_strerror_printf("No memory");
                return -1;
        }
-       memcpy(tlv->vp_tlv, data, data_len);
-       tlv->length = data_len;
+       memcpy((*tlv)->vp_tlv, data, len);
+       (*tlv)->length = len;
 
        return 0;
 }
 
-
 /*
  *     Decode ONE value into a VP
  */
-static int fr_dhcp_attr2vp(RADIUS_PACKET *packet, VALUE_PAIR *vp, uint8_t const *p, size_t alen)
+static int fr_dhcp_attr2vp(VALUE_PAIR **vp_p, TALLOC_CTX *ctx, uint8_t const *data, size_t len)
 {
-       char *q;
+       VALUE_PAIR *vp = *vp_p;
+       VERIFY_VP(vp);
 
        switch (vp->da->type) {
        case PW_TYPE_BYTE:
-               if (alen != 1) goto raw;
-               vp->vp_byte = p[0];
+               if (len != 1) goto raw;
+               vp->vp_byte = data[0];
                break;
 
        case PW_TYPE_SHORT:
-               if (alen != 2) goto raw;
-               memcpy(&vp->vp_short, p, 2);
+               if (len != 2) goto raw;
+               memcpy(&vp->vp_short, data, 2);
                vp->vp_short = ntohs(vp->vp_short);
                break;
 
        case PW_TYPE_INTEGER:
-               if (alen != 4) goto raw;
-               memcpy(&vp->vp_integer, p, 4);
+               if (len != 4) goto raw;
+               memcpy(&vp->vp_integer, data, 4);
                vp->vp_integer = ntohl(vp->vp_integer);
                break;
 
        case PW_TYPE_IPV4_ADDR:
-               if (alen != 4) goto raw;
+               if (len != 4) goto raw;
                /*
                 *      Keep value in Network Order!
                 */
-               memcpy(&vp->vp_ipaddr, , 4);
+               memcpy(&vp->vp_ipaddr, data, 4);
                vp->length = 4;
                break;
 
+       /*
+        *      In DHCPv4, string options which can also be arrays,
+        *      have their values '\0' delimited.
+        */
        case PW_TYPE_STRING:
-               vp->vp_strvalue = q = talloc_array(vp, char, alen + 1);
-               vp->type = VT_DATA;
-               memcpy(q, p , alen);
-               q[alen] = '\0';
+       {
+               uint8_t const *p;
+               uint8_t const *q, *end;
+               vp_cursor_t cursor;
+
+               /*
+                *      Initialise the cursor as we may be inserting
+                *      multiple additional VPs
+                */
+               if (vp->da->flags.array) fr_cursor_init(&cursor, vp_p);
+
+               p = data;
+               q = end = data + len;
+               for (;;) {
+                       if (vp->da->flags.array) {
+                               q = memchr(p, '\0', q - p);
+                               /* Malformed but recoverable */
+                               if (!q) q = end;
+                       }
+
+                       pairstrncpy(vp, (char const *)p, q - p);
+                       p = q + 1;
+
+                       /* Need another VP for the next round */
+                       if (p < end) {
+                               vp = pairalloc(ctx, vp->da);
+                               if (!vp) {
+                                       pairfree(vp_p);
+                                       return -1;
+                               }
+                               fr_cursor_insert(&cursor, vp);
+                               continue;
+                       }
+                       break;
+               }
+       }
                break;
 
        case PW_TYPE_ETHERNET:
-               memcpy(vp->vp_ether, p, sizeof(vp->vp_ether));
+               memcpy(vp->vp_ether, data, sizeof(vp->vp_ether));
                vp->length = sizeof(vp->vp_ether);
                break;
 
@@ -622,113 +883,97 @@ static int fr_dhcp_attr2vp(RADIUS_PACKET *packet, VALUE_PAIR *vp, uint8_t const
                if (pair2unknown(vp) < 0) return -1;
 
        case PW_TYPE_OCTETS:
-               if (alen > 255) return -1;
-               pairmemcpy(vp, p, alen);
+               if (len > 255) return -1;
+               pairmemcpy(vp, data, len);
                break;
 
        /*
         *      For option 82 et al...
         */
        case PW_TYPE_TLV:
-               return fr_dhcp_decode_suboption(packet, vp, p, alen);
+               return fr_dhcp_decode_suboption(vp_p, ctx, data, len);
+
+       /*
+        *      For option 82.9
+        */
+       case PW_TYPE_VSA:
+               return fr_dhcp_decode_vsa(vp_p, ctx, data, len);
 
        default:
                fr_strerror_printf("Internal sanity check %d %d", vp->da->type, __LINE__);
                return -1;
        } /* switch over type */
 
-       vp->length = alen;
+       vp->length = len;
        return 0;
 }
 
-ssize_t fr_dhcp_decode_options(RADIUS_PACKET *packet,
-                              uint8_t const *data, size_t len, VALUE_PAIR **head)
+/** Decode DHCP options
+ *
+ * @param[in,out] out Where to write the decoded options.
+ * @param[in] ctx context to alloc new attributes in.
+ * @param[in] data to parse.
+ * @param[in] len of data to parse.
+ */
+ssize_t fr_dhcp_decode_options(VALUE_PAIR **out, TALLOC_CTX *ctx, uint8_t const *data, size_t len)
 {
-       int i;
        VALUE_PAIR *vp;
        vp_cursor_t cursor;
-       uint8_t const *p, *next;
-       next = data;
+       uint8_t const *p, *q;
 
-       *head = NULL;
-       fr_cursor_init(&cursor, head);
+       *out = NULL;
+       fr_cursor_init(&cursor, out);
 
        /*
         *      FIXME: This should also check sname && file fields.
         *      See the dhcp_get_option() function above.
         */
-       while (next < (data + len)) {
-               int num_entries, alen;
-               DICT_ATTR const *da;
+       p = data;
+       q = data + len;
+       while (p < q) {
+               uint8_t const   *a_p;
+               size_t          a_len;
+               int             num_entries, i;
 
-               p = next;
+               DICT_ATTR const *da;
 
                if (*p == 0) {          /* 0x00 - Padding option */
-                       next++;
+                       p++;
                        continue;
                }
-               if (*p == 255) break;   /* 0xff - End of options signifier */
 
-               if ((p + 2) > (data + len)) break;
+               if (*p == 255) {        /* 0xff - End of options signifier */
+                       break;
+               }
 
-               next = p + 2 + p[1];
+               if ((p + 2) > q) break;
 
                da = dict_attrbyvalue(p[0], DHCP_MAGIC_VENDOR);
                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_IPV4_ADDR:
-                       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 */
+                       da = dict_attrunknown(p[0], DHCP_MAGIC_VENDOR, true);
+                       if (!da) {
+                               pairfree(out);
+                               return -1;
                        }
+                       goto next;
                }
 
-               /*
-                *      Loop over all of the entries, building VPs
-                */
+               a_len = p[1];
+               a_p = p + 2;
+               num_entries = fr_dhcp_array_members(&a_len, da);
                for (i = 0; i < num_entries; i++) {
-                       vp = pairmake(packet, NULL, da->name, NULL, T_OP_ADD);
+                       vp = pairalloc(ctx, da);
                        if (!vp) {
-                               fr_strerror_printf("Cannot build attribute %s", fr_strerror());
-                               pairfree(head);
+                               pairfree(out);
                                return -1;
                        }
+                       vp->op = T_OP_ADD;
 
-                       if (fr_dhcp_attr2vp(packet, vp, p, alen) < 0) {
+                       if (fr_dhcp_attr2vp(&vp, ctx, a_p, a_len) < 0) {
                                pairfree(&vp);
-                               pairfree(head);
+                               pairfree(out);
                                return -1;
                        }
-
                        fr_cursor_insert(&cursor, vp);
 
                        for (vp = fr_cursor_current(&cursor);
@@ -736,11 +981,13 @@ ssize_t fr_dhcp_decode_options(RADIUS_PACKET *packet,
                             vp = fr_cursor_next(&cursor)) {
                                debug_pair(vp);
                        }
-                       p += alen;
+                       a_p += a_len;
                } /* loop over array entries */
+       next:
+               p += 2 + p[1];  /* code (1) + len (1) + option len (n)*/
        } /* loop over the entire packet */
 
-       return next - data;
+       return p - data;
 }
 
 int fr_dhcp_decode(RADIUS_PACKET *packet)
@@ -863,9 +1110,7 @@ int fr_dhcp_decode(RADIUS_PACKET *packet)
        {
                VALUE_PAIR *options = NULL;
 
-               if (fr_dhcp_decode_options(packet,
-                                          packet->data + 240, packet->data_len - 240,
-                                          &options) < 0) {
+               if (fr_dhcp_decode_options(&options, packet, packet->data + 240, packet->data_len - 240) < 0) {
                        return -1;
                }
 
index 57fc650..08fc3f7 100644 (file)
@@ -59,8 +59,7 @@ static ssize_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request,
                 return 0;
        }
 
-       if ((fr_dhcp_decode_options(request->packet,
-                                   vp->vp_octets, vp->length, &head) < 0) || (!head)) {
+       if ((fr_dhcp_decode_options(&head, request->packet, vp->vp_octets, vp->length) < 0) || (!head)) {
                RWDEBUG("DHCP option decoding failed: %s", fr_strerror());
                *out = '\0';
                return -1;